Compare commits

..
443 Commits
Author SHA1 Message Date
Andrew Wong 61828d3cfd Release 2.7.15 2023-11-24 21:38:48 +11:00
Andrew Wong 54241d5ce3 Replace the warning of null owner on shutdown with null checks of the owner uuid 2023-11-15 13:06:22 +11:00
Andrew Wong d048665530 Cache the landlord instead of parsing it twice in GeneralRegion#isLandLord 2023-11-15 13:01:34 +11:00
Andrew Wong 23de68431c Replace Random instantiations with ThreadLocalRandom in FindCommand 2023-11-15 12:40:17 +11:00
Andrew Wong 95b4543f19 Fix null pointer when comparing null pre release types in VersionData 2023-11-14 03:46:28 +11:00
Andrew Wong d058bb7271 Refactor platform adapters to legacy (1.17.1) and modern (1.18+). Add semver parsing utilities. Fix bug with github updater treating snapshot versions as newer 2023-11-13 22:30:34 +11:00
Andrew Wong 63d26be369 2.7.15-SNAPSHOT 2023-11-13 21:06:58 +11:00
Andrew Wong 1020388aae Release 2.7.14 2023-11-12 18:04:01 +11:00
Andrew Wong e56107ffd6 Fix signs not being deleted from the global sign cache in memory 2023-11-12 17:59:34 +11:00
Andrew Wong 06fceb4643 Drop support for 1.20 and 1.20.1 2023-11-12 15:50:51 +11:00
Andrew Wong e2077c3b32 Fix region signs losing their interactivity after restart. Resolves #20 2023-11-12 15:40:03 +11:00
Andrew Wong fcb64c2ddb Merge pull request #22 from o0sh4d0w0o/ResoldRegionEvent-resellprice-fix
Fixed getResellPrice unsetted when ResoldRegionEvent is notified
2023-10-04 21:35:03 +11:00
o0sh4d0w0o cc51109720 Fixed getResellPrice unsetted when ResoldRegionEvent is notified 2023-10-01 16:03:07 +02:00
Andrew Wong 0b48f4dd2e Fix not including 1.20.2 adapter 2023-09-25 11:37:12 +10:00
Andrew Wong 4d7f00b0ce Add support for 1.20.2 2023-09-25 11:33:02 +10:00
Andrew Wong b27ea83982 Include 1.18-1.20 adapters for jitpack 2023-09-03 08:47:21 +10:00
Andrew Wong 66da717a77 Fix relocations 2023-09-03 08:47:19 +10:00
Andrew Wong 7f779d2df8 Bump version to 2.7.14-SNAPSHOT 2023-09-03 08:47:16 +10:00
Andrew Wong ed3bd01f2a Fix bad release url
Fixes #19
2023-09-03 07:22:58 +10:00
Andrew Wong ef03501d1c 2.7.13 release 2023-08-30 22:09:28 +10:00
Andrew Wong feb35cfd37 User api for BlockBehaviourImpl 1.18 onwards 2023-08-30 22:08:50 +10:00
Andrew Wong b6a8dcc24c Fix module desc for paper 1.20 2023-08-30 20:23:05 +10:00
Andrew Wong 5619612a6e Update gradle wrapper 2023-08-28 23:10:54 +10:00
Andrew Wong 26ac52cc0a Include plugin adapters in jitpack build 2023-08-28 22:46:14 +10:00
Andrew Wong 5f6bd55afe Don't include nms impls if on jitpack 2023-08-28 22:34:28 +10:00
Andrew Wong aba2e12c5d Don't include nms impls if on jitpack 2023-08-28 22:26:28 +10:00
Andrew Wong 2906de11de Fix merge conflicts 2023-08-28 22:17:42 +10:00
Andrew Wong 436672f85b Don't shadowJar and don't include nms providers if building on jitpack 2023-08-28 22:12:09 +10:00
Andrew Wong 5ba8ed7ad8 Add spotbugs 2023-08-28 20:57:17 +10:00
Andrew Wong 00d97f1beb Update dependencies 2023-08-28 20:36:52 +10:00
Andrew Wong f530fa40af Fix signs not being saved on server stop 2023-08-28 20:14:12 +10:00
Andrew Wong a804df2257 Add jitpack logo 2023-08-09 14:08:12 +10:00
Andrew Wong c96e0b464e More jitpack tests 2023-08-09 13:58:44 +10:00
Andrew Wong 13e52aa4f1 More jitpack tests 2023-08-09 13:55:52 +10:00
Andrew Wong 9aeb531d16 More jitpack tests 2023-08-09 13:52:23 +10:00
Andrew Wong 44f83473d1 Add jitpack.yml 2023-08-09 13:46:05 +10:00
Andrew Wong d7260998f5 Update Guice to 7.0.0 and use JakartaEE annotations 2023-06-12 00:59:16 +10:00
Andrew Wong 638382eacf Snapshot 2.7.13 2023-06-12 00:58:34 +10:00
Andrew Wong e18fe99161 Release 2.7.12 2023-06-12 00:40:46 +10:00
Andrew Wong bdc9e32c13 Add support for 1.20 2023-06-12 00:40:08 +10:00
Andrew Wong 9a46d5ae3b Bump version to 2.7.12-SNAPSHOT 2023-03-29 16:14:26 +11:00
Andrew Wong 6795fc43d9 Release 2.7.11 2023-03-29 16:13:54 +11:00
Andrew Wong dd9bd2c434 Drop support for 1.19.3 and add support for 1.19.4 2023-03-29 16:13:23 +11:00
Andrew Wong 9438166180 Prepare for development 2.7.11 2023-02-27 21:58:41 +11:00
Andrew Wong 9e06b9f268 Release 2.7.10 2023-02-27 21:58:14 +11:00
Andrew Wong 4562bf23f6 Update libs 2023-02-27 21:57:49 +11:00
Andrew Wong 24edb01754 Refactor GeneralRegion#getOwner, #isOwner and #setOwner 2023-02-27 21:46:59 +11:00
Andrew Wong 2739bf6ad4 Fix javadocs for IFileManager 2023-02-27 21:41:28 +11:00
Andrew Wong 2ce84186ab Use Server#getOfflinePlayer in PlayerOfflinePlayerHelper 2023-02-27 21:39:48 +11:00
Andrew Wong 279fa001ea Only support 1.19.3 2023-02-27 11:56:41 +11:00
Andrew Wong b56d5320a6 Add support for 1.19.3 2022-12-15 16:25:06 +08:00
Andrew Wong 30665908b9 Bump to 2.7.10-SNAPSHOT 2022-12-09 16:53:03 +08:00
Andrew Wong 0fd384833a Bump version to 2.7.9 release 2022-12-09 16:45:57 +08:00
Andrew Wong 09634de4bf Fix BukkitSchedulerExecutor not having an injectable ctor 2022-12-07 12:13:34 +08:00
Andrew Wong 7fafee82a7 Minor cleanup 2022-12-07 11:59:37 +08:00
Andrew Wong 2899b2978c Rewrite SetownerCommand 2022-12-07 11:56:50 +08:00
Andrew Wong 7afcba79c9 Rewrite SetlandlordCommand 2022-12-07 11:53:44 +08:00
Andrew Wong 72084b38f8 Use OfflinePlayerHelper in MeCommand 2022-12-07 11:49:01 +08:00
Andrew Wong 6a291feedd Rewrite DelFriendCommand 2022-12-07 11:45:16 +08:00
Andrew Wong b85566882c Rewrite AddfriendCommand 2022-12-07 11:39:09 +08:00
Andrew Wong c14407e14b Add PaperPlatform 2022-12-07 01:23:12 +08:00
Andrew Wong ae9bd60d1b Fix typo 2022-09-14 21:25:04 +08:00
Andrew Wong c689c2fae0 Fix dependency versions for WG and WE, and add a section for FAWE 2022-09-14 21:24:48 +08:00
Andrew Wong 728cfb6dfa Add a compiling.md 2022-09-14 21:16:03 +08:00
Andrew Wong 3abb6c06e4 Update readme 2022-09-14 20:56:00 +08:00
Andrew Wong 51c11738af Prepare for snapshot development 2022-09-12 22:39:45 +08:00
Andrew Wong 172b5a99f1 Bump version to release 2022-09-12 22:21:28 +08:00
Andrew Wong 4c5d025e54 Add config option to toggle whether entities are forcefully cleared 2022-09-12 22:20:55 +08:00
Andrew Wong e93a2d04de Forcefully remove entities from FAWE/WE regions before schematic pasting 2022-09-12 22:12:18 +08:00
Andrew Wong af5980d555 Change the messages for update checking and report file manager loading failures 2022-09-12 17:58:09 +08:00
Andrew Wong 3f7249f4a1 Merge pull request #13 from cleonyc/dev/bleeding
Allow unrented event to use placeholders
2022-09-12 17:39:56 +08:00
Andrew Wong 73c6fc858e Update the logging methods to invoke conditionally 2022-09-12 17:37:47 +08:00
Andrew Wong 360c3e7e21 Use Suppliers when logging in the plugin adapters 2022-09-12 17:35:55 +08:00
Andrew Wong 73d0115b78 Actually fix the FAWE and WE handlers when pasting 2022-09-12 17:25:27 +08:00
Andrew Wong baf7eaabf1 Bump versions of WE and FAWE in the libs.toml 2022-09-11 00:50:36 +08:00
Andrew Wong ca8c0487a8 Fix origin setting so that the blocks paste properly 2022-09-11 00:44:47 +08:00
Andrew Wong 24156a03c4 Rewrite region saving and restoring in the adapters 2022-09-10 23:14:28 +08:00
Andrew Wong ec677766d2 Attempt to prevent duplicate signs from loading 2022-09-10 19:27:03 +08:00
Andrew Wong ef6c8fbf9d Only remove leashes, paintings and item frames 2022-09-10 17:54:48 +08:00
Andrew Wong 4f52fe9e6e Forcefully remvoe Entities in the region before pasting 2022-09-10 17:43:07 +08:00
Andrew Wong 3ebe5a6fa9 Use the WG converter to convert WG regions to WE ones 2022-09-10 17:02:29 +08:00
Andrew Wong 3cebe5d2c7 Fix NPE with sign deletion after worlededit operations 2022-09-10 14:59:35 +08:00
Andrew Wong c76415c4f3 Fix NPE when tabbing region restore 2022-09-10 12:47:45 +08:00
Andrew Wong b7bc02aef3 Compile against 1.19.2 and update the gradle wrapper 2022-09-07 15:31:33 +08:00
Cleo d75094e191 Remove unrenting section in eventCommandProfile 2022-07-07 13:51:02 -04:00
Cleo 4045914b48 Fix placeholders in unrented command event [#11] 2022-07-07 13:50:04 -04:00
Andrew Wong 1fac002ee0 Remove the Bukkit113 module and update the versioning url 2022-06-20 19:57:22 +08:00
Andrew Wong 64301e74e0 Bump version 2022-06-20 19:27:28 +08:00
Andrew Wong 5a5a7fc5ef Bump version 2022-06-20 19:25:42 +08:00
Andrew Wong b928caf0c2 Bump worldedit 2022-06-20 19:19:49 +08:00
Andrew Wong 111f629787 Update dependencies and add adapter for 1.19 2022-06-11 00:59:19 +08:00
Andrew Wong 718474ca95 Update gradle wrapper 2022-06-10 23:37:12 +08:00
Andrew Wong 93b9df3e54 Update gradle wrapper to 7.4.1 2022-03-24 18:48:03 +08:00
Andrew Wong bad983a0b8 Update worldedit and fawe adapters 2022-03-24 18:47:02 +08:00
Andrew Wong e2c8b06b68 Bump dependency versions 2022-03-24 16:30:02 +08:00
Andrew Wong 7249e39c02 bump version 2022-03-24 16:27:49 +08:00
Andrew Wong 9d9a58a170 bump to 2.7.6 2022-03-24 16:24:56 +08:00
Andrew Wong e64e884c2a Updates to the fawe module 2022-03-05 00:00:34 +08:00
Andrew Wong 6ad4ff9deb Shade in the provider for 1.18.1 2022-03-05 00:00:23 +08:00
Andrew Wong eec9806492 Add back support for 1.18.1 2022-03-04 23:08:43 +08:00
Andrew Wong 43b2ae8278 Fix bad website in plugin.yml 2022-03-04 22:54:40 +08:00
Andrew Wong 6d5a92e389 1.18.2 2022-03-04 22:48:39 +08:00
Andrew Wong 7d7a62ffa0 Bump gradle wrapper to 7.4 2022-03-03 17:41:52 +08:00
md5sha256 44456d87b6 Fix the sign error file extension 2022-01-25 05:49:47 +08:00
md5sha256 d92c710b27 bump version to snapshot 2022-01-25 05:49:35 +08:00
md5sha256 3dceaf98cd Bump version to 2.7.5 2022-01-25 05:35:08 +08:00
md5sha256 33c29a3756 Fix the fawe integration and do operations async when possible 2022-01-25 05:34:34 +08:00
md5sha256 771734360f add workflow 2022-01-16 23:24:28 +08:00
md5sha256 539e908d32 Initial work on fixing the deadlock in the fawe module 2022-01-16 21:45:11 +08:00
md5sha256 088471aa3c more project restructuring 2022-01-16 21:28:28 +08:00
md5sha256 5a95c95670 Fix gitignore and restructure project 2022-01-16 20:58:44 +08:00
md5sha256 5a5d627d51 Fix publishing 2022-01-16 15:26:54 +08:00
md5sha256 9b5434a4b2 Add type safe project acessors and version catalog 2022-01-16 14:40:19 +08:00
md5sha256 e13717950b Kotlin dsl 2022-01-16 12:05:39 +08:00
md5sha256 03ed2803bd Bump version to 2.7.3 2022-01-15 07:38:40 +08:00
md5sha256 b3f1225934 Deprecate the FAWE WG handler and fix fawe version parsing 2022-01-15 07:36:43 +08:00
md5sha256 68651899b7 Update the fawe module 2022-01-10 22:59:03 +08:00
md5sha256 4a7a4ca4ee Fix reobf 2022-01-10 22:34:35 +08:00
md5sha256 0d272c8fe9 Publish nms provider poms 2021-12-05 23:07:05 +08:00
md5sha256 6b2c905939 Parallel builds 2021-12-05 22:57:17 +08:00
md5sha256 7ab782799b Fix maven publish tasks 2021-12-05 22:57:03 +08:00
md5sha256 bea82eae88 Fix maven publish tasks 2021-12-05 22:56:37 +08:00
md5sha256 4ac0e045bd Add maven publish task 2021-12-05 18:22:41 +08:00
md5sha256 e5061b218e Java 17 and 1.18 2021-12-01 17:59:17 +08:00
md5sha256 40fdfba47d Fix silly typo in the buildscript 2021-11-18 22:16:36 +08:00
md5sha256 e9260cede6 Bump gradle wrapper version to 7.3 2021-11-18 22:14:56 +08:00
md5sha256 bb297c9ff1 Bump version 2021-11-16 15:28:14 +08:00
Andrew Wong 666742ad56 Merge pull request #2 from md5sha256/feature/purge 2021-11-16 15:25:54 +08:00
md5sha256 36eb8c3a5e Fix imports 2021-11-16 15:25:01 +08:00
Olzie-12 06a9f7b28e file management 2021-11-14 14:41:41 +00:00
md5sha256 e101780420 Fix compat for file manager 2021-11-11 13:31:09 +08:00
md5sha256 6abe99e807 Log sign warnings to the sign error logger 2021-11-09 19:06:02 +08:00
md5sha256 a1d18521ee Introduce interface for FileManager 2021-10-11 00:00:57 +08:00
md5sha256 363b416c59 Retain compat for FileManager 2021-10-10 03:48:12 +08:00
md5sha256 7bf516108b Rename AreaShop again and fix teleport feature 2021-10-09 22:22:53 +08:00
md5sha256 7567cb5fe4 Cleanup global features 2021-10-09 21:33:19 +08:00
md5sha256 d2ca54e8ea Fix signs not updating 2021-10-09 21:03:25 +08:00
md5sha256 e47cda4254 Fix plugin.yml 2021-10-09 17:44:01 +08:00
md5sha256 98d4c6a778 Fix relocations and cleanup DI for Economy and Permission 2021-10-09 17:40:10 +08:00
md5sha256 2a44dd29cc Fix relocations 2021-10-09 17:29:36 +08:00
md5sha256 c1665c33ff Finish cleaning up DI 2021-10-09 17:24:28 +08:00
md5sha256 2cdab9b6db Initial work on guice 2021-10-09 13:27:46 +08:00
md5sha256 72faf76619 Fix ClassCastException in RegionSign 2021-10-09 13:27:28 +08:00
md5sha256 ad66ce00b1 Actually register the sign listener 2021-10-09 02:00:38 +08:00
md5sha256 edc7126b87 Performance improvements to FileManager and various commands 2021-10-09 01:02:00 +08:00
md5sha256 a7f0ac248c Initial work on sign refactoring 2021-10-08 17:44:43 +08:00
md5sha256 675dee2cd3 Add sign error logger 2021-10-07 03:12:45 +08:00
md5sha256 c7226750ab Don't check for WG v5 2021-10-07 01:36:36 +08:00
md5sha256 2ecbb61053 Performance improvements for the teleport feature 2021-10-07 01:34:18 +08:00
md5sha256 fc132558a3 Add a BlockBehaviourHelper 2021-10-07 01:24:20 +08:00
md5sha256 b172413b62 Minor performance improvements 2021-10-07 00:11:09 +08:00
md5sha256 dcbb1f5896 Fix buildscripts and Java 16 support 2021-10-06 23:59:23 +08:00
md5sha256 6e3855c0c3 Remove legacy modules, remove use of ScriptEngine 2021-10-06 23:46:58 +08:00
md5sha256 27991d17a2 Gradle 2021-10-06 23:39:57 +08:00
md5sha256 159221d53d Initial work on updating to 1.17 2021-10-05 23:45:25 +08:00
dependabot[bot] 557d11dfb4 Bump checkstyle from 8.21 to 8.29
Bumps [checkstyle](https://github.com/checkstyle/checkstyle) from 8.21 to 8.29.
- [Release notes](https://github.com/checkstyle/checkstyle/releases)
- [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-8.21...checkstyle-8.29)

Signed-off-by: dependabot[bot] <support@github.com>
2020-02-26 11:02:43 +01:00
Thijs Wiefferink b9547e2c67 Remove github action experiment 2019-08-16 18:22:19 +02:00
Thijs Wiefferink 221785ae55 Add test workflow maven build 2019-08-16 17:42:22 +02:00
Thijs Wiefferink 0d04676bce Fix including bukkit into the AreaShop jar 2019-06-23 22:27:35 +02:00
Thijs Wiefferink c8cd68c17b Fixup javadoc build 2019-06-23 22:07:34 +02:00
Thijs Wiefferink 539887449c pom.xml: build with craftbukkit 1.13 2019-06-23 21:59:04 +02:00
Thijs Wiefferink 4cda8a9292 Fix sign handling for 1.14
- Setup modules to load different classes for different Bukkit/Spigot versions
- Create BukkitHandler to get/set the sign facing and get the block it is attached to
- Fix `/as addsign` to use new sign methods
- Fix sign updates to use new sign methods
- Implement full forwards and backwards compatibility of the saved sign material type
    - On Bukkit 1.12 and lower all special wood types are translated to the standard sign
    - On Bukkit 1.13 the internal sign name is translated (and special wood types to the standard one)
    - On Bukkit 1.14 the signs of 1.13- are translated to the OAK variants
- Cleanup the .gitignore files inside of the module folders
2019-06-23 21:53:48 +02:00
Thijs Wiefferink fe131dc157 BuyRegion/RentRegion: fix %player% placeholders for selling/unrenting confirmation messages 2019-06-16 21:40:58 +02:00
Thijs Wiefferink 41ced9ee6c Update checkstyle from 7.6 to 8.21 2019-06-01 17:08:30 +02:00
Thijs Wiefferink 03de0ff252 default.yml: fix spelling mistake 2019-06-01 16:24:15 +02:00
Thijs Wiefferink b057271394 Fix permission check of /as rent, fix 'already bought' check of /as buy 2019-02-20 19:33:37 +01:00
Thijs Wiefferink d7cdff0b22 /as setteleport: fix check that hides it from /as help 2019-02-08 22:29:57 +01:00
Thijs Wiefferink 1035b6b41b Move event commands to a feature class, add two events
- Move eventCommandProfile code to a separate class
- Add AddingRegionEvent
- Add DeletingRegionEvent
- Cleanup a couple of duplicate code paths (merge RentRegion/BuyRegion branches)
2019-01-28 23:37:43 +01:00
Thijs Wiefferink e0bad7397c Refactoring based on SonarLint and Intellij IDEA inspections 2019-01-26 21:23:49 +01:00
Thijs Wiefferink b7124e8cdd Remove mcstats.org metrics class, I don't believe they will be online again 2019-01-26 20:11:26 +01:00
Thijs Wiefferink f0952c32c9 Update issue templates
Add 'NEW' label to issues created using the template
2019-01-13 22:26:54 +01:00
Thijs Wiefferink d3dfa86a65 Set version to 2.6.0 2019-01-13 17:34:50 +01:00
Thijs Wiefferink 8f73b8af3c documentation/release.md: add instructions for releasing a new version 2019-01-13 17:34:37 +01:00
Thijs Wiefferink 51b24c74b4 langHeader.yml: add the template used to generate the language file headers to the repository 2019-01-13 17:33:54 +01:00
Thijs Wiefferink 9beb1eb61b Move the changelog from the wiki to the repository 2019-01-13 17:33:09 +01:00
Thijs Wiefferink de5fdafca0 Pull translations from Transifex 2019-01-08 21:18:19 +01:00
Thijs Wiefferink 0aea914d20 Add FastAsyncWorldEdit support on Spigot 1.13
- FastAsyncWorldEdit now supported on Spigot 1.13
- Improved support for the beta-01 versions of WorldGuard and WorldEdit
- Resolves #436
2019-01-08 00:19:48 +01:00
Thijs Wiefferink b4efe7d5bf Add default labels to issue templates 2019-01-05 16:27:05 +01:00
Thijs Wiefferink ec7983e126 Fix handling of sign updates at places where they will pop off immediately
1. When placing back a missing sign it could be placed in the middle of the air
2. BlockPhysicsEvent would fire immediately (synchronously) breaking the sign
3. Sign update would break because the sign has not actually been placed
2019-01-01 19:32:34 +01:00
Thijs Wiefferink e45c353030 Fix detecting region by location when using WorldEdit 7 beta-2 or later
- The WorldEdit 7 beta-2 handler was inheriting a function from the handler of older version that uses the old Vector class
- Fixes #466
- Fixes #467
2019-01-01 19:30:16 +01:00
Thijs Wiefferink 44b1cb0fc8 /as add: fix setting player as landlord when he is an owner of the region
- Fixes #427
- Adds a debug message to check the behavior easily in the future
2018-12-28 17:11:48 +01:00
Thijs Wiefferink c7299fa56c Update Discord image to a widget image 2018-12-27 23:14:56 +01:00
pmme 12bf467714 Simplify WorldEdit 7 flushQueue vs. flushSession 2018-12-27 22:47:14 +01:00
Thijs Wiefferink 5b327db316 signs: instead of cancelling indirect break, remove the sign to prevent lagg 2018-12-27 19:44:30 +01:00
Thijs Wiefferink b4f33589a1 Cleanup old directory 2018-12-27 17:45:19 +01:00
Thijs Wiefferink 530c74ccd2 Add support for the WorldEdit vector refactor update
- Add WorldEdit and WorldGuard module that supports the new vector system
- WorldGuard/WordEdit dependencies are in the repository itself to make builds more reliable
- Change code that uses WorldEdit vectors to use Spigot vectors
- Rename all modules to areashop-[worldguard|worldedit]-<version>
- Change maven versions of internal modules to 'latest' to be less confusing
2018-12-27 17:42:24 +01:00
Thijs Wiefferink fa4857cbce Simplify code in the handler for WorldEdit 7 2018-12-27 15:00:53 +01:00
Thijs Wiefferink 93fd4e96cb Fix restoring schematics for the latest WorldEdit dev builds
- Fixes #447
- Thanks pmme and x1p for reporting the issue!
2018-10-28 17:58:09 +01:00
Thijs Wiefferink a6863ae782 Also put the Vault dependency into /dependencies
- Now it should be easy again to built AreaShop yourself, only need to run BuildTools for 1.13.0 and it should work
2018-10-28 01:15:12 +02:00
Thijs Wiefferink e5e2c4ca08 Fixup dependencies, use Bukkit/CraftBukkit 1.7.9 instead of 1.8
- 1.7.9 is the lowest supported version, so it is good to use that as base
2018-10-28 00:53:30 +02:00
Thijs Wiefferink b0341e2a3e Fix compatibility with 1.13 Material class
- Fixes sign handling to properly detect and store signs, fixes #440
- Fixes safe teleporting (also improves block detection, making it even safer)
2018-10-28 00:12:24 +02:00
Thijs Wiefferink c8e1ebb068 Change WorldGuard/WorldEdit/Bukkit dependencies to file links
- Fixes the issue that olders WorldEdit and Bukkit versions are not available on Maven anymore
- This speeds up the build a lot
2018-10-28 00:00:41 +02:00
Thijs Wiefferink 39c8eb5f73 Fix tab complete types for /as setduration 2018-10-22 23:44:31 +02:00
Thijs Wiefferink 2483cf1614 Try to add compatibility with the FAWE schematic format 2018-10-21 21:59:45 +02:00
Thijs Wiefferink 769f8ee4ca Set version to 2.5.0, ready for release! 2018-10-20 21:58:14 +02:00
Thijs Wiefferink 2c04b87a9f Add the latest Transifex translations for DE, ES and FR 2018-10-20 21:57:05 +02:00
Thijs Wiefferink 8ce1ed0264 Fix showing /as del and /as add in /help for landlords 2018-10-20 18:36:51 +02:00
Thijs Wiefferink 035f8e1932 Possibly fix default value for areashop.limits.default (documentation is inconsistent for not op) 2018-10-20 18:36:51 +02:00
Thijs Wiefferink 4d3482994f Smaller discord image 2018-10-20 15:53:14 +02:00
Thijs Wiefferink 45f1c510e5 Add link to the AreaShop Discord 2018-10-20 15:49:36 +02:00
Thijs Wiefferink 5c13223b9e Fix for 1.13.1, depend on WorldGuard and WorldEdit 7 beta-01 2018-10-20 14:54:52 +02:00
Thijs Wiefferink 8c47ae9b3c Add README.md files in a couple of packages, closes #128 2018-08-12 16:41:03 +02:00
Thijs Wiefferink 28d03a6a3a Small fixes suggested by Intellij IDEA 2018-08-12 16:23:43 +02:00
Thijs Wiefferink aa585e9b11 Deal with empty date/time settings (handle as 'unlimited'/'disabled')
- Fixes #412
2018-08-12 16:17:02 +02:00
Thijs Wiefferink 36d2ce60b9 Disable copying entities (WorldEdit 7), and enable removing entities (WorldEdit 6)
- Fixes security problem of #273:
    - Throw item into a region
    - Rent/buy the region -> it gets saved as schematic
    - Pick up items
    - Unrent/sell the region -> items appear again
    - Pick up second set of items, they got duplicated
2018-08-11 22:31:15 +02:00
Thijs Wiefferink bdea9448a4 Update WorldEdit 7 to build 3817, update WorldGuard 7 to build 1735 2018-08-11 22:19:25 +02:00
Thijs Wiefferink bd2f40f5c8 Use new schematic format when using WorldEdit 7 2018-08-11 22:18:48 +02:00
Thijs Wiefferink 9e908cee73 Fix getting region by location for WorldGuard 7 2018-08-11 22:17:13 +02:00
Thijs Wiefferink 0d326d973d Fix saving/restoring schematics with WorldEdit 7 2018-08-05 12:23:32 +02:00
Thijs Wiefferink 27eaa27765 Add WorldGuard 7 and WorldEdit 7 support, now 1.13 works
- Closes #411
2018-08-05 00:34:50 +02:00
Thijs Wiefferink fb35153eb2 Pull translations from Transifex 2018-07-22 17:52:25 +02:00
Thijs Wiefferink aec6295516 Remove dependency on javafx 2018-06-17 17:14:23 +02:00
Thijs Wiefferink 38cd77e487 Fix compilation error in the import command 2018-06-01 19:54:02 +02:00
Thijs Wiefferink 6b9866a4c4 Ignore hidden files in the /regions directory (like .DS_Store) 2018-05-29 22:58:15 +02:00
Thijs Wiefferink 5aceb81cfd Import: setup permanent region settings when not rentable and buyable 2018-05-29 22:21:43 +02:00
Thijs Wiefferink 50f6bce64e Fix region limits, thanks @ZachryWilsn for reporting! 2018-05-29 21:59:55 +02:00
Thijs Wiefferink f713edc5b5 Disable moneyback message in /as info if sellDisabled is true 2018-05-26 01:33:23 +02:00
Thijs Wiefferink 012920a51c Cleanups because of Intellij IDEA inspections 2018-05-23 23:46:57 +02:00
Thijs Wiefferink 6154454f96 Indicate if a region has already been added with another world
- Closes #287
2018-05-23 22:53:07 +02:00
Thijs Wiefferink e34daeb5ff Update readme to use issue template selection link 2018-05-23 22:38:12 +02:00
Thijs Wiefferink 0a2dc22c59 Setup issue templates 2018-05-23 22:29:29 +02:00
Thijs Wiefferink 2482bd88c2 Add option to ignore regions for limit calculation
Set `countForLimit` to `false` in default.yml, a group or the region file to ignore it for limits
2018-05-23 22:13:42 +02:00
Thijs Wiefferink e86281ab28 Safer sign update, don't assume location/world still exists 2018-05-12 14:41:45 +02:00
Thijs Wiefferink 5a9ca74b75 Add option to disable selling and reselling your own regions 2018-05-10 20:07:10 +02:00
Thijs Wiefferink d301be2304 Add licence, issues and CI to the root pom file 2018-04-03 22:44:16 +02:00
Thijs Wiefferink a23c6fa66f Hide progress messages of region update, speed up region updates 2018-04-02 20:05:38 +02:00
Thijs Wiefferink cd63517041 Fix list of region names for error indicating missing worlds 2018-04-02 20:00:22 +02:00
Thijs Wiefferink e3043c476b Fix available languages link 2018-04-02 12:10:34 +02:00
Thijs Wiefferink 3be7573863 Parse currency prefix and suffix with InteractiveMessenger
- Allows proper colors and escaping
2018-03-09 11:50:08 +01:00
Thijs Wiefferink 51161fffee Remove unused chunkunload event 2018-03-09 11:08:15 +01:00
Thijs Wiefferink c82067ad0b Add back Paypal donate button 2018-02-17 19:04:18 +01:00
Thijs Wiefferink 46dbb8d22c More customizable metric suffixes, better currency formatting
- 'metricSymbols' section in 'config.yml' to customize the used suffixes for large numbers
- Remove trailing zeros from numbers with metric suffix
- Properly handle rounding of 1.235 to 1.24 (different double to BigDecimal conversion)
- Closes #373
2018-02-12 14:44:48 +01:00
Thijs Wiefferink cace086466 Add UTF8 awareness flag in plugin.yml
- I think it only does something if you use JavaPlugin#getConfig(), but it does not harm either
2018-02-12 12:49:04 +01:00
Thijs Wiefferink 06a716b1fd Make %uuid% available to schematics when selling a region
- Schematic save/restore would happen after removing the owner, which means %uuid% would be null
- Unrenting already does the save/restore before removing the renter, so now it is more consistent
- Thanks to Behoston on Github for reporting the issue
- Closes #383
2018-02-01 13:59:48 +01:00
Thijs Wiefferink 4530717045 Command confirmation for /as import
- Can also be used for all WordEdit based add/delete actions in the future
2018-01-26 16:16:11 +01:00
Thijs Wiefferink 741bbd879f Fix hashCode() of RegionSign 2018-01-26 14:33:27 +01:00
Thijs Wiefferink 8474edd29d Save region name as given to /as add and /as stack
- Previously the WorldGuard region id was used, converting the name to lowercase
2018-01-26 13:55:41 +01:00
Thijs Wiefferink d4df691166 FIx usage of > in javadocs 2018-01-26 12:15:10 +01:00
Thijs Wiefferink 3e247e6cb1 Move sign listeners into SignsFeature
- Fix removing signs not removing it from the signsByChunk map
2018-01-26 12:08:22 +01:00
Thijs Wiefferink f3527134e8 Prevent chunk loading by sign updates
- When sign needs update, only do it if the chunk is already loaded
- If a chunk gets loaded (by a player or something else), update any signs in that chunk
- Improve some javadocs, let checkstyle require @param, @throws and @returns
2018-01-22 18:12:15 +01:00
Thijs Wiefferink 0e14b121b8 InfoCommand: fix showing list of signs (was only showing the first one) 2018-01-22 18:08:16 +01:00
Thijs Wiefferink 8cc632fc05 Pull latest translations from Transifex (automated script) 2018-01-22 16:31:03 +01:00
Thijs Wiefferink 77c1b3f288 Fix error in help message of the import command 2018-01-22 16:10:02 +01:00
Thijs Wiefferink 21339cbf7c Fix incorrect message reference for /as message 2018-01-22 16:07:52 +01:00
Thijs Wiefferink 0351188671 Update live support link 2018-01-21 22:10:02 +01:00
Thijs Wiefferink 47e1d80821 Add live support chat link to 99messages 2018-01-18 16:16:40 +01:00
Thijs Wiefferink 1d0472b052 Improve feedback when placing [as] sign for region that is not added 2017-12-22 12:25:30 +01:00
Thijs Wiefferink 22dcfc76af Fix confirmation messages of /as del (failed refactor)
Thanks Noudux for reporting it!
2017-12-21 18:09:55 +01:00
Thijs Wiefferink 6b59176048 Fix startup error: call init method of the scheduling library 2017-11-27 10:02:51 +01:00
Thijs Wiefferink 5dc4d11408 Fix missing spaces in /as message messages 2017-11-26 23:49:42 +01:00
Thijs Wiefferink 00bd305381 Use Map#putIfAbsent where applicable 2017-11-26 16:01:05 +01:00
Thijs Wiefferink 9daa50b23b Use BukkitDo scheduling library
- Much cleaner code for scheduling tasks,
- Makes proper use of Java 8 functionality
2017-11-26 15:47:51 +01:00
Thijs Wiefferink 33b73a0369 Remove Paypal donate button 2017-11-25 17:07:54 +01:00
Thijs Wiefferink 591540af30 Partially fix problem with importing regions from RegionForSale
- When checking the type of a region AreaShop would only look at the settings of the region, not the settings of the world, parent regions and config.yml
- Checking parent regions for these settings should still be added (requires bigger changes)
2017-11-23 22:54:55 +01:00
Thijs Wiefferink 4e1e50e993 Fix /as linksigns <profile> for configs without signProfiles
The `signProfiles` section is not in the default config.yml file anymore, so getting that section can return null
2017-11-20 09:19:35 +01:00
Thijs Wiefferink 9a52ca3d9b Add missing @return Javadoc for RegionGroup#getAutoRegions() 2017-11-10 00:04:39 +01:00
Thijs Wiefferink 858b019806 Add option to teleport X blocks away from the sign
- Implements #364
- Option `general.teleportSignDistance` to `default.yml` to set the number of blocks to teleport away from the sign
- Changes the pitch/yaw of the player so that he faces the sign
- Handles wall and floor signs in any direction
- Improve SignsFeature code structure
2017-11-09 23:56:18 +01:00
Thijs Wiefferink b1dfe93088 Add RegionForSale import, rental auto-extend and other things
- `/as import RegionForSale`, as requested in #98:
    - Imports all regions from RegionForSale
    - Converts almost all settings to their AreaShop equivalent
    - Handles parent regions
- Add `rent.autoExtend` for use with rental regions to automatically extend the rent when the time is out, start of #29, still need the command
    - Vault compatible permissions plugin is required to let this work while the player is offline
    - All permissions, limits and the player balance are checked as during a normal rent
- Add option to region groups to automatically add all regions of a certain world
- BREAKING: Region events now use OfflinePlayer instead of Player
- Automatically import existing region owners/members when adding a region to AreaShop

There are still a couple of things missing in the import and it needs to be tested a lot.
2017-10-20 13:41:30 +02:00
Thijs Wiefferink 0bf2314f2a Add licence (as already listed on BukkitDev) 2017-10-16 11:56:04 +02:00
Thijs Wiefferink 33ddb8ae88 Fix %volume% for polygon regions
- WorldGuard does not implement volume() for Polygon regions, always returns 0
- Volume result is cached, calculating it for polygon regions could be slow
2017-10-05 16:53:42 +02:00
Thijs Wiefferink 234e4b6261 Prevent indirectly breaking signs with non-solid blocks
- Fixes #358
2017-10-05 13:43:27 +02:00
Thijs Wiefferink ed2ffaeaa3 Move base command to EN.yml for /as info paging 2017-10-05 13:15:51 +02:00
Thijs Wiefferink e073438862 Add %volume% variable to get the number of blocks in a region
- Works properly for polygon regions
2017-10-05 12:20:03 +02:00
Thijs Wiefferink 07194f07da Add region masking to schematic restore
- When restoring a schematic the paste is masked to the region blocks, so that only blocks that are actually inside the region are pasted (necessary for polygon regions)
- Closes #20
2017-10-01 19:49:05 +02:00
Thijs Wiefferink dfa3e0be66 Checkstyle fix 2017-09-04 17:09:31 +02:00
Thijs Wiefferink 8eef029400 Fix %month% variable using 0-11 instead of 1-12
- Fixes #354, thanks skippyaut
2017-09-04 16:58:39 +02:00
Thijs Wiefferink c8d869ce15 Fix usage of teleport location set by '/as settp'
- It was accidentally trying to get the teleport location from the plugin config file instead of the region config file.
- Also added the possibility to set the teleport location in groups and default.yml
2017-08-05 20:25:06 +02:00
Thijs Wiefferink 053d75181d Update to InteractiveMessenger 1.1-SNAPSHOT
- Ensures AreaShop is rebuild when InteractiveMessenger updates
- Fixes crash bug that was in InteractiveMessenger
- Fixes #334
2017-07-30 17:25:49 +02:00
Thijs Wiefferink f0128c221b Update README.md 2017-06-23 14:59:16 +02:00
Thijs Wiefferink ce6c01c5d0 Add Transifex translation stats image 2017-06-23 14:54:19 +02:00
Thijs Wiefferink b835c4dded Only reset WorldGuard flag/groupFlag if not already empty 2017-06-12 22:05:09 +02:00
Thijs Wiefferink 07e6782714 Fix /as setowner for regions that are not rented yet
Add a rental period to the time when setting owner of a rental region that is not rented yet, otherwise it would get expired immediately
2017-06-12 21:58:42 +02:00
Thijs Wiefferink 4002956aeb Fix javadoc comments 2017-06-12 20:42:36 +02:00
Thijs Wiefferink 0b96b9cb42 Github update checker, Java 8
- Add Github update checker, closes #201, it is implemented to allow reuse by other plugins, it will probably move to a separate repository later
- Switch to Java 8, if people did not update already they probably never will
- Add used Bukkit version as property, use that in all pom files
- Add Task class with methods to easily use BukkitRunnable (not all code is migrated to this pattern yet)
2017-06-12 20:39:28 +02:00
Thijs Wiefferink 0c192c2444 Cleanup setOwners/setMembers, only edit WorldGuard region if different
- Improve code layout for changing owners and members of WorldGuard regions
- Only edit flags and other WorldGuard region settings if they are different than the current option, this prevents useless saves in WorldGuard (which shows a message in console)
2017-06-12 20:39:28 +02:00
Thijs Wiefferink 2381aaeacc Remove link to Shop plugin because it is not fixed yet 2017-04-30 14:15:35 +02:00
Thijs Wiefferink 906b331758 Fix line endings in config.yml 2017-04-20 23:35:49 +02:00
Thijs Wiefferink f0f2e09be0 Always add English time indicators
- Internal timers specified using settings in hiddenConfig.yml use the English words, so if you remove the English words from config.yml timers do not run (regions not expiring, no notifications, no file saving)
- Warn in console when an unknown time specifier is found, might get spammy but at least gets the point across
2017-04-20 23:23:57 +02:00
Thijs Wiefferink 395cc6de07 Fix usage of 'resale' in 'default.yml' for 'flagProfile' section
- 'resale' was used instead of 'resell' (as defined in GeneralRegion#RegionState) in the 'flagProfile' section, causing the flags not getting applied when the region is in reselling state.
- Added fallback to 'resale' section if 'resell' section is not present (to properly read old config.yml/default.yml files)
- Fixed usage of '%resaleprice%' in the resale greeting, should be '%resellprice%'
- Fixes #303, thanks electro2560 for the great bug report!
2017-04-20 15:26:26 +02:00
Thijs Wiefferink 3b9d6187ca Fix checkstyle fail 2017-04-17 21:34:32 +02:00
Thijs Wiefferink 9d1585f513 Fix groups file getting cleared on server start
- Fixes #299
- Because of an earlier change the groups file is loaded before regions are loaded on server start, and region groups remove themselves if there are no valid regions in them anymore.
- Keep groups even though they are empty (so that configuration done in the group is not lost)
2017-04-17 21:32:15 +02:00
Thijs Wiefferink 9d6955039a Fix '/as groupdel' with non-existing group creating an empty one 2017-04-17 21:26:24 +02:00
Thijs Wiefferink 747b47057b Allow # in region name for /as stack to determine counter position 2017-04-11 21:22:35 +02:00
Thijs Wiefferink a973e254e6 Fix '/as addfriend' not allowing you to add players that are playing for the first time
- Fixes #297
2017-04-02 22:34:02 +02:00
Thijs Wiefferink a5105af3ad Add 'use' flag to default.yml again since people mess it up 2017-03-29 20:18:14 +02:00
Thijs Wiefferink 0bb0a05b76 Cleanup checkstyle from pom.xml in AreaShop module 2017-03-29 20:17:42 +02:00
Thijs Wiefferink dad502ec0c Add checkstyle to enforce formatting, change all formatting to comply 2017-03-29 20:15:44 +02:00
Thijs Wiefferink f471b9bbf0 Don't show 'in advance renting:' in '/as info' if maxExtends is zero
- Thanks to R0bstar for noticing this weird weird message combination
2017-03-09 16:14:30 +01:00
Thijs Wiefferink f69d1c41e6 Code cleanup and stricter visibility of methods/variables 2017-03-09 16:01:36 +01:00
Thijs Wiefferink 4ea6f59c49 Remove GeneralRegion#is{Rent|Buy}Region()
- It served no purpose and prevents IDEs from detecting type safety
2017-03-09 15:29:01 +01:00
Thijs Wiefferink 0b7158290f Add some more bStats statistics 2017-03-09 15:09:05 +01:00
Thijs Wiefferink e6757d23de Add bStats analytics
- Add bStats analytics because MCStats is down constantly
- Add custom graphs:
  - Language pie chart
  - Number of registered regions
  - Number of rental regions
  - Number of buy regions
- Closes #287
- In the future MCStats can be removed, for now I would like to keep complete data there for the times it does work
- Could add more graphs in the future for number of friends and things like that
2017-03-02 18:14:07 +01:00
Thijs Wiefferink 7cda35f4da Remove duplicate link in the readme 2017-02-12 20:04:35 +01:00
Thijs Wiefferink ee293d14b9 Fix link to CI in the readme 2017-02-12 20:03:46 +01:00
Thijs Wiefferink 2e0fce4955 Update repository url 2017-02-09 14:59:53 +01:00
Thijs Wiefferink 7afdefc9ed Add missing javadoc to loadFiles methods 2017-02-02 23:03:55 +01:00
Thijs Wiefferink 11f80e7ed1 Start using InteractiveMessenger
- Messaging functionality has been split into the InteractiveMessenger project: https://github.com/NLthijs48/InteractiveMessenger
- Jenkins build server has been changed to publish a Maven repository: http://jenkins.wiefferink.me/plugin/repository/everything
2017-02-02 22:59:00 +01:00
Thijs Wiefferink 40d45c7786 Fix using landlordName setting without using landlord (uuid) setting 2017-02-02 17:20:26 +01:00
Thijs Wiefferink 6b8e161e43 Ensure buy/rent/resell price is never below zero
This prevents problems with negative payment or withdrawal in Vault if input validation of a command is not good enough or if a price has manually been set below zero.
2017-02-02 17:18:26 +01:00
Thijs Wiefferink e1b2f7391c Improve message when you cannot rent/buy a region
The message did not specify which region cannot be rented/bought, which has now been added.
2017-02-02 16:45:15 +01:00
leNic 89ae8b9b4a Fixed reselling with negative prices (#280)
Prevents setting a negative resell price,. Some economy plugins accept a withdraw with a negative number, which has bad consequences (for example EssentialsEco does not accept it).
2017-01-28 20:25:24 +01:00
Thijs Wiefferink fab415bf19 Speed up expiration and expireWarning tasks
The expire task was triggering more often than the expireWarning task, causing lost warnings when warnings with a low time are specified
2017-01-23 14:26:25 +01:00
Thijs Wiefferink c7aa92be81 Indicate in default.yml that expressions can be used for region prices 2017-01-13 19:25:31 +01:00
Thijs Wiefferink 705c8d6891 Update README.md 2017-01-13 18:42:04 +01:00
Thijs Wiefferink 429d87c9ff Improve feature list 2017-01-13 18:41:16 +01:00
Thijs Wiefferink 1fdd8b32cc Add prices depending on a formula
- Price can be specified with an expression (JavaScript syntax)
- You can also use a block of JavaScript code, the value of the last line is used as result (for more complex logic)
- Supported settings: rent.price, buy.price, rent.moneyBack, buy.moneyBack (need more? ask me)
- Before evaluating the expression all region variables as defined in the config.yml file will be replaced (allows you to use %width% etc)
- Added %timesExtended% variable for use in expressions (to know how long the user is renting for)
- '/as setprice' and signs still only allow number input to prevent security problems (this means actual config access is required to do anything of harm with JavaScript)
- Closes #23
2017-01-13 16:56:02 +01:00
Thijs Wiefferink 7900f78a8c Change default WorldGuard flags to include block-place and block-break
Way too many people set the block-break and block-place flags on bigger or even the global region to deny (which is unnecessary and wrong relatively, for global use 'passthrough: deny' instead) so this patches up those mistakes by setting them at the AreaShop region
2017-01-13 16:06:18 +01:00
Thijs Wiefferink 3a65a88849 Fix replacing variables into language variables
- Previously it would insert it as a message, causing the language variable to be split up into multiple parts (which prevents recognizing it later)
- If a Message is marked as inline with Message#inline() it will be inserted without breaking up the message
2016-12-27 16:17:04 +01:00
Thijs Wiefferink 7b491d0389 Lazily instantiate feature classes to improve performance 2016-12-27 16:15:17 +01:00
Thijs Wiefferink fd02c51492 Add setting to prevent cross-world teleportation with '/as find'
- If a region is in another world and 'findCrossWorld' is off, it will be filtered from the results
- 'teleportCrossWorld' will not be checked anymore if the teleport is called from '/as find'
- Resolves  #234
2016-12-26 23:29:40 +01:00
Thijs Wiefferink bb1f592807 Add setting to prevent cross-world teleportation with '/as tp'
Related to #234
2016-12-26 23:20:12 +01:00
Thijs Wiefferink 16c7a044ea Add and make use of escaping in messages
- Add escaping support to FancyMessageFormat: put a backslash before a tag and it will not be interpreted, also works for color codes (&2 and §2) and variables
- Make use of escaping in the Message class to properly handle user input as plaintext (previously user input that got printed back also got interpreted, like colors apply to it)
- All replacements given to Message are escaped, unless you wrap them in a Message before it.
- Replacement limits and infinite replacing detection have changed slightly
2016-12-26 22:56:35 +01:00
Thijs Wiefferink 152f183670 Fix region update after /as reload, cleanup unused code 2016-12-25 23:02:23 +01:00
Thijs Wiefferink 09b6e4c584 Fix string setting as profile in default.yml
- This has been broken in b525c5902c
- Fixes #267
2016-12-25 22:40:11 +01:00
Thijs Wiefferink aeb70196a3 Fix shutdown exception in SignsFeature 2016-12-24 19:59:04 +01:00
Thijs Wiefferink 62148ea577 Move teleportation code to a feature class 2016-12-23 23:25:41 +01:00
Thijs Wiefferink fcc9fb0945 Refactor to introduce Manager and RegionFeature
- Manager to serve as abstract class for managers (like FileManager), implements a lifecycle to allow for shutdown operations (like save all files)
- RegionFeature to server as abstract class for region features
- Improve instantiation and lifecycle of features (global features and region specific ones)
- This is preparation for moving more region functionality into feature specific classes (which makes GeneralRegion smaller and easier to maintain)
2016-12-23 16:03:22 +01:00
Thijs Wiefferink b525c5902c Fix default settings leaking when getting configuration sections
- Default settings and custom settings are in separate YamlConfiguration instances now to prevent leaking
- When getting a configuration section you now know that either it is from the current default.yml file or the default.yml file in the jar (querying settings from the section will always have the same source)
- Fixed a startup null pointer
- Fixed #266
2016-12-23 14:07:26 +01:00
Thijs Wiefferink b18a7cc6d5 Fix [break] in hover lines
Hover lines are still a bit broken when trying to insert complex messages into them, this is hard to do in a proper way though.
2016-12-10 15:19:34 +01:00
Thijs Wiefferink 54b6e84558 File loading improvements to be more robust 2016-12-04 21:14:31 +01:00
Thijs Wiefferink bae3c0a199 Ask for config files in the issue template 2016-12-04 17:56:37 +01:00
Very Evil Olaf 508270a280 Update ISSUE_TEMPLATE.md (#258)
Note that 'latest' is not a correct version
2016-12-04 17:48:36 +01:00
Thijs Wiefferink 87b2cb9ecd Prevent exceptions if a schematicProfile does not exist 2016-12-04 17:31:22 +01:00
Thijs Wiefferink 271b17b6be Set version to 2.4.0 and update language files 2016-12-02 14:37:27 +01:00
Desjardins Jérôme 1dfedea50c Create a permission for teleporting to available regions (#241)
* Update GeneralRegion.java
Add Available abstract function
Check Permission modification

Update BuyRegion.java
Add Available function for selled

Update RentRegion.java
Add Available function for rented

Update TeleportCommand.java
Check new permission

Finish feature teleport available region

* Fix translation
2016-11-19 17:15:23 +01:00
Thijs Wiefferink d9abe2e347 FIx problem with null regionFlags sections 2016-11-14 18:02:59 +01:00
Thijs Wiefferink 1ba2e21ae9 Make profile based settings easier to understand
- Remove 'signProfiles', 'flagProfiles', 'eventCommandProfiles', 'schematicProfiles' and 'expirationWarningProfiles' from the config.yml file
- Add the removed profile contents directly to the setting without the trailing 's' in the default.yml file (this prevents the need to understand the creation of profiles, and then assigning them, taking out a step in the thought process of users)
- Added method in GeneralRegion to get a ConfigurationSection from the region file, group files or default file while also taking into account the translation from a string to a profile that is defined in the config.yml file. This ensures backwards compatibility with current config files (the old style will not be promoted anymore though).
- The 'expirationWarningProfiles' does not have the subkeys 'warnPlayer' and 'commands' anymore, it is just a list of commands now, using the '/areashop message' command to warn a player.
- Add a workaround for the empty sign problem after unrent/sell of a region while having restore enabled, an AsyncWorldEdit plugin installed and signs inside the region (sign will be updated again half a second after unrent/sell).
2016-11-13 00:44:35 +01:00
Thijs Wiefferink fcdfdc6094 Update language files
- Swedish is added
- German, French and Russian are improved
2016-11-07 14:13:03 +01:00
Thijs Wiefferink 8a7e2ba5c2 Indicate limits when a MaxChangedBlocksException is thrown by WorldEdit 2016-11-07 13:52:54 +01:00
Thijs Wiefferink 0950452692 Fix a couple of problems caused by vertical /as stack addition 2016-10-31 21:07:08 +01:00
Thijs Wiefferink 293978f068 Decouple Message class from replacement providers
- Instead of applying replacements by testing for a replacement that has the GeneralRegion type, it now checks for a ReplacementProvider instances (one step closer to making Message independent of AreaShop).
- Replacements are handled one by one with a switch-case instead of getting them from a map, helps performance a lot.
2016-10-25 20:57:58 +02:00
Thijs Wiefferink 5efe14b8e9 Add up and down direction for '/as stack'
Prevents creating regions above and below the world limits, still needs
testing.
Progress for #105
2016-10-24 15:02:49 +02:00
Thijs Wiefferink 839d510151 Switch from Bukkit to Spigot Maven repo since it is down
Also switched from Bukkit 1.7.9 to Bukkit 1.8.0 because that is the lowest version available on the Spigot repo
2016-10-17 23:45:08 +02:00
Thijs Wiefferink 9180d9b8ee Fix small language file issue
When you only have a formatting code before a language variable, the hover/click effects will accidentally be applied to one text part earlier
2016-10-16 18:29:26 +02:00
Thijs Wiefferink 7aa786f4ce Fix default config trying to execute /as message as player
Fixes #221
2016-09-04 22:43:03 +02:00
Thijs Wiefferink 10ea8540e7 Add tab completion for '/as me [player]'
Closes #220
2016-09-04 00:59:04 +02:00
Thijs Wiefferink dba00769f6 Fix problem with line breaks 2016-09-03 20:58:45 +02:00
Thijs Wiefferink 44c70c6a33 Cleanup messages about region that does not exist in WorldGuard 2016-09-03 19:46:05 +02:00
Thijs Wiefferink 570f7b4055 Don't extend rent when using '/as setowner' to set a different owner
Also remove new owner from the friends list.
Fixes #219
2016-09-03 19:19:40 +02:00
Thijs Wiefferink feafa012dd Fix issue with \n on 1.7.x servers
- Each line of a message will now be sent separately instead of using '\n' in the text
- Drastically reduced the number of StringBuilders that are instantiated during message construction
- Fixes #222
2016-09-03 17:22:40 +02:00
Thijs Wiefferink 82b340ce1a Messages: also parse section signs into color tags properly
Fixes line-wrapping removing color in some cases
2016-09-02 21:43:58 +02:00
Thijs Wiefferink 93809f27fc Rename events to be consistent with commands
- AddFriendEvent -> AddedFriendEvent
- DeleteFriendEvent -> DeletedFriendEvent
- RemovedRegionEvent -> DeletedRegionEvent
2016-08-28 22:45:20 +02:00
Thijs Wiefferink f8ed3654fd Fix compilation error 2016-08-28 22:31:06 +02:00
Thijs Wiefferink 58a1afccf3 Invert inheritance of RegionEvent and CancellabelRegionEvent/NotifyRegionEvent 2016-08-28 22:29:45 +02:00
Thijs Wiefferink d9c3573225 Change RegionUpdateEvent to UpdateRegionEvent
This breaks plugins using the event
2016-08-28 22:21:33 +02:00
Thijs Wiefferink 1577443203 Generalize events better
Adds generic classes for events with a region (BuyRegion, RentRegion or GeneralRegion)
2016-08-28 22:11:17 +02:00
Thijs Wiefferink ab7a92c57d Update README.md 2016-08-26 19:49:21 +02:00
Thijs Wiefferink 2e09b0f60f Update README.md 2016-08-26 19:48:59 +02:00
Thijs Wiefferink 6e5c35f95b Add extension plugins section 2016-08-26 19:45:37 +02:00
Thijs Wiefferink 066ceedafd Add 'build' flag by default to prevent inheriting from parent regions. 2016-08-21 22:20:25 +02:00
Thijs Wiefferink c8ebe24bd3 Put break in '/as info' help message in the right spot 2016-08-19 20:52:07 +02:00
Thijs Wiefferink ad7db5ee23 Attempt to fix unable to add new players with '/as addfriend' 2016-08-16 22:43:09 +02:00
Thijs Wiefferink f80a8178ff Fix formatting not clearing on new lines 2016-08-13 22:28:15 +02:00
Thijs Wiefferink 05c8e1a7e4 Make loggin methods more generic 2016-08-10 16:07:39 +02:00
Thijs Wiefferink 9904805310 Improve version detection of WorldGuard
- First detect version 5
- Then detect lower than 6.1.3
- Else check build number:
  - Build number exists and lower than 1672: 6
  - Build number exists and Is 1672: broken
  - Else: 6_1_3
2016-08-09 22:56:21 +02:00
Thijs Wiefferink 0df2f429c0 Only print build number warning if we found it 2016-08-09 22:38:33 +02:00
Thijs Wiefferink b3408a2648 Fix compilation error and WorldGuard version detection 2016-08-09 21:52:53 +02:00
Thijs Wiefferink e1f89e7904 Cleanup some leftover logging in an old style 2016-08-08 22:06:31 +02:00
Thijs Wiefferink 064b129205 Change version to 2.3.2 and update languages
- Russian has been added
- Spelling errors in French have been corrected
2016-08-07 22:21:57 +02:00
Thijs Wiefferink 8111387f32 Improve friend line of /as me, add click hint
Change blue color of hover popup to gold, which will be used as action color
2016-08-07 22:06:09 +02:00
Thijs Wiefferink a4b23bebba Improve line of rented regions in info command 2016-08-07 21:49:44 +02:00
Thijs Wiefferink 7421e685fa Improve robustness of variable replacing in messages
- Once the limit is reached the argument replacer would still continue sometimes (if a the replacement itself triggers the limit and the inserted message increases the size of the message we are building and adds a parameter tag again, like %0%)
- The language tag replacer also stops earlier now, it did not have the above problem though
- Fixed FancyMessageFormat.insertMessage() not checking its parameters
- Message.get() with a limit will now return an empty message if the limit is reached to prevent problems like above.
- Closes #205
2016-08-07 00:09:30 +02:00
Christoph Krüger @Androkai 374b68d8fa Update VaultAPI-repo (#206)
The url of the Vault Maven repository has changed.
2016-08-06 22:36:26 +02:00
Thijs Wiefferink ca7a9664f3 Fix error in info command of rental region 2016-08-02 22:27:55 +02:00
Thijs Wiefferink e957b4fc73 Remove all reflection for sending fancy messages
- Reflection removed
- JSON is now fully escaped instead of lenient escaping
- Sending is now done with /tellraw
2016-08-01 21:31:14 +02:00
Thijs Wiefferink 6493bc4f78 Add empty lines if a page does not fill the screen
Relevant for commands like `/as info all` that can have pages
2016-08-01 21:28:11 +02:00
Thijs Wiefferink 2db1b6a243 Prevent updater throwing errors sometimes when BukkitDev is offline 2016-08-01 20:46:40 +02:00
Thijs Wiefferink 864662a3c4 Add javadocs to limitgroup inner class and setting methods 2016-08-01 20:23:49 +02:00
Thijs Wiefferink 59b960b87c Update languages, set version number to 2.3.1
- Added German translation
- Added French translation
- Added Norwegian translation
2016-08-01 19:36:50 +02:00
Thijs Wiefferink 2de653d73f Add automatic conversion of language files downloaded from Transifex 2016-08-01 18:03:09 +02:00
Thijs Wiefferink 4feee59834 Cleanup info and warning messages. 2016-08-01 16:18:05 +02:00
Thijs Wiefferink 816d136a62 Change version number to 2.3.0, update languages
- Only English and Dutch are available yet
2016-07-24 21:48:39 +02:00
Thijs Wiefferink 121de7dc7e Add build number option to Maven
- Build number option can be used by Jenkins to add the build number
- Build number of soruce on Github will be #CUSTOM to mark custom builds
- Release versions will not contain a build number
- Closes #185
2016-07-24 20:59:25 +02:00
Thijs Wiefferink f7c86881cc Fix the default flagProfiles settings, fix migration issues. 2016-07-24 20:25:58 +02:00
Thijs Wiefferink 351d51b664 Correct message for reselling region entries. 2016-07-23 19:31:45 +02:00
Thijs Wiefferink 5a28476cf0 Add better limits for messages
- Recursive replacements are handled by catching stackoverflow exceptions
- Messages are limited 30000 in length for the produced JSON string since the client cannot handle more
- Error message print out the message key and start of the message
2016-07-23 17:21:08 +02:00
Thijs Wiefferink 8143bdae27 Add pages to the /as info command to prevent crashes
- Output displayed in pages of 18 (+ header and footer it fills the max of 20 you can see without scrolling)
- Footer shows current and max page, and has buttons to go to next/previous
- Commands accept [group] [page] both as optional arguments, works because page is a number (number as group would be weird)
- Entries show basic info about regions (owner/price/duration), and of course can be clicked to show all info
- Made debug command more generic
- Closes #172
2016-07-23 02:57:31 +02:00
Thijs Wiefferink b62744fe10 Add warnings for missing settings in limit groups.
Closes #164
2016-07-21 23:00:19 +02:00
Thijs Wiefferink fe0b63c760 Remove incorrect check 2016-07-17 23:51:24 +02:00
Thijs Wiefferink def774c7a1 Link javadoc of external classes properly
Classes of Bukkit, WorldGuard and WorldEdit that appear in the javadoc now link to the javadoc of them.
2016-06-27 23:38:44 +02:00
Thijs Wiefferink 7c5cb5f5aa Add javadoc to getApplicableRegions methods 2016-06-27 23:16:21 +02:00
Thijs Wiefferink 8af6e1d6f1 Fix small Maven warning 2016-06-25 22:20:21 +02:00
Thijs Wiefferink e0e6c39dae Fix WorldGuard version detection for later stable releases 2016-06-21 22:42:56 +02:00
Thijs Wiefferink dabe46bd47 Move all sign related methods into SignsFeature
- Each GeneralRegion now has a SignsFeature
- Each SignsFeature contains RegionSign instances representing the actual signs
 - The RegionSign class contains methods to update them etc
 - There is one SignsFeature that is not linked to a region, but registered as listener to update the signs at events
2016-06-20 20:16:13 +02:00
Thijs Wiefferink ec478a05d1 Move region friends code to separate Feature class 2016-06-20 18:22:35 +02:00
Thijs Wiefferink e21c0aafde Add events for adding/removing friends
- Cancellable event for adding friends: AddFriendEvent (region, friend and who is trying to add it available)
- Cancellable event for removing friends: DeleteFriendEvent (region, friend and who is trying to delete it available)
- Closes #54
2016-06-20 17:03:12 +02:00
Thijs Wiefferink 52bf88f4a5 Prevent RemovedRegionEvent triggering twice
Add debug logging for RemovedRegionEvent and AddedRegionEvent
2016-06-20 16:32:10 +02:00
Thijs Wiefferink e4de560a65 Prevent Maven warning
It was trying to include the WorldGuard6 handler class twice in the jar file
2016-06-20 16:28:23 +02:00
Thijs Wiefferink 6481da29e4 Add compatibility for WorldGuard build 1674 and up.
- Added a module for WorldGuard 6.1.3 to adapt to changes in flag related methods.
- No compatibility with build 1672, since there is no dependency in the Maven repo of WorldGuard to target this (this is a dev build though so I don't expect people using it long-term)
- Move properties for setting Java version to the parent pom file (other modules inherit them)
- Move repositories to the parent pom file (other modules inherit them)
- Fix AreaShop module not declaring its parent
- Fixes #170
2016-06-18 23:23:22 +02:00
Thijs Wiefferink f287cc91a6 Remove leftover logging 2016-06-13 21:11:00 +02:00
Thijs Wiefferink 95b7b50346 Add development builds and javadocs links 2016-06-13 20:59:08 +02:00
Thijs Wiefferink 21db2ff440 Improve Maven setup for javadoc and source
- Generate a .jar file with the sources if -Dsources is provided
- Generate javadocs if -Djavadoc=/path/to/destination is provided
- Copy final .jar file to directory if -DcopyResult=/path/to/server is provided
- Adding missing javadoc comments
2016-06-13 20:36:17 +02:00
Thijs Wiefferink 36dd978a92 Cleanup the config file and settings
- Moved scheduled task settings to hiddenConfig.yml (settings that are not visible, but still usable)
- Remove 'postCommandErrors' setting
- Also execute commands defined in 'expirationWarningProfiles' when player is not online
- Move around settings in config.yml and improve explanations
- Closes #168
2016-06-13 17:03:20 +02:00
Thijs Wiefferink 6cc3f2a52f Combine all messages about region not registered
And replace mentions of 'this region' with the actual region name.
2016-06-13 14:47:10 +02:00
Thijs Wiefferink 852731b1ad Add action confirmations when using signs
- Confirmations by default for selling/unrenting by signs, not for renting/buying
- Fixed a bug with message composing which would duplicate hover/click parts.
- Added `/as message <player> <message...>` for internal/console use (not shown in help).
- Closes #50
2016-06-12 21:39:12 +02:00
Thijs Wiefferink 30faa77da9 Fix small error in multiple regions found message 2016-06-12 19:38:05 +02:00
Thijs Wiefferink ec57b922b8 Messages: Version fixes, fallback to plain.
- Properly parse NMS version number to support 1.10
- Fallback to plain messages if sending a fancy message breaks (we try again after restart/reload)
2016-06-12 16:48:47 +02:00
Thijs Wiefferink c2bcc6008b Add hover/click to some forgotten mentions of regions 2016-06-12 13:25:55 +02:00
Thijs Wiefferink 72a05bedce Merge personal/all help messages. 2016-06-11 22:40:09 +02:00
Thijs Wiefferink bc73a8b20d Cleanup links to issues 2016-06-11 17:35:00 +02:00
Thijs Wiefferink 832e8218b6 Add issue template for bugs/feature requests 2016-06-11 17:29:47 +02:00
Thijs Wiefferink 6fcb629133 Move to the me.wiefferink package
The nl.evolutioncoding domain is soon not mine anymore, and this new one I do intend on keeping.
2016-06-07 22:39:45 +02:00
Thijs Wiefferink 7d6a1f35c1 Messages: Add hover/click items to region info
- The hover/click parts only appear if you have permission to use the command linked to it
- Red crosses added next to the friends in the friend list to remove them
2016-06-05 17:04:38 +02:00
Thijs Wiefferink 60257d45c2 Messages: Add a bunch of click/hover actions to '/as info region <region>'
- rent/unrent/buy/sell/extend
- teleport, set teleport location
2016-06-04 22:46:10 +02:00
Thijs Wiefferink 2300227d87 Messages: Fix problem with message insertion sometimes missing leftover part 2016-06-04 20:57:14 +02:00
Thijs Wiefferink b1852b10bb Improve /as me, remove /as info player
And change underline to bold for clickable things (underline leaks through in the next line, giving annoying layout issues)
2016-06-02 22:39:30 +02:00
Thijs Wiefferink 88fdb0c3fc Add 'ALL' flagProfiles state that is always applied
And add block-break and block-place by default.
2016-05-31 22:36:18 +02:00
Christoph Krüger 491e55fc77 Change Vault dependency to Maven repo instead of .jar
Use the Maven repository of Vault instead of including a jar file in the repository.
2016-05-28 23:19:23 +02:00
Thijs Wiefferink c7b0a2c663 Let /as help respect the areashop.help permission
Closes #155
2016-05-28 17:04:06 +02:00
Thijs Wiefferink 868f4d6b58 Events: Add refunded money to UnrentedRegionEvent and SoldRegionEvent
Closes #147
2016-05-28 17:00:52 +02:00
Thijs Wiefferink 0d4b034181 Messages: Detect and stop replacement loops. 2016-05-28 16:51:32 +02:00
Thijs Wiefferink 5f20689ba3 Improve language variable argument detection 2016-05-27 21:01:14 +02:00
Thijs Wiefferink 38b658fccf Message changes: click on regions/players
- Click on region name for region details
- Click on player name for regions of the player
- Click on landlord name for regions of the landlord
- Change color codes to things like [blue] instead of &9 (both are supported though)
2016-05-24 23:18:25 +02:00
Thijs Wiefferink 28ed20c0b5 Message system fixes and improved robustness
Hover/click message for '/as info'
2016-05-23 12:16:19 +02:00
Thijs Wiefferink aaff36b2eb Add hover/click support for messages #18
- Language files support a way to add hover tooltips, click commands and click insert commands (using FancyMessageFormat created together with PhoenixIV)
- Language variables support arguments
- Help listing (/as help) uses the new hover/click things, more to come
- See #18 for the TODO list
2016-05-22 21:36:58 +02:00
Spidev46 49b536cab0 There is a mistake with the messages (#153)
Confirmation message of reselling a region does not go to the old owner.
2016-05-22 21:19:10 +02:00
Thijs Wiefferink 9a9969dea3 Fix problems with detecting most important region when placing a sign.
WorldGuard filters child regions from the ApplicableRegionSet for some reason, so setting parent/child relations on regions messes up the detection because the children can never be found.
2016-03-07 12:21:33 +01:00
Thijs Wiefferink 84e4b49bfd Update language files
English, Dutch and German are complete, others are only partially complete.
2016-03-06 00:07:31 +01:00
Thijs Wiefferink 6daaf6997d 1.9 compatibility fixes (getOnlinePlayers() method) 2016-03-05 22:15:21 +01:00
Thijs Wiefferink c445a62334 Add variable support to all messages where a region is known (mostly messages of commands were not using it yet). 2016-02-08 19:39:33 +01:00
Thijs Wiefferink 0d205b0d9e Change general.teleportLocationY to also accept a number. 2016-02-08 14:43:24 +01:00
Thijs Wiefferink 8c33826c2c Add build flag to default flagprofiles, users are often having trouble with this 2016-01-26 22:35:01 +01:00
Thijs Wiefferink 51e1dbafb7 Fix running sign commands going wrong.
Player commands are run first, which normally rents/buys a region, so when the console commands are ran after that, then it will run the commands of the wrong state (since the state just changed). Also fixed a minor WorldGuard version dependency issue.
2016-01-26 22:20:45 +01:00
Thijs Wiefferink 6edad3faff Add priority and interact flag to config.yml by default
50% of the issues are having more regions at the same place, and therefore requiring a higher priority for the AreaShop region and having chests accessible by anyone instead of only the region owner/member. This should fix these problems.
2016-01-12 21:43:04 +01:00
Thijs Wiefferink f89e0f861b Add a link to a preview server 2016-01-08 20:18:47 +01:00
Thijs Wiefferink 770b1d98df Refactoring:
- Move a bunch of utility methods from the AreaShop class to Utils
- Comments styling cleanup
- Display warning in console when region file does not load correctly (is empty)
2016-01-05 22:31:55 +01:00
Thijs Wiefferink 8e5baa47af Fix variables of sell messages 2015-12-26 20:08:01 +01:00
Thijs Wiefferink 367466735e Fix the 'n:<name>' option for member/owner region flags
Before it would keep the 'n:' part when adding the name to the region, therefore adding an incorrect name.
2015-12-26 00:45:05 +01:00
Thijs Wiefferink ade6bc3971 Add AddedRegionEvent+RemovedRegionEvent, moved sign/flags updating
- Sign updating now uses the event system
- WorldGuard region flag updating now uses the event system
- After changes made by commands/other user input it will now use update() on the region instead of the sign and region flag commands.
2015-12-13 19:55:47 +01:00
Thijs Wiefferink 05fd8c0156 Implement event system
- 'ask' type events that can be cancelled, broadcasted before the action happens
- 'notify' type events that cannot be cancelled, but only let you know that it has happened
- Events for renting (with extending flag), unrenting, buying, selling, reselling
- General 'RegionUpdateEvent' to listen to as display service (update signs, update regions flags, etc)

All messages in the GeneralRegion, RentRegion and BuyRegion classes can now use all region variables (in the future all other messages will also be worked through)

Closes #84
Closes #86
2015-12-11 20:54:12 +01:00
Thijs Wiefferink 5403472476 Cleanup/update config.yml and default.yml comments
- Add links to the wiki.
- Make things more consistent.
2015-12-10 23:16:19 +01:00
Thijs Wiefferink c4ee7bc7aa Add 'areashop.linksigns' to the 'areashop.*' permission group 2015-11-24 20:59:58 +01:00
Thijs Wiefferink 940f1969b6 Add link to dev builds on Jenkins 2015-11-24 20:43:44 +01:00
Thijs Wiefferink 07eb5477b8 Update Metrics 2015-11-17 21:21:27 +01:00
Thijs Wiefferink a4112092b5 Fix inclusion of the version specific classes of WorldGuard/WorldEdit in the final jar 2015-11-15 23:47:23 +01:00
Thijs Wiefferink 872d81b0a9 Cleanup code, fix some small bugs
Most of these things have been found with help of Intellij IDEA, preferring it over Eclipse already
2015-11-15 23:35:48 +01:00
Thijs Wiefferink b04871c553 Add rental to max when rental would go above maxRentTime otherwise
- Setting `extendToFullWhenAboveMaxRentTime` to set if AreaShop should
automatically extend to max when the player cannot rent a full duration
because of `maxRentTime`.

Fixes #94
2015-11-11 11:56:16 +01:00
Thijs Wiefferink fd1b8fba4c Fix inactiveSetting debug messages
The messages still assumed the inactiveSettings was in minutes instead
of in milliseconds.
2015-11-02 22:39:40 +01:00
Thijs Wiefferink 991e7b1961 Fix lastActive sometimes not updating correctly
- Update the lastActive time at rent/buy to ensure it is non-zero
- Ignore lastActive time of zero, give back current time
- Update lastActive time at server shutdown for players that are still
online, quit events are not fired at shutdown
2015-11-02 22:32:51 +01:00
Thijs Wiefferink aac963325a CI run :) 2015-11-01 23:03:41 +01:00
Thijs Wiefferink 3e7c7ce76b Fix message variables for '/as info player <player>' feedback 2015-10-31 23:15:11 +01:00
Thijs Wiefferink c858d30481 Add .gitignore to the repository 2015-10-19 19:07:31 +02:00
Thijs Wiefferink b090fd5577 Remove MCStats image, site is more offline than online 2015-10-17 22:10:38 +02:00
248 changed files with 26536 additions and 17253 deletions

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
---
name: Bug report
about: Report a problem in the plugin
title: ''
labels: 'NEW, Type: Bug'
assignees: ''
---
**Current behavior**
<!--- Tell what happens currently and/or show the stacktrace if an error occured -->
**Expected behavior**
<!--- Tell what you expected to happen instead -->
**Steps to reproduce**
<!--- Exactly show how this bug can be triggered -->
1.
2.
3.
4.
**Your environment**
<!--- Indicate which versions you use -->
<!--- Attention: "LATEST" or "NEWEST" are invalid answers! -->
<!--- Use '/version' for the Platform version and '/version <plugin>' for plugin versions -->
* Server platform (Bukkit, Spigot, etc.):
* Server version:
* AreaShop version:
* WorldEdit version:
* WorldGuard version:
* Vault version:
**Your config files**
<!--- Attach a '.zip' file of the '/plugins/AreaShop' and '/plugins/WorldGuard' directories to show your configuration setup -->
+15
View File
@@ -0,0 +1,15 @@
---
name: Feature request
about: Suggest an idea
title: ''
labels: 'NEW, Type: Feature'
assignees: ''
---
**Describe the problem you are trying to solve with this feature request**
<!-- When I know the problem you are trying to solve I can help you to think about the best solution -->
**Describe the solution you'd like**
<!-- Describe the config settings, commands and other features that should be added -->
@@ -0,0 +1,31 @@
---
name: Support question
about: Ask help with the setup or usage of the plugin
title: ''
labels: 'NEW, Type: Support'
assignees: ''
---
**Current behavior**
<!--- Tell what happens currently -->
**Desired behavior**
<!--- Tell what you want to happen instead -->
**Your environment**
<!--- Indicate which versions you use -->
<!--- Attention: "LATEST" or "NEWEST" are invalid answers! -->
<!--- Use '/version' for the Platform version and '/version <plugin>' for plugin versions -->
* Server platform (Bukkit, Spigot, etc.):
* Server version:
* AreaShop version:
* WorldEdit version:
* WorldGuard version:
* Vault version:
**Your config files**
<!--- Attach a '.zip' file of the '/plugins/AreaShop' and '/plugins/WorldGuard' directories to show your configuration setup -->
+23
View File
@@ -0,0 +1,23 @@
name: Java CI with Gradle
on: [ push, pull_request ]
jobs:
build:
# Only run on PRs if the source branch is on someone else's repo
if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}
strategy:
matrix:
os: ["ubuntu-latest", "windows-latest"]
runs-on: "${{ matrix.os }}"
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1.0.4
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'zulu'
cache: 'gradle'
- name: Build with Gradle
run: ./gradlew clean build
+194
View File
@@ -0,0 +1,194 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,windows,intellij+all,gradle,maven
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,intellij+all,gradle,maven
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/*
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
# Eclipse m2e generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
### Gradle ###
.gradle
build/
build-cache/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
### Gradle Patch ###
**/build/
# End of https://www.toptal.com/developers/gitignore/api/macos,windows,intellij+all,gradle,maven
-1
View File
@@ -1 +0,0 @@
/target
+105
View File
@@ -0,0 +1,105 @@
plugins {
id("com.github.johnrengelman.shadow") version "8.1.1"
}
description = "AreaShop"
dependencies {
// Platform
compileOnlyApi(libs.spigot)
compileOnlyApi(libs.worldeditCore) {
exclude("com.google.guava", "guava")
}
compileOnlyApi(libs.worldeditBukkit) {
exclude("com.google.guava", "guava")
}
compileOnlyApi(libs.worldguardCore) {
exclude("com.google.guava", "guava")
}
compileOnlyApi(libs.worldguardBukkit) {
exclude("com.google.guava", "guava")
}
compileOnlyApi("com.github.MilkBowl:VaultAPI:1.7") {
exclude("com.google.guava", "guava")
}
// 3rd party libraries
implementation("io.papermc:paperlib:1.0.8")
implementation("com.github.NLthijs48:InteractiveMessenger:e7749258ca")
implementation("com.github.NLthijs48:BukkitDo:819d51ec2b")
implementation("io.github.baked-libs:dough-data:1.2.0")
implementation("com.google.inject:guice:7.0.0") {
exclude("com.google.guava", "guava")
}
implementation("com.google.inject.extensions:guice-assistedinject:7.0.0") {
exclude("com.google.guava", "guava")
}
// Project submodules
implementation(projects.areashopInterface)
implementation(projects.areashopNms)
implementation(projects.adapters.platform.platformInterface)
implementation(projects.adapters.platform.paper)
runtimeOnly(projects.adapters.plugins.worldedit)
runtimeOnly(projects.adapters.plugins.worldguard)
runtimeOnly(projects.adapters.plugins.fastasyncworldedit)
// Adapters
if (!providers.environmentVariable("JITPACK").isPresent) {
runtimeOnly(project(":adapters:platform:bukkit-1-17", "reobf"))
}
runtimeOnly(projects.adapters.platform.bukkitModern)
testImplementation("com.github.seeseemelk:MockBukkit-v1.20:3.9.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
}
repositories {
mavenCentral()
}
tasks {
processResources {
filesMatching("plugin.yml") {
expand("version" to project.version)
}
}
assemble {
if (!providers.systemProperty("JITPACK").isPresent) {
dependsOn(shadowJar)
}
}
jar {
archiveClassifier.set("original")
}
java {
withSourcesJar()
}
val javaComponent = project.components["java"] as AdhocComponentWithVariants
javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
skip()
}
shadowJar {
archiveClassifier.set("")
base {
archiveBaseName.set("AreaShop")
}
val base = "me.wiefferink.areashop.libraries"
relocate("me.wiefferink.interactivemessenger", "${base}.interactivemessenger")
relocate("me.wiefferink.bukkitdo", "${base}.bukkitdo")
relocate("io.papermc.lib", "${base}.paperlib")
relocate("io.github.bakedlibs.dough", "${base}.dough")
relocate("com.google.inject", "${base}.inject")
relocate("com.google.errorprone", "${base}.errorprone")
relocate("org.aopalliance", "${base}.aopalliance")
relocate("javax.annotation", "${base}.javax.annotation")
relocate("jakarta.inject", "${base}.jakarta.inject")
}
}
-159
View File
@@ -1,159 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>nl.evolutioncoding</groupId>
<artifactId>areashop</artifactId>
<packaging>jar</packaging>
<name>AreaShop</name>
<version>2.2.1</version> <!-- Main plugin version -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>bukkit-repo</id>
<url>http://repo.bukkit.org/content/groups/public</url>
</repository>
<repository>
<id>sk89q-repo-wg</id>
<url>http://maven.sk89q.com/artifactory/repo/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.7.9-R0.2</version>
<type>jar</type>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>nl.evolutioncoding</groupId>
<artifactId>areashop-worldguard-5</artifactId>
<version>WorldGuard-5</version>
<type>jar</type>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>nl.evolutioncoding</groupId>
<artifactId>areashop-worldguard-6</artifactId>
<version>WorldGuard-6</version>
<type>jar</type>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>nl.evolutioncoding</groupId>
<artifactId>areashop-interface</artifactId>
<version>AreaShopInterface</version>
<type>jar</type>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>nl.evolutioncoding</groupId>
<artifactId>areashop-worldedit-5</artifactId>
<version>WorldEdit-5</version>
<type>jar</type>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>nl.evolutioncoding</groupId>
<artifactId>areashop-worldedit-6</artifactId>
<version>WorldEdit-6</version>
<type>jar</type>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.sk89q</groupId>
<artifactId>worldedit</artifactId>
<version>6.0.0-SNAPSHOT</version>
<scope>provided</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.sk89q</groupId>
<artifactId>worldguard</artifactId>
<version>6.0.0-SNAPSHOT</version>
<scope>provided</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>net.milkbowl</groupId>
<artifactId>vault</artifactId>
<version>1.4.1</version>
<scope>system</scope>
<systemPath>${basedir}/../dependencies/Vault.jar</systemPath>
</dependency>
</dependencies>
<build>
<directory>target</directory>
<finalName>AreaShop</finalName>
<!-- Include all required resources -->
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<includes>
<include>plugin.yml</include>
<include>config.yml</include>
<include>default.yml</include>
<include>README.md</include>
<include>lang/*.yml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<!-- Copy resulting file to local server, change to your own path -->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<configuration>
<tasks>
<copy file="./target/${project.build.finalName}.jar" tofile="C:\Games\Minecraft\Spigot\plugins\${project.build.finalName}.jar"/>
</tasks>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target failonerror="false"></target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,797 @@
package me.wiefferink.areashop;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.managers.RegionManager;
import io.papermc.lib.PaperLib;
import me.wiefferink.areashop.adapters.platform.MinecraftPlatform;
import me.wiefferink.areashop.adapters.platform.paper.PaperPlatform;
import me.wiefferink.areashop.features.signs.SignManager;
import me.wiefferink.areashop.interfaces.AreaShopInterface;
import me.wiefferink.areashop.interfaces.WorldEditInterface;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.listeners.PlayerLoginLogoutListener;
import me.wiefferink.areashop.managers.CommandManager;
import me.wiefferink.areashop.managers.FeatureManager;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.managers.FileManager;
import me.wiefferink.areashop.managers.Manager;
import me.wiefferink.areashop.managers.SignErrorLogger;
import me.wiefferink.areashop.managers.SignLinkerManager;
import me.wiefferink.areashop.modules.AreaShopModule;
import me.wiefferink.areashop.modules.BukkitModule;
import me.wiefferink.areashop.modules.DependencyModule;
import me.wiefferink.areashop.modules.PlatformModule;
import me.wiefferink.areashop.platform.adapter.PlatformAdapter;
import me.wiefferink.areashop.tools.GithubUpdateCheck;
import me.wiefferink.areashop.tools.SimpleMessageBridge;
import me.wiefferink.areashop.tools.SpigotPlatform;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.areashop.tools.version.Version;
import me.wiefferink.areashop.tools.version.VersionData;
import me.wiefferink.areashop.tools.version.VersionUtil;
import me.wiefferink.bukkitdo.Do;
import me.wiefferink.interactivemessenger.source.LanguageManager;
import net.milkbowl.vault.economy.Economy;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
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.HandlerList;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import javax.annotation.Nonnull;
import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* Main class for the AreaShop plugin.
* Contains methods to get parts of the plugins functionality and definitions for constants.
*/
public final class AreaShop extends JavaPlugin implements AreaShopApi {
// Statically available instance
private static AreaShop instance = null;
// General variables
private Injector injector;
private WorldGuardPlugin worldGuard = null;
private WorldGuardInterface worldGuardInterface = null;
private WorldEditPlugin worldEdit = null;
private WorldEditInterface worldEditInterface = null;
private PlatformAdapter platformAdapter;
private MessageBridge messageBridge;
private IFileManager fileManager = null;
private LanguageManager languageManager = null;
private CommandManager commandManager = null;
private SignLinkerManager signLinkerManager = null;
private FeatureManager featureManager = null;
private SignManager signManager;
private SignErrorLogger signErrorLogger;
private Set<Manager> managers = null;
private boolean debug = false;
private List<String> chatprefix = null;
private boolean ready = false;
private GithubUpdateCheck githubUpdateCheck = null;
// Folders and file names
public static final String languageFolder = "lang";
public static final String schematicFolder = "schem";
public static final String regionsFolder = "regions";
public static final String groupsFile = "groups.yml";
public static final String defaultFile = "default.yml";
public static final String configFile = "config.yml";
public static final String configFileHidden = "hiddenConfig.yml";
public static final String signLogFile = "sign-errors.log";
public static final String versionFile = "versions";
// Euro tag for in the config
public static final String currencyEuro = "%euro%";
// Constants for handling file versions
public static final String versionFiles = "files";
public static final int versionFilesCurrent = 3;
// Keys for replacing parts of flags, commands, strings
public static final String tagPlayerName = "player";
public static final String tagPlayerUUID = "uuid";
public static final String tagWorldName = "world";
public static final String tagRegionName = "region";
public static final String tagRegionType = "type";
public static final String tagPrice = "price";
public static final String tagRawPrice = "rawprice";
public static final String tagDuration = "duration";
public static final String tagRentedUntil = "until";
public static final String tagRentedUntilShort = "untilshort";
public static final String tagWidth = "width"; // x-axis
public static final String tagHeight = "height"; // y-axis
public static final String tagDepth = "depth"; // z-axis
public static final String tagVolume = "volume"; // Number of blocks in the region (accounting for polygon regions)
public static final String tagTimeLeft = "timeleft";
public static final String tagClicker = "clicker";
public static final String tagResellPrice = "resellprice";
public static final String tagRawResellPrice = "rawresellprice";
public static final String tagFriends = "friends";
public static final String tagFriendsUUID = "friendsuuid";
public static final String tagMoneyBackPercentage = "moneybackpercent";
public static final String tagMoneyBackAmount = "moneyback";
public static final String tagRawMoneyBackAmount = "rawmoneyback";
public static final String tagTimesExtended = "timesExtended";
public static final String tagMaxExtends = "maxextends";
public static final String tagExtendsLeft = "extendsleft";
public static final String tagMaxRentTime = "maxrenttime";
public static final String tagMaxInactiveTime = "inactivetime";
public static final String tagLandlord = "landlord";
public static final String tagLandlordUUID = "landlorduuid";
public static final String tagDateTime = "datetime";
public static final String tagDateTimeShort = "datetimeshort";
public static final String tagYear = "year";
public static final String tagMonth = "month";
public static final String tagDay = "day";
public static final String tagHour = "hour";
public static final String tagMinute = "minute";
public static final String tagSecond = "second";
public static final String tagMillisecond = "millisecond";
public static final String tagEpoch = "epoch";
public static final String tagTeleportX = "tpx";
public static final String tagTeleportY = "tpy";
public static final String tagTeleportZ = "tpz";
public static final String tagTeleportBlockX = "tpblockx";
public static final String tagTeleportBlockY = "tpblocky";
public static final String tagTeleportBlockZ = "tpblockz";
public static final String tagTeleportPitch = "tppitch";
public static final String tagTeleportYaw = "tpyaw";
public static final String tagTeleportPitchRound = "tppitchround";
public static final String tagTeleportYawRound = "tpyawround";
public static final String tagTeleportWorld = "tpworld";
public static AreaShop getInstance() {
return AreaShop.instance;
}
/**
* Called on start or reload of the server.
*/
@Override
public void onEnable() {
AreaShop.instance = this;
Do.init(this);
managers = new HashSet<>();
messageBridge = new SimpleMessageBridge();
signErrorLogger = new SignErrorLogger(new File(getDataFolder(), signLogFile));
// Setup NMS Impl
Version currentServerVersion = VersionUtil.parseMinecraftVersion(Bukkit.getBukkitVersion());
if (currentServerVersion.versionData().isOlderThan(VersionUtil.MC_1_17_1)) {
error("Unsupported minecraft version: " + currentServerVersion + "! Minimum is 1.17.1");
shutdownOnError();
return;
}
if (currentServerVersion.versionData().equals(VersionUtil.MC_1_17_1)) {
try {
Class<?> adapterImpl = Class.forName("me.wiefferink.areashop.adapters.platform.legacy.LegacyPlatformAdapter");
this.platformAdapter = adapterImpl.asSubclass(PlatformAdapter.class).getConstructor().newInstance();
} catch (ReflectiveOperationException ex) {
ex.printStackTrace();
error("Failed to initialize legacy PlatformAdapter implementation!");
shutdownOnError();
return;
}
} else {
try {
Class<?> adapterImpl = Class.forName("me.wiefferink.areashop.adapters.platform.modern.ModernPlatformAdapter");
this.platformAdapter = adapterImpl.asSubclass(PlatformAdapter.class).getConstructor().newInstance();
} catch (ReflectiveOperationException ex) {
ex.printStackTrace();
error("Failed to initialize modern PlatformAdapter implementation!");
shutdownOnError();
return;
}
}
final MinecraftPlatform platform;
if (PaperLib.isPaper()) {
platform = new PaperPlatform(this);
info("Detected Paper; using the PaperPlatform impl");
} else {
platform = new SpigotPlatform(this);
info("Detected Spigot; using the SpigotPlatform impl");
}
final PlatformModule platformModule = new PlatformModule(platform, this.platformAdapter);
// Check if Vault is present
if(getServer().getPluginManager().getPlugin("Vault") == null) {
error("Vault plugin is not present or has not loaded correctly");
shutdownOnError();
return;
}
// Find WorldEdit integration version to load
String weHandlerVersion;
Plugin plugin = getServer().getPluginManager().getPlugin("WorldEdit");
if(!(plugin instanceof WorldEditPlugin) || !plugin.isEnabled()) {
error("WorldEdit plugin is not present or has not loaded correctly");
return;
} else {
this.worldEdit = (WorldEditPlugin) plugin;
Plugin fawePlugin = getServer().getPluginManager().getPlugin("FastAsyncWorldEdit");
if (fawePlugin != null && fawePlugin.isEnabled()) {
weHandlerVersion = "FastAsyncWorldEditHandler";
info("Detected FastAsyncWorldEdit, using the fawe handler");
} else {
weHandlerVersion = "WorldEditHandler";
}
}
plugin = getServer().getPluginManager().getPlugin("WorldGuard");
if(!(plugin instanceof WorldGuardPlugin) || !plugin.isEnabled()) {
error("WorldGuard plugin is not present or has not loaded correctly");
shutdownOnError();
return;
} else {
this.worldGuard = (WorldGuardPlugin) plugin;
}
DependencyModule dependencyModule = new DependencyModule(this.worldEdit, this.worldGuard, getEconomy(), getPermissionProvider());
// Load WorldEdit
try {
Class<?> clazz = Class.forName("me.wiefferink.areashop.adapters.plugins." + weHandlerVersion);
if(WorldEditInterface.class.isAssignableFrom(clazz)) { // Make sure it actually implements WorldEditInterface
worldEditInterface = (WorldEditInterface)clazz.getConstructor(AreaShopInterface.class).newInstance(this); // Set our handler
}
} catch(Exception e) {
error("Could not load the handler for WorldEdit (tried to load " + weHandlerVersion + "), report this problem to the author: " + ExceptionUtils.getStackTrace(e));
shutdownOnError();
return;
}
// Load WorldGuard
try {
Class<?> clazz = Class.forName("me.wiefferink.areashop.adapters.plugins.WorldGuardHandler");
// Check if we have a NMSHandler class at that location.
if(WorldGuardInterface.class.isAssignableFrom(clazz)) { // Make sure it actually implements WorldGuardInterface
worldGuardInterface = (WorldGuardInterface)clazz.getConstructor(AreaShopInterface.class).newInstance(this); // Set our handler
}
} catch(Exception e) {
error("Could not load the handler for WorldGuard, report this problem to the author:" + ExceptionUtils.getStackTrace(e));
shutdownOnError();
return;
}
AreaShopModule asModule = new AreaShopModule(this, messageBridge,
platformAdapter, worldEditInterface, worldGuardInterface, signErrorLogger, platformModule, dependencyModule);
injector = Guice.createInjector(Stage.PRODUCTION, new BukkitModule(getServer()), asModule);
// Load all data from files and check versions
fileManager = injector.getInstance(IFileManager.class);
managers.add((FileManager) fileManager);
boolean loadFilesResult = fileManager.loadFiles(false);
if (!loadFilesResult) {
shutdownOnError();
return;
}
setupLanguageManager();
featureManager = injector.getInstance(FeatureManager.class);
featureManager.initializeFeatures(injector);
managers.add(featureManager);
signManager = injector.getInstance(SignManager.class);
managers.add(signManager);
// Register the event listeners
getServer().getPluginManager().registerEvents(new PlayerLoginLogoutListener(this, messageBridge), this);
setupTasks();
// Startup the CommandManager (registers itself for the command)
commandManager = injector.getInstance(CommandManager.class);
managers.add(commandManager);
// Create a signLinkerManager
signLinkerManager = injector.getInstance(SignLinkerManager.class);
managers.add(signLinkerManager);
// Register dynamic permission (things declared in config)
registerDynamicPermissions();
// Don't initialize the updatechecker if disabled in the config
if(getConfig().getBoolean("checkForUpdates")) {
githubUpdateCheck = new GithubUpdateCheck(
this,
"md5sha256",
"AreaShop"
).withVersionComparator((latestVersion, currentVersion) -> {
Version latest = Version.parse(cleanVersion(latestVersion));
Version current = Version.parse(cleanVersion(currentVersion));
return latest.versionData().isNewerThan(current.versionData());
}
).checkUpdate(result -> {
AreaShop.debug("Update check result:", result);
if(!result.hasUpdate()) {
return;
}
AreaShop.info("Update from AreaShop V" + cleanVersion(result.getCurrentVersion()) + " to AreaShop V" + cleanVersion(result.getLatestVersion()) + " available, get the latest version at https://github.com/md5sha256/AreaShop/releases");
for(Player player : Utils.getOnlinePlayers()) {
notifyUpdate(player);
}
});
}
}
/**
* Notify a player about an update if he wants notifications about it and an update is available.
* @param sender CommandSender to notify
*/
public void notifyUpdate(CommandSender sender) {
if(githubUpdateCheck != null && githubUpdateCheck.hasUpdate() && sender.hasPermission("areashop.notifyupdate")) {
messageBridge.message(sender, "update-playerNotify", cleanVersion(githubUpdateCheck.getCurrentVersion()), cleanVersion(githubUpdateCheck.getLatestVersion()));
}
}
private void shutdownOnError() {
error("The plugin has not started, fix the errors listed above");
getServer().getPluginManager().disablePlugin(this);
}
/**
* Cleanup a version number.
* @param version Version to clean
* @return Cleaned up version (removed prefixes and suffixes)
*/
private String cleanVersion(String version) {
version = version.toLowerCase();
// Strip 'v' as used on Github tags
if(version.startsWith("v")) {
version = version.substring(1);
}
// Strip build number as used by Jenkins
if(version.contains("#")) {
version = version.substring(0, version.indexOf("#"));
}
return version;
}
/**
* Called on shutdown or reload of the server.
*/
@Override
public void onDisable() {
Bukkit.getServer().getScheduler().cancelTasks(this);
// Cleanup managers
for(Manager manager : managers) {
manager.shutdown();
}
signErrorLogger.saveIfDirty();
HandlerList.unregisterAll(this);
}
/**
* Indicates if the plugin is ready to be used.
* @return true if the plugin is ready, false otherwise
*/
public boolean isReady() {
return ready;
}
/**
* Set if the plugin is ready to be used or not (not to be used from another plugin!).
* @param ready Indicate if the plugin is ready to be used
*/
public void setReady(boolean ready) {
this.ready = ready;
}
/**
* Set if the plugin should output debug messages (loaded from config normally).
* @param debug Indicates if the plugin should output debug messages or not
*/
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* Setup a new LanguageManager.
*/
private void setupLanguageManager() {
languageManager = new LanguageManager(
this,
languageFolder,
getConfig().getString("language"),
"EN",
chatprefix
);
}
/**
* Set the chatprefix to use in the chat (loaded from config normally).
* @param chatprefix The string to use in front of chat messages (supports formatting codes)
*/
public void setChatprefix(List<String> chatprefix) {
this.chatprefix = chatprefix;
}
public PlatformAdapter getPlatformAdapter() {
return platformAdapter;
}
public SignErrorLogger getSignErrorLogger() {
return signErrorLogger;
}
/**
* Function to get the WorldGuard plugin.
* @return WorldGuardPlugin
*/
@Override
public WorldGuardPlugin getWorldGuard() {
return worldGuard;
}
/**
* Function to get WorldGuardInterface for version dependent things.
* @return WorldGuardInterface
*/
public WorldGuardInterface getWorldGuardHandler() {
return this.worldGuardInterface;
}
@Nonnull
public SignManager getSignManager() {
return signManager;
}
/**
* Get the RegionManager.
* @param world World to get the RegionManager for
* @return RegionManager for the given world, if there is one, otherwise null
*/
public RegionManager getRegionManager(World world) {
return this.worldGuardInterface.getRegionManager(world);
}
/**
* Function to get the WorldEdit plugin.
* @return WorldEditPlugin
*/
@Override
public WorldEditPlugin getWorldEdit() {
return worldEdit;
}
@Override
public Plugin pluginInstance() {
return this;
}
/**
* Function to get WorldGuardInterface for version dependent things.
* @return WorldGuardInterface
*/
public WorldEditInterface getWorldEditHandler() {
return this.worldEditInterface;
}
/**
* Function to get the LanguageManager.
* @return the LanguageManager
*/
public LanguageManager getLanguageManager() {
return languageManager;
}
/**
* Function to get the CommandManager.
* @return the CommandManager
*/
@Nonnull
@Override
public CommandManager getCommandManager() {
return commandManager;
}
/**
* Get the SignLinkerManager.
* Handles sign linking mode.
* @return The SignLinkerManager
*/
@Nonnull
@Override
public SignLinkerManager getSignlinkerManager() {
return signLinkerManager;
}
/**
* Get the FeatureManager.
* Manages region specific features.
* @return The FeatureManager
*/
@Nonnull
@Override
public FeatureManager getFeatureManager() {
return featureManager;
}
/**
* Function to get the Vault plugin.
* @return Economy
*/
@Deprecated
private Economy getEconomy() {
RegisteredServiceProvider<Economy> economy = getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class);
if(economy == null) {
error("There is no economy provider to support Vault, make sure you installed an economy plugin");
return null;
}
return economy.getProvider();
}
/**
* Get the Vault permissions provider.
* @return Vault permissions provider
*/
@Deprecated
private net.milkbowl.vault.permission.Permission getPermissionProvider() {
RegisteredServiceProvider<net.milkbowl.vault.permission.Permission> permissionProvider = getServer().getServicesManager().getRegistration(net.milkbowl.vault.permission.Permission.class);
if (permissionProvider == null) {
return null;
}
return permissionProvider.getProvider();
}
/**
* Check for a permission of a (possibly offline) player.
* @param offlinePlayer OfflinePlayer to check
* @param permission Permission to check
* @return true if the player has the permission, false if the player does not have permission or, is offline and there is not Vault-compatible permission plugin
*/
public boolean hasPermission(OfflinePlayer offlinePlayer, String permission) {
// Online, return through Bukkit
if(offlinePlayer.getPlayer() != null) {
return offlinePlayer.getPlayer().hasPermission(permission);
}
// Resolve while offline if possible
net.milkbowl.vault.permission.Permission permissionProvider = getPermissionProvider();
if(permissionProvider != null) {
// TODO: Should we provide a world here?
return permissionProvider.playerHas(null, offlinePlayer, permission);
}
// Player offline and no offline permission provider available, safely say that there is no permission
return false;
}
/**
* Method to get the FileManager (loads/save regions and can be used to get regions).
* @return The fileManager
*/
public IFileManager getFileManager() {
return fileManager;
}
/**
* Register dynamic permissions controlled by config settings.
*/
private void registerDynamicPermissions() {
// Register limit groups of amount of regions a player can have
ConfigurationSection section = getConfig().getConfigurationSection("limitGroups");
if(section == null) {
return;
}
for(String group : section.getKeys(false)) {
if(!"default".equals(group)) {
Permission perm = new Permission("areashop.limits." + group);
try {
Bukkit.getPluginManager().addPermission(perm);
} catch(IllegalArgumentException e) {
warn("Could not add the following permission to be used as limit: " + perm.getName());
}
}
}
Bukkit.getPluginManager().recalculatePermissionDefaults(Bukkit.getPluginManager().getPermission("playerwarps.limits"));
}
/**
* Register all required tasks.
*/
private void setupTasks() {
long signLogTicks = Utils.millisToTicks(TimeUnit.MINUTES.toMillis(5));
Bukkit.getScheduler().runTaskTimerAsynchronously(this,
() -> signErrorLogger.saveIfDirty(),
signLogTicks,
signLogTicks);
// Rent expiration timer
long expirationCheck = Utils.millisToTicks(Utils.getDurationFromSecondsOrString("expiration.delay"));
final AreaShop finalPlugin = this;
if(expirationCheck > 0) {
Do.syncTimer(expirationCheck, () -> {
if(isReady()) {
finalPlugin.getFileManager().checkRents();
AreaShop.debugTask("Checking rent expirations...");
} else {
AreaShop.debugTask("Skipped checking rent expirations, plugin not ready");
}
});
}
// Inactive unrenting/selling timer
long inactiveCheck = Utils.millisToTicks(Utils.getDurationFromMinutesOrString("inactive.delay"));
if(inactiveCheck > 0) {
Do.syncTimer(inactiveCheck, () -> {
if(isReady()) {
finalPlugin.getFileManager().checkForInactiveRegions();
AreaShop.debugTask("Checking for regions with players that are inactive too long...");
} else {
AreaShop.debugTask("Skipped checking for regions of inactive players, plugin not ready");
}
});
}
// Periodic updating of signs for timeleft tags
long periodicUpdate = Utils.millisToTicks(Utils.getDurationFromSecondsOrString("signs.delay"));
if(periodicUpdate > 0) {
Do.syncTimer(periodicUpdate, () -> {
if(isReady()) {
finalPlugin.getFileManager().performPeriodicSignUpdate();
AreaShop.debugTask("Performing periodic sign update...");
} else {
AreaShop.debugTask("Skipped performing periodic sign update, plugin not ready");
}
});
}
// Saving regions and group settings
long saveFiles = Utils.millisToTicks(Utils.getDurationFromMinutesOrString("saving.delay"));
if(saveFiles > 0) {
Do.syncTimer(saveFiles, () -> {
if(isReady()) {
finalPlugin.getFileManager().saveRequiredFiles();
AreaShop.debugTask("Saving required files...");
} else {
AreaShop.debugTask("Skipped saving required files, plugin not ready");
}
});
}
// Sending warnings about rent regions to online players
long expireWarning = Utils.millisToTicks(Utils.getDurationFromMinutesOrString("expireWarning.delay"));
if(expireWarning > 0) {
Do.syncTimer(expireWarning, () -> {
if(isReady()) {
finalPlugin.getFileManager().sendRentExpireWarnings();
AreaShop.debugTask("Sending rent expire warnings...");
} else {
AreaShop.debugTask("Skipped sending rent expire warnings, plugin not ready");
}
});
}
// Update all regions on startup
if(getConfig().getBoolean("updateRegionsOnStartup")) {
Do.syncLater(20, () -> {
finalPlugin.getFileManager().updateAllRegions();
AreaShop.debugTask("Updating all regions at startup...");
});
}
}
/**
* Return the config.
*/
@Override
public YamlConfiguration getConfig() {
return fileManager.getConfig();
}
/**
* Sends an debug message to the console.
* @param message The message that should be printed to the console
*/
public static void debug(Object... message) {
if(AreaShop.getInstance().debug) {
info("Debug: " + StringUtils.join(message, " "));
}
}
/**
* Non-static debug to use as implementation of the interface.
* @param message Object parts of the message that should be logged, toString() will be used
*/
@Override
public void debugI(Object... message) {
AreaShop.debug(StringUtils.join(message, " "));
}
@Override
public void debugI(Supplier<String> message) {
if (debug) {
info("Debug: " + message.get());
}
}
/**
* Print an information message to the console.
* @param message The message to print
*/
public static void info(Object... message) {
AreaShop.getInstance().getLogger().info(() -> StringUtils.join(message, " "));
}
/**
* Print a warning to the console.
* @param message The message to print
*/
public static void warn(Object... message) {
AreaShop.getInstance().getLogger().warning(() -> StringUtils.join(message, " "));
}
/**
* Print an error to the console.
* @param message The message to print
*/
public static void error(Object... message) {
AreaShop.getInstance().getLogger().severe(() -> StringUtils.join(message, " "));
}
/**
* Print debug message for periodic task.
* @param message The message to print
*/
public static void debugTask(Object... message) {
if(AreaShop.getInstance().getConfig().getBoolean("debugTask")) {
AreaShop.debug(StringUtils.join(message, " "));
}
}
/**
* Reload all files of the plugin and update all regions.
* @param confirmationReceiver The CommandSender which should be notified when complete, null for nobody
*/
public void reload(final CommandSender confirmationReceiver) {
setReady(false);
fileManager.saveRequiredFilesAtOnce();
fileManager.loadFiles(true);
setupLanguageManager();
messageBridge.message(confirmationReceiver, "reload-reloading");
fileManager.checkRents();
fileManager.updateAllRegions(confirmationReceiver);
}
/**
* Reload all files of the plugin and update all regions.
*/
public void reload() {
reload(null);
}
}
@@ -0,0 +1,39 @@
package me.wiefferink.areashop;
import me.wiefferink.areashop.features.signs.SignManager;
import me.wiefferink.areashop.interfaces.AreaShopInterface;
import me.wiefferink.areashop.managers.CommandManager;
import me.wiefferink.areashop.managers.FeatureManager;
import me.wiefferink.areashop.managers.SignLinkerManager;
import javax.annotation.Nonnull;
public interface AreaShopApi extends AreaShopInterface {
/**
* Function to get the CommandManager.
*
* @return the CommandManager
*/
@Nonnull CommandManager getCommandManager();
/**
* Get the SignLinkerManager.
* Handles sign linking mode.
*
* @return The SignLinkerManager
*/
@Nonnull SignLinkerManager getSignlinkerManager();
/**
* Get the FeatureManager.
* Manages region specific features.
*
* @return The FeatureManager
*/
@Nonnull FeatureManager getFeatureManager();
@Nonnull SignManager getSignManager();
}
@@ -0,0 +1,21 @@
package me.wiefferink.areashop;
public interface MessageBridge {
/**
* Send a message to a target without a prefix.
*
* @param target The target to send the message to
* @param key The key of the language string
* @param replacements The replacements to insert in the message
*/
void messageNoPrefix(Object target, String key, Object... replacements);
/**
* Send a message to a target, prefixed by the default chat prefix.
*
* @param target The target to send the message to
* @param key The key of the language string
* @param replacements The replacements to insert in the message
*/
void message(Object target, String key, Object... replacements);
}
@@ -0,0 +1,345 @@
package me.wiefferink.areashop.commands;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.events.ask.AddingRegionEvent;
import me.wiefferink.areashop.events.ask.BuyingRegionEvent;
import me.wiefferink.areashop.events.ask.RentingRegionEvent;
import me.wiefferink.areashop.events.notify.BoughtRegionEvent;
import me.wiefferink.areashop.events.notify.RentedRegionEvent;
import me.wiefferink.areashop.interfaces.WorldEditInterface;
import me.wiefferink.areashop.interfaces.WorldEditSelection;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RegionFactory;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.bukkitdo.Do;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
@Singleton
public class AddCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private AreaShop plugin;
@Inject
private WorldEditInterface worldEditInterface;
@Inject
private WorldGuardInterface worldGuardInterface;
@Inject
private RegionFactory regionFactory;
@Override
public String getCommandStart() {
return "areashop add";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.createrent")
|| target.hasPermission("areashop.createrent.member")
|| target.hasPermission("areashop.createrent.owner")
|| target.hasPermission("areashop.createbuy")
|| target.hasPermission("areashop.createbuy.member")
|| target.hasPermission("areashop.createbuy.owner")) {
return "help-add";
}
return null;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
if(!sender.hasPermission("areashop.createrent")
&& !sender.hasPermission("areashop.createrent.member")
&& !sender.hasPermission("areashop.createrent.owner")
&& !sender.hasPermission("areashop.createbuy")
&& !sender.hasPermission("areashop.createbuy.member")
&& !sender.hasPermission("areashop.createbuy.owner")) {
messageBridge.message(sender, "add-noPermission");
return;
}
if(args.length < 2 || args[1] == null || (!"rent".equalsIgnoreCase(args[1]) && !"buy".equalsIgnoreCase(args[1]))) {
messageBridge.message(sender, "add-help");
return;
}
Map<String, ProtectedRegion> regions = new HashMap<>();
World world;
Player player = null;
if(sender instanceof Player) {
player = (Player)sender;
}
if(args.length == 2) {
if(player == null) {
messageBridge.message(sender, "cmd-weOnlyByPlayer");
return;
}
WorldEditSelection selection = worldEditInterface.getPlayerSelection(player);
if(selection == null) {
messageBridge.message(player, "cmd-noSelection");
return;
}
world = selection.getWorld();
regions = Utils.getWorldEditRegionsInSelection(selection).stream().collect(Collectors.toMap(ProtectedRegion::getId, region -> region));
if(regions.isEmpty()) {
messageBridge.message(player, "cmd-noWERegionsFound");
return;
}
} else {
if(player != null) {
if(args.length == 4) {
world = Bukkit.getWorld(args[3]);
if(world == null) {
messageBridge.message(sender, "add-incorrectWorld", args[3]);
return;
}
} else {
world = ((Player)sender).getWorld();
}
} else {
if(args.length < 4) {
messageBridge.message(sender, "add-specifyWorld");
return;
} else {
world = Bukkit.getWorld(args[3]);
if(world == null) {
messageBridge.message(sender, "add-incorrectWorld", args[3]);
return;
}
}
}
ProtectedRegion region = plugin.getRegionManager(world).getRegion(args[2]);
if(region == null) {
messageBridge.message(sender, "cmd-noRegion", args[2]);
return;
}
regions.put(args[2], region);
}
final boolean isRent = "rent".equalsIgnoreCase(args[1]);
final Player finalPlayer = player;
AreaShop.debug("Starting add task with " + regions.size() + " regions");
TreeSet<GeneralRegion> regionsSuccess = new TreeSet<>();
TreeSet<GeneralRegion> regionsAlready = new TreeSet<>();
TreeSet<GeneralRegion> regionsAlreadyOtherWorld = new TreeSet<>();
TreeSet<GeneralRegion> regionsRentCancelled = new TreeSet<>(); // Denied by an event listener
TreeSet<GeneralRegion> regionsBuyCancelled = new TreeSet<>(); // Denied by an event listener
TreeSet<String> namesBlacklisted = new TreeSet<>();
TreeSet<String> namesNoPermission = new TreeSet<>();
TreeSet<String> namesAddCancelled = new TreeSet<>(); // Denied by an event listener
Do.forAll(
plugin.getConfig().getInt("adding.regionsPerTick"),
regions.entrySet(),
regionEntry -> {
String regionName = regionEntry.getKey();
ProtectedRegion region = regionEntry.getValue();
// Determine if the player is an owner or member of the region
boolean isMember = finalPlayer != null && worldGuardInterface.containsMember(region, finalPlayer.getUniqueId());
boolean isOwner = finalPlayer != null && worldGuardInterface.containsOwner(region, finalPlayer.getUniqueId());
String type;
if(isRent) {
type = "rent";
} else {
type = "buy";
}
IFileManager.AddResult result = plugin.getFileManager().checkRegionAdd(sender, region, world, isRent ? GeneralRegion.RegionType.RENT : GeneralRegion.RegionType.BUY);
if(result == IFileManager.AddResult.ALREADYADDED) {
regionsAlready.add(plugin.getFileManager().getRegion(regionName));
} else if(result == IFileManager.AddResult.ALREADYADDEDOTHERWORLD) {
regionsAlreadyOtherWorld.add(plugin.getFileManager().getRegion(regionName));
} else if(result == IFileManager.AddResult.BLACKLISTED) {
namesBlacklisted.add(regionName);
} else if(result == IFileManager.AddResult.NOPERMISSION) {
namesNoPermission.add(regionName);
} else {
// Check if the player should be landlord
boolean landlord = (!sender.hasPermission("areashop.create" + type)
&& ((sender.hasPermission("areashop.create" + type + ".owner") && isOwner)
|| (sender.hasPermission("areashop.create" + type + ".member") && isMember)));
List<UUID> existing = new ArrayList<>();
existing.addAll(worldGuardInterface.getOwners(region).asUniqueIdList());
existing.addAll(worldGuardInterface.getMembers(region).asUniqueIdList());
AreaShop.debug("regionAddLandlordStatus:", regionName,
"landlord:", landlord,
"existing:", existing,
"isMember:", isMember,
"isOwner:", isOwner,
"createPermission:", sender.hasPermission("areashop.create" + type),
"ownerPermission:", sender.hasPermission("areashop.create" + type + ".owner"),
"memberPermission:", sender.hasPermission("areashop.create" + type + ".member"));
if(isRent) {
RentRegion rent = regionFactory.createRentRegion(regionName, world);
// Set landlord
if(landlord) {
rent.setLandlord(finalPlayer.getUniqueId(), finalPlayer.getName());
}
AddingRegionEvent event = plugin.getFileManager().addRegion(rent);
if (event.isCancelled()) {
namesAddCancelled.add(rent.getName());
return;
}
rent.handleSchematicEvent(GeneralRegion.RegionEvent.CREATED);
rent.update();
// Add existing owners/members if any
if(!landlord && !existing.isEmpty()) {
UUID rentBy = existing.remove(0);
OfflinePlayer rentByPlayer = Bukkit.getOfflinePlayer(rentBy);
RentingRegionEvent rentingRegionEvent = new RentingRegionEvent(rent, rentByPlayer, false);
Bukkit.getPluginManager().callEvent(rentingRegionEvent);
if(rentingRegionEvent.isCancelled()) {
regionsRentCancelled.add(rent);
} else {
// Add values to the rent and send it to FileManager
rent.setRentedUntil(Calendar.getInstance().getTimeInMillis() + rent.getDuration());
rent.setRenter(rentBy);
rent.updateLastActiveTime();
// Fire schematic event and updated times extended
rent.handleSchematicEvent(GeneralRegion.RegionEvent.RENTED);
// Add others as friends
for(UUID friend : existing) {
rent.getFriendsFeature().addFriend(friend, null);
}
rent.notifyAndUpdate(new RentedRegionEvent(rent, false));
}
}
regionsSuccess.add(rent);
} else {
BuyRegion buy = regionFactory.createBuyRegion(regionName, world);
// Set landlord
if(landlord) {
buy.setLandlord(finalPlayer.getUniqueId(), finalPlayer.getName());
}
AddingRegionEvent event = plugin.getFileManager().addRegion(buy);
if (event.isCancelled()) {
namesAddCancelled.add(buy.getName());
return;
}
buy.handleSchematicEvent(GeneralRegion.RegionEvent.CREATED);
buy.update();
// Add existing owners/members if any
if(!landlord && !existing.isEmpty()) {
UUID buyBy = existing.remove(0);
OfflinePlayer buyByPlayer = Bukkit.getOfflinePlayer(buyBy);
BuyingRegionEvent buyingRegionEvent = new BuyingRegionEvent(buy, buyByPlayer);
Bukkit.getPluginManager().callEvent(buyingRegionEvent);
if(buyingRegionEvent.isCancelled()) {
regionsBuyCancelled.add(buy);
} else {
// Set the owner
buy.setBuyer(buyBy);
buy.updateLastActiveTime();
// Update everything
buy.handleSchematicEvent(GeneralRegion.RegionEvent.BOUGHT);
// Add others as friends
for (UUID friend : existing) {
buy.getFriendsFeature().addFriend(friend, null);
}
buy.notifyAndUpdate(new BoughtRegionEvent(buy));
}
}
regionsSuccess.add(buy);
}
}
},
() -> {
if(!regionsSuccess.isEmpty()) {
messageBridge.message(sender, "add-success", args[1], Utils.combinedMessage(regionsSuccess, "region"));
}
if(!regionsAlready.isEmpty()) {
messageBridge.message(sender, "add-failed", Utils.combinedMessage(regionsAlready, "region"));
}
if(!regionsAlreadyOtherWorld.isEmpty()) {
messageBridge.message(sender, "add-failedOtherWorld", Utils.combinedMessage(regionsAlreadyOtherWorld, "region"));
}
if(!regionsRentCancelled.isEmpty()) {
messageBridge.message(sender, "add-rentCancelled", Utils.combinedMessage(regionsRentCancelled, "region"));
}
if(!regionsBuyCancelled.isEmpty()) {
messageBridge.message(sender, "add-buyCancelled", Utils.combinedMessage(regionsBuyCancelled, "region"));
}
if(!namesBlacklisted.isEmpty()) {
messageBridge.message(sender, "add-blacklisted", Utils.createCommaSeparatedList(namesBlacklisted));
}
if(!namesNoPermission.isEmpty()) {
messageBridge.message(sender, "add-noPermissionRegions", Utils.createCommaSeparatedList(namesNoPermission));
messageBridge.message(sender, "add-noPermissionOwnerMember");
}
if(!namesAddCancelled.isEmpty()) {
messageBridge.message(sender, "add-rentCancelled", Utils.createCommaSeparatedList(namesAddCancelled));
}
}
);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
if(sender.hasPermission("areashop.createrent")) {
result.add("rent");
}
if(sender.hasPermission("areashop.createbuy")) {
result.add("buy");
}
} else if(toComplete == 3) {
if(sender instanceof Player player) {
if(sender.hasPermission("areashop.createrent") || sender.hasPermission("areashop.createbuy")) {
for(ProtectedRegion region : plugin.getRegionManager(player.getWorld()).getRegions().values()) {
result.add(region.getId());
}
}
}
}
return result;
}
}
@@ -0,0 +1,148 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.adapters.platform.OfflinePlayerHelper;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.BukkitSchedulerExecutor;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class AddfriendCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private Plugin plugin;
@Inject
private OfflinePlayerHelper offlinePlayerHelper;
@Inject
private BukkitSchedulerExecutor executor;
@Override
public String getCommandStart() {
return "areashop addfriend";
}
@Override
public String getHelp(CommandSender target) {
if (target.hasPermission("areashop.addfriendall") || target.hasPermission("areashop.addfriend")) {
return "help-addFriend";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!sender.hasPermission("areashop.addfriend") && !sender.hasPermission("areashop.addfriendall")) {
messageBridge.message(sender, "addfriend-noPermission");
return;
}
if (args.length < 2) {
messageBridge.message(sender, "addfriend-help");
return;
}
GeneralRegion region;
if (args.length == 2) {
if (sender instanceof Player player) {
// get the region by location
List<GeneralRegion> regions = Utils.getImportantRegions(player.getLocation());
if (regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if (regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
region = regions.get(0);
}
} else {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
} else {
region = fileManager.getRegion(args[2]);
if (region == null) {
messageBridge.message(sender, "cmd-notRegistered", args[2]);
return;
}
}
if (sender.hasPermission("areashop.addfriendall")) {
if ((region instanceof RentRegion rentRegion && !rentRegion.isRented())
|| (region instanceof BuyRegion buyRegion && !buyRegion.isSold())) {
messageBridge.message(sender, "addfriend-noOwner", region);
return;
}
this.offlinePlayerHelper.lookupOfflinePlayerAsync(args[1]).thenAcceptAsync(friend -> {
if (friend.hasPlayedBefore() && !friend.isOnline() && !plugin.getConfig().getBoolean("addFriendNotExistingPlayers")) {
messageBridge.message(sender, "addfriend-notVisited", args[1], region);
} else if (region.getFriendsFeature().getFriends().contains(friend.getUniqueId())) {
messageBridge.message(sender, "addfriend-alreadyAdded", friend.getName(), region);
} else if (region.isOwner(friend.getUniqueId())) {
messageBridge.message(sender, "addfriend-self", friend.getName(), region);
} else if (region.getFriendsFeature().addFriend(friend.getUniqueId(), sender)) {
region.update();
messageBridge.message(sender, "addfriend-successOther", friend.getName(), region);
}
}, this.executor);
return;
}
if (!sender.hasPermission("areashop.addfriend") || !(sender instanceof Player player)) {
messageBridge.message(sender, "addfriend-noPermission", region);
return;
}
if (!region.isOwner(player)) {
messageBridge.message(sender, "addfriend-noPermissionOther", region);
return;
}
this.offlinePlayerHelper.lookupOfflinePlayerAsync(args[1]).thenAcceptAsync(friend -> {
if (friend.getLastPlayed() == 0 && !friend.isOnline() && !plugin.getConfig().getBoolean("addFriendNotExistingPlayers")) {
messageBridge.message(sender, "addfriend-notVisited", args[1], region);
} else if (region.getFriendsFeature().getFriends().contains(friend.getUniqueId())) {
messageBridge.message(sender, "addfriend-alreadyAdded", friend.getName(), region);
} else if (region.isOwner(friend.getUniqueId())) {
messageBridge.message(sender, "addfriend-self", friend.getName(), region);
} else if (region.getFriendsFeature().addFriend(friend.getUniqueId(), sender)) {
region.update();
messageBridge.message(sender, "addfriend-success", friend.getName(), region);
}
}, this.executor);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if (toComplete == 2) {
for (Player player : Utils.getOnlinePlayers()) {
result.add(player.getName());
}
} else if (toComplete == 3) {
result.addAll(fileManager.getRegionNames());
}
return result;
}
}
@@ -0,0 +1,141 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.features.signs.RegionSign;
import me.wiefferink.areashop.features.signs.SignManager;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.tools.Materials;
import me.wiefferink.areashop.tools.SignUtils;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.BlockIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@Singleton
public class AddsignCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private SignManager signManager;
@Inject
private Plugin plugin;
@Override
public String getCommandStart() {
return "areashop addsign";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.addsign")) {
return "help-addsign";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.addsign")) {
messageBridge.message(sender, "addsign-noPermission");
return;
}
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-onlyByPlayer");
return;
}
// Get the sign
Block block = null;
BlockIterator blockIterator = new BlockIterator(player, 100);
while(blockIterator.hasNext() && block == null) {
Block next = blockIterator.next();
if(next.getType() != Material.AIR) {
block = next;
}
}
if(block == null || !Materials.isSign(block.getType())) {
messageBridge.message(sender, "addsign-noSign");
return;
}
GeneralRegion region;
if(args.length > 1) {
// Get region by argument
region = fileManager.getRegion(args[1]);
if(region == null) {
messageBridge.message(sender, "cmd-notRegistered", args[1]);
return;
}
} else {
// Get region by sign position
List<GeneralRegion> regions = Utils.getImportantRegions(block.getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "addsign-noRegions");
return;
} else if(regions.size() > 1) {
messageBridge.message(sender, "addsign-couldNotDetect", regions.get(0).getName(), regions.get(1).getName());
return;
}
region = regions.get(0);
}
String profile = null;
if(args.length > 2) {
profile = args[2];
Set<String> profiles = plugin.getConfig().getConfigurationSection("signProfiles").getKeys(false);
if(!profiles.contains(profile)) {
messageBridge.message(sender, "addsign-wrongProfile", Utils.createCommaSeparatedList(profiles), region);
return;
}
}
Optional<RegionSign> optionalRegionSign = signManager.signFromLocation(block.getLocation());
if(optionalRegionSign.isPresent()) {
RegionSign regionSign = optionalRegionSign.get();
messageBridge.message(sender, "addsign-alreadyRegistered", regionSign.getRegion());
return;
}
region.getSignsFeature().addSign(block.getLocation(), block.getType(), SignUtils.getSignFacing(block), profile);
if(profile == null) {
messageBridge.message(sender, "addsign-success", region);
} else {
messageBridge.message(sender, "addsign-successProfile", profile, region);
}
region.update();
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result.addAll(fileManager.getRegionNames());
} else if(toComplete == 3) {
result.addAll(plugin.getConfig().getStringList("signProfiles"));
}
return result;
}
}
@@ -0,0 +1,81 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class BuyCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private Plugin plugin;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop buy";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.buy")) {
return "help-buy";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.buy")) {
messageBridge.message(sender, "buy-noPermission");
return;
}
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-onlyByPlayer");
return;
}
if(args.length > 1 && args[1] != null) {
BuyRegion region = fileManager.getBuy(args[1]);
if(region == null) {
messageBridge.message(player, "buy-notBuyable", args[1]);
} else {
region.buy(player);
}
} else {
// get the region by location
List<BuyRegion> regions = Utils.getImportantBuyRegions(((Player)sender).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
} else {
regions.get(0).buy(player);
}
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
for(BuyRegion region : fileManager.getBuysRef()) {
if(!region.isSold()) {
result.add(region.getName());
}
}
}
return result;
}
}
@@ -0,0 +1,99 @@
package me.wiefferink.areashop.commands;
import me.wiefferink.interactivemessenger.processing.Message;
import org.apache.commons.lang.StringUtils;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Abstract class for generalising command classes.
*/
public abstract class CommandAreaShop {
private final Map<String, CommandTime> lastUsed;
protected CommandAreaShop() {
lastUsed = new HashMap<>();
}
/**
* Check if this Command instance can execute the given command and arguments.
* @param command The command to check for execution
* @param args The arguments to check
* @return true if it can execute the command, false otherwise
*/
public boolean canExecute(Command command, String[] args) {
String commandString = command.getName() + " " + StringUtils.join(args, " ");
if(commandString.length() > getCommandStart().length()) {
return commandString.toLowerCase().startsWith(getCommandStart().toLowerCase() + " ");
}
return commandString.toLowerCase().startsWith(getCommandStart().toLowerCase());
}
/**
* Get a list of string to complete a command with (raw list, not matching ones not filtered out).
* @param toComplete The number of the argument that has to be completed
* @param start The already given start of the command
* @param sender The CommandSender that wants to tab complete
* @return A collection with all the possibilities for argument to complete
*/
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
return new ArrayList<>();
}
/**
* Get the argument that comes after the base command that this command reacts to.
* @return The string that should be in front of the command for this class to act
*/
public abstract String getCommandStart();
/**
* Returns the correct help string key to be used on the help page.
* @param target The CommandSender that the help message is for
* @return The help message key according to the permissions of the reciever
*/
public abstract String getHelp(CommandSender target);
/**
* Execute a (sub)command if the conditions are met.
* @param sender The commandSender that executed the command
* @param args The arguments that are given
*/
public abstract void execute(CommandSender sender, String[] args);
/**
* Confirm a command.
* @param sender To confirm it for, or send a message to confirm
* @param args Command args
* @param message Message to send when confirmation is required
* @return true if confirmed, false if confirmation is required
*/
public boolean confirm(CommandSender sender, String[] args, Message message) {
String command = "/" + getCommandStart() + " " + StringUtils.join(args, " ", 1, args.length);
long now = System.currentTimeMillis();
CommandTime last = lastUsed.get(sender.getName());
if(last != null && last.command.equalsIgnoreCase(command) && last.time > (now - 1000 * 60)) {
return true;
}
message.prefix().append(Message.fromKey("confirm-yes").replacements(command)).send(sender);
lastUsed.put(sender.getName(), new CommandTime(command, now));
return false;
}
private static class CommandTime {
public final String command;
public final long time;
CommandTime(String command, long time) {
this.command = command;
this.time = time;
}
}
}
@@ -0,0 +1,160 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.events.ask.DeletingRegionEvent;
import me.wiefferink.areashop.interfaces.WorldEditInterface;
import me.wiefferink.areashop.interfaces.WorldEditSelection;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
@Singleton
public class DelCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private WorldEditInterface worldEditInterface;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop del";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.destroyrent") || target.hasPermission("areashop.destroybuy") || target.hasPermission("areashop.destroyrent.landlord") || target.hasPermission("areashop.destroybuy.landlord")) {
return "help-del";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.destroybuy")
&& !sender.hasPermission("areashop.destroybuy.landlord")
&& !sender.hasPermission("areashop.destroyrent")
&& !sender.hasPermission("areashop.destroyrent.landlord")) {
messageBridge.message(sender, "del-noPermission");
return;
}
if(args.length < 2) {
// Only players can have a selection
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-weOnlyByPlayer");
return;
}
WorldEditSelection selection = worldEditInterface.getPlayerSelection(player);
if(selection == null) {
messageBridge.message(player, "cmd-noSelection");
return;
}
List<GeneralRegion> regions = Utils.getRegionsInSelection(selection);
if(regions == null || regions.isEmpty()) {
messageBridge.message(player, "cmd-noRegionsFound");
return;
}
// Start removing the regions that he has permission for
ArrayList<String> namesSuccess = new ArrayList<>();
TreeSet<GeneralRegion> regionsFailed = new TreeSet<>();
TreeSet<GeneralRegion> regionsCancelled = new TreeSet<>();
for(GeneralRegion region : regions) {
boolean isLandlord = region.isLandlord(((Player)sender).getUniqueId());
if(region instanceof RentRegion) {
if(!sender.hasPermission("areashop.destroyrent") && !(isLandlord && sender.hasPermission("areashop.destroyrent.landlord"))) {
regionsFailed.add(region);
continue;
}
} else if(region instanceof BuyRegion) {
if(!sender.hasPermission("areashop.destroybuy") && !(isLandlord && sender.hasPermission("areashop.destroybuy.landlord"))) {
regionsFailed.add(region);
continue;
}
}
DeletingRegionEvent event = fileManager.deleteRegion(region, true);
if (event.isCancelled()) {
regionsCancelled.add(region);
} else {
namesSuccess.add(region.getName());
}
}
// Send messages
if(!namesSuccess.isEmpty()) {
messageBridge.message(sender, "del-success", Utils.createCommaSeparatedList(namesSuccess));
}
if(!regionsFailed.isEmpty()) {
messageBridge.message(sender, "del-failed", Utils.combinedMessage(regionsFailed, "region"));
}
if(!regionsCancelled.isEmpty()) {
messageBridge.message(sender, "del-cancelled", Utils.combinedMessage(regionsCancelled, "region"));
}
} else {
GeneralRegion region = fileManager.getRegion(args[1]);
if(region == null) {
messageBridge.message(sender, "cmd-notRegistered", args[1]);
return;
}
boolean isLandlord = sender instanceof Player && region.isLandlord(((Player)sender).getUniqueId());
if(region instanceof RentRegion) {
// Remove the rent if the player has permission
if(sender.hasPermission("areashop.destroyrent") || (isLandlord && sender.hasPermission("areashop.destroyrent.landlord"))) {
DeletingRegionEvent event = fileManager.deleteRegion(region, true);
if (event.isCancelled()) {
messageBridge.message(sender, "general-cancelled", event.getReason());
} else {
messageBridge.message(sender, "destroy-successRent", region);
}
} else {
messageBridge.message(sender, "destroy-noPermissionRent", region);
}
} else if(region instanceof BuyRegion) {
// Remove the buy if the player has permission
if(sender.hasPermission("areashop.destroybuy") || (isLandlord && sender.hasPermission("areashop.destroybuy.landlord"))) {
DeletingRegionEvent event = fileManager.deleteRegion(region, true);
if (event.isCancelled()) {
messageBridge.message(sender, "general-cancelled", event.getReason());
} else {
messageBridge.message(sender, "destroy-successBuy", region);
}
} else {
messageBridge.message(sender, "destroy-noPermissionBuy", region);
}
}
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result = fileManager.getRegionNames();
}
return result;
}
}
@@ -0,0 +1,153 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.adapters.platform.OfflinePlayerHelper;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.BukkitSchedulerExecutor;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class DelfriendCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private OfflinePlayerHelper offlinePlayerHelper;
@Inject
private BukkitSchedulerExecutor executor;
@Override
public String getCommandStart() {
return "areashop delfriend";
}
@Override
public String getHelp(CommandSender target) {
if (target.hasPermission("areashop.delfriendall") || target.hasPermission("areashop.delfriend")) {
return "help-delFriend";
}
return null;
}
/**
* Check if a person can remove friends.
*
* @param person The person to check
* @param region The region to check for
* @return true if the person can remove friends, otherwise false
*/
public static boolean canUse(CommandSender person, GeneralRegion region) {
if (person.hasPermission("areashop.delfriendall")) {
return true;
}
if (person instanceof Player player) {
return region.isOwner(player) && player.hasPermission("areashop.delfriend");
}
return false;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!sender.hasPermission("areashop.delfriend") && !sender.hasPermission("areashop.delfriendall")) {
messageBridge.message(sender, "delfriend-noPermission");
return;
}
if (args.length < 2) {
messageBridge.message(sender, "delfriend-help");
return;
}
GeneralRegion region;
if (args.length == 2) {
if (!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
// get the region by location
List<GeneralRegion> regions = Utils.getImportantRegions(player.getLocation());
if (regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if (regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
region = regions.get(0);
}
} else {
region = fileManager.getRegion(args[2]);
if (region == null) {
messageBridge.message(sender, "cmd-notRegistered", args[2]);
return;
}
}
if (sender.hasPermission("areashop.delfriendall")) {
if ((region instanceof RentRegion rentRegion && !rentRegion.isRented())
|| (region instanceof BuyRegion buyRegion && !buyRegion.isSold())) {
messageBridge.message(sender, "delfriend-noOwner", region);
return;
}
this.offlinePlayerHelper.lookupOfflinePlayerAsync(args[1]).thenAcceptAsync(friend -> {
if (!region.getFriendsFeature().getFriends().contains(friend.getUniqueId())) {
messageBridge.message(sender, "delfriend-notAdded", friend.getName(), region);
return;
}
if (region.getFriendsFeature().deleteFriend(friend.getUniqueId(), sender)) {
region.update();
messageBridge.message(sender, "delfriend-successOther", friend.getName(), region);
}
}, this.executor);
return;
}
if (!sender.hasPermission("areashop.delfriend") || !(sender instanceof Player player)) {
messageBridge.message(sender, "delfriend-noPermission", region);
return;
}
if (!region.isOwner(player)) {
messageBridge.message(sender, "delfriend-noPermissionOther", region);
return;
}
this.offlinePlayerHelper.lookupOfflinePlayerAsync(args[1]).thenAcceptAsync(friend -> {
if (!region.getFriendsFeature().getFriends().contains(friend.getUniqueId())) {
messageBridge.message(sender, "delfriend-notAdded", friend.getName(), region);
} else if (region.getFriendsFeature().deleteFriend(friend.getUniqueId(), sender)) {
region.update();
messageBridge.message(sender, "delfriend-success", friend.getName(), region);
}
}, this.executor);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
for(Player player : Utils.getOnlinePlayers()) {
result.add(player.getName());
}
} else if(toComplete == 3) {
result.addAll(fileManager.getRegionNames());
}
return result;
}
}
@@ -0,0 +1,91 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.features.signs.RegionSign;
import me.wiefferink.areashop.features.signs.SignManager;
import me.wiefferink.areashop.tools.Materials;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.util.BlockIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Singleton
public class DelsignCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private SignManager signManager;
@Override
public String getCommandStart() {
return "areashop delsign";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.delsign")) {
return "help-delsign";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.delsign")) {
messageBridge.message(sender, "delsign-noPermission");
return;
}
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-onlyByPlayer");
return;
}
// Get the sign
Block block = null;
BlockIterator blockIterator = new BlockIterator(player, 100);
while(blockIterator.hasNext() && block == null) {
Block next = blockIterator.next();
if(!next.getType().isAir()) {
block = next;
}
}
if(block == null || !Materials.isSign(block.getType())) {
messageBridge.message(sender, "delsign-noSign");
return;
}
Optional<RegionSign> optionalSign = signManager.removeSign(block.getLocation());
if(optionalSign.isEmpty()) {
messageBridge.message(sender, "delsign-noRegion");
return;
}
RegionSign regionSign = optionalSign.get();
messageBridge.message(sender, "delsign-success", regionSign.getRegion());
regionSign.remove();
// Sometimes the RegionSign data is corrupted. Forcefully set the block to air
block.setType(Material.AIR);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
return new ArrayList<>();
}
}
@@ -0,0 +1,205 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.RegionGroup;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.interactivemessenger.processing.Message;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
@Singleton
public class FindCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private Economy economy;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop find";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.find")) {
return "help-find";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.find")) {
messageBridge.message(sender, "find-noPermission");
return;
}
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-onlyByPlayer");
return;
}
if(args.length <= 1 || args[1] == null || (!args[1].equalsIgnoreCase("buy") && !args[1].equalsIgnoreCase("rent"))) {
messageBridge.message(sender, "find-help");
return;
}
double balance = 0.0;
if(economy != null) {
balance = economy.getBalance(player);
}
double maxPrice = 0;
boolean maxPriceSet = false;
RegionGroup group = null;
// Parse optional price argument
if(args.length >= 3) {
try {
maxPrice = Double.parseDouble(args[2]);
maxPriceSet = true;
} catch(NumberFormatException e) {
messageBridge.message(sender, "find-wrongMaxPrice", args[2]);
return;
}
}
// Parse optional group argument
if(args.length >= 4) {
group = fileManager.getGroup(args[3]);
if(group == null) {
messageBridge.message(sender, "find-wrongGroup", args[3]);
return;
}
}
// Find buy regions
if(args[1].equalsIgnoreCase("buy")) {
Collection<BuyRegion> regions = fileManager.getBuysRef();
List<BuyRegion> results = new LinkedList<>();
for(BuyRegion region : regions) {
if(!region.isSold()
&& ((region.getPrice() <= balance && !maxPriceSet) || (region.getPrice() <= maxPrice && maxPriceSet))
&& (group == null || group.isMember(region))
&& (region.getBooleanSetting("general.findCrossWorld") || player.getWorld().equals(region.getWorld()))) {
results.add(region);
}
}
if(!results.isEmpty()) {
// Draw a random one
BuyRegion region = results.get(ThreadLocalRandom.current().nextInt(results.size()));
Message onlyInGroup = Message.empty();
if(group != null) {
onlyInGroup = Message.fromKey("find-onlyInGroup").replacements(args[3]);
}
// Teleport
if(maxPriceSet) {
messageBridge.message(player, "find-successMax", "buy", Utils.formatCurrency(maxPrice), onlyInGroup, region);
} else {
messageBridge.message(player, "find-success", "buy", Utils.formatCurrency(balance), onlyInGroup, region);
}
region.getTeleportFeature().teleportPlayer(player, region.getBooleanSetting("general.findTeleportToSign"), false);
} else {
Message onlyInGroup = Message.empty();
if(group != null) {
onlyInGroup = Message.fromKey("find-onlyInGroup").replacements(args[3]);
}
if(maxPriceSet) {
messageBridge.message(player, "find-noneFoundMax", "buy", Utils.formatCurrency(maxPrice), onlyInGroup);
} else {
messageBridge.message(player, "find-noneFound", "buy", Utils.formatCurrency(balance), onlyInGroup);
}
}
}
// Find rental regions
else {
Collection<RentRegion> regions = fileManager.getRentsRef();
List<RentRegion> results = new LinkedList<>();
for(RentRegion region : regions) {
if(!region.isRented()
&& ((region.getPrice() <= balance && !maxPriceSet) || (region.getPrice() <= maxPrice && maxPriceSet))
&& (group == null || group.isMember(region))
&& (region.getBooleanSetting("general.findCrossWorld") || player.getWorld().equals(region.getWorld()))) {
results.add(region);
}
}
if(!results.isEmpty()) {
// Draw a random one
RentRegion region = results.get(ThreadLocalRandom.current().nextInt(results.size()));
Message onlyInGroup = Message.empty();
if(group != null) {
onlyInGroup = Message.fromKey("find-onlyInGroup").replacements(args[3]);
}
// Teleport
if(maxPriceSet) {
messageBridge.message(player, "find-successMax", "rent", Utils.formatCurrency(maxPrice), onlyInGroup, region);
} else {
messageBridge.message(player, "find-success", "rent", Utils.formatCurrency(balance), onlyInGroup, region);
}
region.getTeleportFeature().teleportPlayer(player, region.getBooleanSetting("general.findTeleportToSign"), false);
} else {
Message onlyInGroup = Message.empty();
if(group != null) {
onlyInGroup = Message.fromKey("find-onlyInGroup").replacements(args[3]);
}
if(maxPriceSet) {
messageBridge.message(player, "find-noneFoundMax", "rent", Utils.formatCurrency(maxPrice), onlyInGroup);
} else {
messageBridge.message(player, "find-noneFound", "rent", Utils.formatCurrency(balance), onlyInGroup);
}
}
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
result.add("buy");
result.add("rent");
}
return result;
}
}
@@ -0,0 +1,129 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.interfaces.WorldEditInterface;
import me.wiefferink.areashop.interfaces.WorldEditSelection;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RegionFactory;
import me.wiefferink.areashop.regions.RegionGroup;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
@Singleton
public class GroupaddCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private RegionFactory regionFactory;
@Inject
private WorldEditInterface worldEditInterface;
@Override
public String getCommandStart() {
return "areashop groupadd";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.groupadd")) {
return "help-groupadd";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.groupadd")) {
messageBridge.message(sender, "groupadd-noPermission");
return;
}
if(args.length < 2 || args[1] == null) {
messageBridge.message(sender, "groupadd-help");
return;
}
RegionGroup group = fileManager.getGroup(args[1]);
if(group == null) {
group = regionFactory.createRegionGroup(args[1]);
fileManager.addGroup(group);
}
if(args.length == 2) {
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-weOnlyByPlayer");
return;
}
WorldEditSelection selection = worldEditInterface.getPlayerSelection(player);
if(selection == null) {
messageBridge.message(player, "cmd-noSelection");
return;
}
List<GeneralRegion> regions = Utils.getRegionsInSelection(selection);
if(regions.isEmpty()) {
messageBridge.message(player, "cmd-noRegionsFound");
return;
}
TreeSet<GeneralRegion> regionsSuccess = new TreeSet<>();
TreeSet<GeneralRegion> regionsFailed = new TreeSet<>();
for(GeneralRegion region : regions) {
if(group.addMember(region)) {
regionsSuccess.add(region);
} else {
regionsFailed.add(region);
}
}
if(!regionsSuccess.isEmpty()) {
messageBridge.message(player, "groupadd-weSuccess", group.getName(), Utils.combinedMessage(regionsSuccess, "region"));
}
if(!regionsFailed.isEmpty()) {
messageBridge.message(player, "groupadd-weFailed", group.getName(), Utils.combinedMessage(regionsFailed, "region"));
}
// Update all regions, this does it in a task, updating them without lag
fileManager.updateRegions(new ArrayList<>(regionsSuccess), player);
} else {
GeneralRegion region = fileManager.getRegion(args[2]);
if(region == null) {
messageBridge.message(sender, "cmd-notRegistered", args[2]);
return;
}
if(group.addMember(region)) {
region.update();
messageBridge.message(sender, "groupadd-success", group.getName(), group.getMembers().size(), region);
} else {
messageBridge.message(sender, "groupadd-failed", group.getName(), region);
}
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result = fileManager.getGroupNames();
} else if(toComplete == 3) {
result = fileManager.getRegionNames();
}
return result;
}
}
@@ -0,0 +1,130 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.interfaces.WorldEditInterface;
import me.wiefferink.areashop.interfaces.WorldEditSelection;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RegionFactory;
import me.wiefferink.areashop.regions.RegionGroup;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
@Singleton
public class GroupdelCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private RegionFactory regionFactory;
@Inject
private WorldEditInterface worldEditInterface;
@Override
public String getCommandStart() {
return "areashop groupdel";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.groupdel")) {
return "help-groupdel";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.groupdel")) {
messageBridge.message(sender, "groupdel-noPermission");
return;
}
if(args.length < 2 || args[1] == null) {
messageBridge.message(sender, "groupdel-help");
return;
}
RegionGroup group = fileManager.getGroup(args[1]);
if(group == null) {
messageBridge.message(sender, "groupdel-wrongGroup", args[1]);
return;
}
if(args.length == 2) {
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-weOnlyByPlayer");
return;
}
WorldEditSelection selection = worldEditInterface.getPlayerSelection(player);
if(selection == null) {
messageBridge.message(player, "cmd-noSelection");
return;
}
List<GeneralRegion> regions = Utils.getRegionsInSelection(selection);
if(regions.isEmpty()) {
messageBridge.message(player, "cmd-noRegionsFound");
return;
}
TreeSet<GeneralRegion> regionsSuccess = new TreeSet<>();
TreeSet<GeneralRegion> regionsFailed = new TreeSet<>();
for(GeneralRegion region : regions) {
if(group.removeMember(region)) {
regionsSuccess.add(region);
} else {
regionsFailed.add(region);
}
}
if(!regionsSuccess.isEmpty()) {
messageBridge.message(player, "groupdel-weSuccess", group.getName(), Utils.combinedMessage(regionsSuccess, "region"));
}
if(!regionsFailed.isEmpty()) {
messageBridge.message(player, "groupdel-weFailed", group.getName(), Utils.combinedMessage(regionsFailed, "region"));
}
// Update all regions, this does it in a task, updating them without lag
fileManager.updateRegions(regionsSuccess, player);
group.saveRequired();
} else {
GeneralRegion region = fileManager.getRegion(args[2]);
if(region == null) {
messageBridge.message(sender, "cmd-notRegistered", args[2]);
return;
}
if(group.removeMember(region)) {
region.update();
messageBridge.message(sender, "groupdel-success", group.getName(), group.getMembers().size(), region);
} else {
messageBridge.message(sender, "groupdel-failed", group.getName(), region);
}
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result = fileManager.getGroupNames();
} else if(toComplete == 3) {
result = fileManager.getRegionNames();
}
return result;
}
}
@@ -0,0 +1,79 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.RegionGroup;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Singleton
public class GroupinfoCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop groupinfo";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.groupinfo")) {
return "help-groupinfo";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.groupinfo")) {
messageBridge.message(sender, "groupinfo-noPermission");
return;
}
if(args.length < 2 || args[1] == null) {
messageBridge.message(sender, "groupinfo-help");
return;
}
RegionGroup group = fileManager.getGroup(args[1]);
if(group == null) {
messageBridge.message(sender, "groupinfo-noGroup", args[1]);
return;
}
Set<String> members = group.getMembers();
if(members.isEmpty()) {
messageBridge.message(sender, "groupinfo-noMembers", group.getName());
} else {
messageBridge.message(sender, "groupinfo-members", group.getName(), Utils.createCommaSeparatedList(members));
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result = fileManager.getGroupNames();
}
return result;
}
}
@@ -0,0 +1,63 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class GrouplistCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop grouplist";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.grouplist")) {
return "help-grouplist";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.grouplist")) {
messageBridge.message(sender, "grouplist-noPermission");
return;
}
List<String> groups = fileManager.getGroupNames();
if(groups.isEmpty()) {
messageBridge.message(sender, "grouplist-noGroups");
} else {
messageBridge.message(sender, "grouplist-success", Utils.createCommaSeparatedList(groups));
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
return new ArrayList<>();
}
}
@@ -0,0 +1,35 @@
package me.wiefferink.areashop.commands;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import me.wiefferink.areashop.managers.CommandManager;
import org.bukkit.command.CommandSender;
import javax.annotation.Nonnull;
public class HelpCommand extends CommandAreaShop {
private final CommandManager commandManager;
public HelpCommand(@Nonnull CommandManager commandManager) {
this.commandManager = commandManager;
}
@Override
public String getCommandStart() {
return "areashop help";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.help")) {
return "help-help";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
commandManager.showHelp(sender);
}
}
@@ -0,0 +1,88 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.regions.ImportJobFactory;
import me.wiefferink.interactivemessenger.processing.Message;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class ImportCommand extends CommandAreaShop {
/* RegionGroup priority usage:
0: Settings from /config.yml
1: Settings from /worlds/<world>/config.yml
2: Settings from /worlds/<world>/parent-regions.yml (if their priority is set it is added to this value)
*/
@Inject
private MessageBridge messageBridge;
@Inject
private ImportJobFactory importJobFactory;
@Override
public String getCommandStart() {
return "areashop import";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.import")) {
return "help-import";
}
return null;
}
// TODO:
// - Landlord?
// - Friends
// - Region flags?
// - Settings from the 'permissions' section in RegionForSale/config.yml?
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.import")) {
messageBridge.message(sender, "import-noPermission");
return;
}
if(args.length < 2) {
messageBridge.message(sender, "import-help");
return;
}
if(!"RegionForSale".equalsIgnoreCase(args[1])) {
messageBridge.message(sender, "import-wrongSource");
return;
}
if(!confirm(sender, args, Message.fromKey("import-confirm"))) {
return;
}
importJobFactory.createImportJob(sender).execute();
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result.add("RegionForSale");
}
return result;
}
}
@@ -0,0 +1,494 @@
package me.wiefferink.areashop.commands;
import io.github.bakedlibs.dough.blocks.BlockPosition;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RegionGroup;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.interactivemessenger.processing.Message;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
// TODO fix '/as info' help message hitting replacement limit (improve depth tracking?)
// TODO add info about autoExtend
@Singleton
public class InfoCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop info";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.info")) {
return "help-info";
}
return null;
}
/**
* Display a page of a list of regions.
* @param sender The CommandSender to send the messages to
* @param regions The regions to display
* @param filterGroup The group to filter the regions by
* @param keyHeader The header to print above the page
* @param pageInput The page number, if any
* @param baseCommand The command to execute for next/previous page (/areashop will be added)
*/
private void showSortedPagedList(CommandSender sender, List<? extends GeneralRegion> regions, RegionGroup filterGroup, String keyHeader, String pageInput, String baseCommand) {
int maximumItems = 20;
int itemsPerPage = maximumItems - 2;
int page = 1;
if(pageInput != null && Utils.isNumeric(pageInput)) {
try {
page = Integer.parseInt(pageInput);
} catch(NumberFormatException e) {
messageBridge.message(sender, "info-wrongPage", pageInput);
return;
}
}
if(filterGroup != null) {
regions.removeIf(generalRegion -> !filterGroup.isMember(generalRegion));
}
if(regions.isEmpty()) {
messageBridge.message(sender, "info-noRegions");
} else {
// First sort by type, then by name
regions.sort((one, two) -> {
int typeCompare = getTypeOrder(two).compareTo(getTypeOrder(one));
if(typeCompare != 0) {
return typeCompare;
} else {
return one.getName().compareTo(two.getName());
}
});
// Header
Message limitedToGroup = Message.empty();
if(filterGroup != null) {
limitedToGroup = Message.fromKey("info-limitedToGroup").replacements(filterGroup.getName());
}
messageBridge.message(sender, keyHeader, limitedToGroup);
// Page entries
int totalPages = (int)Math.ceil(regions.size() / (double)itemsPerPage); // Clip page to correct boundaries, not much need to tell the user
if(regions.size() == itemsPerPage + 1) { // 19 total items is mapped to 1 page of 19
itemsPerPage++;
totalPages = 1;
}
page = Math.max(1, Math.min(totalPages, page));
int linesPrinted = 1; // header
for(int i = (page - 1) * itemsPerPage; i < page * itemsPerPage && i < regions.size(); i++) {
String state;
GeneralRegion region = regions.get(i);
if(region.getType() == GeneralRegion.RegionType.RENT) {
if(region.getOwner() == null) {
state = "Forrent";
} else {
state = "Rented";
}
} else {
if(region.getOwner() == null) {
state = "Forsale";
} else if(!((BuyRegion)region).isInResellingMode()) {
state = "Sold";
} else {
state = "Reselling";
}
}
messageBridge.messageNoPrefix(sender, "info-entry" + state, region);
linesPrinted++;
}
Message footer = Message.empty();
// Previous button
if(page > 1) {
footer.append(Message.fromKey("info-pagePrevious").replacements(baseCommand + " " + (page - 1)));
} else {
footer.append(Message.fromKey("info-pageNoPrevious"));
}
// Page status
if(totalPages > 1) {
StringBuilder pageString = new StringBuilder("" + page);
for(int i = pageString.length(); i < (totalPages + "").length(); i++) {
pageString.insert(0, "0");
}
footer.append(Message.fromKey("info-pageStatus").replacements(page, totalPages));
if(page < totalPages) {
footer.append(Message.fromKey("info-pageNext").replacements(baseCommand + " " + (page + 1)));
} else {
footer.append(Message.fromKey("info-pageNoNext"));
}
// Fill up space if the page is not full (aligns header nicely)
for(int i = linesPrinted; i < maximumItems - 1; i++) {
sender.sendMessage(" ");
}
footer.send(sender);
}
}
}
/**
* Get an integer to order by type, usable for Comparators.
* @param region The region to get the order for
* @return An integer for sorting by type
*/
private Integer getTypeOrder(GeneralRegion region) {
if(region.getType() == GeneralRegion.RegionType.RENT) {
if(region.getOwner() == null) {
return 1;
} else {
return 2;
}
} else {
if(region.getOwner() == null) {
return 3;
} else if(!((BuyRegion)region).isInResellingMode()) {
return 4;
} else {
return 5;
}
}
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.info")) {
messageBridge.message(sender, "info-noPermission");
return;
}
if(args.length > 1 && args[1] != null) {
// Get filter group (only used by some commands)
RegionGroup filterGroup = null;
Set<String> groupFilters = new HashSet<>(Arrays.asList("all", "rented", "forrent", "sold", "forsale", "reselling"));
if(groupFilters.contains(args[1].toLowerCase()) && args.length > 2) {
if(!Utils.isNumeric(args[2])) {
filterGroup = fileManager.getGroup(args[2]);
if(filterGroup == null) {
messageBridge.message(sender, "info-noFiltergroup", args[2]);
return;
}
// Pass page number to args[2] if available
if(args.length > 3) {
args[2] = args[3];
}
}
}
// All regions
if(args[1].equalsIgnoreCase("all")) {
showSortedPagedList(sender, new ArrayList<>(fileManager.getRegionsRef()), filterGroup, "info-allHeader", (args.length > 2 ? args[2] : null), "info all");
}
// Rented regions
else if(args[1].equalsIgnoreCase("rented")) {
List<RentRegion> regions = new ArrayList<>(fileManager.getRentsRef());
regions.removeIf(RentRegion::isAvailable);
showSortedPagedList(sender, regions, filterGroup, "info-rentedHeader", (args.length > 2 ? args[2] : null), "info rented");
}
// Forrent regions
else if(args[1].equalsIgnoreCase("forrent")) {
List<RentRegion> regions = new ArrayList<>(fileManager.getRentsRef());
regions.removeIf(RentRegion::isRented);
showSortedPagedList(sender, regions, filterGroup, "info-forrentHeader", (args.length > 2 ? args[2] : null), "info forrent");
}
// Sold regions
else if(args[1].equalsIgnoreCase("sold")) {
List<BuyRegion> regions = new ArrayList<>(fileManager.getBuysRef());
regions.removeIf(BuyRegion::isAvailable);
showSortedPagedList(sender, regions, filterGroup, "info-soldHeader", (args.length > 2 ? args[2] : null), "info sold");
}
// Forsale regions
else if(args[1].equalsIgnoreCase("forsale")) {
List<BuyRegion> regions = new ArrayList<>(fileManager.getBuysRef());
regions.removeIf(BuyRegion::isSold);
showSortedPagedList(sender, regions, filterGroup, "info-forsaleHeader", (args.length > 2 ? args[2] : null), "info forsale");
}
// Reselling regions
else if(args[1].equalsIgnoreCase("reselling")) {
List<BuyRegion> regions = new ArrayList<>(fileManager.getBuysRef());
regions.removeIf(region -> !region.isInResellingMode());
showSortedPagedList(sender, regions, filterGroup, "info-resellingHeader", (args.length > 2 ? args[2] : null), "info reselling");
}
// List of regions without a group
else if(args[1].equalsIgnoreCase("nogroup")) {
List<GeneralRegion> regions = new ArrayList<>(fileManager.getRegionsRef());
for(RegionGroup group : fileManager.getGroups()) {
regions.removeAll(group.getMemberRegions());
}
if(regions.isEmpty()) {
messageBridge.message(sender, "info-nogroupNone");
} else {
showSortedPagedList(sender, regions, filterGroup, "info-nogroupHeader", (args.length > 2 ? args[2] : null), "info nogroup");
}
}
// Region info
else if(args[1].equalsIgnoreCase("region")) {
if(args.length > 1) {
RentRegion rent = null;
BuyRegion buy = null;
if(args.length > 2) {
rent = fileManager.getRent(args[2]);
buy = fileManager.getBuy(args[2]);
} else {
if(sender instanceof Player) {
// get the region by location
List<GeneralRegion> regions = Utils.getImportantRegions(((Player)sender).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
if(regions.get(0) instanceof RentRegion) {
rent = (RentRegion)regions.get(0);
} else if(regions.get(0) instanceof BuyRegion) {
buy = (BuyRegion)regions.get(0);
}
}
} else {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
}
if(rent == null && buy == null) {
messageBridge.message(sender, "info-regionNotExisting", args[2]);
return;
}
if(rent != null) {
messageBridge.message(sender, "info-regionHeaderRent", rent);
if(rent.isRented()) {
messageBridge.messageNoPrefix(sender, "info-regionRented", rent);
messageBridge.messageNoPrefix(sender, "info-regionExtending", rent);
// Money back
if(UnrentCommand.canUse(sender, rent)) {
messageBridge.messageNoPrefix(sender, "info-regionMoneyBackRentClick", rent);
} else {
messageBridge.messageNoPrefix(sender, "info-regionMoneyBackRent", rent);
}
// Friends
if(!rent.getFriendsFeature().getFriendNames().isEmpty()) {
String messagePart = "info-friend";
if(DelfriendCommand.canUse(sender, rent)) {
messagePart = "info-friendRemove";
}
messageBridge.messageNoPrefix(sender, "info-regionFriends", rent, Utils.combinedMessage(rent.getFriendsFeature().getFriendNames(), messagePart));
}
} else {
messageBridge.messageNoPrefix(sender, "info-regionCanBeRented", rent);
}
if(rent.getLandlordName() != null) {
messageBridge.messageNoPrefix(sender, "info-regionLandlord", rent);
}
// Maximum extends
if(rent.getMaxExtends() != -1) {
if(rent.getMaxExtends() == 0) {
messageBridge.messageNoPrefix(sender, "info-regionNoExtending", rent);
} else if(rent.isRented()) {
messageBridge.messageNoPrefix(sender, "info-regionExtendsLeft", rent);
} else {
messageBridge.messageNoPrefix(sender, "info-regionMaxExtends", rent);
}
}
// If maxExtends is zero it does not make sense to show this message
if(rent.getMaxRentTime() != -1 && rent.getMaxExtends() != 0) {
messageBridge.messageNoPrefix(sender, "info-regionMaxRentTime", rent);
}
if(rent.getInactiveTimeUntilUnrent() != -1) {
messageBridge.messageNoPrefix(sender, "info-regionInactiveUnrent", rent);
}
// Teleport
Message tp = Message.fromKey("info-prefix");
boolean foundSomething = false;
if(TeleportCommand.canUse(sender, rent)) {
foundSomething = true;
tp.append(Message.fromKey("info-regionTeleport").replacements(rent));
}
if(SetteleportCommand.canUse(sender, rent)) {
if(foundSomething) {
tp.append(", ");
}
foundSomething = true;
tp.append(Message.fromKey("info-setRegionTeleport").replacements(rent));
}
if(foundSomething) {
tp.append(".");
tp.send(sender);
}
// Signs
List<String> signLocations = new ArrayList<>();
for(BlockPosition location : rent.getSignsFeature().signManager().allSignLocations()) {
signLocations.add(Message.fromKey("info-regionSignLocation").replacements(location.getWorld().getName(), location.getX(), location.getY(), location.getZ()).getPlain());
}
if(!signLocations.isEmpty()) {
messageBridge.messageNoPrefix(sender, "info-regionSigns", Utils.createCommaSeparatedList(signLocations));
}
// Groups
if(sender.hasPermission("areashop.groupinfo") && !rent.getGroupNames().isEmpty()) {
messageBridge.messageNoPrefix(sender, "info-regionGroups", Utils.createCommaSeparatedList(rent.getGroupNames()));
}
// Restoring
if(rent.isRestoreEnabled()) {
messageBridge.messageNoPrefix(sender, "info-regionRestoringRent", rent);
}
// Restrictions
if(!rent.isRented()) {
if(rent.restrictedToRegion()) {
messageBridge.messageNoPrefix(sender, "info-regionRestrictedRegionRent", rent);
} else if(rent.restrictedToWorld()) {
messageBridge.messageNoPrefix(sender, "info-regionRestrictedWorldRent", rent);
}
}
messageBridge.messageNoPrefix(sender, "info-regionFooterRent", rent);
} else if(buy != null) {
messageBridge.message(sender, "info-regionHeaderBuy", buy);
if(buy.isSold()) {
if(buy.isInResellingMode()) {
messageBridge.messageNoPrefix(sender, "info-regionReselling", buy);
messageBridge.messageNoPrefix(sender, "info-regionReselPrice", buy);
} else {
messageBridge.messageNoPrefix(sender, "info-regionBought", buy);
}
// Money back
if(!buy.getBooleanSetting("buy.sellDisabled")) {
if (SellCommand.canUse(sender, buy)) {
messageBridge.messageNoPrefix(sender, "info-regionMoneyBackBuyClick", buy);
} else {
messageBridge.messageNoPrefix(sender, "info-regionMoneyBackBuy", buy);
}
}
// Friends
if(!buy.getFriendsFeature().getFriendNames().isEmpty()) {
String messagePart = "info-friend";
if(DelfriendCommand.canUse(sender, buy)) {
messagePart = "info-friendRemove";
}
messageBridge.messageNoPrefix(sender, "info-regionFriends", buy, Utils.combinedMessage(buy.getFriendsFeature().getFriendNames(), messagePart));
}
} else {
messageBridge.messageNoPrefix(sender, "info-regionCanBeBought", buy);
}
if(buy.getLandlord() != null) {
messageBridge.messageNoPrefix(sender, "info-regionLandlord", buy);
}
if(buy.getInactiveTimeUntilSell() != -1) {
messageBridge.messageNoPrefix(sender, "info-regionInactiveSell", buy);
}
// Teleport
Message tp = Message.fromKey("info-prefix");
boolean foundSomething = false;
if(TeleportCommand.canUse(sender, buy)) {
foundSomething = true;
tp.append(Message.fromKey("info-regionTeleport").replacements(buy));
}
if(SetteleportCommand.canUse(sender, buy)) {
if(foundSomething) {
tp.append(", ");
}
foundSomething = true;
tp.append(Message.fromKey("info-setRegionTeleport").replacements(buy));
}
if(foundSomething) {
tp.append(".");
tp.send(sender);
}
// Signs
List<String> signLocations = new ArrayList<>();
for(BlockPosition location : buy.getSignsFeature().signManager().allSignLocations()) {
signLocations.add(Message.fromKey("info-regionSignLocation").replacements(location.getWorld().getName(), location.getX(), location.getY(), location.getZ()).getPlain());
}
if(!signLocations.isEmpty()) {
messageBridge.messageNoPrefix(sender, "info-regionSigns", Utils.createCommaSeparatedList(signLocations));
}
// Groups
if(sender.hasPermission("areashop.groupinfo") && !buy.getGroupNames().isEmpty()) {
messageBridge.messageNoPrefix(sender, "info-regionGroups", Utils.createCommaSeparatedList(buy.getGroupNames()));
}
// Restoring
if(buy.isRestoreEnabled()) {
messageBridge.messageNoPrefix(sender, "info-regionRestoringBuy", buy);
}
// Restrictions
if(!buy.isSold()) {
if(buy.restrictedToRegion()) {
messageBridge.messageNoPrefix(sender, "info-regionRestrictedRegionBuy", buy);
} else if(buy.restrictedToWorld()) {
messageBridge.messageNoPrefix(sender, "info-regionRestrictedWorldBuy", buy);
}
}
messageBridge.messageNoPrefix(sender, "info-regionFooterBuy", buy);
}
} else {
messageBridge.message(sender, "info-regionHelp");
}
} else {
messageBridge.message(sender, "info-help");
}
} else {
messageBridge.message(sender, "info-help");
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result.addAll(Arrays.asList("all", "rented", "forrent", "sold", "forsale", "player", "region", "nogroup", "reselling"));
} else if(toComplete == 3) {
if(start[2].equalsIgnoreCase("player")) {
for(Player player : Utils.getOnlinePlayers()) {
result.add(player.getName());
}
} else if(start[2].equalsIgnoreCase("region")) {
result.addAll(fileManager.getBuyNames());
result.addAll(fileManager.getRentNames());
}
}
return result;
}
}
@@ -0,0 +1,99 @@
package me.wiefferink.areashop.commands;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.SignLinkerManager;
import me.wiefferink.interactivemessenger.processing.Message;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Singleton
public class LinksignsCommand extends CommandAreaShop {
@Inject
private SignLinkerManager signLinkerManager;
@Inject
private AreaShop plugin;
@Inject
private MessageBridge messageBridge;
@Override
public String getCommandStart() {
return "areashop linksigns";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.linksigns")) {
return "help-linksigns";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.linksigns")) {
messageBridge.message(sender, "linksigns-noPermission");
return;
}
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-onlyByPlayer");
return;
}
if(signLinkerManager.isInSignLinkMode(player)) {
signLinkerManager.exitSignLinkMode(player);
} else {
// Get the profile
String profile = null;
if(args.length > 1) {
profile = args[1];
ConfigurationSection signProfilesSection = plugin.getConfig().getConfigurationSection("signProfiles");
if(signProfilesSection != null) {
Set<String> profiles = signProfilesSection.getKeys(false);
if(!profiles.contains(profile)) {
ArrayList<String> message = new ArrayList<>();
for(String p : profiles) {
if(!message.isEmpty()) {
message.add(", ");
}
message.addAll(Message.fromKey("addsign-profile").replacements(p).get());
}
messageBridge.message(sender, "addsign-wrongProfile", Message.fromList(message));
return;
}
}
}
signLinkerManager.enterSignLinkMode(player, profile);
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result.addAll(plugin.getFileManager().getRegionNames());
} else if(toComplete == 3) {
result.addAll(plugin.getConfig().getStringList("signProfiles"));
}
return result;
}
}
@@ -0,0 +1,161 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.adapters.platform.OfflinePlayerHelper;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.BukkitSchedulerExecutor;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Singleton
public class MeCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private OfflinePlayerHelper offlinePlayerHelper;
@Inject
private BukkitSchedulerExecutor executor;
@Override
public String getCommandStart() {
return "areashop me";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.me")) {
return "help-me";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.me")) {
messageBridge.message(sender, "me-noPermission");
return;
}
OfflinePlayer player = null;
if(!(sender instanceof Player)) {
if(args.length <= 1) {
messageBridge.message(sender, "me-notAPlayer");
return;
}
} else {
player = (OfflinePlayer)sender;
}
if(args.length > 1) {
// Lookup offline players async
this.offlinePlayerHelper.lookupOfflinePlayerAsync(args[1]).thenAcceptAsync(offlinePlayer -> {
if (!offlinePlayer.hasPlayedBefore()) {
messageBridge.message(sender, "me-noPlayer", args[1]);
} else {
sendMessageAboutRegions(sender, offlinePlayer);
}
}, this.executor);
return;
}
sendMessageAboutRegions(sender, player);
}
private void sendMessageAboutRegions(CommandSender sender, OfflinePlayer target) {
// Get the regions owned by the player
Set<RentRegion> rentRegions = new HashSet<>();
for(RentRegion region : fileManager.getRentsRef()) {
if(region.isOwner(target)) {
rentRegions.add(region);
}
}
Set<BuyRegion> buyRegions = new HashSet<>();
for(BuyRegion region : fileManager.getBuysRef()) {
if(region.isOwner(target)) {
buyRegions.add(region);
}
}
// Get the regions the player is added as friend
Set<GeneralRegion> friendRegions = new HashSet<>();
for(GeneralRegion region : fileManager.getRegionsRef()) {
if(region.getFriendsFeature().getFriends().contains(target.getUniqueId())) {
friendRegions.add(region);
}
}
// Send messages
boolean foundSome = !rentRegions.isEmpty() || !buyRegions.isEmpty() || !friendRegions.isEmpty();
if(!foundSome) {
messageBridge.message(sender, "me-nothing", target.getName());
return;
}
messageBridge.message(sender, "me-header", target.getName());
if(!rentRegions.isEmpty()) {
for(RentRegion region : rentRegions) {
messageBridge.messageNoPrefix(sender, "me-rentLine", region);
}
}
if(!buyRegions.isEmpty()) {
for(BuyRegion region : buyRegions) {
messageBridge.messageNoPrefix(sender, "me-buyLine", region);
}
}
if(!friendRegions.isEmpty()) {
for(GeneralRegion region : friendRegions) {
messageBridge.messageNoPrefix(sender, "me-friendLine", region);
}
}
messageBridge.messageNoPrefix(sender, "me-clickHint");
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
for(Player player : Utils.getOnlinePlayers()) {
result.add(player.getName());
}
}
return result;
}
}
@@ -0,0 +1,79 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.interactivemessenger.processing.Message;
import org.apache.commons.lang.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class MessageCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Override
public String getCommandStart() {
return "areashop message";
}
@Override
public String getHelp(CommandSender target) {
// Internal command, no need to show in the help list
return null;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
if(!sender.hasPermission("areashop.message")) {
messageBridge.message(sender, "message-noPermission");
return;
}
if(args.length < 3) {
messageBridge.message(sender, "message-help");
return;
}
Player player = Bukkit.getPlayer(args[1]);
if(player == null) {
messageBridge.message(sender, "message-notOnline", args[1]);
return;
}
String[] messageArgs = new String[args.length - 2];
System.arraycopy(args, 2, messageArgs, 0, args.length - 2);
String message = StringUtils.join(messageArgs, " ");
Message.fromString(message).send(player);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
for(Player player : Utils.getOnlinePlayers()) {
result.add(player.getName());
}
}
return result;
}
}
@@ -0,0 +1,7 @@
# Commands
This module contains all AreaShop commands.
* `CommandAreaShop` is extended by all other classes, and implements some general functionality and defines which methods should be implemented.
* Other classes implement specific sub-commands, supply their help messages and tab completion.
* `CommandManager` class in the `managers` package handles incoming commands and dispatches them to the classes in this package.
@@ -0,0 +1,39 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import org.bukkit.command.CommandSender;
@Singleton
public class ReloadCommand extends CommandAreaShop {
@Inject
private AreaShop plugin;
@Inject
private MessageBridge messageBridge;
@Override
public String getCommandStart() {
return "areashop reload";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.reload")) {
return "help-reload";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(sender.hasPermission("areashop.reload")) {
// Reload the configuration files and update all region flags/signs
plugin.reload(sender);
} else {
messageBridge.message(sender, "reload-noPermission");
}
}
}
@@ -0,0 +1,78 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class RentCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop rent";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.rent")) {
return "help-rent";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.rent")) {
messageBridge.message(sender, "rent-noPermission");
return;
}
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-onlyByPlayer");
return;
}
if(args.length > 1 && args[1] != null) {
RentRegion rent = fileManager.getRent(args[1]);
if(rent == null) {
messageBridge.message(sender, "rent-notRentable", args[1]);
} else {
rent.rent(player);
}
} else {
// get the region by location
List<RentRegion> regions = Utils.getImportantRentRegions(((Player)sender).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
} else {
regions.get(0).rent(player);
}
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
for(RentRegion region : fileManager.getRentsRef()) {
if(!region.isRented()) {
result.add(region.getName());
}
}
}
return result;
}
}
@@ -0,0 +1,144 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class ResellCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop resell";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.resellall") || target.hasPermission("areashop.resell")) {
return "help-resell";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.resell") && !sender.hasPermission("areashop.resellall")) {
messageBridge.message(sender, "resell-noPermissionOther");
return;
}
if(args.length <= 1) {
messageBridge.message(sender, "resell-help");
return;
}
double price;
try {
price = Double.parseDouble(args[1]);
} catch(NumberFormatException e) {
messageBridge.message(sender, "resell-wrongPrice", args[1]);
return;
}
if(price < 0) {
messageBridge.message(sender, "resell-wrongPrice", args[1]);
return;
}
BuyRegion buy;
if(args.length <= 2) {
if(sender instanceof Player) {
// get the region by location
List<BuyRegion> regions = Utils.getImportantBuyRegions(((Player)sender).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
buy = regions.get(0);
}
} else {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
} else {
buy = fileManager.getBuy(args[2]);
if(buy == null) {
messageBridge.message(sender, "resell-notRegistered", args[2]);
return;
}
}
if(buy == null) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
}
if(!buy.isSold()) {
messageBridge.message(sender, "resell-notBought", buy);
return;
}
if(sender.hasPermission("areashop.resellall")) {
buy.enableReselling(price);
buy.update();
messageBridge.message(sender, "resell-success", buy);
} else if(sender.hasPermission("areashop.resell") && sender instanceof Player) {
if(!buy.isOwner((Player)sender)) {
messageBridge.message(sender, "resell-noPermissionOther", buy);
return;
}
if(buy.getBooleanSetting("buy.resellDisabled")) {
messageBridge.message(sender, "resell-disabled", buy);
return;
}
buy.enableReselling(price);
buy.update();
messageBridge.message(sender, "resell-success", buy);
} else {
messageBridge.message(sender, "resell-noPermission", buy);
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 3) {
for(BuyRegion region : fileManager.getBuysRef()) {
if(region.isSold() && !region.isInResellingMode()) {
result.add(region.getName());
}
}
}
return result;
}
}
@@ -0,0 +1,96 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Singleton
public class SchematiceventCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop schemevent";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.schematicevents")) {
return "help-schemevent";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.schematicevents")) {
messageBridge.message(sender, "schemevent-noPermission");
return;
}
if(args.length < 3 || args[1] == null || args[2] == null) {
messageBridge.message(sender, "schemevent-help");
return;
}
GeneralRegion region = fileManager.getRegion(args[1]);
if(region == null) {
messageBridge.message(sender, "cmd-notRegistered", args[1]);
return;
}
if(region.getRegion() == null) {
messageBridge.message(sender, "general-noRegion", region);
return;
}
GeneralRegion.RegionEvent event = null;
boolean exception = false;
try {
event = GeneralRegion.RegionEvent.valueOf(args[2].toUpperCase());
} catch(IllegalArgumentException e) {
exception = true;
}
// Check for a totally wrong event or a non matching event
if(exception) {
ArrayList<String> values = new ArrayList<>();
for(GeneralRegion.RegionEvent value : GeneralRegion.RegionEvent.values()) {
values.add(value.getValue().toLowerCase());
}
messageBridge.message(sender, "schemevent-wrongEvent", args[2], Utils.createCommaSeparatedList(values), region);
return;
}
region.handleSchematicEvent(event);
region.update();
messageBridge.message(sender, "schemevent-success", args[2], region);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
result.addAll(fileManager.getRegionNames());
} else if(toComplete == 3) {
GeneralRegion region = fileManager.getRegion(start[2]);
if(region != null) {
if(region instanceof RentRegion) {
result.addAll(Arrays.asList("created", "deleted", "rented", "unrented"));
} else if(region instanceof BuyRegion) {
result.addAll(Arrays.asList("created", "deleted", "bought", "sold"));
}
}
}
return result;
}
}
@@ -0,0 +1,119 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class SellCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop sell";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.sell") || target.hasPermission("areashop.sellown")) {
return "help-sell";
}
return null;
}
/**
* Check if a person can sell the region.
* @param person The person to check
* @param region The region to check for
* @return true if the person can sell it, otherwise false
*/
public static boolean canUse(CommandSender person, GeneralRegion region) {
if(person.hasPermission("areashop.sell")) {
return true;
}
if(person instanceof Player player) {
return region.isOwner(player) && person.hasPermission("areashop.sellown");
}
return false;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.sell") && !sender.hasPermission("areashop.sellown")) {
messageBridge.message(sender, "sell-noPermission");
return;
}
BuyRegion buy;
if(args.length <= 1) {
if(sender instanceof Player player) {
// get the region by location
List<BuyRegion> regions = Utils.getImportantBuyRegions((player).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
buy = regions.get(0);
}
} else {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
} else {
buy = fileManager.getBuy(args[1]);
}
if(buy == null) {
messageBridge.message(sender, "sell-notRegistered");
return;
}
if(!buy.isSold()) {
messageBridge.message(sender, "sell-notBought", buy);
return;
}
buy.sell(true, sender);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
for(BuyRegion region : fileManager.getBuysRef()) {
if(region.isSold()) {
result.add(region.getName());
}
}
}
return result;
}
}
@@ -0,0 +1,118 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class SetdurationCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private Plugin plugin;
@Override
public String getCommandStart() {
return "areashop setduration";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.setduration")) {
return "help-setduration";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.setduration") && (!sender.hasPermission("areashop.setduration.landlord") && sender instanceof Player)) {
messageBridge.message(sender, "setduration-noPermission");
return;
}
if(args.length < 3 || args[1] == null || args[2] == null) {
messageBridge.message(sender, "setduration-help");
return;
}
int regionArgument = 3;
if(args.length >= 2 && ("default".equalsIgnoreCase(args[1]) || "reset".equalsIgnoreCase(args[1]))) {
regionArgument = 2;
}
RentRegion rent;
if(args.length <= regionArgument) {
if(sender instanceof Player) {
// get the region by location
List<RentRegion> regions = Utils.getImportantRentRegions(((Player)sender).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
rent = regions.get(0);
}
} else {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
} else {
rent = fileManager.getRent(args[regionArgument]);
}
if(rent == null) {
messageBridge.message(sender, "setduration-notRegistered", args[regionArgument]);
return;
}
if(!sender.hasPermission("areashop.setduration") && !(sender instanceof Player && rent.isLandlord(((Player)sender).getUniqueId()))) {
messageBridge.message(sender, "setduration-noLandlord", rent);
return;
}
if("default".equalsIgnoreCase(args[1]) || "reset".equalsIgnoreCase(args[1])) {
rent.setDuration(null);
rent.update();
messageBridge.message(sender, "setduration-successRemoved", rent);
return;
}
try {
Integer.parseInt(args[1]);
} catch(NumberFormatException e) {
messageBridge.message(sender, "setduration-wrongAmount", args[1], rent);
return;
}
if(!Utils.checkTimeFormat(args[1] + " " + args[2])) {
messageBridge.message(sender, "setduration-wrongFormat", args[1] + " " + args[2], rent);
return;
}
rent.setDuration(args[1] + " " + args[2]);
rent.update();
messageBridge.message(sender, "setduration-success", rent);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 3) {
result.addAll(plugin.getConfig().getStringList("minutes"));
result.addAll(plugin.getConfig().getStringList("hours"));
result.addAll(plugin.getConfig().getStringList("days"));
result.addAll(plugin.getConfig().getStringList("months"));
result.addAll(plugin.getConfig().getStringList("years"));
} else if(toComplete == 4) {
result = fileManager.getRentNames();
}
return result;
}
}
@@ -0,0 +1,100 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.adapters.platform.OfflinePlayerHelper;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.tools.BukkitSchedulerExecutor;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class SetlandlordCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private OfflinePlayerHelper offlinePlayerHelper;
@Inject
private BukkitSchedulerExecutor executor;
@Override
public String getCommandStart() {
return "areashop setlandlord";
}
@Override
public String getHelp(CommandSender target) {
if (target.hasPermission("areashop.setlandlord")) {
return "help-setlandlord";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!sender.hasPermission("areashop.setlandlord")) {
messageBridge.message(sender, "setlandlord-noPermission");
return;
}
if (args.length < 2) {
messageBridge.message(sender, "setlandlord-help");
return;
}
GeneralRegion region;
if (args.length < 3) {
if (!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
// get the region by location
List<GeneralRegion> regions = Utils.getImportantRegions(player.getLocation());
if (regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if (regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
region = regions.get(0);
}
} else {
region = fileManager.getRegion(args[2]);
}
if (region == null) {
messageBridge.message(sender, "cmd-notRegistered", args[2]);
return;
}
this.offlinePlayerHelper.lookupOfflinePlayerAsync(args[1]).thenAcceptAsync(offlinePlayer -> {
String playerName = offlinePlayer.getName();
if (playerName == null || playerName.isEmpty()) {
playerName = args[1];
}
region.setLandlord(offlinePlayer.getUniqueId(), playerName);
region.update();
messageBridge.message(sender, "setlandlord-success", playerName, region);
}, this.executor);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if (toComplete == 2) {
for (Player player : Utils.getOnlinePlayers()) {
result.add(player.getName());
}
} else if (toComplete == 3) {
result.addAll(fileManager.getRegionNames());
}
return result;
}
}
@@ -0,0 +1,142 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.adapters.platform.OfflinePlayerHelper;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.BukkitSchedulerExecutor;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.UUID;
@Singleton
public class SetownerCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private OfflinePlayerHelper offlinePlayerHelper;
@Inject
private BukkitSchedulerExecutor executor;
@Override
public String getCommandStart() {
return "areashop setowner";
}
@Override
public String getHelp(CommandSender target) {
if (target.hasPermission("areashop.setownerrent") || target.hasPermission("areashop.setownerbuy")) {
return "help-setowner";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!sender.hasPermission("areashop.setownerrent") && !sender.hasPermission("areashop.setownerbuy")) {
messageBridge.message(sender, "setowner-noPermission");
return;
}
GeneralRegion region;
if (args.length < 2) {
messageBridge.message(sender, "setowner-help");
return;
}
if (args.length == 2) {
if (!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
// get the region by location
List<GeneralRegion> regions = Utils.getImportantRegions(player.getLocation());
if (regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if (regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
region = regions.get(0);
}
} else {
region = fileManager.getRegion(args[2]);
}
if (region == null) {
messageBridge.message(sender, "setowner-notRegistered");
return;
}
if (region instanceof RentRegion && !sender.hasPermission("areashop.setownerrent")) {
messageBridge.message(sender, "setowner-noPermissionRent", region);
return;
}
if (region instanceof BuyRegion && !sender.hasPermission("areashop.setownerbuy")) {
messageBridge.message(sender, "setowner-noPermissionBuy", region);
return;
}
this.offlinePlayerHelper.lookupUuidAsync(args[1]).thenAcceptAsync(optionalUuid -> {
if (optionalUuid.isEmpty()) {
messageBridge.message(sender, "setowner-noPlayer", args[1], region);
return;
}
final UUID uuid = optionalUuid.get();
if (region instanceof RentRegion rent) {
if (rent.isRenter(uuid)) {
// extend
rent.setRentedUntil(rent.getRentedUntil() + rent.getDuration());
rent.setRenter(uuid);
messageBridge.message(sender, "setowner-succesRentExtend", region);
} else {
// change
if (!rent.isRented()) {
rent.setRentedUntil(Calendar.getInstance().getTimeInMillis() + rent.getDuration());
}
rent.setRenter(uuid);
messageBridge.message(sender, "setowner-succesRent", region);
}
}
if (region instanceof BuyRegion buy) {
buy.setBuyer(uuid);
messageBridge.message(sender, "setowner-succesBuy", region);
}
region.getFriendsFeature().deleteFriend(region.getOwner(), null);
region.update();
region.saveRequired();
}, this.executor);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if (toComplete == 2) {
for (Player player : Utils.getOnlinePlayers()) {
result.add(player.getName());
}
} else if (toComplete == 3) {
result.addAll(fileManager.getRegionNames());
}
return result;
}
}
@@ -0,0 +1,113 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class SetpriceCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop setprice";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.setprice")) {
return "help-setprice";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.setprice") && (!sender.hasPermission("areashop.setprice.landlord") && sender instanceof Player)) {
messageBridge.message(sender, "setprice-noPermission");
return;
}
if(args.length < 2 || args[1] == null) {
messageBridge.message(sender, "setprice-help");
return;
}
GeneralRegion region;
if(args.length < 3) {
if(sender instanceof Player) {
// get the region by location
List<GeneralRegion> regions = Utils.getImportantRegions(((Player)sender).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
region = regions.get(0);
}
} else {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
} else {
region = fileManager.getRegion(args[2]);
}
if(region == null) {
messageBridge.message(sender, "setprice-notRegistered", args[2]);
return;
}
if(!sender.hasPermission("areashop.setprice") && !(sender instanceof Player && region.isLandlord(((Player)sender).getUniqueId()))) {
messageBridge.message(sender, "setprice-noLandlord", region);
return;
}
if("default".equalsIgnoreCase(args[1]) || "reset".equalsIgnoreCase(args[1])) {
if(region instanceof RentRegion) {
((RentRegion)region).setPrice(null);
} else if(region instanceof BuyRegion) {
((BuyRegion)region).setPrice(null);
}
region.update();
messageBridge.message(sender, "setprice-successRemoved", region);
return;
}
double price;
try {
price = Double.parseDouble(args[1]);
} catch(NumberFormatException e) {
messageBridge.message(sender, "setprice-wrongPrice", args[1], region);
return;
}
if(region instanceof RentRegion) {
((RentRegion)region).setPrice(price);
messageBridge.message(sender, "setprice-successRent", region);
} else if(region instanceof BuyRegion) {
((BuyRegion)region).setPrice(price);
messageBridge.message(sender, "setprice-successBuy", region);
}
region.update();
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 3) {
result = fileManager.getRegionNames();
}
return result;
}
}
@@ -0,0 +1,90 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.GeneralRegion;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class SetrestoreCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private Plugin plugin;
@Override
public String getCommandStart() {
return "areashop setrestore";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.setrestore")) {
return "help-setrestore";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.setrestore")) {
messageBridge.message(sender, "setrestore-noPermission");
return;
}
if(args.length <= 2 || args[1] == null || args[2] == null) {
messageBridge.message(sender, "setrestore-help");
return;
}
GeneralRegion region = fileManager.getRegion(args[1]);
if(region == null) {
messageBridge.message(sender, "setrestore-notRegistered", args[1]);
return;
}
Boolean value = null;
if(args[2].equalsIgnoreCase("true")) {
value = true;
} else if(args[2].equalsIgnoreCase("false")) {
value = false;
}
region.setRestoreSetting(value);
String valueString = "general";
if(value != null) {
valueString = value + "";
}
if(args.length > 3) {
region.setSchematicProfile(args[3]);
messageBridge.message(sender, "setrestore-successProfile", valueString, args[3], region);
} else {
messageBridge.message(sender, "setrestore-success", valueString, region);
}
region.update();
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result = fileManager.getRegionNames();
} else if(toComplete == 3) {
result.add("true");
result.add("false");
result.add("general");
} else if(toComplete == 4) {
ConfigurationSection schemProfiles = fileManager.getConfig().getConfigurationSection("schematicProfiles");
if (schemProfiles != null) {
result.addAll(schemProfiles.getKeys(false));
}
}
return result;
}
}
@@ -0,0 +1,126 @@
package me.wiefferink.areashop.commands;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class SetteleportCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop settp";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.setteleportall") || target.hasPermission("areashop.setteleport")) {
return "help-setteleport";
}
return null;
}
/**
* Check if a person can set the teleport location of the region.
* @param person The person to check
* @param region The region to check for
* @return true if the person can set the teleport location, otherwise false
*/
public static boolean canUse(CommandSender person, GeneralRegion region) {
if(!(person instanceof Player player)) {
return false;
}
return player.hasPermission("areashop.setteleportall")
|| region.isOwner(player) && player.hasPermission("areashop.setteleport");
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.setteleport") && !sender.hasPermission("areashop.setteleportall")) {
messageBridge.message(sender, "setteleport-noPermission");
return;
}
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "onlyByPlayer");
return;
}
GeneralRegion region;
if(args.length < 2) {
// get the region by location
List<GeneralRegion> regions = Utils.getImportantRegions(((Player)sender).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
region = regions.get(0);
}
} else {
region = fileManager.getRegion(args[1]);
}
boolean owner;
if(region == null) {
messageBridge.message(player, "setteleport-noRentOrBuy", args[1]);
return;
}
if(region instanceof RentRegion) {
owner = player.getUniqueId().equals(((RentRegion)region).getRenter());
} else {
owner = player.getUniqueId().equals(((BuyRegion)region).getBuyer());
}
if(!player.hasPermission("areashop.setteleport")) {
messageBridge.message(player, "setteleport-noPermission", region);
return;
} else if(!owner && !player.hasPermission("areashop.setteleportall")) {
messageBridge.message(player, "setteleport-noPermissionOther", region);
return;
}
ProtectedRegion wgRegion = region.getRegion();
if(args.length > 2 && args[2] != null && (args[2].equalsIgnoreCase("reset") || args[2].equalsIgnoreCase("yes") || args[2].equalsIgnoreCase("true"))) {
region.getTeleportFeature().setTeleport(null);
region.update();
messageBridge.message(player, "setteleport-reset", region);
return;
}
if(!player.hasPermission("areashop.setteleportoutsideregion") && (wgRegion == null || !wgRegion.contains(player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ()))) {
messageBridge.message(player, "setteleport-notInside", region);
return;
}
region.getTeleportFeature().setTeleport(player.getLocation());
region.update();
messageBridge.message(player, "setteleport-success", region);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
result.addAll(fileManager.getRegionNames());
} else if(toComplete == 3) {
result.add("reset");
}
return result;
}
}
@@ -0,0 +1,282 @@
package me.wiefferink.areashop.commands;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.events.ask.AddingRegionEvent;
import me.wiefferink.areashop.interfaces.WorldEditInterface;
import me.wiefferink.areashop.interfaces.WorldEditSelection;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RegionFactory;
import me.wiefferink.areashop.regions.RegionGroup;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.interactivemessenger.processing.Message;
import org.bukkit.Location;
import org.bukkit.block.BlockFace;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class StackCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Inject
private WorldEditInterface worldEditInterface;
@Inject
private WorldGuardInterface worldGuardInterface;
@Inject
private RegionFactory regionFactory;
@Inject
private Plugin plugin;
@Override
public String getCommandStart() {
return "areashop stack";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.stack")) {
return "help-stack";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
// Check permission
if(!sender.hasPermission("areashop.stack")) {
messageBridge.message(sender, "stack-noPermission");
return;
}
// Only from ingame
if(!(sender instanceof final Player player)) {
messageBridge.message(sender, "cmd-onlyByPlayer");
return;
}
// Specify enough arguments
if(args.length < 5) {
messageBridge.message(sender, "stack-help");
return;
}
// Check amount
int tempAmount = -1;
try {
tempAmount = Integer.parseInt(args[1]);
} catch(NumberFormatException e) {
// Incorrect number
}
if(tempAmount <= 0) {
messageBridge.message(player, "stack-wrongAmount", args[1]);
return;
}
// Check gap
int gap;
try {
gap = Integer.parseInt(args[2]);
} catch(NumberFormatException e) {
messageBridge.message(player, "stack-wrongGap", args[2]);
return;
}
// Check region type
if(!"rent".equalsIgnoreCase(args[4]) && !"buy".equalsIgnoreCase(args[4])) {
messageBridge.message(sender, "stack-help");
return;
}
// Get WorldEdit selection
final WorldEditSelection selection = worldEditInterface.getPlayerSelection(player);
if(selection == null) {
messageBridge.message(player, "stack-noSelection");
return;
}
// Get or create group
RegionGroup group = null;
if(args.length > 5) {
group = fileManager.getGroup(args[5]);
if(group == null) {
group = regionFactory.createRegionGroup(args[5]);
fileManager.addGroup(group);
}
}
// Get facing of the player (must be clearly one of the four directions to make sure it is no mistake)
BlockFace facing = Utils.yawToFacing(player.getLocation().getYaw());
if(player.getLocation().getPitch() > 45) {
facing = BlockFace.DOWN;
} else if(player.getLocation().getPitch() < -45) {
facing = BlockFace.UP;
}
if(!(facing == BlockFace.NORTH || facing == BlockFace.EAST || facing == BlockFace.SOUTH || facing == BlockFace.WEST || facing == BlockFace.UP || facing == BlockFace.DOWN)) {
messageBridge.message(player, "stack-unclearDirection", facing.toString().toLowerCase().replace('_', '-'));
return;
}
Vector shift = new Vector(0, 0, 0);
if(facing == BlockFace.SOUTH) {
shift = shift.setZ(-selection.getLength() - gap);
} else if(facing == BlockFace.WEST) {
shift = shift.setX(selection.getWidth() + gap);
} else if(facing == BlockFace.NORTH) {
shift = shift.setZ(selection.getLength() + gap);
} else if(facing == BlockFace.EAST) {
shift = shift.setX(-selection.getWidth() - gap);
} else if(facing == BlockFace.DOWN) {
shift = shift.setY(-selection.getHeight() - gap);
} else if(facing == BlockFace.UP) {
shift = shift.setY(selection.getHeight() + gap);
}
AreaShop.debug(" calculated shift vector: " + shift + ", with facing=" + facing);
// Create regions and add them to AreaShop
final String nameTemplate = args[3];
final int regionsPerTick = plugin.getConfig().getInt("adding.regionsPerTick");
final boolean rentRegions = "rent".equalsIgnoreCase(args[4]);
final int amount = tempAmount;
final RegionGroup finalGroup = group;
final Vector finalShift = shift;
String type;
if(rentRegions) {
type = "rent";
} else {
type = "buy";
}
Message groupsMessage = Message.empty();
if(group != null) {
groupsMessage = Message.fromKey("stack-addToGroup").replacements(group.getName());
}
messageBridge.message(player, "stack-accepted", amount, type, gap, nameTemplate, groupsMessage);
messageBridge.message(player, "stack-addStart", amount, regionsPerTick * 20);
Location minimumLocation = selection.getMinimumLocation();
Vector minimumVector = new Vector(minimumLocation.getX(), minimumLocation.getY(), minimumLocation.getZ());
Location maximumLocation = selection.getMaximumLocation();
Vector maximumVector = new Vector(maximumLocation.getX(), maximumLocation.getY(), maximumLocation.getZ());
new BukkitRunnable() {
private int current = -1;
private final RegionManager manager = AreaShop.getInstance().getRegionManager(selection.getWorld());
private int counter = 1;
private int tooLow = 0;
private int tooHigh = 0;
@Override
public void run() {
for(int i = 0; i < regionsPerTick; i++) {
current++;
if(current < amount) {
// Create the region name
String regionName = countToName(nameTemplate, counter);
while(manager.getRegion(regionName) != null || AreaShop.getInstance().getFileManager().getRegion(regionName) != null) {
counter++;
regionName = countToName(nameTemplate, counter);
}
// Add the region to WorldGuard (at startposition shifted by the number of this region times the blocks it should shift)
Vector minimum = minimumVector.clone().add(finalShift.clone().multiply(current));
Vector maximum = maximumVector.clone().add(finalShift.clone().multiply(current));
// Check for out of bounds
if(minimum.getBlockY() < 0) {
tooLow++;
continue;
} else if(maximum.getBlockY() > 256) {
tooHigh++;
continue;
}
ProtectedCuboidRegion region = worldGuardInterface.createCuboidRegion(regionName, minimum,maximum);
manager.addRegion(region);
// Add the region to AreaShop
GeneralRegion newRegion;
if(rentRegions) {
newRegion = regionFactory.createRentRegion(regionName, selection.getWorld());
} else {
newRegion = regionFactory.createBuyRegion(regionName, selection.getWorld());
}
if(finalGroup != null) {
finalGroup.addMember(newRegion);
}
AddingRegionEvent event = fileManager.addRegion(newRegion);
if (event.isCancelled()) {
messageBridge.message(player, "general-cancelled", event.getReason());
continue;
}
newRegion.handleSchematicEvent(GeneralRegion.RegionEvent.CREATED);
newRegion.update();
}
}
if(current >= amount) {
if(player.isOnline()) {
int added = amount - tooLow - tooHigh;
Message wrong = Message.empty();
if(tooHigh > 0) {
wrong.append(Message.fromKey("stack-tooHigh").replacements(tooHigh));
}
if(tooLow > 0) {
wrong.append(Message.fromKey("stack-tooLow").replacements(tooLow));
}
messageBridge.message(player, "stack-addComplete", added, wrong);
}
this.cancel();
}
}
}.runTaskTimer(plugin, 1, 1);
}
/**
* Build a name from a count, with the right length.
* @param template Template to put the name in (# to put the count there, otherwise count is appended)
* @param count Number to use
* @return name with prepended 0's
*/
private String countToName(String template, int count) {
StringBuilder counterName = new StringBuilder().append(count);
int minimumLength = plugin.getConfig().getInt("stackRegionNumberLength");
while(counterName.length() < minimumLength) {
counterName.insert(0, "0");
}
if(template.contains("#")) {
return template.replace("#", counterName);
} else {
return template + counterName;
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 5) {
result.add("rent");
result.add("buy");
} else if(toComplete == 6) {
result.addAll(fileManager.getGroupNames());
}
return result;
}
}
@@ -0,0 +1,121 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class StopresellCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop stopresell";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.stopresellall") || target.hasPermission("areashop.stopresell")) {
return "help-stopResell";
}
return null;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.stopresell") && !sender.hasPermission("areashop.stopresellall")) {
messageBridge.message(sender, "stopresell-noPermissionOther");
return;
}
BuyRegion buy;
if(args.length <= 1) {
if(sender instanceof Player) {
// get the region by location
List<BuyRegion> regions = Utils.getImportantBuyRegions(((Player)sender).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
buy = regions.get(0);
}
} else {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
} else {
buy = fileManager.getBuy(args[1]);
if(buy == null) {
messageBridge.message(sender, "stopresell-notRegistered", args[1]);
return;
}
}
if(buy == null) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
}
if(!buy.isInResellingMode()) {
messageBridge.message(sender, "stopresell-notResell", buy);
return;
}
if(sender.hasPermission("areashop.stopresellall")) {
buy.disableReselling();
buy.update();
messageBridge.message(sender, "stopresell-success", buy);
} else if(sender.hasPermission("areashop.stopresell") && sender instanceof Player) {
if(buy.isOwner((Player)sender)) {
buy.disableReselling();
buy.update();
messageBridge.message(sender, "stopresell-success", buy);
} else {
messageBridge.message(sender, "stopresell-noPermissionOther", buy);
}
} else {
messageBridge.message(sender, "stopresell-noPermission", buy);
}
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
for(BuyRegion region : fileManager.getBuysRef()) {
if(region.isSold() && region.isInResellingMode()) {
result.add(region.getName());
}
}
}
return result;
}
}
@@ -0,0 +1,90 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.GeneralRegion;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class TeleportCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop tp";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.teleportall") || target.hasPermission("areashop.teleport")) {
return "help-teleport";
}
return null;
}
/**
* Check if a person can teleport to the region (assuming he is not teleporting to a sign).
* @param person The person to check
* @param region The region to check for
* @return true if the person can teleport to it, otherwise false
*/
public static boolean canUse(CommandSender person, GeneralRegion region) {
if(!(person instanceof Player player)) {
return false;
}
return player.hasPermission("areashop.teleportall")
|| region.isOwner(player) && player.hasPermission("areashop.teleport")
|| region.isAvailable() && player.hasPermission("areashop.teleportavailable")
|| region.getFriendsFeature().getFriends().contains(player.getUniqueId()) && player.hasPermission("areashop.teleportfriend");
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.teleport") && !sender.hasPermission("areashop.teleportall") && !sender.hasPermission("areashop.teleportavailable") && !sender.hasPermission("areashop.teleportavailablesign") && !sender.hasPermission("areashop.teleportsign") && !sender.hasPermission("areashop.teleportsignall") && !sender.hasPermission("areashop.teleportfriend") && !sender.hasPermission("teleportfriendsign")) {
messageBridge.message(sender, "teleport-noPermission");
return;
}
if(!(sender instanceof Player player)) {
messageBridge.message(sender, "cmd-onlyByPlayer");
return;
}
if(args.length <= 1 || args[1] == null) {
messageBridge.message(sender, "teleport-help");
return;
}
GeneralRegion region = fileManager.getRegion(args[1]);
if(region == null) {
messageBridge.message(player, "teleport-noRentOrBuy", args[1]);
return;
}
boolean toSign = args.length >= 3 && (
args[2].equalsIgnoreCase("sign")
|| args[2].equalsIgnoreCase("yes")
|| args[2].equalsIgnoreCase("true")
);
region.getTeleportFeature().teleportPlayer(player, toSign);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
result.addAll(fileManager.getRegionNames());
} else if(toComplete == 3) {
result.add("sign");
}
return result;
}
}
@@ -0,0 +1,111 @@
package me.wiefferink.areashop.commands;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class UnrentCommand extends CommandAreaShop {
@Inject
private MessageBridge messageBridge;
@Inject
private IFileManager fileManager;
@Override
public String getCommandStart() {
return "areashop unrent";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.unrent") || target.hasPermission("areashop.unrentown")) {
return "help-unrent";
}
return null;
}
/**
* Check if a person can unrent the region.
* @param person The person to check
* @param region The region to check for
* @return true if the person can unrent it, otherwise false
*/
public static boolean canUse(CommandSender person, GeneralRegion region) {
if(person.hasPermission("areashop.unrent")) {
return true;
}
if(person instanceof Player player) {
return region.isOwner(player) && person.hasPermission("areashop.unrentown");
}
return false;
}
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.unrent") && !sender.hasPermission("areashop.unrentown")) {
messageBridge.message(sender, "unrent-noPermission");
return;
}
RentRegion rent;
if(args.length <= 1) {
if(sender instanceof Player) {
// get the region by location
List<RentRegion> regions = Utils.getImportantRentRegions(((Player)sender).getLocation());
if(regions.isEmpty()) {
messageBridge.message(sender, "cmd-noRegionsAtLocation");
return;
} else if(regions.size() > 1) {
messageBridge.message(sender, "cmd-moreRegionsAtLocation");
return;
} else {
rent = regions.get(0);
}
} else {
messageBridge.message(sender, "cmd-automaticRegionOnlyByPlayer");
return;
}
} else {
rent = fileManager.getRent(args[1]);
}
if(rent == null) {
messageBridge.message(sender, "unrent-notRegistered");
return;
}
if(!rent.isRented()) {
messageBridge.message(sender, "unrent-notRented", rent);
return;
}
rent.unRent(true, sender);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
ArrayList<String> result = new ArrayList<>();
if(toComplete == 2) {
for(RentRegion region : fileManager.getRentsRef()) {
if(region.isRented()) {
result.add(region.getName());
}
}
}
return result;
}
}
@@ -0,0 +1,45 @@
package me.wiefferink.areashop.events;
public class CancellableRegionEvent<T> extends RegionEvent<T> {
private boolean cancelled;
private String reason;
public CancellableRegionEvent(T region) {
super(region);
}
/**
* Cancel the event from happening.
* @param reason The reason of cancelling, used for display to the user, should end with a dot
*/
public void cancel(String reason) {
this.cancelled = true;
this.reason = reason;
}
/**
* Let the event continue, possible overwriting a cancel() call from another plugin.
*/
public void allow() {
this.cancelled = false;
this.reason = null;
}
/**
* Check if the event has been cancelled.
* @return true if the event has been cancelled, otherwise false
*/
public boolean isCancelled() {
return cancelled;
}
/**
* Get the reason why this event is cancelled.
* @return null if there is no reason or the event is not cancelled, otherwise a string
*/
public String getReason() {
return reason;
}
}
@@ -0,0 +1,7 @@
package me.wiefferink.areashop.events;
public class NotifyRegionEvent<T> extends RegionEvent<T> {
public NotifyRegionEvent(T region) {
super(region);
}
}
@@ -0,0 +1,7 @@
# Events
This package contains event classes that allow other plugins to listen to events about AreaShop regions.
* `RegionEvent` class represents a generic event.
* `CancellableRegionEvent` represents an event that has a region and can be cancelled.
* `NotifyRegionEvent` represents events that are to inform only, and cannot be cancelled anymore (an `CancellableRegionEvent` would be sent first).
@@ -0,0 +1,31 @@
package me.wiefferink.areashop.events;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class RegionEvent<T> extends Event {
protected T region;
private static final HandlerList handlers = new HandlerList();
public RegionEvent(T region) {
this.region = region;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
// Required by Bukkit/Spigot
public static HandlerList getHandlerList() {
return handlers;
}
/**
* Get the region of this event.
* @return The region the event is about
*/
public T getRegion() {
return region;
}
}
@@ -0,0 +1,19 @@
package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.GeneralRegion;
/**
* Broadcasted when a region has been added to AreaShop.
*/
public class AddingRegionEvent extends CancellableRegionEvent<GeneralRegion> {
/**
* Constructor.
* @param region The region that has been added
*/
public AddingRegionEvent(GeneralRegion region) {
super(region);
}
}
@@ -0,0 +1,31 @@
package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.BuyRegion;
import org.bukkit.OfflinePlayer;
/**
* Broadcasted when a player tries to buy a region.
*/
public class BuyingRegionEvent extends CancellableRegionEvent<BuyRegion> {
private final OfflinePlayer player;
/**
* Constructor.
* @param region The region that is about to get bought
* @param player The player that tries to buy the region
*/
public BuyingRegionEvent(BuyRegion region, OfflinePlayer player) {
super(region);
this.player = player;
}
/**
* Get the player that is trying to buy the region.
* @return The player that is trying to buy the region
*/
public OfflinePlayer getPlayer() {
return player;
}
}
@@ -0,0 +1,19 @@
package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.GeneralRegion;
/**
* Broadcasted when a region has been added to AreaShop.
*/
public class DeletingRegionEvent extends CancellableRegionEvent<GeneralRegion> {
/**
* Constructor.
* @param region The region that has been added
*/
public DeletingRegionEvent(GeneralRegion region) {
super(region);
}
}
@@ -0,0 +1,42 @@
package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.RentRegion;
import org.bukkit.OfflinePlayer;
/**
* Broadcasted when a player tries to rent a region.
*/
public class RentingRegionEvent extends CancellableRegionEvent<RentRegion> {
private final OfflinePlayer player;
private final boolean extending;
/**
* Constructor.
* @param region The region that is about to be rented
* @param player The player that tries to rent the region
* @param extending true if the player is extending the rental of the region, otherwise false
*/
public RentingRegionEvent(RentRegion region, OfflinePlayer player, boolean extending) {
super(region);
this.player = player;
this.extending = extending;
}
/**
* Get the player that is trying to rent the region.
* @return The player that is trying to rent the region
*/
public OfflinePlayer getPlayer() {
return player;
}
/**
* Check if the player is extending the region or renting it for the first time.
* @return true if the player tries to extend the region, false if he tries to rent it the first time
*/
public boolean isExtending() {
return extending;
}
}
@@ -0,0 +1,31 @@
package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.BuyRegion;
import org.bukkit.OfflinePlayer;
/**
* Broadcasted when a player tries to resell a region.
*/
public class ResellingRegionEvent extends CancellableRegionEvent<BuyRegion> {
private final OfflinePlayer player;
/**
* Contructor.
* @param region The region that the player is trying to resell
* @param player The player that is trying to buy this region from the current owner
*/
public ResellingRegionEvent(BuyRegion region, OfflinePlayer player) {
super(region);
this.player = player;
}
/**
* Get the player that is trying to buy the region.
* @return The player that is trying to buy the region
*/
public OfflinePlayer getBuyer() {
return player;
}
}
@@ -0,0 +1,18 @@
package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.BuyRegion;
/**
* Broadcasted when a region is about to get sold.
*/
public class SellingRegionEvent extends CancellableRegionEvent<BuyRegion> {
/**
* Constructor.
* @param region The region that is about to get sold
*/
public SellingRegionEvent(BuyRegion region) {
super(region);
}
}
@@ -0,0 +1,18 @@
package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.RentRegion;
/**
* Broadcasted when a region is about to be unrented.
*/
public class UnrentingRegionEvent extends CancellableRegionEvent<RentRegion> {
/**
* Constructor.
* @param region The region that is about to be unrented
*/
public UnrentingRegionEvent(RentRegion region) {
super(region);
}
}
@@ -0,0 +1,43 @@
package me.wiefferink.areashop.events.askandnotify;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.GeneralRegion;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
/**
* Broadcasted when a friend is being added to a region.
*/
public class AddedFriendEvent extends CancellableRegionEvent<GeneralRegion> {
private final OfflinePlayer friend;
private final CommandSender by;
/**
* Constructor.
* @param region The region the friend is getting added to
* @param friend The friend that is about to be added
* @param by The CommandSender that is adding the friend, or null if none
*/
public AddedFriendEvent(GeneralRegion region, OfflinePlayer friend, CommandSender by) {
super(region);
this.friend = friend;
this.by = by;
}
/**
* Get the OfflinePlayer that is getting added as friend.
* @return The friend that is getting added
*/
public OfflinePlayer getFriend() {
return friend;
}
/**
* Get the CommandSender that is adding the friend.
* @return null if none, a CommandSender if done by someone (likely Player or ConsoleCommandSender)
*/
public CommandSender getBy() {
return by;
}
}
@@ -0,0 +1,43 @@
package me.wiefferink.areashop.events.askandnotify;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.GeneralRegion;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
/**
* Broadcasted when a friend is being added to a region.
*/
public class DeletedFriendEvent extends CancellableRegionEvent<GeneralRegion> {
private final OfflinePlayer friend;
private final CommandSender by;
/**
* Constructor.
* @param region The region the friend is getting added to
* @param friend The friend that is about to be added
* @param by The CommandSender that is adding the friend, or null if none
*/
public DeletedFriendEvent(GeneralRegion region, OfflinePlayer friend, CommandSender by) {
super(region);
this.friend = friend;
this.by = by;
}
/**
* Get the OfflinePlayer that is getting added as friend.
* @return The friend that is getting added
*/
public OfflinePlayer getFriend() {
return friend;
}
/**
* Get the CommandSender that is adding the friend.
* @return null if none, a CommandSender if done by someone (likely Player or ConsoleCommandSender)
*/
public CommandSender getBy() {
return by;
}
}
@@ -0,0 +1,19 @@
package me.wiefferink.areashop.events.notify;
import me.wiefferink.areashop.events.NotifyRegionEvent;
import me.wiefferink.areashop.regions.GeneralRegion;
/**
* Broadcasted when a region has been added to AreaShop.
*/
public class AddedRegionEvent extends NotifyRegionEvent<GeneralRegion> {
/**
* Constructor.
* @param region The region that has been added
*/
public AddedRegionEvent(GeneralRegion region) {
super(region);
}
}
@@ -0,0 +1,19 @@
package me.wiefferink.areashop.events.notify;
import me.wiefferink.areashop.events.NotifyRegionEvent;
import me.wiefferink.areashop.regions.BuyRegion;
/**
* Broadcasted when a region has been bought.
*/
public class BoughtRegionEvent extends NotifyRegionEvent<BuyRegion> {
/**
* Constructor.
* @param region The region that has been bought
*/
public BoughtRegionEvent(BuyRegion region) {
super(region);
}
}
@@ -0,0 +1,18 @@
package me.wiefferink.areashop.events.notify;
import me.wiefferink.areashop.events.NotifyRegionEvent;
import me.wiefferink.areashop.regions.GeneralRegion;
/**
* Broadcasted when a region has been removed from AreaShop.
*/
public class DeletedRegionEvent extends NotifyRegionEvent<GeneralRegion> {
/**
* Constructor.
* @param region The region that has been removed
*/
public DeletedRegionEvent(GeneralRegion region) {
super(region);
}
}
@@ -0,0 +1,31 @@
package me.wiefferink.areashop.events.notify;
import me.wiefferink.areashop.events.NotifyRegionEvent;
import me.wiefferink.areashop.regions.RentRegion;
/**
* Broadcasted when a region has been rented.
*/
public class RentedRegionEvent extends NotifyRegionEvent<RentRegion> {
private final boolean extended;
/**
* Constructor.
* @param region The region that has been rented
* @param extended true if the region has been extended, false if this is the first time buying the region
*/
public RentedRegionEvent(RentRegion region, boolean extended) {
super(region);
this.region = region;
this.extended = extended;
}
/**
* Check if the region was extended or rented for the first time.
* @return true if the region was extended, false when rented for the first time
*/
public boolean hasExtended() {
return extended;
}
}
@@ -0,0 +1,32 @@
package me.wiefferink.areashop.events.notify;
import me.wiefferink.areashop.events.NotifyRegionEvent;
import me.wiefferink.areashop.regions.BuyRegion;
import java.util.UUID;
/**
* Broadcasted when a region has been resold.
*/
public class ResoldRegionEvent extends NotifyRegionEvent<BuyRegion> {
private final UUID from;
/**
* Constructor.
* @param region The region that has been resold
* @param from The player from who the region has been resold to the current owner
*/
public ResoldRegionEvent(BuyRegion region, UUID from) {
super(region);
this.from = from;
}
/**
* Get the player that the region has been bought from.
* @return The UUID of the player that the region has been bought from
*/
public UUID getFromPlayer() {
return from;
}
}
@@ -0,0 +1,43 @@
package me.wiefferink.areashop.events.notify;
import me.wiefferink.areashop.events.NotifyRegionEvent;
import me.wiefferink.areashop.regions.BuyRegion;
import java.util.UUID;
/**
* Broadcasted when a region is sold.
*/
public class SoldRegionEvent extends NotifyRegionEvent<BuyRegion> {
private final UUID oldBuyer;
private final double refundedMoney;
/**
* Constructor.
* @param region The region that has been sold
* @param oldBuyer The player for which the region has been sold
* @param refundedMoney The amount of money that has been refunded
*/
public SoldRegionEvent(BuyRegion region, UUID oldBuyer, double refundedMoney) {
super(region);
this.oldBuyer = oldBuyer;
this.refundedMoney = refundedMoney;
}
/**
* Get the player that the region is sold for.
* @return The UUID of the player that the region is sold for
*/
public UUID getOldBuyer() {
return oldBuyer;
}
/**
* Get the amount that is paid back to the player.
* @return The amount of money paid back to the player
*/
public double getRefundedMoney() {
return refundedMoney;
}
}
@@ -0,0 +1,43 @@
package me.wiefferink.areashop.events.notify;
import me.wiefferink.areashop.events.NotifyRegionEvent;
import me.wiefferink.areashop.regions.RentRegion;
import java.util.UUID;
/**
* Broadcasted when a region has been unrented.
*/
public class UnrentedRegionEvent extends NotifyRegionEvent<RentRegion> {
private final UUID oldRenter;
private final double refundedMoney;
/**
* Constructor.
* @param region The region that has been unrented
* @param oldRenter The player that rented the region before it was unrented
* @param refundedMoney The amount of money that has been refunded
*/
public UnrentedRegionEvent(RentRegion region, UUID oldRenter, double refundedMoney) {
super(region);
this.oldRenter = oldRenter;
this.refundedMoney = refundedMoney;
}
/**
* Get the player that the region was unrented for.
* @return The UUID of the player that the region was unrented for
*/
public UUID getOldRenter() {
return oldRenter;
}
/**
* Get the amount that is paid back to the player.
* @return The amount of money paid back to the player
*/
public double getRefundedMoney() {
return refundedMoney;
}
}
@@ -0,0 +1,19 @@
package me.wiefferink.areashop.events.notify;
import me.wiefferink.areashop.events.NotifyRegionEvent;
import me.wiefferink.areashop.regions.GeneralRegion;
/**
* Broadcasted when the data of a region changes.
* Should be used for updating displays that use region data.
*/
public class UpdateRegionEvent extends NotifyRegionEvent<GeneralRegion> {
/**
* Contructor.
* @param region The region that has been updated
*/
public UpdateRegionEvent(GeneralRegion region) {
super(region);
}
}
@@ -0,0 +1,127 @@
package me.wiefferink.areashop.features;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.events.ask.AddingRegionEvent;
import me.wiefferink.areashop.events.ask.BuyingRegionEvent;
import me.wiefferink.areashop.events.ask.DeletingRegionEvent;
import me.wiefferink.areashop.events.ask.RentingRegionEvent;
import me.wiefferink.areashop.events.ask.ResellingRegionEvent;
import me.wiefferink.areashop.events.ask.SellingRegionEvent;
import me.wiefferink.areashop.events.ask.UnrentingRegionEvent;
import me.wiefferink.areashop.events.notify.AddedRegionEvent;
import me.wiefferink.areashop.events.notify.BoughtRegionEvent;
import me.wiefferink.areashop.events.notify.DeletedRegionEvent;
import me.wiefferink.areashop.events.notify.RentedRegionEvent;
import me.wiefferink.areashop.events.notify.ResoldRegionEvent;
import me.wiefferink.areashop.events.notify.SoldRegionEvent;
import me.wiefferink.areashop.events.notify.UnrentedRegionEvent;
import me.wiefferink.areashop.regions.GeneralRegion;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import java.util.List;
@Singleton
public class CommandsFeature extends RegionFeature {
@Inject
CommandsFeature(AreaShop plugin) {
super(plugin);
}
/**
* Run command for a certain event.
* @param region Region to execute the events for
* @param event The event
* @param before The 'before' or 'after' commands
*/
public void runEventCommands(GeneralRegion region, GeneralRegion.RegionEvent event, boolean before) {
ConfigurationSection eventCommandProfileSection = region.getConfigurationSectionSetting("general.eventCommandProfile", "eventCommandProfiles");
if(eventCommandProfileSection == null) {
return;
}
List<String> commands = eventCommandProfileSection.getStringList(event.getValue() + "." + (before ? "before" : "after"));
if(commands.isEmpty()) {
return;
}
region.runCommands(Bukkit.getConsoleSender(), commands);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void addingRegion(AddingRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.CREATED, true);
}
@EventHandler
public void addedRegion(AddedRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.CREATED, false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void deletingRegion(DeletingRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.DELETED, true);
}
@EventHandler
public void deletedRegion(DeletedRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.DELETED, false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void rentingRegion(RentingRegionEvent event) {
// Technically the rent can still be cancelled if the payment fails...
runEventCommands(event.getRegion(), event.isExtending() ? GeneralRegion.RegionEvent.EXTENDED : GeneralRegion.RegionEvent.RENTED, true);
}
@EventHandler
public void rentedRegion(RentedRegionEvent event) {
runEventCommands(event.getRegion(), event.hasExtended() ? GeneralRegion.RegionEvent.EXTENDED : GeneralRegion.RegionEvent.RENTED, false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void buyingRegion(BuyingRegionEvent event) {
// Technically the buy can still be cancelled if the payment fails...
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.BOUGHT, true);
}
@EventHandler
public void boughtRegion(BoughtRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.BOUGHT, false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void resellingRegion(ResellingRegionEvent event) {
// Technically the resell can still be cancelled if the payment fails...
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.RESELL, true);
}
@EventHandler
public void resoldRegion(ResoldRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.RESELL, false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void sellingRegion(SellingRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.SOLD, true);
}
@EventHandler
public void soldRegion(SoldRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.SOLD, false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void unrentingRegion(UnrentingRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.UNRENTED, true);
}
@EventHandler
public void unrentedRegion(UnrentedRegionEvent event) {
runEventCommands(event.getRegion(), GeneralRegion.RegionEvent.UNRENTED, false);
}
}
@@ -0,0 +1,108 @@
package me.wiefferink.areashop.features;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.events.ask.BuyingRegionEvent;
import me.wiefferink.areashop.events.ask.RentingRegionEvent;
import me.wiefferink.areashop.events.ask.ResellingRegionEvent;
import me.wiefferink.areashop.events.ask.SellingRegionEvent;
import me.wiefferink.areashop.events.ask.UnrentingRegionEvent;
import me.wiefferink.areashop.events.askandnotify.AddedFriendEvent;
import me.wiefferink.areashop.events.askandnotify.DeletedFriendEvent;
import me.wiefferink.areashop.events.notify.AddedRegionEvent;
import me.wiefferink.areashop.events.notify.BoughtRegionEvent;
import me.wiefferink.areashop.events.notify.DeletedRegionEvent;
import me.wiefferink.areashop.events.notify.RentedRegionEvent;
import me.wiefferink.areashop.events.notify.ResoldRegionEvent;
import me.wiefferink.areashop.events.notify.SoldRegionEvent;
import me.wiefferink.areashop.events.notify.UnrentedRegionEvent;
import me.wiefferink.areashop.events.notify.UpdateRegionEvent;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.event.EventHandler;
import javax.annotation.Nonnull;
@Singleton
public class DebugFeature extends RegionFeature {
@Inject
DebugFeature(@Nonnull AreaShop plugin) {
super(plugin);
}
@EventHandler
public void buyingRegion(BuyingRegionEvent event) {
AreaShop.debug("BuyingRegionEvent: " + event.getPlayer().getName() + " is trying to buy " + event.getRegion().getName());
}
@EventHandler
public void boughtRegion(BoughtRegionEvent event) {
AreaShop.debug("BoughtRegionEvent: " + event.getRegion().getPlayerName() + " bought " + event.getRegion().getName());
}
@EventHandler
public void sellingRegion(SellingRegionEvent event) {
AreaShop.debug("SellingRegionEvent: " + event.getRegion().getName() + " is trying to sell " + event.getRegion().getName());
}
@EventHandler
public void soldRegion(SoldRegionEvent event) {
AreaShop.debug("SoldRegionEvent: " + Utils.toName(event.getOldBuyer()) + " sold " + event.getRegion().getName());
}
@EventHandler
public void resellingRegion(ResellingRegionEvent event) {
AreaShop.debug("ResellingRegionEvent: " + event.getRegion().getName() + " is trying to resell " + event.getRegion().getName());
}
@EventHandler
public void resoldRegion(ResoldRegionEvent event) {
AreaShop.debug("ResoldRegionEvent: " + Utils.toName(event.getFromPlayer()) + " resold " + event.getRegion().getName() + " to " + event.getRegion().getPlayerName());
}
@EventHandler
public void rentingRegion(RentingRegionEvent event) {
AreaShop.debug("RentingRegionEvent: " + event.getPlayer().getName() + " is trying to rent " + event.getRegion().getName() + ", extending=" + event.isExtending());
}
@EventHandler
public void rentedRegion(RentedRegionEvent event) {
AreaShop.debug("RentedRegionEvent: " + event.getRegion().getPlayerName() + " rented " + event.getRegion().getName() + ", extending=" + event.hasExtended());
}
@EventHandler
public void unrentingRegion(UnrentingRegionEvent event) {
AreaShop.debug("UnrentingRegionEvent: " + event.getRegion().getPlayerName() + " is trying to unrent " + event.getRegion().getName());
}
@EventHandler
public void unrentedRegion(UnrentedRegionEvent event) {
AreaShop.debug("UnrentedRegionEvent: " + Utils.toName(event.getOldRenter()) + " unrented " + event.getRegion().getName());
}
@EventHandler
public void regionUpdate(UpdateRegionEvent event) {
//AreaShop.debug("UpdateRegionEvent: " + event.getRegion().getName() + " updated");
}
@EventHandler
public void addedRegion(AddedRegionEvent event) {
AreaShop.debug("AddedRegionEvent: " + event.getRegion().getName());
}
@EventHandler
public void removedRegion(DeletedRegionEvent event) {
AreaShop.debug("DeletedRegionEvent: " + event.getRegion().getName());
}
@EventHandler
public void addedFriend(AddedFriendEvent event) {
AreaShop.debug("AddedFriendEvent: region " + event.getRegion().getName() + ", " + event.getFriend().getName() + " by " + (event.getBy() == null ? "<nobody>" : event.getBy().getName()));
}
@EventHandler
public void deleteFriend(DeletedFriendEvent event) {
AreaShop.debug("DeletedFriendEvent: region " + event.getRegion().getName() + ", " + event.getFriend().getName() + " by " + (event.getBy() == null ? "<nobody>" : event.getBy().getName()));
}
}
@@ -0,0 +1,19 @@
package me.wiefferink.areashop.features;
import me.wiefferink.areashop.features.signs.SignsFeature;
import me.wiefferink.areashop.regions.GeneralRegion;
import javax.annotation.Nonnull;
public interface FeatureFactory {
@Nonnull
SignsFeature createSignsFeature(@Nonnull GeneralRegion generalRegion);
@Nonnull
TeleportFeature createTeleportFeature(@Nonnull GeneralRegion generalRegion);
@Nonnull
FriendsFeature createFriendsFeature(@Nonnull GeneralRegion generalRegion);
}
@@ -0,0 +1,120 @@
package me.wiefferink.areashop.features;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.events.askandnotify.AddedFriendEvent;
import me.wiefferink.areashop.events.askandnotify.DeletedFriendEvent;
import me.wiefferink.areashop.regions.GeneralRegion;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public class FriendsFeature extends RegionFeature {
@Inject
private MessageBridge messageBridge;
@AssistedInject
public FriendsFeature(@Nonnull AreaShop plugin, @Assisted @Nonnull GeneralRegion region) {
super(plugin);
setRegion(region);
}
/**
* Add a friend to the region.
* @param player The UUID of the player to add
* @param by The CommandSender that is adding the friend, or null
* @return true if the friend has been added, false if adding a friend was cancelled by another plugin
*/
public boolean addFriend(UUID player, CommandSender by) {
// Fire and check event
AddedFriendEvent event = new AddedFriendEvent(getRegion(), Bukkit.getOfflinePlayer(player), by);
Bukkit.getPluginManager().callEvent(event);
if(event.isCancelled()) {
messageBridge.message(by, "general-cancelled", event.getReason(), this);
return false;
}
Set<String> friends = new HashSet<>(getRegion().getConfig().getStringList("general.friends"));
friends.add(player.toString());
List<String> list = new ArrayList<>(friends);
getRegion().setSetting("general.friends", list);
return true;
}
/**
* Delete a friend from the region.
* @param player The UUID of the player to delete
* @param by The CommandSender that is adding the friend, or null
* @return true if the friend has been added, false if adding a friend was cancelled by another plugin
*/
public boolean deleteFriend(UUID player, CommandSender by) {
// Fire and check event
DeletedFriendEvent event = new DeletedFriendEvent(getRegion(), Bukkit.getOfflinePlayer(player), by);
Bukkit.getPluginManager().callEvent(event);
if(event.isCancelled()) {
messageBridge.message(by, "general-cancelled", event.getReason(), this);
return false;
}
Set<String> friends = new HashSet<>(getRegion().getConfig().getStringList("general.friends"));
friends.remove(player.toString());
List<String> list = new ArrayList<>(friends);
if(list.isEmpty()) {
getRegion().setSetting("general.friends", null);
} else {
getRegion().setSetting("general.friends", list);
}
return true;
}
/**
* Get the list of friends added to this region.
* @return Friends added to this region
*/
public Set<UUID> getFriends() {
HashSet<UUID> result = new HashSet<>();
for(String friend : getRegion().getConfig().getStringList("general.friends")) {
try {
UUID id = UUID.fromString(friend);
result.add(id);
} catch(IllegalArgumentException e) {
// Don't add it
}
}
return result;
}
/**
* Get the list of friends added to this region.
* @return Friends added to this region
*/
public Set<String> getFriendNames() {
HashSet<String> result = new HashSet<>();
for(UUID friend : getFriends()) {
OfflinePlayer player = Bukkit.getOfflinePlayer(friend);
if(player != null && player.getName() != null) {
result.add(player.getName());
}
}
return result;
}
/**
* Remove all friends that are added to this region.
*/
public void clearFriends() {
getRegion().setSetting("general.friends", null);
}
}
@@ -0,0 +1,6 @@
# Features
This package contains classes that represent a certain feature and implement it for all regions.
* `RegionFeature` represents a single feature, for each implementing class one instance is created without an assigned region, to listen for events etc, and an instance per region is created on-demand to handle region specific things.
* More features will be moved from `GeneralRegion` to these kind of classes, to keep all code for a feature together and cleanup `GeneralRegion`.
@@ -0,0 +1,77 @@
package me.wiefferink.areashop.features;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.regions.GeneralRegion;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import javax.annotation.Nonnull;
/*
Possible future feature classes:
- RestrictedToRegion feature
- rent/buy restricted to the region itself
- RestrictedToWorld feature
- rent/buy restricted to the world of the region
- CountLimitsFeature:
- Check region limits
- TimeLimitsFeatures:
- maxExtends
- maxRentTime
- SchematicFeature
- save/load schematics
- CommandsFeature
- execute commands after extend/rent/etc
- LandlordFeature
- manage landlords
- ExpirationWarningsFeature
- sending rental expiration warnings
*/
public abstract class RegionFeature implements Listener {
protected final AreaShop plugin;
private GeneralRegion region;
protected RegionFeature(@Nonnull AreaShop plugin) {
this.plugin = plugin;
}
/**
* Set the region for this feature.
* @param region Feature region
*/
public void setRegion(GeneralRegion region) {
this.region = region;
}
/**
* Get the region of this feature.
* @return region of this feature, or null if generic
*/
public GeneralRegion getRegion() {
return region;
}
/**
* Start listening to events.
*/
public void listen() {
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
/**
* Destroy the feature and deregister everything.
*/
public void shutdownFeature() {
HandlerList.unregisterAll(this);
shutdown();
}
/**
* Dummy method a RegionFeature implementation can override.
*/
public void shutdown() {
}
}
@@ -0,0 +1,572 @@
package me.wiefferink.areashop.features;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import jakarta.inject.Inject;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.features.signs.RegionSign;
import me.wiefferink.areashop.features.signs.SignManager;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.areashop.tools.Value;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Set;
public class TeleportFeature extends RegionFeature {
@Inject
private WorldGuardInterface worldGuardInterface;
@AssistedInject
public TeleportFeature(@Nonnull AreaShop plugin, @Assisted @Nonnull GeneralRegion region) {
super(plugin);
setRegion(region);
}
/**
* Get the teleportlocation set for this region.
* @return The teleport location, or null if not set
*/
public Location getTeleportLocation() {
return Utils.configToLocation(getRegion().getConfigurationSectionSetting("general.teleportLocation"));
}
/**
* Check if the region has a teleportLocation specified.
* @return true if the region has a teleportlocation, false otherwise
*/
public boolean hasTeleportLocation() {
return getRegion().getConfigurationSectionSetting("general.teleportLocation") != null;
}
/**
* Set the teleport location of this region.
* @param location The location to set as teleport location
*/
public void setTeleport(Location location) {
if(location == null) {
getRegion().setSetting("general.teleportLocation", null);
} else {
getRegion().setSetting("general.teleportLocation", Utils.locationToConfig(location, true));
}
}
/**
* Teleport a player to the region or sign.
* @param player Player that should be teleported
* @param toSign true to teleport to the first sign of the region, false for teleporting to the region itself
* @param checkRestrictions Set to true if teleport permissions should be checked, false otherwise, also toggles cross-world check
* @return true if the teleport succeeded, otherwise false
*/
public boolean teleportPlayer(Player player, boolean toSign, boolean checkRestrictions) {
// Check basics
if(getRegion().getWorld() == null) {
getRegion().message(player, "general-noWorld");
return false;
}
if(getRegion().getRegion() == null) {
getRegion().message(player, "general-noRegion");
return false;
}
if(checkRestrictions) {
// Check correct world
if(!getRegion().getBooleanSetting("general.teleportCrossWorld") && !player.getWorld().equals(getRegion().getWorld())) {
getRegion().message(player, "teleport-wrongWorld", player.getWorld().getName());
return false;
}
boolean owner = player.getUniqueId().equals(getRegion().getOwner());
boolean friend = getRegion().getFriendsFeature().getFriends().contains(player.getUniqueId());
boolean available = getRegion().isAvailable();
// Teleport to sign instead if they dont have permission for teleporting to region
if((!toSign && owner && !player.hasPermission("areashop.teleport") && player.hasPermission("areashop.teleportsign")
|| !toSign && !owner && !friend && !player.hasPermission("areashop.teleportall") && player.hasPermission("areashop.teleportsignall")
|| !toSign && !owner && friend && !player.hasPermission("areashop.teleportfriend") && player.hasPermission("areashop.teleportfriendsign")
|| !toSign && !owner && !friend && available && !player.hasPermission("areashop.teleportavailable") && player.hasPermission("areashop.teleportavailablesign"))) {
getRegion().message(player, "teleport-changedToSign");
toSign = true;
}
// Check permissions
if(owner && !available && !player.hasPermission("areashop.teleport") && !toSign) {
getRegion().message(player, "teleport-noPermission");
return false;
} else if(!owner && !available && !player.hasPermission("areashop.teleportall") && !toSign && !friend) {
getRegion().message(player, "teleport-noPermissionOther");
return false;
} else if(!owner && !available && !player.hasPermission("areashop.teleportfriend") && !toSign && friend) {
getRegion().message(player, "teleport-noPermissionFriend");
return false;
} else if(available && !player.hasPermission("areashop.teleportavailable") && !toSign) {
getRegion().message(player, "teleport-noPermissionAvailable");
return false;
} else if(owner && !available && !player.hasPermission("areashop.teleportsign") && toSign) {
getRegion().message(player, "teleport-noPermissionSign");
return false;
} else if(!owner && !available && !player.hasPermission("areashop.teleportsignall") && toSign && !friend) {
getRegion().message(player, "teleport-noPermissionOtherSign");
return false;
} else if(!owner && !available && !player.hasPermission("areashop.teleportfriendsign") && toSign && friend) {
getRegion().message(player, "teleport-noPermissionFriendSign");
return false;
} else if(available && !player.hasPermission("areashop.teleportavailablesign") && toSign) {
getRegion().message(player, "teleport-noPermissionAvailableSign");
return false;
}
}
// Get the starting location
Value<Boolean> toSignRef = new Value<>(toSign);
Location startLocation = getStartLocation(player, toSignRef);
toSign = toSignRef.get();
boolean insideRegion;
if(toSign) {
insideRegion = getRegion().getBooleanSetting("general.teleportToSignIntoRegion");
} else {
insideRegion = getRegion().getBooleanSetting("general.teleportIntoRegion");
}
// Check locations starting from startLocation and then a cube that increases
// radius around that (until no block in the region is found at all cube sides)
Location safeLocation = startLocation;
ProtectedRegion worldguardRegion = getRegion().getRegion();
boolean blocksInRegion = worldguardRegion.contains(startLocation.getBlockX(), startLocation.getBlockY(), startLocation.getBlockZ());
if(!blocksInRegion && insideRegion) {
getRegion().message(player, "teleport-blocked");
return false;
}
// Tries limit tracking
int radius = 1;
int checked = 1;
int maxTries = plugin.getConfig().getInt("maximumTries");
// Tracking of which sides to continue the search
boolean done = isSafe(safeLocation);
boolean northDone = false, eastDone = false, southDone = false, westDone = false, topDone = false, bottomDone = false;
boolean continueThisDirection;
while((blocksInRegion || !insideRegion) && !done) {
blocksInRegion = false;
// North side
continueThisDirection = false;
for(int x = -radius + 1; x <= radius && !done && !northDone; x++) {
for(int y = -radius + 1; y < radius && !done; y++) {
safeLocation = startLocation.clone().add(x, y, -radius);
if(safeLocation.getBlockY() > 256 || safeLocation.getBlockY() < 0) {
continue;
}
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
}
northDone = northDone || !continueThisDirection;
// East side
continueThisDirection = false;
for(int z = -radius + 1; z <= radius && !done && !eastDone; z++) {
for(int y = -radius + 1; y < radius && !done; y++) {
safeLocation = startLocation.clone().add(radius, y, z);
if(safeLocation.getBlockY() > 256 || safeLocation.getBlockY() < 0) {
continue;
}
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
}
eastDone = eastDone || !continueThisDirection;
// South side
continueThisDirection = false;
for(int x = radius - 1; x >= -radius && !done && !southDone; x--) {
for(int y = -radius + 1; y < radius && !done; y++) {
safeLocation = startLocation.clone().add(x, y, radius);
if(safeLocation.getBlockY() > 256 || safeLocation.getBlockY() < 0) {
continue;
}
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
}
southDone = southDone || !continueThisDirection;
// West side
continueThisDirection = false;
for(int z = radius - 1; z >= -radius && !done && !westDone; z--) {
for(int y = -radius + 1; y < radius && !done; y++) {
safeLocation = startLocation.clone().add(-radius, y, z);
if(safeLocation.getBlockY() > 256 || safeLocation.getBlockY() < 0) {
continue;
}
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
}
westDone = westDone || !continueThisDirection;
// Top side
continueThisDirection = false;
// Middle block of the top
if((startLocation.getBlockY() + radius) > 256) {
topDone = true;
}
if(!done && !topDone) {
safeLocation = startLocation.clone().add(0, radius, 0);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
for(int r = 1; r <= radius && !done && !topDone; r++) {
// North
for(int x = -r + 1; x <= r && !done; x++) {
safeLocation = startLocation.clone().add(x, radius, -r);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
// East
for(int z = -r + 1; z <= r && !done; z++) {
safeLocation = startLocation.clone().add(r, radius, z);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
// South side
for(int x = r - 1; x >= -r && !done; x--) {
safeLocation = startLocation.clone().add(x, radius, r);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
// West side
for(int z = r - 1; z >= -r && !done; z--) {
safeLocation = startLocation.clone().add(-r, radius, z);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
}
topDone = topDone || !continueThisDirection;
// Bottom side
continueThisDirection = false;
// Middle block of the bottom
if(startLocation.getBlockY() - radius < 0) {
bottomDone = true;
}
if(!done && !bottomDone) {
safeLocation = startLocation.clone().add(0, -radius, 0);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
for(int r = 1; r <= radius && !done && !bottomDone; r++) {
// North
for(int x = -r + 1; x <= r && !done; x++) {
safeLocation = startLocation.clone().add(x, -radius, -r);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
// East
for(int z = -r + 1; z <= r && !done; z++) {
safeLocation = startLocation.clone().add(r, -radius, z);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
// South side
for(int x = r - 1; x >= -r && !done; x--) {
safeLocation = startLocation.clone().add(x, -radius, r);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
// West side
for(int z = r - 1; z >= -r && !done; z--) {
safeLocation = startLocation.clone().add(-r, -radius, z);
if(worldguardRegion.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
continueThisDirection = true;
}
}
}
bottomDone = bottomDone || !continueThisDirection;
// Increase cube radius
radius++;
}
if(done && isSafe(safeLocation)) {
if(toSign) {
getRegion().message(player, "teleport-successSign");
// Let the player look at the sign
Vector playerVector = safeLocation.toVector();
playerVector.setY(playerVector.getY() + player.getEyeHeight(true));
SignManager signManager = getRegion().getSignsFeature().signManager();
Iterator<RegionSign> iterator = signManager.allSigns().iterator();
Vector signVector = iterator.next().getLocation().toVector().add(new Vector(0.5, 0.5, 0.5));
Vector direction = playerVector.clone().subtract(signVector).normalize();
safeLocation.setYaw(180 - (float)Math.toDegrees(Math.atan2(direction.getX(), direction.getZ())));
safeLocation.setPitch(90 - (float)Math.toDegrees(Math.acos(direction.getY())));
} else {
getRegion().message(player, "teleport-success");
}
player.teleport(safeLocation);
AreaShop.debug("Found location: " + safeLocation.toString() + " Tries: " + (checked - 1));
return true;
} else {
getRegion().message(player, "teleport-noSafe", checked - 1, maxTries);
AreaShop.debug("No location found, checked " + (checked - 1) + " spots of max " + maxTries);
return false;
}
}
/**
* Teleport a player to the region or sign when he has permissions for it.
* @param player Player that should be teleported
* @param toSign true to teleport to the first sign of the region, false for teleporting to the region itself
* @return true if the teleport succeeded, otherwise false
*/
public boolean teleportPlayer(Player player, boolean toSign) {
return teleportPlayer(player, toSign, true);
}
/**
* Teleport a player to the region when he has permissions for it.
* @param player Player that should be teleported
* @return true if the teleport succeeded, otherwise false
*/
public boolean teleportPlayer(Player player) {
return teleportPlayer(player, false, true);
}
/**
* Checks if a certain location is safe to teleport to.
* @param location The location to check
* @return true if it is safe, otherwise false
*/
private boolean isSafe(Location location) {
Block feet = location.getBlock();
Block head = feet.getRelative(BlockFace.UP);
Block below = feet.getRelative(BlockFace.DOWN);
Block above = head.getRelative(BlockFace.UP);
// Check the block at the feet and head of the player
if((feet.getType().isSolid() && !canSpawnIn(feet.getType())) || feet.isLiquid()) {
return false;
} else if((head.getType().isSolid() && !canSpawnIn(head.getType())) || head.isLiquid()) {
return false;
} else if(!below.getType().isSolid() || cannotSpawnOn(below.getType()) || below.isLiquid()) {
return false;
} else if(above.isLiquid() || cannotSpawnBeside(above.getType())) {
return false;
}
// Get all blocks around the player (below foot level, foot level, head level and above head level)
Set<Material> around = EnumSet.noneOf(Material.class);
for(int y = 0; y <= 3; y++) {
for(int x = -1; x <= 1; x++) {
for(int z = -1; z <= 1; z++) {
// Skip blocks in the column of the player
if(x == 0 && z == 0) {
continue;
}
around.add(below.getRelative(x, y, z).getType());
}
}
}
// Check the blocks around the player
for(Material material : around) {
if(cannotSpawnBeside(material)) {
return false;
}
}
return true;
}
/**
* Check if a player can spawn in here.
* @param material Material to check (assumed that this is at the feet or head level)
* @return true when it is safe to spawn inside, otherwise false
*/
private static boolean canSpawnIn(Material material) {
return material == Material.DRAGON_EGG
|| Tag.DOORS.isTagged(material)
|| Tag.SIGNS.isTagged(material)
|| Tag.PRESSURE_PLATES.isTagged(material);
}
/**
* Check if a player can spawn on here.
* @param material Material to check (assumed that this is below the feet)
* @return true when it is safe to spawn on top of, otherwise false
*/
private static boolean cannotSpawnOn(Material material) {
return material == Material.CACTUS
|| material == Material.PISTON
|| material == Material.PISTON_HEAD
|| material == Material.MOVING_PISTON
|| material == Material.DRAGON_EGG
|| material == Material.MAGMA_BLOCK
|| material == Material.REDSTONE_LAMP
|| Tag.SIGNS.isTagged(material)
|| Tag.DOORS.isTagged(material)
|| Tag.FENCE_GATES.isTagged(material)
|| Tag.FENCES.isTagged(material)
|| material.name().contains("GLASS_PANE");
}
/**
* Check if a player can spawn next to it.
* @param material Material to check (assumed that this is somewhere around the player)
* @return true when it is safe to spawn next to, otherwise false
*/
private static boolean cannotSpawnBeside(Material material) {
return material == Material.LAVA
|| material == Material.FIRE
|| material == Material.MAGMA_BLOCK;
}
/**
* Get the start location of a safe teleport search.
* @param player The player to get it for
* @param toSign true to try teleporting to the first sign, false for teleporting to the region
* @return The start location
*/
private Location getStartLocation(Player player, Value<Boolean> toSign) {
Location startLocation = null;
ProtectedRegion worldguardRegion = getRegion().getRegion();
// Try to get sign location
Collection<RegionSign> signs = getRegion().getSignsFeature().signManager().allSigns();
if(toSign.get()) {
boolean signAvailable = !signs.isEmpty();
if(signAvailable) {
RegionSign firstSign = signs.iterator().next();
// Use the location 1 below the sign to prevent weird spawing above the sign
startLocation = firstSign.getLocation(); //.subtract(0.0, 1.0, 0.0);
startLocation.setPitch(player.getLocation().getPitch());
startLocation.setYaw(player.getLocation().getYaw());
// Move player x blocks away from the sign
double distance = getRegion().getDoubleSetting("general.teleportSignDistance");
if(distance > 0) {
BlockFace facing = firstSign.getFacing();
Vector facingVector = new Vector(facing.getModX(), facing.getModY(), facing.getModZ())
.normalize()
.multiply(distance);
startLocation.setX(startLocation.getBlockX() + 0.5);
startLocation.setZ(startLocation.getBlockZ() + 0.5);
startLocation.add(facingVector);
}
} else {
// No sign available
getRegion().message(player, "teleport-changedToNoSign");
toSign.set(false);
}
}
// Use teleportation location that is set for the region
if(startLocation == null && hasTeleportLocation()) {
startLocation = getTeleportLocation();
}
// Calculate a default location
if(startLocation == null) {
// Set to block in the middle, y configured in the config
Vector regionMin = worldGuardInterface.getMinimumPoint(worldguardRegion);
Vector regionMax = worldGuardInterface.getMaximumPoint(worldguardRegion);
Vector middle = regionMin.clone().midpoint(regionMax);
String configSetting = getRegion().getStringSetting("general.teleportLocationY");
if("bottom".equalsIgnoreCase(configSetting)) {
middle = middle.setY(regionMin.getBlockY());
} else if("top".equalsIgnoreCase(configSetting)) {
middle = middle.setY(regionMax.getBlockY());
} else if("middle".equalsIgnoreCase(configSetting)) {
middle = middle.setY(middle.getBlockY());
} else {
try {
int vertical = Integer.parseInt(configSetting);
middle = middle.setY(vertical);
} catch(NumberFormatException e) {
AreaShop.warn("Could not parse general.teleportLocationY: '" + configSetting + "'");
}
}
startLocation = new Location(getRegion().getWorld(), middle.getX(), middle.getY(), middle.getZ(), player.getLocation().getYaw(), player.getLocation().getPitch());
}
// Set location in the center of the block
startLocation.setX(startLocation.getBlockX() + 0.5);
startLocation.setZ(startLocation.getBlockZ() + 0.5);
return startLocation;
}
}
@@ -0,0 +1,278 @@
package me.wiefferink.areashop.features;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.flags.Flag;
import com.sk89q.worldguard.protection.flags.InvalidFlagFormat;
import com.sk89q.worldguard.protection.flags.RegionGroupFlag;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.events.notify.UpdateRegionEvent;
import me.wiefferink.areashop.interfaces.RegionAccessSet;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.interactivemessenger.processing.Message;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.EventHandler;
import javax.annotation.Nonnull;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@Singleton
public class WorldGuardRegionFlagsFeature extends RegionFeature {
@Inject
private WorldGuardInterface worldGuardInterface;
@Inject
private WorldGuardPlugin worldGuard;
@Inject
WorldGuardRegionFlagsFeature(@Nonnull AreaShop plugin) {
super(plugin);
}
@EventHandler
public void regionUpdate(UpdateRegionEvent event) {
updateRegionFlags(event.getRegion());
}
/**
* Set the region flags/options to the values of a ConfigurationSection.
* @param region The region to update the flags for
* @return true if the flags have been set correctly, otherwise false
*/
private boolean updateRegionFlags(GeneralRegion region) {
boolean result = true;
// Get section defining the region flag profile
ConfigurationSection flagProfileSection = region.getConfigurationSectionSetting("general.flagProfile", "flagProfiles");
if(flagProfileSection == null) {
return false;
}
// Region flags for all states
ConfigurationSection allFlags = flagProfileSection.getConfigurationSection("ALL");
if(allFlags != null) {
result = updateRegionFlags(region, allFlags);
}
// Region flags for the current state
ConfigurationSection stateFlags = flagProfileSection.getConfigurationSection(region.getState().getValue());
// If in reselling mode, fallback to 'resale' section if 'resell' is not found (legacy configuration problem: https://github.com/NLthijs48/AreaShop/issues/303)
if(stateFlags == null && region.getState() == GeneralRegion.RegionState.RESELL) {
stateFlags = flagProfileSection.getConfigurationSection("resale");
}
if(stateFlags != null) {
result &= updateRegionFlags(region, stateFlags);
}
return result;
}
/**
* Set the region flags/options to the values of a ConfigurationSection.
* @param region The region to update the flags for
* @param flags The flags to apply
* @return true if the flags have been set correctly, otherwise false
*/
private boolean updateRegionFlags(GeneralRegion region, ConfigurationSection flags) {
boolean result = true;
Set<String> flagNames = flags.getKeys(false);
// Get the region
ProtectedRegion worldguardRegion = region.getRegion();
if(worldguardRegion == null) {
AreaShop.debug("Region '" + region.getName() + "' does not exist, setting flags failed");
return false;
}
// Loop through all flags that are set in the config
for(String flagName : flagNames) {
String value = Message.fromString(flags.getString(flagName)).replacements(region).getPlain();
// In the config normal Bukkit color codes are used, those only need to be translated on 5.X WorldGuard versions
if(worldGuard.getDescription().getVersion().startsWith("5.")) {
value = translateBukkitToWorldGuardColors(value);
}
if(flagName.equalsIgnoreCase("members")) {
worldGuardInterface.setMembers(worldguardRegion, parseAccessSet(value));
//AreaShop.debug(" Flag " + flagName + " set: " + members.toUserFriendlyString());
} else if(flagName.equalsIgnoreCase("owners")) {
worldGuardInterface.setOwners(worldguardRegion, parseAccessSet(value));
//AreaShop.debug(" Flag " + flagName + " set: " + owners.toUserFriendlyString());
} else if(flagName.equalsIgnoreCase("priority")) {
try {
int priority = Integer.parseInt(value);
if(worldguardRegion.getPriority() != priority) {
worldguardRegion.setPriority(priority);
}
//AreaShop.debug(" Flag " + flagName + " set: " + value);
} catch(NumberFormatException e) {
AreaShop.warn("The value of flag " + flagName + " is not a number");
result = false;
}
} else if(flagName.equalsIgnoreCase("parent")) {
if(region.getWorld() == null || plugin.getRegionManager(region.getWorld()) == null) {
continue;
}
ProtectedRegion parentRegion = plugin.getRegionManager(region.getWorld()).getRegion(value);
if(parentRegion != null) {
if(!parentRegion.equals(worldguardRegion.getParent())) {
try {
worldguardRegion.setParent(parentRegion);
//AreaShop.debug(" Flag " + flagName + " set: " + value);
} catch(ProtectedRegion.CircularInheritanceException e) {
AreaShop.warn("The parent set in the config is not correct (circular inheritance)");
}
}
} else {
AreaShop.warn("The parent set in the config is not correct (region does not exist)");
}
} else {
// Parse all other normal flags (groups are also handled)
String flagSetting = null;
com.sk89q.worldguard.protection.flags.RegionGroup groupValue = null;
Flag<?> foundFlag = worldGuardInterface.fuzzyMatchFlag(flagName);
if(foundFlag == null) {
AreaShop.warn("Found wrong flag in flagProfiles section: " + flagName + ", check if that is the correct WorldGuard flag");
continue;
}
RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag();
if(value == null || value.isEmpty()) {
if(worldguardRegion.getFlag(foundFlag) != null) {
worldguardRegion.setFlag(foundFlag, null);
}
if(groupFlag != null && worldguardRegion.getFlag(groupFlag) != null) {
worldguardRegion.setFlag(groupFlag, null);
}
//AreaShop.debug(" Flag " + flagName + " reset (+ possible group of flag)");
} else {
if(groupFlag == null) {
flagSetting = value;
} else {
for(String part : value.split(" ")) {
if(part.startsWith("g:")) {
if(part.length() > 2) {
try {
groupValue = worldGuardInterface.parseFlagGroupInput(groupFlag, part.substring(2));
} catch(InvalidFlagFormat e) {
AreaShop.warn("Found wrong group value for flag " + flagName);
}
}
} else {
if(flagSetting == null) {
flagSetting = part;
} else {
flagSetting += " " + part;
}
}
}
}
if(flagSetting != null) {
try {
setFlag(worldguardRegion, foundFlag, flagSetting);
//AreaShop.debug(" Flag " + flagName + " set: " + flagSetting);
} catch(InvalidFlagFormat e) {
AreaShop.warn("Found wrong value for flag " + flagName);
}
}
if(groupValue != null) {
if(groupValue == groupFlag.getDefault()) {
worldguardRegion.setFlag(groupFlag, null);
//AreaShop.debug(" Group of flag " + flagName + " set to default: " + groupValue);
} else {
worldguardRegion.setFlag(groupFlag, groupValue);
//AreaShop.debug(" Group of flag " + flagName + " set: " + groupValue);
}
}
}
}
}
// Indicate that the regions needs to be saved
if(worldGuard.getDescription().getVersion().startsWith("5.")) {
plugin.getFileManager().saveIsRequiredForRegionWorld(region.getWorldName());
}
return result;
}
/**
* Build an RegionAccessSet from an input that specifies player names, player uuids and groups.
* @param input Input string defining the access set
* @return RegionAccessSet containing the entities parsed from the input
*/
public RegionAccessSet parseAccessSet(String input) {
RegionAccessSet result = new RegionAccessSet();
String[] inputParts = input.split(", ");
for(String access : inputParts) {
if(access != null && !access.isEmpty()) {
// Check for groups
if(access.startsWith("g:")) {
if(access.length() > 2) {
result.getGroupNames().add(access.substring(2));
}
} else if(access.startsWith("n:")) {
if(access.length() > 2) {
result.getPlayerNames().add(access.substring(2));
}
} else {
try {
result.getPlayerUniqueIds().add(UUID.fromString(access));
} catch(IllegalArgumentException e) {
AreaShop.warn("Tried using '" + access + "' as uuid for a region member/owner, is your flagProfiles section correct?");
}
}
}
}
return result;
}
/**
* Set a WorldGuard region flag.
* @param region The WorldGuard region to set
* @param flag The flag to set
* @param value The value to set the flag to
* @param <V> They type of flag to set
* @throws InvalidFlagFormat When the value of the flag is wrong
*/
private <V> void setFlag(ProtectedRegion region, Flag<V> flag, String value) throws InvalidFlagFormat {
V current = region.getFlag(flag);
V next = worldGuardInterface.parseFlagInput(flag, value);
if(!Objects.equals(current, next)) {
region.setFlag(flag, next);
}
}
/**
* Translate the color codes you put in greeting/farewell messages to the weird color codes of WorldGuard.
* @param message The message where the color codes should be translated (this message has bukkit color codes)
* @return The string with the WorldGuard color codes
*/
private String translateBukkitToWorldGuardColors(String message) {
String result = message;
result = result.replace("&c", "&r");
result = result.replace("&4", "&R");
result = result.replace("&e", "&y");
result = result.replace("&6", "&Y");
result = result.replace("&a", "&g");
result = result.replace("&2", "&G");
result = result.replace("&b", "&c");
result = result.replace("&3", "&C");
result = result.replace("&9", "&b");
result = result.replace("&1", "&B");
result = result.replace("&d", "&p");
result = result.replace("&5", "&P");
result = result.replace("&0", "&0");
result = result.replace("&8", "&1");
result = result.replace("&7", "&2");
result = result.replace("&f", "&w");
result = result.replace("&r", "&x");
return result;
}
}
@@ -0,0 +1,295 @@
package me.wiefferink.areashop.features.signs;
import com.google.common.base.Objects;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import io.github.bakedlibs.dough.blocks.BlockPosition;
import io.papermc.lib.PaperLib;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.managers.SignErrorLogger;
import me.wiefferink.areashop.platform.adapter.BlockBehaviourHelper;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.tools.Materials;
import me.wiefferink.areashop.tools.SignUtils;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.interactivemessenger.processing.Message;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
/**
* Sign that is connected to a region to display information and interact with the region.
*/
public class RegionSign {
private final BlockBehaviourHelper blockHelper;
private final SignErrorLogger errorLogger;
private final SignsFeature signsFeature;
private final String key;
@AssistedInject
RegionSign(
@Nonnull BlockBehaviourHelper blockBehaviourHelper,
@Nonnull SignErrorLogger signErrorLogger,
@Assisted @Nonnull SignsFeature signsFeature,
@Assisted @Nonnull String key
) {
this.blockHelper = blockBehaviourHelper;
this.errorLogger = signErrorLogger;
this.signsFeature = signsFeature;
this.key = key;
}
public BlockPosition getPosition() {
return new BlockPosition(getLocation());
}
/**
* Get the location of this sign.
* @return The location of this sign
*/
public Location getLocation() {
return Utils.configToLocation(getRegion().getConfig().getConfigurationSection("general.signs." + key + ".location"));
}
/**
* Location string to be used as key in maps.
* @return Location string
*/
public String getStringLocation() {
return SignsFeature.locationToString(getLocation());
}
/**
* Chunk string to be used as key in maps.
* @return Chunk string
*/
public String getStringChunk() {
return SignsFeature.chunkToString(getLocation());
}
/**
* Get the region this sign is linked to.
* @return The region this sign is linked to
*/
public GeneralRegion getRegion() {
return signsFeature.getRegion();
}
/**
* Remove this sign from the region.
*/
public void remove() {
Location location = getLocation();
if (location != null && location.getWorld() != null) {
location.getBlock().setType(Material.AIR);
this.signsFeature.signManager().removeSign(this);
}
getRegion().setSetting("general.signs." + key, null);
// Remove the sign from the region's sign manager
}
/**
* Get the ConfigurationSection defining the sign layout.
* @return The sign layout config
*/
public ConfigurationSection getProfile() {
return getRegion().getConfigurationSectionSetting("general.signProfile", "signProfiles", getRegion().getConfig().get("general.signs." + key + ".profile"));
}
/**
* Get the facing of the sign as saved in the config.
* @return BlockFace the sign faces, or null if unknown
*/
public BlockFace getFacing() {
try {
return BlockFace.valueOf(getRegion().getConfig().getString("general.signs." + key + ".facing"));
} catch(NullPointerException | IllegalArgumentException e) {
return null;
}
}
/**
* Get the material of the sign as saved in the config.
* @return Material of the sign,Material.AIR if none.
*/
public Material getMaterial() {
String name = getRegion().getConfig().getString("general.signs." + key + ".signType");
Material result = Materials.signNameToMaterial(name);
return result == null ? Material.AIR : result;
}
/**
* Update this sign.
* @return true if the update was successful, otherwise false
*/
public boolean update() {
// Ignore updates of signs in chunks that are not loaded
Location signLocation = getLocation();
if(signLocation == null
|| signLocation.getWorld() == null
|| !signLocation.getWorld().isChunkLoaded(signLocation.getBlockX() >> 4, signLocation.getBlockZ() >> 4)) {
return false;
}
if(getRegion().isDeleted()) {
return false;
}
YamlConfiguration regionConfig = getRegion().getConfig();
ConfigurationSection signConfig = getProfile();
Block block = signLocation.getBlock();
if(signConfig == null || !signConfig.isSet(getRegion().getState().getValue())) {
block.setType(Material.AIR);
return true;
}
ConfigurationSection stateConfig = signConfig.getConfigurationSection(getRegion().getState().getValue());
// Get the lines
String[] signLines = new String[4];
boolean signEmpty = true;
for(int i = 0; i < 4; i++) {
signLines[i] = stateConfig.getString("line" + (i + 1));
signEmpty &= (signLines[i] == null || signLines[i].isEmpty());
}
if(signEmpty) {
block.setType(Material.AIR);
return true;
}
// Place the sign back (with proper rotation and type) after it has been hidden or (indirectly) destroyed
if(!Materials.isSign(block.getType())) {
Material signType = getMaterial();
if (!blockHelper.canPlace(block.getLocation(), Bukkit.createBlockData(signType))) {
errorLogger.submitWarning("Setting sign" + key + "of region" + getRegion().getName() + "failed, could not set sign block back");
this.remove();
return false;
}
// Don't do physics here, we first need to update the direction
block.setType(signType, false);
BlockState blockState = PaperLib.getBlockState(block, false).getState();
BlockData blockData = blockState.getBlockData();
if(blockData instanceof WallSign) {
((WallSign) blockData).setFacing(getFacing());
} else if(blockData instanceof org.bukkit.block.data.type.Sign) {
((org.bukkit.block.data.type.Sign) blockData).setRotation(getFacing());
} else {
errorLogger.submitWarning("Failed to update the facing direction of the sign at" + getStringLocation() + "to " + getFacing() + ", region:" + getRegion().getName());
return false;
}
block.setBlockData(blockData);
// Check if the sign has popped
if(!Materials.isSign(block.getType())) {
return false;
}
}
// Save current rotation and type
if(!regionConfig.isString("general.signs." + key + ".signType")) {
getRegion().setSetting("general.signs." + key + ".signType", block.getType().name());
}
if(!regionConfig.isString("general.signs." + key + ".facing")) {
BlockFace signFacing = SignUtils.getSignFacing(block);
getRegion().setSetting("general.signs." + key + ".facing", signFacing == null ? null : signFacing.toString());
}
// Apply replacements and color and then set it on the sign
Sign signState = (Sign) PaperLib.getBlockState(block, false).getState();
for(int i = 0; i < signLines.length; i++) {
if(signLines[i] == null) {
signState.setLine(i, "");
continue;
}
signLines[i] = Message.fromString(signLines[i]).replacements(getRegion()).getSingle();
signLines[i] = Utils.applyColors(signLines[i]);
signState.setLine(i, signLines[i]);
}
signState.update(false, false);
return true;
}
/**
* Check if the sign needs to update periodically.
* @return true if it needs periodic updates, otherwise false
*/
public boolean needsPeriodicUpdate() {
ConfigurationSection signConfig = getProfile();
if(signConfig == null || !signConfig.isSet(getRegion().getState().getValue().toLowerCase())) {
return false;
}
ConfigurationSection stateConfig = signConfig.getConfigurationSection(getRegion().getState().getValue().toLowerCase());
if(stateConfig == null) {
return false;
}
// Check the lines for the timeleft tag
for(int i = 1; i <= 4; i++) {
String line = stateConfig.getString("line" + i);
if(line != null && line.contains(Message.VARIABLE_START + AreaShop.tagTimeLeft + Message.VARIABLE_END)) {
return true;
}
}
return false;
}
/**
* Run commands when a player clicks a sign.
* @param clicker The player that clicked the sign
* @param clickType The type of clicking
* @return true if the commands ran successfully, false if any of them failed
*/
public boolean runSignCommands(Player clicker, GeneralRegion.ClickType clickType) {
ConfigurationSection signConfig = getProfile();
if(signConfig == null) {
return false;
}
ConfigurationSection stateConfig = signConfig.getConfigurationSection(getRegion().getState().getValue().toLowerCase());
// Run player commands if specified
List<String> playerCommands = new ArrayList<>();
for(String command : stateConfig.getStringList(clickType.getValue() + "Player")) {
// TODO move variable checking code to InteractiveMessenger?
playerCommands.add(command.replace(Message.VARIABLE_START + AreaShop.tagClicker + Message.VARIABLE_END, clicker.getName()));
}
getRegion().runCommands(clicker, playerCommands);
// Run console commands if specified
List<String> consoleCommands = new ArrayList<>();
for(String command : stateConfig.getStringList(clickType.getValue() + "Console")) {
consoleCommands.add(command.replace(Message.VARIABLE_START + AreaShop.tagClicker + Message.VARIABLE_END, clicker.getName()));
}
getRegion().runCommands(Bukkit.getConsoleSender(), consoleCommands);
return !playerCommands.isEmpty() || !consoleCommands.isEmpty();
}
@Override
public boolean equals(Object object) {
return object instanceof RegionSign && ((RegionSign)object).getRegion().equals(this.getRegion()) && ((RegionSign)object).key.equals(this.key);
}
@Override
public int hashCode() {
return Objects.hashCode(key, getRegion().getName());
}
}
@@ -0,0 +1,83 @@
package me.wiefferink.areashop.features.signs;
import io.github.bakedlibs.dough.blocks.BlockPosition;
import io.github.bakedlibs.dough.blocks.ChunkPosition;
import org.bukkit.Location;
import org.bukkit.World;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
public class SignCache {
private final Map<Long, RegionSign> allSigns = new HashMap<>();
private final Map<Long, Collection<RegionSign>> signsByChunk = new HashMap<>();
public Optional<RegionSign> signAtLocation(long position) {
return Optional.ofNullable(this.allSigns.get(position));
}
public Collection<RegionSign> signsAtChunk(long position) {
return Collections.unmodifiableCollection(this.signsByChunk.getOrDefault(position, Collections.emptyList()));
}
public Collection<RegionSign> allSigns() {
return Collections.unmodifiableCollection(this.allSigns.values());
}
public Collection<BlockPosition> allSignLocations() {
Collection<BlockPosition> positions = new ArrayList<>(this.signsByChunk.size());
for (RegionSign sign : this.allSigns.values()) {
positions.add(sign.getPosition());
}
return positions;
}
public void addSign(RegionSign regionSign) {
BlockPosition blockPosition = regionSign.getPosition();
this.allSigns.put(blockPosition.getPosition(), regionSign);
ChunkPosition chunkPosition = new ChunkPosition(blockPosition.getChunk());
Collection<RegionSign> signs = this.signsByChunk.computeIfAbsent(chunkPosition.getPosition(), x -> new HashSet<>());
signs.add(regionSign);
}
public Optional<RegionSign> removeSign(Location location) {
return removeSign(new BlockPosition(location));
}
public Optional<RegionSign> removeSign(BlockPosition position) {
RegionSign removed = this.allSigns.remove(position.getPosition());
if (removed != null) {
World world = position.getWorld();
ChunkPosition chunkPosition = new ChunkPosition(world, position.getChunkX(), position.getChunkZ());
this.signsByChunk.remove(chunkPosition.getPosition());
// Remove the sign from the region
}
return Optional.ofNullable(removed);
}
public void removeSign(RegionSign regionSign) {
BlockPosition blockPosition = regionSign.getPosition();
this.allSigns.remove(blockPosition.getPosition());
ChunkPosition chunkPosition = new ChunkPosition(blockPosition.getChunk());
long packedChunkPos = chunkPosition.getPosition();
Collection<RegionSign> signs = this.signsByChunk.get(packedChunkPos);
if (signs != null) {
signs.remove(regionSign);
if (signs.isEmpty()) {
this.signsByChunk.remove(packedChunkPos);
}
}
}
public void clear() {
this.allSigns.clear();
this.signsByChunk.clear();
}
}
@@ -0,0 +1,10 @@
package me.wiefferink.areashop.features.signs;
import javax.annotation.Nonnull;
public interface SignFactory {
@Nonnull RegionSign createRegionSign(@Nonnull SignsFeature signsFeature, @Nonnull String key);
}
@@ -0,0 +1,449 @@
package me.wiefferink.areashop.features.signs;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import io.github.bakedlibs.dough.blocks.ChunkPosition;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.events.ask.AddingRegionEvent;
import me.wiefferink.areashop.events.notify.UpdateRegionEvent;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.managers.SignLinkerManager;
import me.wiefferink.areashop.platform.adapter.BlockBehaviourHelper;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RegionFactory;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Materials;
import me.wiefferink.areashop.tools.SignUtils;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.bukkitdo.Do;
import org.bukkit.Chunk;
import org.bukkit.Material;
import org.bukkit.block.Block;
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.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public class SignListener implements Listener {
private final BlockBehaviourHelper behaviourHelper;
private final AreaShop plugin;
private final MessageBridge messageBridge;
private final SignManager signManager;
private final SignLinkerManager signLinkerManager;
private final WorldGuardInterface worldGuardInterface;
private final RegionFactory regionFactory;
private final IFileManager fileManager;
public SignListener(
@Nonnull AreaShop plugin,
@Nonnull BlockBehaviourHelper behaviourHelper,
@Nonnull RegionFactory regionFactory,
@Nonnull MessageBridge messageBridge,
@Nonnull SignLinkerManager signLinkerManager,
@Nonnull WorldGuardInterface worldGuardInterface,
@Nonnull SignManager signManager,
@Nonnull IFileManager fileManager) {
this.fileManager = fileManager;
this.signManager = signManager;
this.signLinkerManager = signLinkerManager;
this.regionFactory = regionFactory;
this.behaviourHelper = behaviourHelper;
this.plugin = plugin;
this.worldGuardInterface = worldGuardInterface;
this.messageBridge = messageBridge;
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void regionUpdate(UpdateRegionEvent event) {
SignsFeature signsFeature = event.getRegion().getSignsFeature();
signsFeature.signManager().update();
}
@EventHandler(priority = EventPriority.MONITOR)
public void onChunkLoad(ChunkLoadEvent event) {
Chunk chunk = event.getChunk();
Optional<SignCache> optional = this.signManager.getCacheForWorld(chunk.getWorld());
if (optional.isEmpty()) {
return;
}
final SignCache signCache = optional.get();
Collection<RegionSign> chunkSigns = new ArrayList<>(signCache.signsAtChunk(ChunkPosition.getAsLong(chunk.getX(), chunk.getZ())));
if(chunkSigns.isEmpty()) {
return;
}
Do.forAll(chunkSigns, RegionSign::update);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onIndirectSignBreak(BlockPhysicsEvent event) {
// Check if the block is a sign
if(!Materials.isSign(event.getBlock().getType()) || behaviourHelper.isBlockValid(event.getBlock())) {
return;
}
// Check if the sign is really the same as a saved rent
final Optional<RegionSign> optionalSign = this.signManager.signFromLocation(event.getBlock().getLocation());
if(optionalSign.isEmpty()) {
return;
}
RegionSign regionSign = optionalSign.get();
// Remove the sign so that it does not fall on the floor as an item (next region update will place it back when possible)
AreaShop.debug("onIndirectSignBreak: Removed block of sign for", regionSign.getRegion().getName(), "at", regionSign.getStringLocation());
event.getBlock().setType(Material.AIR);
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGH)
public void onSignBreak(BlockBreakEvent event) {
if(event.isCancelled()) {
return;
}
Block block = event.getBlock();
// Check if it is a sign
if(Materials.isSign(block.getType())) {
// Check if the rent sign is really the same as a saved rent
Optional<RegionSign> optional = signManager.signFromLocation(block.getLocation());
if(optional.isEmpty()) {
return;
}
RegionSign regionSign = optional.get();
// Remove the sign of the rental region if the player has permission
if(event.getPlayer().hasPermission("areashop.delsign")) {
signManager.removeSign(regionSign);
messageBridge.message(event.getPlayer(), "delsign-success", regionSign.getRegion());
} else { // Cancel the breaking of the sign
event.setCancelled(true);
messageBridge.message(event.getPlayer(), "delsign-noPermission", regionSign.getRegion());
}
}
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onSignClick(PlayerInteractEvent event) {
Block block = event.getClickedBlock();
if (block == null) {
return;
}
// Only listen to left and right clicks on blocks
if (!(event.getAction() == Action.RIGHT_CLICK_BLOCK || event.getAction() == Action.LEFT_CLICK_BLOCK)) {
return;
}
// Only care about clicking blocks
if(!Materials.isSign(block.getType())) {
return;
}
// Check if this sign belongs to a region
Optional<RegionSign> optional = signManager.signFromLocation(block.getLocation());
if(optional.isEmpty()) {
return;
}
RegionSign regionSign = optional.get();
// Ignore players that are in sign link mode (which will handle the event itself)
Player player = event.getPlayer();
if(signLinkerManager.isInSignLinkMode(player)) {
return;
}
// Get the clicktype
GeneralRegion.ClickType clickType = null;
if(player.isSneaking() && event.getAction() == Action.LEFT_CLICK_BLOCK) {
clickType = GeneralRegion.ClickType.SHIFTLEFTCLICK;
} else if(!player.isSneaking() && event.getAction() == Action.LEFT_CLICK_BLOCK) {
clickType = GeneralRegion.ClickType.LEFTCLICK;
} else if(player.isSneaking() && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
clickType = GeneralRegion.ClickType.SHIFTRIGHTCLICK;
} else if(!player.isSneaking() && event.getAction() == Action.RIGHT_CLICK_BLOCK) {
clickType = GeneralRegion.ClickType.RIGHTCLICK;
}
boolean ran = regionSign.runSignCommands(player, clickType);
// Only cancel event if at least one command has been executed
event.setCancelled(ran);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSignChange(SignChangeEvent event) {
Player player = event.getPlayer();
if(!plugin.isReady()) {
messageBridge.message(player, "general-notReady");
return;
}
// Check if the sign is meant for this plugin
if(event.getLine(0).contains(plugin.getConfig().getString("signTags.rent"))) {
if(!player.hasPermission("areashop.createrent") && !player.hasPermission("areashop.createrent.member") && !player.hasPermission("areashop.createrent.owner")) {
messageBridge.message(player, "setup-noPermissionRent");
return;
}
// Get the other lines
String secondLine = event.getLine(1);
String thirdLine = event.getLine(2);
String fourthLine = event.getLine(3);
// Get the regionManager for accessing regions
RegionManager regionManager = plugin.getRegionManager(event.getPlayer().getWorld());
// If the secondLine does not contain a name try to find the region by location
if(secondLine == null || secondLine.isEmpty()) {
Set<ProtectedRegion> regions = worldGuardInterface.getApplicableRegionsSet(event.getBlock().getLocation());
if(regions != null) {
boolean first = true;
ProtectedRegion candidate = null;
for(ProtectedRegion pr : regions) {
if(first) {
candidate = pr;
first = false;
} else {
if(pr.getPriority() > candidate.getPriority()) {
candidate = pr;
} else if(pr.getParent() != null && pr.getParent().equals(candidate)) {
candidate = pr;
} else {
messageBridge.message(player, "setup-couldNotDetect", candidate.getId(), pr.getId());
return;
}
}
}
if(candidate != null) {
secondLine = candidate.getId();
}
}
}
boolean priceSet = fourthLine != null && !fourthLine.isEmpty();
boolean durationSet = thirdLine != null && !thirdLine.isEmpty();
// check if all the lines are correct
if(secondLine == null || secondLine.isEmpty()) {
messageBridge.message(player, "setup-noRegion");
return;
}
ProtectedRegion region = regionManager.getRegion(secondLine);
if(region == null) {
messageBridge.message(player, "cmd-noRegion", secondLine);
return;
}
IFileManager.AddResult addResult = fileManager.checkRegionAdd(player, regionManager.getRegion(secondLine), event.getPlayer().getWorld(), GeneralRegion.RegionType.RENT);
if(addResult == IFileManager.AddResult.BLACKLISTED) {
messageBridge.message(player, "setup-blacklisted", secondLine);
} else if(addResult == IFileManager.AddResult.ALREADYADDED) {
messageBridge.message(player, "setup-alreadyRentSign");
} else if(addResult == IFileManager.AddResult.ALREADYADDEDOTHERWORLD) {
messageBridge.message(player, "setup-alreadyOtherWorld");
} else if(addResult == IFileManager.AddResult.NOPERMISSION) {
messageBridge.message(player, "setup-noPermission", secondLine);
} else if(thirdLine != null && !thirdLine.isEmpty() && !Utils.checkTimeFormat(thirdLine)) {
messageBridge.message(player, "setup-wrongDuration");
} else {
double price = 0.0;
if(priceSet) {
// Check the fourth line
try {
price = Double.parseDouble(fourthLine);
} catch(NumberFormatException e) {
messageBridge.message(player, "setup-wrongPrice");
return;
}
}
// Add rent to the FileManager
final RentRegion rent = regionFactory.createRentRegion(secondLine, event.getPlayer().getWorld());
boolean isMember = worldGuardInterface.containsMember(rent.getRegion(), player.getUniqueId());
boolean isOwner = worldGuardInterface.containsOwner(rent.getRegion(), player.getUniqueId());
boolean landlord = (!player.hasPermission("areashop.createrent")
&& ((player.hasPermission("areashop.createrent.owner") && isOwner)
|| (player.hasPermission("areashop.createrent.member") && isMember)));
if(landlord) {
rent.setLandlord(player.getUniqueId(), player.getName());
}
if(priceSet) {
rent.setPrice(price);
}
if(durationSet) {
rent.setDuration(thirdLine);
}
rent.getSignsFeature().addSign(event.getBlock().getLocation(), event.getBlock().getType(), SignUtils.getSignFacing(event.getBlock()), null);
AddingRegionEvent addingRegionEvent = plugin.getFileManager().addRegion(rent);
if (addingRegionEvent.isCancelled()) {
messageBridge.message(player, "general-cancelled", addingRegionEvent.getReason());
return;
}
rent.handleSchematicEvent(GeneralRegion.RegionEvent.CREATED);
messageBridge.message(player, "setup-rentSuccess", rent);
// Update the region after the event has written its lines
Do.sync(rent::update);
}
} else if(event.getLine(0).contains(plugin.getConfig().getString("signTags.buy"))) {
// Check for permission
if(!player.hasPermission("areashop.createbuy") && !player.hasPermission("areashop.createbuy.member") && !player.hasPermission("areashop.createbuy.owner")) {
messageBridge.message(player, "setup-noPermissionBuy");
return;
}
// Get the other lines
String secondLine = event.getLine(1);
String thirdLine = event.getLine(2);
// Get the regionManager for accessing regions
RegionManager regionManager = plugin.getRegionManager(event.getPlayer().getWorld());
// If the secondLine does not contain a name try to find the region by location
if(secondLine == null || secondLine.isEmpty()) {
Set<ProtectedRegion> regions = worldGuardInterface.getApplicableRegionsSet(event.getBlock().getLocation());
if(regions != null) {
boolean first = true;
ProtectedRegion candidate = null;
for(ProtectedRegion pr : regions) {
if(first) {
candidate = pr;
first = false;
} else {
if(pr.getPriority() > candidate.getPriority()) {
candidate = pr;
} else if(pr.getParent() != null && pr.getParent().equals(candidate)) {
candidate = pr;
} else {
messageBridge.message(player, "setup-couldNotDetect", candidate.getId(), pr.getId());
return;
}
}
}
if(candidate != null) {
secondLine = candidate.getId();
}
}
}
boolean priceSet = thirdLine != null && !thirdLine.isEmpty();
// Check if all the lines are correct
if(secondLine == null || secondLine.isEmpty()) {
messageBridge.message(player, "setup-noRegion");
return;
}
ProtectedRegion region = regionManager.getRegion(secondLine);
if(region == null) {
messageBridge.message(player, "cmd-noRegion", secondLine);
return;
}
IFileManager.AddResult addResult = plugin.getFileManager().checkRegionAdd(player, region, event.getPlayer().getWorld(), GeneralRegion.RegionType.BUY);
if(addResult == IFileManager.AddResult.BLACKLISTED) {
messageBridge.message(player, "setup-blacklisted", secondLine);
} else if(addResult == IFileManager.AddResult.ALREADYADDED) {
messageBridge.message(player, "setup-alreadyRentSign");
} else if(addResult == IFileManager.AddResult.ALREADYADDEDOTHERWORLD) {
messageBridge.message(player, "setup-alreadyOtherWorld");
} else if(addResult == IFileManager.AddResult.NOPERMISSION) {
messageBridge.message(player, "setup-noPermission", secondLine);
} else {
double price = 0.0;
if(priceSet) {
// Check the fourth line
try {
price = Double.parseDouble(thirdLine);
} catch(NumberFormatException e) {
messageBridge.message(player, "setup-wrongPrice");
return;
}
}
// Add buy to the FileManager
final BuyRegion buy = regionFactory.createBuyRegion(secondLine, event.getPlayer().getWorld());
boolean isMember = worldGuardInterface.containsMember(buy.getRegion(), player.getUniqueId());
boolean isOwner = worldGuardInterface.containsOwner(buy.getRegion(), player.getUniqueId());
boolean landlord = (!player.hasPermission("areashop.createbuy")
&& ((player.hasPermission("areashop.createbuy.owner") && isOwner)
|| (player.hasPermission("areashop.createbuy.member") && isMember)));
if(landlord) {
buy.setLandlord(player.getUniqueId(), player.getName());
}
if(priceSet) {
buy.setPrice(price);
}
buy.getSignsFeature().addSign(event.getBlock().getLocation(), event.getBlock().getType(), SignUtils.getSignFacing(event.getBlock()), null);
AddingRegionEvent addingRegionEvent = plugin.getFileManager().addRegion(buy);
if (addingRegionEvent.isCancelled()) {
messageBridge.message(player, "general-cancelled", addingRegionEvent.getReason());
return;
}
buy.handleSchematicEvent(GeneralRegion.RegionEvent.CREATED);
messageBridge.message(player, "setup-buySuccess", buy);
// Update the region after the event has written its lines
Do.sync(buy::update);
}
} else if(event.getLine(0).contains(plugin.getConfig().getString("signTags.add"))) {
// Check for permission
if(!player.hasPermission("areashop.addsign")) {
messageBridge.message(player, "addsign-noPermission");
return;
}
// Get the other lines
String secondLine = event.getLine(1);
String thirdLine = event.getLine(2);
GeneralRegion region;
if(secondLine != null && !secondLine.isEmpty()) {
// Get region by secondLine of the sign
region = fileManager.getRegion(secondLine);
if(region == null) {
messageBridge.message(player, "addSign-notRegistered", secondLine);
return;
}
} else {
// Get region by sign position
List<GeneralRegion> regions = Utils.getImportantRegions(event.getBlock().getLocation());
if(regions.isEmpty()) {
messageBridge.message(player, "addsign-noRegions");
return;
} else if(regions.size() > 1) {
messageBridge.message(player, "addsign-couldNotDetectSign", regions.get(0).getName(), regions.get(1).getName());
return;
}
region = regions.get(0);
}
if(thirdLine == null || thirdLine.isEmpty()) {
region.getSignsFeature().addSign(event.getBlock().getLocation(), event.getBlock().getType(), SignUtils.getSignFacing(event.getBlock()), null);
messageBridge.message(player, "addsign-success", region);
} else {
region.getSignsFeature().addSign(event.getBlock().getLocation(), event.getBlock().getType(), SignUtils.getSignFacing(event.getBlock()), thirdLine);
messageBridge.message(player, "addsign-successProfile", thirdLine, region);
}
// Update the region later because this event will do it first
Do.sync(region::update);
}
}
}
@@ -0,0 +1,115 @@
package me.wiefferink.areashop.features.signs;
import io.github.bakedlibs.dough.blocks.BlockPosition;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.managers.Manager;
import org.bukkit.Location;
import org.bukkit.World;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class SignManager extends Manager {
private final Map<World, SignCache> signCacheMap = new HashMap<>();
public Collection<BlockPosition> allSignLocations() {
final Set<BlockPosition> set = new HashSet<>(signCacheMap.size());
for (SignCache signCache : signCacheMap.values()) {
set.addAll(signCache.allSignLocations());
}
return set;
}
public Collection<RegionSign> allSigns() {
final Set<RegionSign> signs = new HashSet<>(signCacheMap.size());
for (SignCache signCache : signCacheMap.values()) {
signs.addAll(signCache.allSigns());
}
return signs;
}
public SignCache cacheForWorld(World world) {
return this.signCacheMap.computeIfAbsent(world, x -> new SignCache());
}
public Optional<SignCache> getCacheForWorld(World world) {
return Optional.ofNullable(this.signCacheMap.get(world));
}
public Optional<RegionSign> signFromLocation(Location location) {
final World world = location.getWorld();
return getCacheForWorld(world)
.flatMap(cache -> cache.signAtLocation(BlockPosition.getAsLong(location)));
}
public void addSign(RegionSign regionSign) {
Location location = regionSign.getLocation();
cacheForWorld(location.getWorld()).addSign(regionSign);
}
public void removeSign(RegionSign regionSign) {
Location location = regionSign.getLocation();
if (location.getWorld() == null) {
return;
}
cacheForWorld(location.getWorld()).removeSign(regionSign);
}
public Optional<RegionSign> removeSign(Location location) {
if (location == null || location.getWorld() == null) {
return Optional.empty();
}
return removeSign(new BlockPosition(location));
}
public Optional<RegionSign> removeSign(BlockPosition blockPosition) {
return getCacheForWorld(blockPosition.getWorld()).flatMap(cache -> cache.removeSign(blockPosition));
}
/**
* Update all signs connected to this region.
* @return true if all signs are updated correctly, false if one or more updates failed
*/
public boolean update() {
boolean result = true;
for (SignCache signCache : this.signCacheMap.values()) {
for (RegionSign sign : signCache.allSigns()) {
result &= sign.update();
}
}
return result;
}
/**
* Check if any of the signs need periodic updating.
* @return true if one or more of the signs need periodic updating, otherwise false
*/
public boolean needsPeriodicUpdate() {
boolean result = false;
for (SignCache signCache : this.signCacheMap.values()) {
for (RegionSign sign : signCache.allSigns()) {
if (result) {
break;
}
if (sign.needsPeriodicUpdate()) {
result = true;
break;
}
}
}
return result;
}
public void shutdown() {
update();
this.signCacheMap.values().forEach(SignCache::clear);
this.signCacheMap.clear();
}
}
@@ -0,0 +1,108 @@
package me.wiefferink.areashop.features.signs;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import io.github.bakedlibs.dough.blocks.BlockPosition;
import io.github.bakedlibs.dough.blocks.ChunkPosition;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.features.RegionFeature;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.ConfigurationSection;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
public class SignsFeature extends RegionFeature {
private final SignManager internalSignManager = new SignManager();
private final SignManager globalSignManager;
private final SignFactory signFactory;
/**
* Constructor.
* @param region The region to bind to
*/
@AssistedInject
SignsFeature(@Nonnull AreaShop plugin,
@Nonnull SignManager signManager,
@Nonnull SignFactory signFactory,
@Assisted @Nonnull GeneralRegion region
) {
super(plugin);
this.globalSignManager = signManager;
this.signFactory = signFactory;
setRegion(region);
// Setup current signs
ConfigurationSection signSection = region.getConfig().getConfigurationSection("general.signs");
if (signSection == null) {
return;
}
for(String signKey : signSection.getKeys(false)) {
RegionSign sign = signFactory.createRegionSign(this, signKey);
Location location = sign.getLocation();
if(location == null) {
AreaShop.warn("Sign with key " + signKey + " of region " + region.getName() + " does not have a proper location");
continue;
}
this.globalSignManager.cacheForWorld(location.getWorld()).addSign(sign);
this.internalSignManager.addSign(sign);
}
}
/**
* Convert a location to a string to use as map key.
* @param location The location to get the key for
* @return A string to use in a map for a location
*/
public static String locationToString(Location location) {
return location.getWorld().getName() + ";" + location.getBlockX() + ";" + location.getBlockY() + ";" + location.getBlockZ();
}
/**
* Convert a chunk to a string to use as map key.
* @param location The location to get the key for
* @return A string to use in a map for a chunk
*/
public static String chunkToString(Location location) {
return location.getWorld().getName() + ";" + (location.getBlockX() >> 4) + ";" + (location.getBlockZ() >> 4);
}
@Override
public void shutdown() {
}
public SignManager signManager() {
return this.internalSignManager;
}
/**
* Add a sign to this region.
* @param location The location of the sign
* @param signType The type of the sign (WALL_SIGN or SIGN_POST)
* @param facing The orientation of the sign
* @param profile The profile to use with this sign (null for default)
*/
public void addSign(Location location, Material signType, BlockFace facing, String profile) {
int i = 0;
while(getRegion().getConfig().isSet("general.signs." + i)) {
i++;
}
String signPath = "general.signs." + i + ".";
getRegion().setSetting(signPath + "location", Utils.locationToConfig(location));
getRegion().setSetting(signPath + "facing", facing != null ? facing.name() : null);
getRegion().setSetting(signPath + "signType", signType != null ? signType.name() : null);
if(profile != null && !profile.isEmpty()) {
getRegion().setSetting(signPath + "profile", profile);
}
// Add to the map
RegionSign regionSign = this.signFactory.createRegionSign(this, String.valueOf(i));
this.globalSignManager.addSign(regionSign);
this.internalSignManager.addSign(regionSign);
}
}
@@ -0,0 +1,50 @@
package me.wiefferink.areashop.features.signs;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.managers.SignLinkerManager;
import me.wiefferink.areashop.platform.adapter.BlockBehaviourHelper;
import me.wiefferink.areashop.regions.RegionFactory;
import javax.annotation.Nonnull;
public class SignsModule extends AbstractModule {
@Override
protected void configure() {
bind(SignManager.class).asEagerSingleton();
install(new FactoryModuleBuilder().build(SignFactory.class));
}
@Provides
@Singleton
public SignListener provideSignListener(@Nonnull AreaShop plugin,
@Nonnull BlockBehaviourHelper behaviourHelper,
@Nonnull RegionFactory regionFactory,
@Nonnull MessageBridge messageBridge,
@Nonnull SignLinkerManager signLinkerManager,
@Nonnull WorldGuardInterface worldGuardInterface,
@Nonnull SignManager signManager,
@Nonnull IFileManager fileManager
) {
final SignListener signListener = new SignListener(
plugin,
behaviourHelper,
regionFactory,
messageBridge,
signLinkerManager,
worldGuardInterface,
signManager,
fileManager
);
plugin.getServer().getPluginManager().registerEvents(signListener, plugin);
return signListener;
}
}
@@ -0,0 +1,168 @@
package me.wiefferink.areashop.listeners;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RentRegion;
import me.wiefferink.areashop.tools.Utils;
import me.wiefferink.bukkitdo.Do;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
import org.bukkit.event.player.PlayerQuitEvent;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
/**
* Notify region expiry and track activity time.
*/
public final class PlayerLoginLogoutListener implements Listener {
private final AreaShop plugin;
private final MessageBridge messageBridge;
/**
* Constructor.
* @param plugin The AreaShop plugin
*/
public PlayerLoginLogoutListener(@Nonnull AreaShop plugin, @Nonnull MessageBridge messageBridge) {
this.plugin = plugin;
this.messageBridge = messageBridge;
}
/**
* Called when a sign is changed.
* @param event The event
*/
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLogin(PlayerLoginEvent event) {
if(event.getResult() != Result.ALLOWED) {
return;
}
final Player player = event.getPlayer();
// Schedule task to check for notifications, prevents a lag spike at login
Do.syncTimerLater(25, 25, () -> {
// Delay until all regions are loaded
if(!plugin.isReady()) {
return true;
}
if(!player.isOnline()) {
return false;
}
// Notify for rents that almost run out
for(RentRegion region : plugin.getFileManager().getRentsRef()) {
if(region.isRenter(player)) {
String warningSetting = region.getStringSetting("rent.warningOnLoginTime");
if(warningSetting == null || warningSetting.isEmpty()) {
continue;
}
long warningTime = Utils.durationStringToLong(warningSetting);
if(region.getTimeLeft() < warningTime) {
// Send the warning message later to let it appear after general MOTD messages
messageBridge.message(player, "rent-expireWarning", region);
}
}
}
// Notify admins for plugin updates
this.plugin.notifyUpdate(player);
return false;
});
// Check if the player has regions that use an old name of him and update them
Do.syncTimerLater(22, 10, () -> {
if(!plugin.isReady()) {
return true;
}
List<GeneralRegion> regions = new ArrayList<>();
for(GeneralRegion region : plugin.getFileManager().getRegionsRef()) {
if(region.isOwner(player)) {
regions.add(region);
}
}
Do.forAll(
plugin.getConfig().getInt("nameupdate.regionsPerTick"),
regions,
region -> {
if(region instanceof BuyRegion) {
if(!player.getName().equals(region.getStringSetting("buy.buyerName"))) {
region.setSetting("buy.buyerName", player.getName());
region.update();
}
} else if(region instanceof RentRegion) {
if(!player.getName().equals(region.getStringSetting("rent.renterName"))) {
region.setSetting("rent.renterName", player.getName());
region.update();
}
}
}
);
return false;
});
}
// Active time updates
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLogout(PlayerQuitEvent event) {
updateLastActive(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerKick(PlayerKickEvent event) {
updateLastActive(event.getPlayer());
}
/**
* Update the last active time for all regions the player is owner off.
* @param player The player to update the active times for
*/
private void updateLastActive(Player player) {
for(GeneralRegion region : plugin.getFileManager().getRegionsRef()) {
if(region.isOwner(player)) {
region.updateLastActiveTime();
}
}
}
}
@@ -0,0 +1,234 @@
package me.wiefferink.areashop.managers;
import com.google.inject.Injector;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.commands.AddCommand;
import me.wiefferink.areashop.commands.AddfriendCommand;
import me.wiefferink.areashop.commands.AddsignCommand;
import me.wiefferink.areashop.commands.BuyCommand;
import me.wiefferink.areashop.commands.CommandAreaShop;
import me.wiefferink.areashop.commands.DelCommand;
import me.wiefferink.areashop.commands.DelfriendCommand;
import me.wiefferink.areashop.commands.DelsignCommand;
import me.wiefferink.areashop.commands.FindCommand;
import me.wiefferink.areashop.commands.GroupaddCommand;
import me.wiefferink.areashop.commands.GroupdelCommand;
import me.wiefferink.areashop.commands.GroupinfoCommand;
import me.wiefferink.areashop.commands.GrouplistCommand;
import me.wiefferink.areashop.commands.HelpCommand;
import me.wiefferink.areashop.commands.ImportCommand;
import me.wiefferink.areashop.commands.InfoCommand;
import me.wiefferink.areashop.commands.LinksignsCommand;
import me.wiefferink.areashop.commands.MeCommand;
import me.wiefferink.areashop.commands.MessageCommand;
import me.wiefferink.areashop.commands.ReloadCommand;
import me.wiefferink.areashop.commands.RentCommand;
import me.wiefferink.areashop.commands.ResellCommand;
import me.wiefferink.areashop.commands.SchematiceventCommand;
import me.wiefferink.areashop.commands.SellCommand;
import me.wiefferink.areashop.commands.SetdurationCommand;
import me.wiefferink.areashop.commands.SetlandlordCommand;
import me.wiefferink.areashop.commands.SetownerCommand;
import me.wiefferink.areashop.commands.SetpriceCommand;
import me.wiefferink.areashop.commands.SetrestoreCommand;
import me.wiefferink.areashop.commands.SetteleportCommand;
import me.wiefferink.areashop.commands.StackCommand;
import me.wiefferink.areashop.commands.StopresellCommand;
import me.wiefferink.areashop.commands.TeleportCommand;
import me.wiefferink.areashop.commands.UnrentCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
@Singleton
public class CommandManager extends Manager implements CommandExecutor, TabCompleter {
private final List<CommandAreaShop> commands;
private final AreaShop plugin;
private final MessageBridge messageBridge;
/**
* Constructor.
*/
@Inject
CommandManager(@Nonnull AreaShop plugin, @Nonnull MessageBridge messageBridge, @Nonnull
Injector injector) {
this.plugin = plugin;
this.messageBridge = messageBridge;
commands = new ArrayList<>(34);
commands.add(new HelpCommand(this));
commands.add(injector.getInstance(RentCommand.class));
commands.add(injector.getInstance(UnrentCommand.class));
commands.add(injector.getInstance(BuyCommand.class));
commands.add(injector.getInstance(SellCommand.class));
commands.add(injector.getInstance(MeCommand.class));
commands.add(injector.getInstance(InfoCommand.class));
commands.add(injector.getInstance(TeleportCommand.class));
commands.add(injector.getInstance(SetteleportCommand.class));
commands.add(injector.getInstance(AddfriendCommand.class));
commands.add(injector.getInstance(DelfriendCommand.class));
commands.add(injector.getInstance(FindCommand.class));
commands.add(injector.getInstance(ResellCommand.class));
commands.add(injector.getInstance(StopresellCommand.class));
commands.add(injector.getInstance(SetrestoreCommand.class));
commands.add(injector.getInstance(SetpriceCommand.class));
commands.add(injector.getInstance(SetownerCommand.class));
commands.add(injector.getInstance(SetdurationCommand.class));
commands.add(injector.getInstance(ReloadCommand.class));
commands.add(injector.getInstance(GroupaddCommand.class));
commands.add(injector.getInstance(GroupdelCommand.class));
commands.add(injector.getInstance(GrouplistCommand.class));
commands.add(injector.getInstance(GroupinfoCommand.class));
commands.add(injector.getInstance(SchematiceventCommand.class));
commands.add(injector.getInstance(AddCommand.class));
commands.add(injector.getInstance(DelCommand.class));
commands.add(injector.getInstance(AddsignCommand.class));
commands.add(injector.getInstance(DelsignCommand.class));
commands.add(injector.getInstance(LinksignsCommand.class));
commands.add(injector.getInstance(StackCommand.class));
commands.add(injector.getInstance(SetlandlordCommand.class));
commands.add(injector.getInstance(MessageCommand.class));
commands.add(injector.getInstance(ImportCommand.class));
// Register commands in bukkit
plugin.getCommand("AreaShop").setExecutor(this);
plugin.getCommand("AreaShop").setTabCompleter(this);
}
/**
* Get the list with AreaShop commands.
* @return The list with AreaShop commands
*/
public List<CommandAreaShop> getCommands() {
return commands;
}
/**
* Shows the help page for the CommandSender.
* @param target The CommandSender to show the help to
*/
public void showHelp(CommandSender target) {
if(!target.hasPermission("areashop.help")) {
messageBridge.message(target, "help-noPermission");
return;
}
// Add all messages to a list
ArrayList<String> messages = new ArrayList<>();
messageBridge.message(target, "help-header");
messageBridge.message(target, "help-alias");
for(CommandAreaShop command : commands) {
String help = command.getHelp(target);
if(help != null && !help.isEmpty()) {
messages.add(help);
}
}
// Send the messages to the target
for(String message : messages) {
messageBridge.messageNoPrefix(target, message);
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String alias, String[] args) {
if(!plugin.isReady()) {
messageBridge.message(sender, "general-notReady");
return true;
}
// Redirect `/as info player <player>` to `/as me <player>`
if(args.length == 3 && "info".equals(args[0]) && "player".equals(args[1])) {
args[0] = "me";
args[1] = args[2];
}
// Execute command
boolean executed = false;
for(int i = 0; i < commands.size() && !executed; i++) {
if(commands.get(i).canExecute(command, args)) {
commands.get(i).execute(sender, args);
executed = true;
}
}
// Show help
if (!executed) {
if (args.length == 0) {
this.showHelp(sender);
} else {
// Indicate that the '/as updaterents' and '/as updatebuys' commands are removed
if("updaterents".equalsIgnoreCase(args[0]) || "updatebuys".equalsIgnoreCase(args[0])) {
messageBridge.message(sender, "reload-updateCommandChanged");
} else {
messageBridge.message(sender, "cmd-notValid");
}
}
}
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
List<String> result = new ArrayList<>();
if(!sender.hasPermission("areashop.tabcomplete")) {
return result;
}
int toCompleteNumber = args.length;
String toCompletePrefix = args[args.length - 1].toLowerCase();
//AreaShop.debug("toCompleteNumber=" + toCompleteNumber + ", toCompletePrefix=" + toCompletePrefix + ", length=" + toCompletePrefix.length());
if(toCompleteNumber == 1) {
for(CommandAreaShop c : commands) {
String begin = c.getCommandStart();
result.add(begin.substring(begin.indexOf(' ') + 1));
}
} else {
String[] start = new String[args.length];
start[0] = command.getName();
System.arraycopy(args, 0, start, 1, args.length - 1);
for(CommandAreaShop c : commands) {
if(c.canExecute(command, args)) {
result = c.getTabCompleteList(toCompleteNumber, start, sender);
}
}
}
// Filter and sort the results
if(!result.isEmpty()) {
SortedSet<String> set = new TreeSet<>();
for(String suggestion : result) {
if(suggestion.toLowerCase().startsWith(toCompletePrefix)) {
set.add(suggestion);
}
}
result.clear();
result.addAll(set);
}
//AreaShop.debug("Tabcomplete #" + toCompleteNumber + ", prefix="+ toCompletePrefix + ", result=" + result.toString());
return result;
}
}
@@ -0,0 +1,92 @@
package me.wiefferink.areashop.managers;
import com.google.inject.Injector;
import com.google.inject.ProvisionException;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.features.CommandsFeature;
import me.wiefferink.areashop.features.DebugFeature;
import me.wiefferink.areashop.features.FeatureFactory;
import me.wiefferink.areashop.features.FriendsFeature;
import me.wiefferink.areashop.features.RegionFeature;
import me.wiefferink.areashop.features.TeleportFeature;
import me.wiefferink.areashop.features.WorldGuardRegionFlagsFeature;
import me.wiefferink.areashop.features.signs.SignsFeature;
import me.wiefferink.areashop.regions.GeneralRegion;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
@Singleton
public class FeatureManager extends Manager {
private static final Collection<Class<? extends RegionFeature>> globalFeatureClasses = Arrays.asList(
CommandsFeature.class,
WorldGuardRegionFlagsFeature.class,
DebugFeature.class
);
// One instance of each feature, registered for event handling
private final Set<RegionFeature> globalFeatures = new HashSet<>();
private final Map<Class<? extends RegionFeature>, Function<GeneralRegion, ? extends RegionFeature>> regionFeatureConstructors = new HashMap<>();
/**
* Constructor.
*/
@Inject
FeatureManager(@Nonnull FeatureFactory featureFactory) {
// Setup constructors for region specific features
regionFeatureConstructors.put(SignsFeature.class, wrapInstantiator(SignsFeature.class, featureFactory::createSignsFeature));
regionFeatureConstructors.put(TeleportFeature.class, wrapInstantiator(TeleportFeature.class, featureFactory::createTeleportFeature));
regionFeatureConstructors.put(FriendsFeature.class, wrapInstantiator(FriendsFeature.class, featureFactory::createFriendsFeature));
}
public void initializeFeatures(@Nonnull Injector injector) {
// Instantiate and register global features (one per type, for event handling)
for(Class<? extends RegionFeature> clazz : globalFeatureClasses) {
try {
RegionFeature feature = injector.getInstance(clazz);
feature.listen();
globalFeatures.add(feature);
} catch(ProvisionException e) {
AreaShop.error("Failed to instantiate global feature:", clazz, e);
}
}
}
private <T extends RegionFeature> Function<GeneralRegion, T> wrapInstantiator(Class<T> clazz, Function<GeneralRegion, T> function
) {
return region -> {
try {
return function.apply(region);
} catch (Throwable e) {
AreaShop.error("Failed to instantiate feature", clazz, "for region", region, e, e.getCause());
return null;
}
};
}
@Override
public void shutdown() {
for(RegionFeature feature : globalFeatures) {
feature.shutdown();
}
}
/**
* Instanciate a feature for a certain region.
* @param region The region to create a feature for
* @param featureClazz The class of the feature to create
* @return The feature class
*/
public RegionFeature getRegionFeature(GeneralRegion region, Class<? extends RegionFeature> featureClazz) {
return regionFeatureConstructors.get(featureClazz).apply(region);
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,399 @@
package me.wiefferink.areashop.managers;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import me.wiefferink.areashop.events.ask.AddingRegionEvent;
import me.wiefferink.areashop.events.ask.DeletingRegionEvent;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RegionGroup;
import me.wiefferink.areashop.regions.RentRegion;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
public interface IFileManager {
/**
* Get the folder where schematics are stored.
*
* @return The folder where schematics are stored
*/
String getSchematicFolder();
/**
* Get a group.
*
* @param name The name of the group to get (will be normalized)
* @return The group if found, otherwise null
*/
RegionGroup getGroup(String name);
/**
* Get all groups.
*
* @return Collection with all groups (safe to modify)
*/
Collection<RegionGroup> getGroups();
/**
* Get the default region settings as provided by the user (default.yml).
*
* @return YamlConfiguration with the settings (might miss settings, which should be filled in with {@link #getFallbackRegionSettings()})
*/
YamlConfiguration getRegionSettings();
/**
* Get the default regions settings as provided by AreaShop (default.yml).
*
* @return YamlConfiguration with the default settings
*/
YamlConfiguration getFallbackRegionSettings();
/**
* Get the config file (config.yml).
*
* @return YamlConfiguration with the settings of users, with fallback to the settings provided by AreaShop
*/
YamlConfiguration getConfig();
/**
* Get a region.
*
* @param name The name of the region to get (will be normalized)
* @return The region if found, otherwise null
*/
@Nullable
GeneralRegion getRegion(String name);
/**
* Get a rental region.
*
* @param name The name of the rental region (will be normalized)
* @return RentRegion if it could be found, otherwise null
*/
@Nullable
RentRegion getRent(String name);
/**
* Get a buy region.
*
* @param name The name of the buy region (will be normalized)
* @return BuyRegion if it could be found, otherwise null
*/
@Nullable
BuyRegion getBuy(String name);
/**
* Get all rental regions.
*
* @return List of all rental regions
*/
Collection<RentRegion> getRents();
/**
* Get all rental regions (readonly)
*
* @return Unmodifiable collection of all rental regions
*/
Collection<RentRegion> getRentsRef();
/**
* Get all buy regions.
*
* @return List of all buy regions
*/
List<BuyRegion> getBuys();
/**
* Get all buy regions (readonly)
* @return Unmodifiable collection of all buy regions
*/
Collection<BuyRegion> getBuysRef();
/**
* Get all regions.
*
* @return List of all regions (it is safe to modify the list)
*/
List<GeneralRegion> getRegions();
/**
* Get all regions (readonly)
*
* @return Unmodifiable collection of all regions
*/
Collection<GeneralRegion> getRegionsRef();
/**
* Get a list of names of all buy regions.
*
* @return A String list with all the names
*/
List<String> getBuyNames();
/**
* Get a list of names of all rent regions.
*
* @return A String list with all the names
*/
List<String> getRentNames();
/**
* Get a list of names of all regions.
*
* @return A String list with all the names
*/
List<String> getRegionNames();
/**
* Get a list of names of all groups.
*
* @return A String list with all the names
*/
List<String> getGroupNames();
/**
* Add a region to the list and mark it as to-be-saved.
*
* @param region Then region to add
* @return true when successful, otherwise false (denied by an event listener)
*/
AddingRegionEvent addRegion(GeneralRegion region);
/**
* Add a region to the list without saving it to disk (useful for loading at startup).
*
* @param region The region to add
* @return true when successful, otherwise false (denied by an event listener)
*/
AddingRegionEvent addRegionNoSave(GeneralRegion region);
/**
* Mark all RegionGroups that they should regenerate regions.
*/
void markGroupsAutoDirty();
/**
* Add a RegionGroup.
*
* @param group The RegionGroup to add
*/
void addGroup(RegionGroup group);
/**
* Check if a player can add a certain region as rent or buy region.
*
* @param sender The player/console that wants to add a region
* @param region The WorldGuard region to add
* @param world The world the ProtectedRegion is located in
* @param type The type the region should have in AreaShop
* @return The result if a player would want to add this region
*/
AddResult checkRegionAdd(CommandSender sender,
ProtectedRegion region,
World world,
GeneralRegion.RegionType type);
/**
* Remove a region from the list.
*
* @param region The region to remove
* @param giveMoneyBack use true to give money back to the player if someone is currently holding this region, otherwise false
* @return true if the region has been removed, false otherwise
*/
DeletingRegionEvent deleteRegion(GeneralRegion region, boolean giveMoneyBack);
/**
* Remove a group.
*
* @param group Group to remove
*/
void removeGroup(RegionGroup group);
/**
* Update all signs that need periodic updating.
*/
void performPeriodicSignUpdate();
/**
* Send out rent expire warnings.
*/
void sendRentExpireWarnings();
/**
* Update regions in a task to minimize lag.
*
* @param regions Regions to update
* @param confirmationReceiver The CommandSender that should be notified at completion
*/
void updateRegions(Collection<GeneralRegion> regions, CommandSender confirmationReceiver);
/**
* Update a list of regions.
*
* @param regions The list of regions to update.
*/
void updateRegions(Collection<GeneralRegion> regions);
/**
* Update all regions, happens in a task to minimize lag.
*/
void updateAllRegions();
/**
* Update all regions.
*
* @param confirmationReceiver Optional CommandSender that should receive progress messages
*/
void updateAllRegions(CommandSender confirmationReceiver);
/**
* Save the group file to disk.
*/
void saveGroupsIsRequired();
/**
* Check if saving the groups file is required.
*
* @return true if changes are made and saving is required, otherwise false
*/
boolean isSaveGroupsRequired();
/**
* Save the groups file to disk synchronously.
*/
void saveGroupsNow();
/**
* Save all region related files spread over time (low load).
*/
void saveRequiredFiles();
/**
* Save all region related files directly (only for cases like onDisable()).
*/
void saveRequiredFilesAtOnce();
/**
* Indicates that a/multiple WorldGuard regions need to be saved.
*
* @param worldName The world where the regions that should be saved is in
*/
void saveIsRequiredForRegionWorld(String worldName);
/**
* Save all worldGuard regions that need saving.
*/
void saveWorldGuardRegions();
/**
* Get the folder the region files are located in.
*
* @return The folder where the region.yml files are in
*/
String getRegionFolder();
/**
* Check if a region is on the adding blacklist.
*
* @param region The region name to check
* @return true if the region may not be added, otherwise false
*/
boolean isBlacklisted(String region);
/**
* Unrent regions that have no time left, regions to check per tick is in the config.
*/
void checkRents();
/**
* Check all regions and unrent/sell them if the player is inactive for too long.
*/
void checkForInactiveRegions();
/**
* Load the file with the versions, used to check if the other files need conversion.
*/
@SuppressWarnings("unchecked")
void loadVersions();
/**
* Save the versions file to disk.
*/
void saveVersions();
/**
* Load all files from disk.
*
* @param thisTick Load files in the current tick or a tick later
* @return true if the files are loaded correctly, otherwise false
*/
boolean loadFiles(boolean thisTick);
/**
* Load the default.yml file
*
* @return true if it has been loaded successfully, otherwise false
*/
boolean loadDefaultFile();
/**
* Load the default.yml file
*
* @return true if it has been loaded successfully, otherwise false
*/
boolean loadConfigFile();
/**
* Load the groups.yml file from disk
*
* @return true if succeeded, otherwise false
*/
boolean loadGroupsFile();
/**
* Load all region files.
*/
void loadRegionFiles();
/**
* Get the settings of a group.
*
* @param groupName Name of the group to get the settings from
* @return The settings of the group
*/
ConfigurationSection getGroupSettings(String groupName);
/**
* Set a setting for a group.
*
* @param group The group to set it for
* @param path The path to set
* @param setting The value to set
*/
void setGroupSetting(RegionGroup group, String path, Object setting);
// Enum for region types
public enum AddResult {
BLACKLISTED("blacklisted"),
NOPERMISSION("nopermission"),
ALREADYADDED("alreadyadded"),
ALREADYADDEDOTHERWORLD("alreadyaddedotherworld"),
SUCCESS("success");
private final String value;
AddResult(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}
@@ -0,0 +1,12 @@
package me.wiefferink.areashop.managers;
public abstract class Manager {
/**
* Called at shutdown of the plugin.
*/
public void shutdown() {
// To override by extending classes
}
}
@@ -0,0 +1,79 @@
package me.wiefferink.areashop.managers;
import me.wiefferink.areashop.AreaShop;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicBoolean;
public class SignErrorLogger {
public static final String PREFIX_WARN = "[Warning] [Sign Feature] ";
public static final DateFormat FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
private final List<String> cache = new LinkedList<>();
private final AtomicBoolean needsSaving = new AtomicBoolean(false);
private final File file;
public SignErrorLogger(File file) {
this.file = Objects.requireNonNull(file);
}
public void clearCache() {
synchronized (this.cache) {
this.cache.clear();
}
}
public SignErrorLogger submitWarning(String message) {
final String actual = FORMAT.format(Date.from(Instant.now())) + PREFIX_WARN + message;
synchronized (this.cache) {
this.cache.add(actual);
}
return this;
}
public void queueSave() {
this.needsSaving.set(true);
}
public void saveIfDirty() {
if (this.needsSaving.get()) {
save();
}
}
public synchronized void save() {
AreaShop.debugTask("[Sign Feature] Dumping sign errors to disk...");
final String[] copy;
synchronized (this.cache) {
copy = cache.toArray(new String[0]);
}
final StringJoiner joiner = new StringJoiner(System.lineSeparator());
for (String s : copy) {
joiner.add(s);
}
try (OutputStream os = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
writer.write(joiner.toString());
} catch (IOException ex) {
ex.printStackTrace();
AreaShop.error("Failed to update the sign error log!");
} finally {
AreaShop.debugTask("[Sign Feature] Error dump complete.");
}
}
}
@@ -1,225 +1,245 @@
package nl.evolutioncoding.areashop.managers;
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 nl.evolutioncoding.areashop.AreaShop;
import nl.evolutioncoding.areashop.Utils;
import nl.evolutioncoding.areashop.regions.GeneralRegion;
import org.bukkit.Location;
import org.bukkit.Material;
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.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.material.Sign;
import org.bukkit.util.BlockIterator;
public class SignLinkerManager implements Listener {
private AreaShop plugin = null;
private Map<UUID, SignLinker> signLinkers;
private boolean eventsRegistered;
public SignLinkerManager(AreaShop plugin) {
this.plugin = plugin;
signLinkers = new HashMap<UUID, SignLinker>();
eventsRegistered = false;
}
/**
* Let a player enter sign linking mode
* @param player The player that has to enter sign linking mode
* @param profile The profile to use for the signs (null for default)
*/
public void enterSignLinkMode(Player player, String profile) {
signLinkers.put(player.getUniqueId(), new SignLinker(player, profile));
plugin.message(player, "linksigns-first");
plugin.message(player, "linksigns-next");
if(!eventsRegistered) {
eventsRegistered = true;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
}
/**
* Let a player exit sign linking mode
* @param player The player that has to exit sign linking mode
*/
public void exitSignLinkMode(Player player) {
signLinkers.remove(player.getUniqueId());
if(eventsRegistered && signLinkers.isEmpty()) {
eventsRegistered = false;
HandlerList.unregisterAll(this);
}
plugin.message(player, "linksigns-stopped");
}
/**
* Check if the player is in sign linking mode
* @param player The player to check
* @return true if the player is in sign linking mode, otherwise false
*/
public boolean isInSignLinkMode(Player player) {
return signLinkers.containsKey(player.getUniqueId());
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerInteract(PlayerInteractEvent event) {
AreaShop.debug("PlayerInteractEvent of " + event.getPlayer().getName() + ", " + signLinkers.size() + " signlinkers");
if(isInSignLinkMode(event.getPlayer())) {
event.setCancelled(true);
Player player = event.getPlayer();
SignLinker linker = signLinkers.get(event.getPlayer().getUniqueId());
if(event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
// Get the region
BlockIterator blockIterator = new BlockIterator(player, 100);
while(blockIterator.hasNext()) {
Block next = blockIterator.next();
List<GeneralRegion> regions = Utils.getASRegionsByLocation(next.getLocation());
if(regions.size() == 1) {
linker.setRegion(regions.get(0));
return;
} else if(regions.size() > 1) {
Set<String> names = new HashSet<String>();
for(GeneralRegion region : regions) {
names.add(region.getName());
}
plugin.message(player, "linksigns-multipleRegions", Utils.createCommaSeparatedList(names));
plugin.message(player, "linksigns-multipleRegionsAdvice");
return;
}
}
// No regions found within the maximum range
plugin.message(player, "linksigns-noRegions");
return;
} else if(event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) {
Block block = null;
BlockIterator blockIterator = new BlockIterator(player, 100);
while(blockIterator.hasNext() && block == null) {
Block next = blockIterator.next();
if(next.getType() != Material.AIR) {
block = next;
}
}
if(block == null || !(block.getType() == Material.WALL_SIGN || block.getType() == Material.SIGN_POST)) {
plugin.message(player, "linksigns-noSign");
return;
}
GeneralRegion signRegion = plugin.getFileManager().getRegionBySignLocation(block.getLocation());
if(signRegion != null) {
plugin.message(player, "linksigns-alreadyRegistered", signRegion);
return;
}
Sign sign = (Sign)block.getState().getData();
linker.setSign(block.getLocation(), block.getType(), sign.getFacing());
return;
}
}
}
/**
* Handle disconnection players
*/
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLeave(PlayerQuitEvent event) {
AreaShop.debug("PlayerQuitEvent of " + event.getPlayer().getName() + ", " + signLinkers.size() + " signlinkers");
signLinkers.remove(event.getPlayer().getUniqueId());
}
/**
* Class to keep track of the signlinking data
*/
private class SignLinker {
private boolean hasSign = false;
private boolean hasRegion = false;
public Player linker = null;
public String profile = null;
public GeneralRegion region = null;
public Location location = null;
public Material type = null;
public BlockFace facing = null;
public SignLinker(Player linker, String profile) {
this.linker = linker;
this.profile = profile;
}
public void setRegion(GeneralRegion region) {
this.region = region;
hasRegion = true;
if(!isComplete()) {
plugin.message(linker, "linksigns-regionFound", region);
}
finalize();
}
public void setSign(Location location, Material type, BlockFace facing) {
this.location = location;
this.type = type;
this.facing = facing;
hasSign = true;
if(!isComplete()) {
plugin.message(linker, "linksigns-signFound", location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
finalize();
}
public void finalize() {
if(isComplete()) {
region.addSign(location, type, facing, profile);
if(profile == null) {
plugin.message(linker, "addsign-success", region);
} else {
plugin.message(linker, "addsign-successProfile", region, profile);
}
region.updateSigns();
reset();
plugin.message(linker, "linksigns-next");
}
}
public void reset() {
hasSign = false;
hasRegion = false;
}
public boolean isComplete() {
return hasSign && hasRegion;
}
}
}
package me.wiefferink.areashop.managers;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.features.signs.RegionSign;
import me.wiefferink.areashop.features.signs.SignManager;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.tools.Materials;
import me.wiefferink.areashop.tools.SignUtils;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
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.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.util.BlockIterator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@Singleton
public class SignLinkerManager extends Manager implements Listener {
private final Map<UUID, SignLinker> signLinkers;
private boolean eventsRegistered;
@Inject
private AreaShop plugin;
@Inject
private SignManager signManager;
@Inject
private MessageBridge messageBridge;
SignLinkerManager() {
signLinkers = new HashMap<>();
eventsRegistered = false;
}
@Override
public void shutdown() {
for(UUID uuid : signLinkers.keySet()) {
exitSignLinkMode(Bukkit.getPlayer(uuid));
}
}
/**
* Let a player enter sign linking mode.
* @param player The player that has to enter sign linking mode
* @param profile The profile to use for the signs (null for default)
*/
public void enterSignLinkMode(Player player, String profile) {
signLinkers.put(player.getUniqueId(), new SignLinker(player, profile));
messageBridge.message(player, "linksigns-first");
messageBridge.message(player, "linksigns-next");
if(!eventsRegistered) {
eventsRegistered = true;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
}
/**
* Let a player exit sign linking mode.
* @param player The player that has to exit sign linking mode
*/
public void exitSignLinkMode(Player player) {
signLinkers.remove(player.getUniqueId());
if(eventsRegistered && signLinkers.isEmpty()) {
eventsRegistered = false;
HandlerList.unregisterAll(this);
}
messageBridge.message(player, "linksigns-stopped");
}
/**
* Check if the player is in sign linking mode.
* @param player The player to check
* @return true if the player is in sign linking mode, otherwise false
*/
public boolean isInSignLinkMode(Player player) {
return signLinkers.containsKey(player.getUniqueId());
}
/**
* On player interactions.
* @param event The PlayerInteractEvent
*/
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerInteract(PlayerInteractEvent event) {
if(isInSignLinkMode(event.getPlayer())) {
event.setCancelled(true);
Player player = event.getPlayer();
SignLinker linker = signLinkers.get(event.getPlayer().getUniqueId());
if(event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
// Get the region
BlockIterator blockIterator = new BlockIterator(player, 100);
while(blockIterator.hasNext()) {
Block next = blockIterator.next();
List<GeneralRegion> regions = Utils.getRegions(next.getLocation());
if(regions.size() == 1) {
linker.setRegion(regions.get(0));
return;
} else if(regions.size() > 1) {
Set<String> names = new HashSet<>();
for(GeneralRegion region : regions) {
names.add(region.getName());
}
messageBridge.message(player, "linksigns-multipleRegions", Utils.createCommaSeparatedList(names));
messageBridge.message(player, "linksigns-multipleRegionsAdvice");
return;
}
}
// No regions found within the maximum range
messageBridge.message(player, "linksigns-noRegions");
} else if(event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) {
Block block = null;
BlockIterator blockIterator = new BlockIterator(player, 100);
while(blockIterator.hasNext() && block == null) {
Block next = blockIterator.next();
if(next.getType() != Material.AIR) {
block = next;
}
}
if(block == null || !Materials.isSign(block.getType())) {
messageBridge.message(player, "linksigns-noSign");
return;
}
Optional<RegionSign> optional = signManager.signFromLocation(block.getLocation());
if(optional.isPresent()) {
RegionSign regionSign = optional.get();
messageBridge.message(player, "linksigns-alreadyRegistered", regionSign.getRegion());
return;
}
linker.setSign(block.getLocation(), block.getType(), SignUtils.getSignFacing(block));
}
}
}
/**
* Handle disconnection players.
* @param event The PlayerQuitEvent
*/
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLeave(PlayerQuitEvent event) {
exitSignLinkMode(event.getPlayer());
}
/**
* Class to keep track of the signlinking data.
*/
private class SignLinker {
private boolean hasSign = false;
private boolean hasRegion = false;
public final Player linker;
public final String profile;
public GeneralRegion region = null;
public Location location = null;
public Material type = null;
public BlockFace facing = null;
public SignLinker(Player linker, String profile) {
this.linker = linker;
this.profile = profile;
}
public void setRegion(GeneralRegion region) {
this.region = region;
hasRegion = true;
if(!isComplete()) {
messageBridge.message(linker, "linksigns-regionFound", region);
}
finish();
}
public void setSign(Location location, Material type, BlockFace facing) {
this.location = location;
this.type = type;
this.facing = facing;
hasSign = true;
if(!isComplete()) {
messageBridge.message(linker, "linksigns-signFound", location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
finish();
}
public void finish() {
if(isComplete()) {
region.getSignsFeature().addSign(location, type, facing, profile);
if(profile == null) {
messageBridge.message(linker, "addsign-success", region);
} else {
messageBridge.message(linker, "addsign-successProfile", region, profile);
}
region.update();
reset();
messageBridge.message(linker, "linksigns-next");
}
}
public void reset() {
hasSign = false;
hasRegion = false;
}
public boolean isComplete() {
return hasSign && hasRegion;
}
}
}
@@ -0,0 +1,76 @@
package me.wiefferink.areashop.modules;
import com.google.inject.AbstractModule;
import com.google.inject.Singleton;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.features.FeatureFactory;
import me.wiefferink.areashop.features.signs.SignManager;
import me.wiefferink.areashop.features.signs.SignsModule;
import me.wiefferink.areashop.interfaces.WorldEditInterface;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.managers.CommandManager;
import me.wiefferink.areashop.managers.FeatureManager;
import me.wiefferink.areashop.managers.FileManager;
import me.wiefferink.areashop.managers.IFileManager;
import me.wiefferink.areashop.managers.SignErrorLogger;
import me.wiefferink.areashop.managers.SignLinkerManager;
import me.wiefferink.areashop.platform.adapter.PlatformAdapter;
import me.wiefferink.areashop.regions.ImportJobFactory;
import me.wiefferink.areashop.regions.RegionModule;
import me.wiefferink.areashop.tools.Utils;
import org.bukkit.plugin.Plugin;
import javax.annotation.Nonnull;
import java.util.Arrays;
public class AreaShopModule extends AbstractModule {
private final AreaShop instance;
private final WorldGuardInterface worldGuardInterface;
private final WorldEditInterface worldEditInterface;
private final PlatformAdapter platformAdapter;
private final MessageBridge messageBridge;
private final SignErrorLogger signErrorLogger;
private final AbstractModule[] extras;
public AreaShopModule(@Nonnull AreaShop instance,
@Nonnull MessageBridge messageBridge,
@Nonnull PlatformAdapter platformAdapter,
@Nonnull WorldEditInterface worldEditInterface,
@Nonnull WorldGuardInterface worldGuardInterface,
@Nonnull SignErrorLogger signErrorLogger,
@Nonnull AbstractModule... extras
) {
this.instance = instance;
this.messageBridge = messageBridge;
this.platformAdapter = platformAdapter;
this.signErrorLogger = signErrorLogger;
this.worldEditInterface = worldEditInterface;
this.worldGuardInterface = worldGuardInterface;
this.extras = extras;
}
@Override
protected void configure() {
Arrays.stream(this.extras).forEach(this::install);
bind(Plugin.class).toInstance(this.instance);
bind(AreaShop.class).toInstance(this.instance);
bind(MessageBridge.class).toInstance(this.messageBridge);
bind(WorldGuardInterface.class).toInstance(this.worldGuardInterface);
bind(SignManager.class).asEagerSingleton();
bind(WorldEditInterface.class).toInstance(this.worldEditInterface);
bind(SignErrorLogger.class).toInstance(this.signErrorLogger);
// Setup managers
bind(IFileManager.class).to(FileManager.class).asEagerSingleton();
bind(FeatureManager.class).in(Singleton.class);
bind(CommandManager.class).in(Singleton.class);
bind(SignLinkerManager.class).in(Singleton.class);
install(new SignsModule());
install(new RegionModule());
install(new FactoryModuleBuilder().build(FeatureFactory.class));
install(new FactoryModuleBuilder().build(ImportJobFactory.class));
requestStaticInjection(Utils.class);
}
}
@@ -0,0 +1,34 @@
package me.wiefferink.areashop.modules;
import com.google.inject.AbstractModule;
import org.bukkit.Server;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.ServicesManager;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scoreboard.ScoreboardManager;
import javax.annotation.Nonnull;
public class BukkitModule extends AbstractModule {
private final Server server;
public BukkitModule(@Nonnull Server server) {
this.server = server;
}
@Override
protected void configure() {
bind(Server.class).toInstance(this.server);
bind(PluginManager.class).toInstance(this.server.getPluginManager());
bind(BukkitScheduler.class).toInstance(this.server.getScheduler());
bind(PluginManager.class).toInstance(this.server.getPluginManager());
bind(ServicesManager.class).toInstance(this.server.getServicesManager());
bind(ItemFactory.class).toInstance(this.server.getItemFactory());
bind(ConsoleCommandSender.class).toInstance(this.server.getConsoleSender());
bind(ScoreboardManager.class).toInstance(this.server.getScoreboardManager());
}
}
@@ -0,0 +1,37 @@
package me.wiefferink.areashop.modules;
import com.google.inject.AbstractModule;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class DependencyModule extends AbstractModule {
private final WorldEditPlugin worldEditPlugin;
private final WorldGuardPlugin worldGuardPlugin;
private final Economy economy;
private final Permission permission;
public DependencyModule(@Nonnull WorldEditPlugin worldEditPlugin,
@Nonnull WorldGuardPlugin worldGuardPlugin,
@Nullable Economy economy,
@Nullable Permission permission
) {
this.worldEditPlugin = worldEditPlugin;
this.worldGuardPlugin = worldGuardPlugin;
this.economy = economy;
this.permission = permission;
}
@Override
protected void configure() {
bind(WorldEditPlugin.class).toInstance(this.worldEditPlugin);
bind(WorldGuardPlugin.class).toInstance(this.worldGuardPlugin);
bind(Economy.class).toInstance(this.economy);
bind(Permission.class).toInstance(this.permission);
}
}
@@ -0,0 +1,29 @@
package me.wiefferink.areashop.modules;
import com.google.inject.AbstractModule;
import me.wiefferink.areashop.adapters.platform.MinecraftPlatform;
import me.wiefferink.areashop.adapters.platform.OfflinePlayerHelper;
import me.wiefferink.areashop.platform.adapter.BlockBehaviourHelper;
import me.wiefferink.areashop.platform.adapter.PlatformAdapter;
import me.wiefferink.areashop.tools.BukkitSchedulerExecutor;
import org.jetbrains.annotations.NotNull;
public class PlatformModule extends AbstractModule {
private final MinecraftPlatform minecraftPlatform;
private final PlatformAdapter platformAdapter;
public PlatformModule(@NotNull MinecraftPlatform platform,
@NotNull PlatformAdapter platformAdapter) {
this.minecraftPlatform = platform;
this.platformAdapter = platformAdapter;
}
@Override
protected void configure() {
bind(MinecraftPlatform.class).toInstance(this.minecraftPlatform);
bind(OfflinePlayerHelper.class).toInstance(this.minecraftPlatform.offlinePlayerHelper());
bind(PlatformAdapter.class).toInstance(this.platformAdapter);
bind(BlockBehaviourHelper.class).toInstance(this.platformAdapter.blockBehaviourHelper());
bind(BukkitSchedulerExecutor.class).asEagerSingleton();
}
}
@@ -0,0 +1,622 @@
package me.wiefferink.areashop.regions;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.events.ask.BuyingRegionEvent;
import me.wiefferink.areashop.events.ask.ResellingRegionEvent;
import me.wiefferink.areashop.events.ask.SellingRegionEvent;
import me.wiefferink.areashop.events.notify.BoughtRegionEvent;
import me.wiefferink.areashop.events.notify.ResoldRegionEvent;
import me.wiefferink.areashop.events.notify.SoldRegionEvent;
import me.wiefferink.areashop.interfaces.WorldEditInterface;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.managers.FeatureManager;
import me.wiefferink.areashop.tools.Utils;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.economy.EconomyResponse;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Calendar;
import java.util.UUID;
public class BuyRegion extends GeneralRegion {
private final Economy economy;
@AssistedInject
BuyRegion(
@Nonnull AreaShop plugin,
@Nonnull FeatureManager featureManager,
@Nonnull WorldEditInterface worldEditInterface,
@Nonnull WorldGuardInterface worldGuardInterface,
@Nullable Economy economy,
@Assisted @Nonnull YamlConfiguration config
) {
super(plugin, featureManager, worldEditInterface, worldGuardInterface, config);
this.economy = economy;
}
@AssistedInject
BuyRegion(
@Nonnull AreaShop plugin,
@Nonnull FeatureManager featureManager,
@Nonnull WorldEditInterface worldEditInterface,
@Nonnull WorldGuardInterface worldGuardInterface,
@Nullable Economy economy,
@Assisted @Nonnull String name,
@Assisted @Nonnull World world
) {
super(plugin, featureManager, worldEditInterface, worldGuardInterface, name, world);
this.economy = economy;
}
@Override
public boolean isOwner(UUID player) {
return isBuyer(player);
}
@Override
public UUID getOwner() {
return getBuyer();
}
@Override
public void setOwner(UUID player) {
setBuyer(player);
}
@Override
public RegionType getType() {
return RegionType.BUY;
}
@Override
public RegionState getState() {
if(isSold() && isInResellingMode()) {
return RegionState.RESELL;
} else if(isSold() && !isInResellingMode()) {
return RegionState.SOLD;
} else {
return RegionState.FORSALE;
}
}
@Override
public boolean isAvailable() {
return !isSold();
}
/**
* Get the UUID of the owner of this region.
* @return The UUID of the owner of this region
*/
public UUID getBuyer() {
String buyer = config.getString("buy.buyer");
if(buyer != null) {
try {
return UUID.fromString(buyer);
} catch(IllegalArgumentException e) {
// Incorrect UUID
}
}
return null;
}
/**
* Check if a player is the buyer of this region.
* @param player Player to check
* @return true if this player owns this region, otherwise false
*/
public boolean isBuyer(OfflinePlayer player) {
return player != null && isBuyer(player.getUniqueId());
}
public boolean isBuyer(UUID player) {
UUID buyer = getBuyer();
return !(buyer == null || player == null) && buyer.equals(player);
}
/**
* Set the buyer of this region.
* @param buyer The UUID of the player that should be set as buyer
*/
public void setBuyer(UUID buyer) {
if(buyer == null) {
setSetting("buy.buyer", null);
setSetting("buy.buyerName", null);
} else {
setSetting("buy.buyer", buyer.toString());
setSetting("buy.buyerName", Utils.toName(buyer));
}
}
/**
* Get the name of the player that owns this region.
* @return The name of the player that owns this region, if unavailable by UUID it will return the old cached name, if that is unavailable it will return &lt;UNKNOWN&gt;
*/
public String getPlayerName() {
String result = Utils.toName(getBuyer());
if(result == null || result.isEmpty()) {
result = getStringSetting("buy.buyerName");
if(result == null || result.isEmpty()) {
result = "<UNKNOWN>";
}
}
return result;
}
/**
* Check if the region is sold.
* @return true if the region is sold, otherwise false
*/
public boolean isSold() {
return getBuyer() != null;
}
/**
* Check if the region is being resold.
* @return true if the region is available for reselling, otherwise false
*/
public boolean isInResellingMode() {
return config.getBoolean("buy.resellMode");
}
/**
* Get the price of the region.
* @return The price of the region
*/
public double getPrice() {
return Math.max(0, Utils.evaluateToDouble(getStringSetting("buy.price"), this));
}
/**
* Get the resell price of this region.
* @return The resell price if isInResellingMode(), otherwise 0.0
*/
public double getResellPrice() {
return Math.max(0, config.getDouble("buy.resellPrice"));
}
/**
* Get the formatted string of the price (includes prefix and suffix).
* @return The formatted string of the price
*/
public String getFormattedPrice() {
return Utils.formatCurrency(getPrice());
}
/**
* Get the formatted string of the resellprice (includes prefix and suffix).
* @return The formatted string of the resellprice
*/
public String getFormattedResellPrice() {
return Utils.formatCurrency(getResellPrice());
}
/**
* Change the price of the region.
* @param price The price to set this region to
*/
public void setPrice(Double price) {
setSetting("buy.price", price);
}
/**
* Set the region into resell mode with the given price.
* @param price The price this region should be put up for sale
*/
public void enableReselling(double price) {
setSetting("buy.resellMode", true);
setSetting("buy.resellPrice", price);
}
/**
* Stop this region from being in resell mode.
*/
public void disableReselling() {
setSetting("buy.resellMode", null);
setSetting("buy.resellPrice", null);
}
/**
* Get the moneyBack percentage.
* @return The % of money the player will get back when selling
*/
public double getMoneyBackPercentage() {
return Utils.evaluateToDouble(getStringSetting("buy.moneyBack"), this);
}
/**
* Get the amount of money that should be paid to the player when selling the region.
* @return The amount of money the player should get back
*/
public double getMoneyBackAmount() {
return getPrice() * (getMoneyBackPercentage() / 100.0);
}
/**
* Get the formatted string of the amount of the moneyBack amount.
* @return String with currency symbols and proper fractional part
*/
public String getFormattedMoneyBackAmount() {
return Utils.formatCurrency(getMoneyBackAmount());
}
@Override
public Object provideReplacement(String variable) {
return switch (variable) {
case AreaShop.tagPrice -> getFormattedPrice();
case AreaShop.tagRawPrice -> getPrice();
case AreaShop.tagPlayerName -> getPlayerName();
case AreaShop.tagPlayerUUID -> getBuyer();
case AreaShop.tagResellPrice -> getFormattedResellPrice();
case AreaShop.tagRawResellPrice -> getResellPrice();
case AreaShop.tagMoneyBackAmount -> getFormattedMoneyBackAmount();
case AreaShop.tagRawMoneyBackAmount -> getMoneyBackAmount();
case AreaShop.tagMoneyBackPercentage ->
getMoneyBackPercentage() % 1.0 == 0.0 ? (int) getMoneyBackPercentage() : getMoneyBackPercentage();
case AreaShop.tagMaxInactiveTime -> this.getFormattedInactiveTimeUntilSell();
default -> super.provideReplacement(variable);
};
}
/**
* Minutes until automatic unrent when player is offline.
* @return The number of milliseconds until the region is unrented while player is offline
*/
public long getInactiveTimeUntilSell() {
return Utils.getDurationFromMinutesOrStringInput(getStringSetting("buy.inactiveTimeUntilSell"));
}
/**
* Get a human readable string indicating how long the player can be offline until automatic unrent.
* @return String indicating the inactive time until unrent
*/
public String getFormattedInactiveTimeUntilSell() {
return Utils.millisToHumanFormat(getInactiveTimeUntilSell());
}
/**
* Buy a region.
* @param offlinePlayer The player that wants to buy the region
* @return true if it succeeded and false if not
*/
@SuppressWarnings("deprecation")
public boolean buy(OfflinePlayer offlinePlayer) {
// Check if the player has permission
if(!plugin.hasPermission(offlinePlayer, "areashop.buy")) {
message(offlinePlayer, "buy-noPermission");
return false;
}
if(economy == null) {
message(offlinePlayer, "general-noEconomy");
return false;
}
if(isInResellingMode()) {
if(!plugin.hasPermission(offlinePlayer, "areashop.buyresell")) {
message(offlinePlayer, "buy-noPermissionResell");
return false;
}
} else {
if(!plugin.hasPermission(offlinePlayer, "areashop.buynormal")) {
message(offlinePlayer, "buy-noPermissionNoResell");
return false;
}
}
if(getWorld() == null) {
message(offlinePlayer, "general-noWorld");
return false;
}
if(getRegion() == null) {
message(offlinePlayer, "general-noRegion");
return false;
}
if (isSold() && !(isInResellingMode() && !isBuyer(offlinePlayer))) {
if(isBuyer(offlinePlayer)) {
message(offlinePlayer, "buy-yours");
} else {
message(offlinePlayer, "buy-someoneElse");
}
return false;
}
boolean isResell = isInResellingMode();
// Only relevant if the player is online
Player player = offlinePlayer.getPlayer();
if(player != null) {
// Check if the players needs to be in the region for buying
if(restrictedToRegion() && (!player.getWorld().getName().equals(getWorldName())
|| !getRegion().contains(player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ()))) {
message(offlinePlayer, "buy-restrictedToRegion");
return false;
}
// Check if the players needs to be in the world for buying
if(restrictedToWorld() && !player.getWorld().getName().equals(getWorldName())) {
message(offlinePlayer, "buy-restrictedToWorld", player.getWorld().getName());
return false;
}
}
// Check region limits
LimitResult limitResult = this.limitsAllow(RegionType.BUY, offlinePlayer);
AreaShop.debug("LimitResult: " + limitResult.toString());
if(!limitResult.actionAllowed()) {
if(limitResult.getLimitingFactor() == LimitType.TOTAL) {
message(offlinePlayer, "total-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
return false;
}
if(limitResult.getLimitingFactor() == LimitType.BUYS) {
message(offlinePlayer, "buy-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
return false;
}
// Should not be reached, but is safe like this
return false;
}
// Check if the player has enough money
if (isResell && !economy.has(offlinePlayer, getWorldName(), getResellPrice())) {
message(offlinePlayer, "buy-lowMoneyResell", Utils.formatCurrency(economy.getBalance(offlinePlayer, getWorldName())));
return false;
}
if (!isResell && !economy.has(offlinePlayer, getWorldName(), getPrice())) {
message(offlinePlayer, "buy-lowMoney", Utils.formatCurrency(economy.getBalance(offlinePlayer, getWorldName())));
return false;
}
UUID oldOwner = getBuyer();
if(isResell && oldOwner != null) {
// Broadcast and check event
ResellingRegionEvent event = new ResellingRegionEvent(this, offlinePlayer);
Bukkit.getPluginManager().callEvent(event);
if(event.isCancelled()) {
message(offlinePlayer, "general-cancelled", event.getReason());
return false;
}
getFriendsFeature().clearFriends();
double resellPrice = getResellPrice();
// Transfer the money to the previous owner
EconomyResponse r = economy.withdrawPlayer(offlinePlayer, getWorldName(), getResellPrice());
if(!r.transactionSuccess()) {
message(offlinePlayer, "buy-payError");
AreaShop.debug("Something went wrong with getting money from " + offlinePlayer.getName() + " while buying " + getName() + ": " + r.errorMessage);
return false;
}
r = null;
OfflinePlayer oldOwnerPlayer = Bukkit.getOfflinePlayer(oldOwner);
String oldOwnerName = getPlayerName();
if(oldOwnerPlayer != null && oldOwnerPlayer.getName() != null) {
r = economy.depositPlayer(oldOwnerPlayer, getWorldName(), getResellPrice());
oldOwnerName = oldOwnerPlayer.getName();
} else if(oldOwnerName != null) {
r = economy.depositPlayer(oldOwnerName, getWorldName(), getResellPrice());
}
if(r == null || !r.transactionSuccess()) {
AreaShop.warn("Something went wrong with paying '" + oldOwnerName + "' " + getFormattedPrice() + " for his resell of region " + getName() + " to " + offlinePlayer.getName());
}
// Set the owner
setBuyer(offlinePlayer.getUniqueId());
updateLastActiveTime();
// Update everything
handleSchematicEvent(RegionEvent.RESELL);
// Notify about updates
this.notifyAndUpdate(new ResoldRegionEvent(this, oldOwner));
// Resell is done, disable that now
disableReselling();
// Send message to the player
message(offlinePlayer, "buy-successResale", oldOwnerName);
Player seller = Bukkit.getPlayer(oldOwner);
if(seller != null) {
message(seller, "buy-successSeller", resellPrice);
}
} else {
// Broadcast and check event
BuyingRegionEvent event = new BuyingRegionEvent(this, offlinePlayer);
Bukkit.getPluginManager().callEvent(event);
if(event.isCancelled()) {
message(offlinePlayer, "general-cancelled", event.getReason());
return false;
}
// Substract the money from the players balance
EconomyResponse r = economy.withdrawPlayer(offlinePlayer, getWorldName(), getPrice());
if(!r.transactionSuccess()) {
message(offlinePlayer, "buy-payError");
return false;
}
// Optionally give money to the landlord
OfflinePlayer landlordPlayer = null;
if(getLandlord() != null) {
landlordPlayer = Bukkit.getOfflinePlayer(getLandlord());
}
String landlordName = getLandlordName();
if(landlordName != null) {
if(landlordPlayer != null && landlordPlayer.getName() != null) {
r = economy.depositPlayer(landlordPlayer, getWorldName(), getPrice());
} else {
r = economy.depositPlayer(landlordName, getWorldName(), getPrice());
}
if(r != null && !r.transactionSuccess()) {
AreaShop.warn("Something went wrong with paying '" + landlordName + "' " + getFormattedPrice() + " for his sell of region " + getName() + " to " + offlinePlayer.getName());
}
}
// Set the owner
setBuyer(offlinePlayer.getUniqueId());
updateLastActiveTime();
// Send message to the player
message(offlinePlayer, "buy-succes");
// Update everything
handleSchematicEvent(RegionEvent.BOUGHT);
// Notify about updates
this.notifyAndUpdate(new BoughtRegionEvent(this));
}
return true;
}
/**
* Sell a buyed region, get part of the money back.
* @param giveMoneyBack true if the player should be given money back, otherwise false
* @param executor CommandSender to receive a message when the sell fails, or null
* @return true if the region has been sold, otherwise false
*/
@SuppressWarnings("deprecation")
public boolean sell(boolean giveMoneyBack, CommandSender executor) {
boolean own = executor instanceof Player player && this.isBuyer(player);
if(executor != null) {
if(!executor.hasPermission("areashop.sell") && !own) {
message(executor, "sell-noPermissionOther");
return false;
}
if(!executor.hasPermission("areashop.sell") && !executor.hasPermission("areashop.sellown") && own) {
message(executor, "sell-noPermission");
return false;
}
if(!executor.hasPermission("areashop.sell")
&& executor.hasPermission("areashop.sellown")
&& own
&& getBooleanSetting("buy.sellDisabled")) {
message(executor, "sell-disabled");
return false;
}
}
if(economy == null) {
return false;
}
// Broadcast and check event
SellingRegionEvent event = new SellingRegionEvent(this);
Bukkit.getPluginManager().callEvent(event);
if(event.isCancelled()) {
message(executor, "general-cancelled", event.getReason());
return false;
}
disableReselling();
// Give part of the buying price back
double moneyBack = getMoneyBackAmount();
if(moneyBack > 0 && giveMoneyBack) {
boolean noPayBack = false;
OfflinePlayer landlordPlayer = null;
if(getLandlord() != null) {
landlordPlayer = Bukkit.getOfflinePlayer(getLandlord());
}
String landlordName = getLandlordName();
EconomyResponse r;
if(landlordName != null) {
if(landlordPlayer != null && landlordPlayer.getName() != null) {
r = economy.withdrawPlayer(landlordPlayer, getWorldName(), moneyBack);
} else {
r = economy.withdrawPlayer(landlordName, getWorldName(), moneyBack);
}
if(r == null || !r.transactionSuccess()) {
noPayBack = true;
}
}
// Give back the money
OfflinePlayer player = Bukkit.getOfflinePlayer(getBuyer());
if(player.hasPlayedBefore() && !noPayBack) {
EconomyResponse response = null;
boolean error = false;
try {
if(player.getName() != null) {
response = economy.depositPlayer(player, getWorldName(), moneyBack);
} else if(getPlayerName() != null) {
response = economy.depositPlayer(getPlayerName(), getWorldName(), moneyBack);
}
} catch(Exception e) {
error = true;
}
if(error || response == null || !response.transactionSuccess()) {
AreaShop.warn("Something went wrong with paying back money to " + getPlayerName() + " while selling region " + getName());
}
}
}
// Handle schematic save/restore (while %uuid% is still available)
handleSchematicEvent(RegionEvent.SOLD);
// Send message: before actual removal of the buyer so that it is still available for variables
message(executor, "sell-sold");
// Remove friends and the owner
getFriendsFeature().clearFriends();
UUID oldBuyer = getBuyer();
setBuyer(null);
removeLastActiveTime();
// Notify about updates
this.notifyAndUpdate(new SoldRegionEvent(this, oldBuyer, Math.max(moneyBack, 0)));
return true;
}
@Override
public boolean checkInactive() {
if(isDeleted() || !isSold()) {
return false;
}
long inactiveSetting = getInactiveTimeUntilSell();
OfflinePlayer player = Bukkit.getOfflinePlayer(getBuyer());
if(inactiveSetting <= 0 || player.isOp()) {
return false;
}
long lastPlayed = getLastActiveTime();
//AreaShop.debug("currentTime=" + Calendar.getInstance().getTimeInMillis() + ", getLastPlayed()=" + lastPlayed + ", timeInactive=" + (Calendar.getInstance().getTimeInMillis()-player.getLastPlayed()) + ", inactiveSetting=" + inactiveSetting);
if(Calendar.getInstance().getTimeInMillis() > (lastPlayed + inactiveSetting)) {
AreaShop.info("Region " + getName() + " unrented because of inactivity for player " + getPlayerName());
AreaShop.debug("currentTime=" + Calendar.getInstance().getTimeInMillis() + ", getLastPlayed()=" + lastPlayed + ", timeInactive=" + (Calendar.getInstance().getTimeInMillis() - player.getLastPlayed()) + ", inactiveSetting=" + inactiveSetting);
return this.sell(true, null);
}
return false;
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,501 @@
package me.wiefferink.areashop.regions;
import com.google.common.base.Charsets;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.MessageBridge;
import me.wiefferink.areashop.events.ask.AddingRegionEvent;
import me.wiefferink.areashop.features.signs.RegionSign;
import me.wiefferink.areashop.features.signs.SignManager;
import me.wiefferink.areashop.interfaces.WorldGuardInterface;
import me.wiefferink.areashop.managers.IFileManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class ImportJob {
private final AreaShop plugin;
private final IFileManager fileManager;
private final SignManager signManager;
private final WorldGuardInterface worldGuardInterface;
private final CommandSender sender;
private final MessageBridge messageBridge;
private final RegionFactory regionFactory;
/**
* Create and execute the import.
* @param sender CommandSender that should receive progress updates
*/
@AssistedInject
ImportJob(
@Nonnull AreaShop plugin,
@Nonnull IFileManager fileManager,
@Nonnull SignManager signManager,
@Nonnull WorldGuardInterface worldGuardInterface,
@Nonnull MessageBridge messageBridge,
@Nonnull RegionFactory regionFactory,
@Assisted @Nonnull CommandSender sender) {
this.regionFactory = regionFactory;
this.sender = sender;
this.plugin = plugin;
this.signManager = signManager;
this.fileManager = fileManager;
this.worldGuardInterface = worldGuardInterface;
this.messageBridge = messageBridge;
}
/**
* Execute the job.
*/
public void execute() {
// Check for RegionForSale data
File regionForSaleFolder = new File(plugin.getDataFolder().getParentFile().getAbsolutePath(), "RegionForSale");
if(!regionForSaleFolder.exists()) {
message("import-noPluginFolder", regionForSaleFolder.getName());
return;
}
File worldsFolder = new File(regionForSaleFolder.getAbsolutePath(), "worlds");
if(!worldsFolder.exists()) {
message("import-noWorldsFolder");
return;
}
File[] worldFolders = worldsFolder.listFiles();
if(worldFolders == null) {
message("import-noWorldsFolder");
return;
}
// Import data for each world
message("import-start");
// Group with settings for all imported regions
RegionGroup regionForSaleGroup = regionFactory.createRegionGroup("RegionForSale");
fileManager.addGroup(regionForSaleGroup);
// Import /RegionForSale/config.yml settings
File regionForSaleConfigFile = new File(regionForSaleFolder.getAbsolutePath(), "config.yml");
YamlConfiguration regionForSaleConfig = loadConfiguration(regionForSaleConfigFile);
if(regionForSaleConfig == null) {
messageNoPrefix("import-loadConfigFailed", regionForSaleConfigFile.getAbsolutePath());
regionForSaleConfig = new YamlConfiguration();
} else {
importRegionSettings(regionForSaleConfig, regionForSaleGroup.getSettings(), null, false);
regionForSaleGroup.setSetting("priority", 0);
}
// Import /RegionForSale/general.yml settings
File regionForSaleGeneralFile = new File(regionForSaleFolder.getAbsolutePath(), "config.yml");
YamlConfiguration regionForSaleGeneral = loadConfiguration(regionForSaleConfigFile);
if(regionForSaleGeneral == null) {
messageNoPrefix("import-loadConfigFailed", regionForSaleGeneralFile.getAbsolutePath());
} else {
// Collection interval of RegionForSale maps to rent duration
String duration = "1 day";
if(regionForSaleGeneral.isLong("interval.collect_money")) {
duration = minutesToString(regionForSaleGeneral.getLong("interval.collect_money"));
}
regionForSaleGroup.setSetting("rent.duration", duration);
// Global economy account has an effect close to landlord in AreaShop
if(regionForSaleGeneral.isString("global_econ_account")) {
regionForSaleGroup.setSetting("general.landlordName", regionForSaleGeneral.getString("global_econ_account"));
}
}
regionForSaleGroup.saveRequired();
////////// Handle defaults of RegionForSale
// Set autoExtend, to keep the same behavior as RegionForSale had
regionForSaleGroup.setSetting("rent.autoExtend", true);
// Import regions from each world
for(File worldFolder : worldFolders) {
// Skip files
if(!worldFolder.isDirectory() || worldFolder.isHidden()) {
continue;
}
messageNoPrefix("import-doWorld", worldFolder.getName());
// Get the Bukkit world
World world = Bukkit.getWorld(worldFolder.getName());
if(world == null) {
messageNoPrefix("import-noBukkitWorld");
continue;
}
// Get the WorldGuard RegionManager
RegionManager regionManager = plugin.getRegionManager(world);
if(regionManager == null) {
messageNoPrefix("import-noRegionManger");
continue;
}
// Load the /worlds/<world>/regions.yml file
File regionsFile = new File(worldFolder.getAbsolutePath(), "regions.yml");
YamlConfiguration regions = loadConfiguration(regionsFile);
if(regions == null) {
messageNoPrefix("import-loadRegionsFailed", regionsFile.getAbsolutePath());
continue;
}
// Load /worlds/<world>/config.yml file
File worldConfigFile = new File(worldFolder.getAbsolutePath(), "config.yml");
YamlConfiguration worldConfig = loadConfiguration(worldConfigFile);
if(worldConfig == null) {
messageNoPrefix("import-loadWorldConfigFailed", worldConfigFile.getAbsolutePath());
// Simply skip importing the settings, since this is not really fatal
worldConfig = new YamlConfiguration();
} else {
// RegionGroup with all world settings
RegionGroup worldGroup = regionFactory.createRegionGroup("RegionForSale-" + worldFolder.getName());
importRegionSettings(worldConfig, worldGroup.getSettings(), null, false);
worldGroup.setSetting("priority", 1);
worldGroup.addWorld(worldFolder.getName());
fileManager.addGroup(regionForSaleGroup);
worldGroup.saveRequired();
}
// Create groups to hold settings of /worlds/<world>/parent-regions.yml
File parentRegionsFile = new File(worldFolder.getAbsolutePath(), "parent-regions.yml");
YamlConfiguration parentRegions = loadConfiguration(parentRegionsFile);
if(parentRegions == null) {
messageNoPrefix("import-loadParentRegionsFailed", parentRegionsFile.getAbsolutePath());
// Non-fatal, so just continue
} else {
for(String parentRegionName : parentRegions.getKeys(false)) {
// Get WorldGuard region
ProtectedRegion worldGuardRegion = regionManager.getRegion(parentRegionName);
if(worldGuardRegion == null) {
messageNoPrefix("import-noWorldGuardRegionParent", parentRegionName);
continue;
}
// Get settings section
ConfigurationSection parentRegionSection = parentRegions.getConfigurationSection(parentRegionName);
if(parentRegionSection == null) {
messageNoPrefix("import-improperParentRegion", parentRegionName);
continue;
}
// Skip if it does not have any settings
if(parentRegionSection.getKeys(false).isEmpty()) {
continue;
}
// Import parent region settings into a RegionGroup
RegionGroup parentRegionGroup = regionFactory.createRegionGroup( "RegionForSale-" + worldFolder.getName() + "-" + parentRegionName);
importRegionSettings(parentRegionSection, parentRegionGroup.getSettings(), null, false);
parentRegionGroup.setSetting("priority", 2 + parentRegionSection.getLong("info.priority", 0));
parentRegionGroup.saveRequired();
// TODO add all regions that are contained in this parent region
// Utils.getWorldEditRegionsInSelection()
}
}
// Read and import regions
for(String regionKey : regions.getKeys(false)) {
GeneralRegion existingRegion = fileManager.getRegion(regionKey);
if(existingRegion != null) {
if(world.getName().equalsIgnoreCase(existingRegion.getWorldName())) {
messageNoPrefix("import-alreadyAdded", regionKey);
} else {
messageNoPrefix("import-alreadyAddedOtherWorld", regionKey, existingRegion.getWorldName(), world.getName());
}
continue;
}
ConfigurationSection regionSection = regions.getConfigurationSection(regionKey);
if(regionSection == null) {
messageNoPrefix("import-invalidRegionSection", regionKey);
continue;
}
// Get WorldGuard region
ProtectedRegion worldGuardRegion = regionManager.getRegion(regionKey);
if(worldGuardRegion == null) {
messageNoPrefix("import-noWorldGuardRegion", regionKey);
continue;
}
String owner = regionSection.getString("info.owner", null);
boolean isBought = regionSection.getBoolean("info.is-bought");
// TODO: should also take into config settings of parent regions
boolean rentable = regionSection.getBoolean("economic-settings.rentable", worldConfig.getBoolean("economic-settings.rentable", regionForSaleConfig.getBoolean("economic-settings.rentable")));
boolean buyable = regionSection.getBoolean("economic-settings.buyable", worldConfig.getBoolean("economic-settings.buyable", regionForSaleConfig.getBoolean("economic-settings.buyable")));
// Can be bought and rented, import as buy
if(buyable && rentable) {
messageNoPrefix("import-buyAndRent", regionKey);
}
// Cannot be bought or rented, skip
if(!buyable && !rentable && owner == null) {
messageNoPrefix("import-noBuyAndNoRent", regionKey);
continue;
}
// Create region
GeneralRegion region;
if(rentable || (owner != null && !isBought)) {
region = regionFactory.createRentRegion(regionKey, world);
} else {
region = regionFactory.createBuyRegion(regionKey, world);
}
AddingRegionEvent event = plugin.getFileManager().addRegion(region);
if (event.isCancelled()) {
messageNoPrefix("general-cancelled", event.getReason());
continue;
}
// Import settings
importRegionSettings(regionSection, region.getConfig(), region, !buyable && !rentable);
region.getConfig().set("general.importedFrom", "RegionForSale");
// Get existing owners and members
List<UUID> existing = new ArrayList<>();
if(owner != null) {
@SuppressWarnings("deprecation")
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(owner);
if(offlinePlayer.hasPlayedBefore()) {
existing.add(offlinePlayer.getUniqueId());
}
}
for(UUID uuid : worldGuardInterface.getOwners(worldGuardRegion).asUniqueIdList()) {
if(!existing.contains(uuid)) {
existing.add(uuid);
}
}
for(UUID uuid : worldGuardInterface.getMembers(worldGuardRegion).asUniqueIdList()) {
if(!existing.contains(uuid)) {
existing.add(uuid);
}
}
// First owner (or if none, the first member) will be the renter/buyer
if(!existing.isEmpty()) {
region.setOwner(existing.remove(0));
}
// Add others as friends
for(UUID friend : existing) {
region.getFriendsFeature().addFriend(friend, null);
}
region.saveRequired();
messageNoPrefix("import-imported", regionKey);
}
}
// Update all regions
fileManager.updateAllRegions(sender);
// Write all imported regions and settings to disk
fileManager.saveRequiredFiles();
}
/**
* Load a YamlConfiguration from disk using UTF-8 encoding.
* @param from File to read the configuration from
* @return YamlConfiguration if the file exists and got read wihout problems, otherwise null
*/
private YamlConfiguration loadConfiguration(File from) {
try(
InputStreamReader reader = new InputStreamReader(new FileInputStream(from), Charsets.UTF_8)
) {
return YamlConfiguration.loadConfiguration(reader);
} catch(IOException e) {
return null;
}
}
/**
* Import region specific settings from a RegionForSale source to an AreaShop target ConfigurationSection.
* @param from RegionForSale config section that specifies region settings
* @param to AreaShop config section that specifies region settings
* @param region GeneralRegion to copy settings to, or null if doing generic settings
* @param permanent Region cannot be rented or bought, disables some features
*/
private void importRegionSettings(ConfigurationSection from, ConfigurationSection to, GeneralRegion region, boolean permanent) {
// Maximum rental time, TODO check if this is actually the same
if(from.isLong("permissions.max-rent-time")) {
to.set("rent.maxRentTime", minutesToString(from.getLong("permissions.max-rent-time")));
}
// Region rebuild
if(from.getBoolean("region-rebuilding.auto-rebuild")) {
to.set("general.enableRestore", true);
}
// Get price settings
String unit = from.getString("economic-settings.unit-type");
String rentPrice = from.getString("economic-settings.cost-per-unit.rent");
String buyPrice = from.getString("economic-settings.cost-per-unit.buy");
String sellPrice = from.getString("economic-settings.cost-per-unit.selling-price");
// TODO: There is no easy way to import this, setup eventCommandsProfile?
// String taxes = from.getString("economic-settings.cost-per-unit.taxes");
// Determine unit and add that to the price
String unitSuffix = "";
if("region".equalsIgnoreCase(unit)) {
// add nothing
} else if("m3".equalsIgnoreCase(unit)) {
unitSuffix = "*%volume%";
} else { // m2 or nothing (in case not set, we should actually look in parent files to correctly set this...)
unitSuffix = "*(%volume%/%height%)"; // This is better than width*depth because of polygon regions
}
// Apply settings
if(rentPrice != null) {
to.set("rent.price", rentPrice + unitSuffix);
}
if(buyPrice != null) {
to.set("buy.price", buyPrice + unitSuffix);
if(sellPrice != null) {
try {
double buyPriceAmount = Double.parseDouble(buyPrice);
double sellPriceAmount = Double.parseDouble(sellPrice);
to.set("buy.moneyBack", sellPriceAmount / buyPriceAmount * 100);
} catch(NumberFormatException e) {
// There is not always a region here for the message, should probably indicate something though
message("import-moneyBackFailed", buyPrice, sellPrice);
}
}
}
// Apply permanent region settings
if(permanent) {
to.set("buy.resellDisabled", true);
to.set("buy.sellDisabled", true);
to.set("general.countForLimits", false);
}
// Set rented until
if(from.isLong("info.last-withdrawal")
&& region instanceof RentRegion rentRegion) {
long lastWithdrawal = from.getLong("info.last-withdrawal");
// Because the rental duration is already imported into the region and its parents this should be correct
rentRegion.setRentedUntil(lastWithdrawal + rentRegion.getDuration());
}
// Import signs (list of strings like "297, 71, -22")
if(from.isList("info.signs") && region != null) {
for(String signLocation : from.getStringList("info.signs")) {
String[] locationParts = signLocation.split(", ");
if(locationParts.length != 3) {
message("import-invalidSignLocation", region.getName(), signLocation);
continue;
}
// Parse the location
Location location;
try {
location = new Location(region.getWorld(), Double.parseDouble(locationParts[0]), Double.parseDouble(locationParts[1]), Double.parseDouble(locationParts[2]));
} catch(NumberFormatException e) {
message("import-invalidSignLocation", region.getName(), signLocation);
continue;
}
// Check if this location is already added to a region
Optional<RegionSign> optionalRegionSign = signManager.signFromLocation(location);
if(optionalRegionSign.isPresent()) {
RegionSign regionSign = optionalRegionSign.get();
if(!regionSign.getRegion().equals(region)) {
message("import-signAlreadyAdded", region.getName(), signLocation, regionSign.getRegion().getName());
}
continue;
}
// SignType and Facing will be written when the sign is updated later
region.getSignsFeature().addSign(location, null, null, null);
}
}
}
private static class TimeUnit {
public final long minutes;
public final String identifier;
TimeUnit(long minutes, String identifier) {
this.minutes = minutes;
this.identifier = identifier;
}
}
private static final List<TimeUnit> timeUnitLookup = new ArrayList<>() {
{
add(new TimeUnit(60 * 24 * 30 * 12, "year"));
add(new TimeUnit(60 * 24 * 30, "month"));
add(new TimeUnit(60 * 24, "day"));
add(new TimeUnit(60, "hour"));
}
};
/**
* Convert minutes to a human-readable string.
* @param minutes Value to convert
* @return String that represents the same length of time in a readable format, like "1 day", "5 minutes", "3 months"
*/
private String minutesToString(long minutes) {
// If the specified number of minutes can map nicely to a higher unit, use that one
String resultUnit = "minute";
long resultValue = minutes;
for(TimeUnit unit : timeUnitLookup) {
long result = minutes / unit.minutes;
if(resultValue * unit.minutes == minutes) {
resultUnit = unit.identifier;
resultValue = result;
break;
}
}
return resultValue + " " + resultUnit + (resultValue == 1 ? "" : "s");
}
/**
* Send a message to a target without a prefix.
* @param key The key of the language string
* @param replacements The replacements to insert in the message
*/
public void messageNoPrefix(String key, Object... replacements) {
messageBridge.messageNoPrefix(sender, key, replacements);
if(!(sender instanceof ConsoleCommandSender)) {
messageBridge.messageNoPrefix(Bukkit.getConsoleSender(), key, replacements);
}
}
/**
* Send a message to a target, prefixed by the default chat prefix.
* @param key The key of the language string
* @param replacements The replacements to insert in the message
*/
public void message(String key, Object... replacements) {
messageBridge.message(sender, key, replacements);
if(!(sender instanceof ConsoleCommandSender)) {
messageBridge.message(Bukkit.getConsoleSender(), key, replacements);
}
}
}
@@ -0,0 +1,11 @@
package me.wiefferink.areashop.regions;
import org.bukkit.command.CommandSender;
import javax.annotation.Nonnull;
public interface ImportJobFactory {
@Nonnull ImportJob createImportJob(@Nonnull CommandSender commandSender);
}
@@ -0,0 +1,7 @@
# Regions
This package contains the `GeneralRegion` class which represents a region in AreaShop. Such a region is linked to a WorldGuard region and has its own configuration file at `/plugins/AreaShop/regions/<name>.yml`.
The `BuyRegion` and `RentRegion` classes represent buy and rental regions (mutually exclusive) and inherit from `GeneralRegion`.
To group regions there is `RegionGroup`, which allows configuring settings for a lot of regions at once.
@@ -0,0 +1,20 @@
package me.wiefferink.areashop.regions;
import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration;
import javax.annotation.Nonnull;
public interface RegionFactory {
@Nonnull BuyRegion createBuyRegion(@Nonnull YamlConfiguration yamlConfiguration);
@Nonnull BuyRegion createBuyRegion(@Nonnull String name, @Nonnull World world);
@Nonnull RentRegion createRentRegion(@Nonnull YamlConfiguration yamlConfiguration);
@Nonnull RentRegion createRentRegion(@Nonnull String name, @Nonnull World world);
@Nonnull RegionGroup createRegionGroup(@Nonnull String name);
}
Loaded 100 of 248 files, more files were not shown because too many files have changed in this diff. Show more