230 Commits

Author SHA1 Message Date
0edb800cd3 Adds broken code for yaml storage
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2025-12-17 14:28:14 +01:00
c5a964337a Cleans permissions, and reduces redundancy in permission checking code
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2025-09-14 14:42:26 +02:00
48b4151038 Updates update check URL
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2025-09-14 03:04:20 +02:00
e30f41071c Restructures events, and adds some missing protections
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2025-09-14 03:01:03 +02:00
d080644364 Moves SimpleVectorOperation to its own class
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2025-09-14 01:45:03 +02:00
061430dfe4 Merge branch 'refs/heads/master' into dev
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2025-09-14 01:38:17 +02:00
0f6c29a203 Bumps version to snapshot for jenkins
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2025-09-14 01:38:05 +02:00
3ad13137bd Updates readme
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2025-09-14 01:35:56 +02:00
e9559a2601 Updates links 2025-09-14 01:29:47 +02:00
766b63d2f3 Merge branch 'refs/heads/master' into dev
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2025-09-14 01:22:49 +02:00
1eeefa4593 Adds distribution management to pom
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2025-09-14 01:21:56 +02:00
8c4d492e07 Adds back the Jenkins file
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2025-09-14 01:08:26 +02:00
01dca5aac8 Fixes formatting 2025-09-14 01:06:58 +02:00
3751ef070c Merge branch 'refs/heads/master' into dev
# Conflicts:
#	src/main/java/net/knarcraft/stargate/container/RelativeBlockVector.java
#	src/main/java/net/knarcraft/stargate/portal/Portal.java
2025-09-14 01:06:02 +02:00
407b22fba0 Ignores cancelled block events 2025-09-14 00:55:01 +02:00
bcde89dec4 Updates dependencies, and removes some redundant code 2025-09-14 00:15:19 +02:00
fe8200813f Updates KnarLib, uses improved formatting and removes redundant code 2025-09-13 23:57:05 +02:00
d80907b5fb Adds better default permissions
- Adds three new permission groups for easier setup
- Sets better default permissions
- Updates and prettifies some documentation
- Renames the silent portal option to quiet
2025-07-30 18:58:42 +02:00
e971bd2cf5 Fixes Stargate verification 2025-07-27 20:26:25 +02:00
a861591fec Cleanup and maintenance
Updates Spigot version
Removes MockBukkit and related tests, as it makes updating a hassle
Bumps API version to 1.20
Removes legacy sign updating
2025-07-26 20:21:35 +02:00
932dae0182 Fix #389
Tested on Purpur Version: 1.21.5-2430-603c755 (MC: 1.21.5)
2025-05-10 18:14:18 +02:00
Pheotis
779a8265f5 Preparing for a release. 2024-11-10 18:26:41 -05:00
864eaac940 Removes unnecessary hashmap copying 2024-11-08 18:45:10 +01:00
7dd01995ed Fixes dependency relocations to remove all warnings
Removes unused adventure-platform-bukkit dependency
Explicitly includes bstats-base
Adds and fixes filters and relocations
2024-11-08 16:50:14 +01:00
966fed331f Optimizes portal entrance lookup 2024-11-08 16:09:28 +01:00
534ada874d Implement equalsand hashCode methods for material specifiers 2024-10-09 18:00:45 +02:00
bd12ec138b Revert "Fix validate portals check" 4377a9418f 2024-10-09 17:59:44 +02:00
4377a9418f Fix validate portals check 2024-09-22 21:25:04 +02:00
Pheotis
72258f659d Preparing for a release. 2024-06-01 14:21:18 -04:00
0a4295e8a9 Merge branch 'legacy' of https://github.com/stargate-rewritten/Stargate-Bukkit into legacy 2024-06-01 20:13:33 +02:00
f77ead15c4 Uses optional legacy method when getting sign lines 2024-06-01 20:13:20 +02:00
Pheotis
eb34d998f9 Getting ready for a release. 2024-05-28 20:48:18 -04:00
2c98103db0 Adds missing return statement. 2024-05-17 20:33:56 +02:00
85928a23a3 Merge branch 'legacy' of https://github.com/stargate-rewritten/Stargate-Bukkit into legacy 2024-05-17 20:30:42 +02:00
97643d8ce0 Adds condition to require exactly one exit point
Note that Legacy didn't have a check because the actual exits were calculated based on the bottom opening blocks in order to center a leaving player. A warning would instead be displayed upon teleportation when a player tried to teleport to a Stargate with no exit.
2024-05-17 20:30:27 +02:00
Pheotis
314995aa14 Merge pull request #337 from stargate-rewritten/legacy-localisation-update
Manual import of crowdin localisations to legacy.
2024-05-13 15:34:43 -04:00
905aec8101 Makes the duplicate dynmap marker creation warning into a debug message 2024-05-13 20:56:54 +02:00
4c024e7536 Updates configuration-related language lists 2024-05-13 19:01:51 +02:00
Pheotis
7a71d5b8c7 Fixed! 2024-05-13 11:30:56 -04:00
Pheotis
c86fa2120a Manually backported rewrite's crowdin. 2024-05-13 11:21:31 -04:00
0bcf65b2f1 Adds geyser and floodgate soft depends 2024-04-28 11:34:45 +02:00
36792d4ddf Removes applyStartupFixes, and adds controlUpdateDelay
Now, all updating of Stargates' control blocks happens through a queue. The amount of ticks between each time the queue is polled from is configurable using controlUpdateDelay
2024-04-23 18:35:41 +02:00
50e7586942 Updates Vault and KnarLib 2024-04-22 15:05:16 +02:00
9db73b7bae Adds an option for disabling startup fixes
Also fixes configuration default values not being used when the option is missing from the configuration file.
2024-04-21 23:10:26 +02:00
6287a5e492 Only fetches name from UUID if necessary
Also fixes some minor warnings
2024-04-21 16:13:57 +02:00
484d4f4cf1 Caches result of getOfflinePlayer in PortalOwner 2024-04-21 15:31:13 +02:00
29bae9d793 Adds more debug output during post load tasks 2024-04-20 19:08:11 +02:00
420e4fc185 Optimizes sign updating 2024-04-20 18:35:31 +02:00
58373f7d0f Adds some conditions to the portals updated during loading 2024-04-20 18:17:41 +02:00
ca52f58129 Fixes a class type problem when spawning a new vehicle 2024-03-19 14:51:55 +01:00
be6e7ec87b Checks the Floodgate API in addition to the Geyser API 2024-03-19 14:28:17 +01:00
Pheotis
5b825b5037 Bump version 2024-03-06 20:44:21 -05:00
209ae91d9b Reverts MockBukkit update, and removes inconsistent JUnit version 2024-03-06 15:24:28 +01:00
dfeeaf6999 Adds proper geyser checking 2024-03-06 14:59:09 +01:00
184cb38cbb Updates MockBukkit, and fixes some test-related issues
Fixes JUnit 5's After being used instead of AfterEach
Fixes a potential NullPointerException when tearing down GateLayoutTest
2024-03-06 14:25:45 +01:00
74708914f3 Expands hit-box for END_GATEWAY for players with Bedrock names (WIP) 2024-03-06 14:02:28 +01:00
c2ab3dd8b2 Fixes the problem with Dynmap disabling Stargate 2024-03-06 13:57:31 +01:00
2746e4f8ee Merge pull request #336 from stargate-rewritten/legacy-improvements
Legacy improvements
2024-02-22 03:49:17 +01:00
c03b3fc6b0 Removes some redundancy in createPortal 2024-02-20 22:48:36 +01:00
9abf60bb31 Reduces some code complexity 2024-02-20 20:48:29 +01:00
cde20e35d5 Uses matcher.find instead of matcher.matches
Also removes some code smells
2024-02-20 19:45:41 +01:00
9abf6db27a Alters the RegEx for replacing the plugin folder path 2024-02-20 19:29:27 +01:00
ee9bc21099 Fixes some code style issues, and attempts to fix test failures 2024-02-20 18:54:04 +01:00
e57aa4097c Adds a Message enum to avoid string keys 2024-02-20 16:18:38 +01:00
a9e5855194 Improves button and material customization
Allows specifying a comma-separated list of materials and tags for a portal's button, open-material, closed-material and border blocks. A random value is used if more than one material is available.0
Uses the supplied button if any, instead of enforcing the specified button material.
Always protects the button against block breaking.
Fixes an incorrect permission result in the previous commit, which caused players stargate access to be inverted.
2024-02-20 15:15:52 +01:00
b4a6ce1a77 Adds nullability annotations among other things
Adds nullability annotations for all methods
Fixes some nullability problems and inconsistencies
Gets rid of RelativeBlockVector's inner class
Changes RelativeBlockVector to a record
Simplifies FromTheEndTeleportation's storage, and makes it into a minimal record
Removes the putStringInList method
Gets rid of some primitive list usage
Fixes some incorrect method accessibility
Removes some redundancy in PortalOption
2024-02-20 12:43:01 +01:00
894a692e7b Fix #334 2024-02-19 22:22:09 +01:00
9154074a18 Removes a TODO that's already completed 2024-02-17 18:26:26 +01:00
c560063c06 Fixes incorrect formatting and unused imports 2024-02-17 18:22:27 +01:00
a4d4864bdd Checks markerAPIInitialized during dynmap initialization 2024-01-20 16:11:07 +01:00
d975666154 Fix failed tests 2024-01-07 18:36:54 +01:00
95058e86a6 Merge branch 'legacy' of https://github.com/stargate-rewritten/Stargate-Bukkit into legacy 2024-01-07 18:27:54 +01:00
Pheotis
8d6b233a8f Preparing for legacy release. 2024-01-07 12:13:39 -05:00
e20ddb52ef Update about.md 2023-12-29 16:55:41 +01:00
f657816533 Modify the about command 2023-12-29 12:43:58 +01:00
f37a196dd9 sg debug -> sg 2023-12-28 17:28:59 +01:00
44aadcd225 Implement the debug command 2023-12-28 17:25:13 +01:00
Thorinwasher
7cfec1190e Merge pull request #304 from furplag/legacy
fix exception occur when invalid yaw ( negative value ) given .
2023-12-28 17:11:26 +01:00
furplag
30cba7051c Update DirectionHelper.java
fix exception occur when invalid yaw ( negative value ) given .
2023-12-28 13:18:04 +09:00
e85f133bc2 Uses depositPlayer for negative economy transactions 2023-08-11 14:42:37 +02:00
0dfbcb0b54 Merge branch 'legacy' of https://github.com/stargate-rewritten/Stargate-Bukkit into legacy 2023-06-27 13:47:46 +02:00
3fcae6ca45 Tries to fix a stack trace when Dynmap fails to disable itself 2023-06-27 13:47:35 +02:00
Pheotis
902fc0aa4e Updated readme to prep for minor release. 2023-06-17 09:28:12 -04:00
3e8bfbe5f6 Bumped version 2023-06-17 15:26:49 +02:00
6b6e9d007e Fixes compatibility for pre-1.20 spigot 2023-06-17 14:37:05 +02:00
Pheotis
ef2238a535 Update README.md 2023-06-07 16:56:12 -04:00
27fba221d6 Adds per-sign color settings for the new signs 2023-06-07 22:25:41 +02:00
1c06cf2188 Updates depreciated sign API calls 2023-06-07 18:35:22 +02:00
Pheotis
22f4cb78f6 Bumped Version 2023-06-01 19:24:37 -04:00
Pheotis
732cabd040 Update readme for release bump 2023-06-01 19:24:20 -04:00
9d48374d28 Possibly fixes #296 2023-05-31 17:13:13 +02:00
Pheotis
6f88eecb41 Update changelog and bumps version.
(Preparing for minor release)
2023-05-21 08:21:33 -04:00
0d073a7dad Fixes #291 2023-05-21 10:58:42 +02:00
cbc9e85314 Fixes some formatting again 2023-04-21 17:56:10 +02:00
Pheotis
b3428877ba Cleaned up Config & Updated Changelog 2023-04-21 11:49:27 -04:00
Pheotis
30a5efdadf Bumped version, added changelog, fixed readme . 2023-04-21 10:17:08 -04:00
53126beba3 Adds author to StargateYamlConfiguration 2023-04-21 16:15:03 +02:00
4f7bab8a1a Merge pull request #288 from stargate-rewritten/legacy-config-migration
Legacy config migration
2023-04-21 13:11:56 +00:00
9a215a7c11 Attempts to make SonarCloud happier 2023-04-21 15:10:20 +02:00
e07adacf73 Reduces the complexity of convertYAMLMappingsToComments 2023-04-21 14:53:23 +02:00
05fadfa558 Removes stack trace printing 2023-04-21 14:03:33 +02:00
fab067c94b Fixes #287 2023-04-21 13:56:18 +02:00
45f3bcc415 Improves logging a bit 2023-04-21 13:41:42 +02:00
50015c2912 Adds graceful shutdown for CraftBukkit #286 2023-04-21 12:53:43 +02:00
486149fa01 Improves performance of StargateYamlConfiguration a bit 2023-04-20 18:44:46 +02:00
9d981f2cc6 Fixes a crash caused by StargateYamlConfiguration 2023-04-20 16:02:57 +02:00
aa480faef2 Improves behavior if the config cannot be loaded 2023-04-19 23:13:02 +02:00
24b62ec749 Attempts to properly comment migrated config values
Note that for some reason, YamlConfiguration cannot load the output created by StargateYamlConfiguration causing a major crash
2023-04-19 22:17:29 +02:00
032a4df4d7 Overrides saving 2023-04-19 20:51:35 +02:00
c3752db99b Adds a custom configuration to retain comments 2023-04-19 20:03:23 +02:00
17bb27d553 Merge branch 'legacy' of https://github.com/stargate-rewritten/Stargate-Bukkit into legacy 2023-04-19 19:00:07 +02:00
6737a4f789 Adds the missing taxAccounts option 2023-04-19 19:00:02 +02:00
Pheotis
b0b31e04fc Removed some potentially confusing abbreviations 2023-04-19 12:40:22 -04:00
2074904aef Improves configuration comments 2023-04-19 18:34:37 +02:00
421e0b17e2 Updates the resource id for the update checker
This change makes the update checker look at https://www.spigotmc.org/resources/stargate.109355/ for updates, instead of the legacy with incorrect version numbering: https://www.spigotmc.org/resources/stargate-old.87978/
2023-04-19 11:06:52 +00:00
Pheotis
e6c92f9322 Fixed maven shade to handle bstats and knarlib. 2023-03-25 17:02:55 -04:00
bd947d5c94 Merge branch 'temp-legacy' of https://github.com/stargate-rewritten/Stargate-Bukkit into temp-legacy 2023-03-25 02:57:38 +01:00
7e46fa413e Relocates BStats, but KnarLib is not properly shaded 2023-03-25 02:57:23 +01:00
Pheotis
77fec1d5de Fixed a mismatch re the increased character limit. 2023-03-24 21:51:09 -04:00
Pheotis
24890a4b5d Fixed a text oversight. 2023-03-24 21:48:50 -04:00
Pheotis
3834ef04d4 Updated the readme to reflect the merger. 2023-03-24 21:45:55 -04:00
a20a1c2f00 Renames the I-flag to the Q-flag 2023-03-25 02:18:28 +01:00
472aeda2f8 Implements BStats statistics 2023-03-25 02:18:05 +01:00
c1720e05a0 Adds a wool gate amongst other things
Adds the wool gate type from legacy
Adds some missing material tag-related code
Updates the URL for update checking
2023-03-25 01:54:43 +01:00
91d855312d Changes the E flag to a V flag for rewrite consistency 2023-03-24 23:52:18 +01:00
10a16000c6 Removes unused frameTypes variable 2023-03-24 23:46:21 +01:00
454bac6f14 Implements support for character tags 2023-03-24 23:44:17 +01:00
f8cddea188 Adds one missing legacy config conversion 2023-03-24 22:48:37 +01:00
06271a356e Updates Vault repository 2023-03-24 21:57:06 +01:00
79bf297513 Updates some dependencies 2023-03-24 21:47:41 +01:00
36f09d5b29 Updates website URL to a shortcut 2023-03-24 21:29:13 +01:00
5a2766c4c6 Updates website URL 2023-03-24 20:58:18 +01:00
b63bd19db0 Sets the Spigot API version to 1.16 2023-03-24 20:56:34 +01:00
2bdcc0da8d Merge remote-tracking branch 'origin/temp-legacy' into temp-legacy
# Conflicts:
#	pom.xml
2023-03-24 20:50:28 +01:00
1f4aeefad1 Bumps version and removes Jenkinsfile
Bumps version to 0.11.5.0-SNAPSHOT for consistency
2023-03-24 20:47:31 +01:00
Pheotis
12a2d4eb71 Merged temp-legacy's versioning with legacy's
This is now a major version ahead of SGR's legacy.
2023-03-24 15:45:15 -04:00
Pheotis
69ae5ea3de Merge remote-tracking branch 'knarvik/master' into temp-legacy 2023-03-24 15:37:03 -04:00
2dcf7b7af0 Merge branch 'master' into vertical-stargates
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2023-03-24 20:18:22 +01:00
af9142bb05 Adds missing known legacy config migrations
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2023-03-24 20:02:05 +01:00
116e816a18 Bumps Spigot version to 1.19.3
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2022-12-10 22:40:03 +01:00
0f0b8b7087 Merge branch 'dev' into vertical-stargates
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2022-11-26 17:02:18 +01:00
e4539c3623 Changes version to 0.9.4.3-SNAPSHOT
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2022-11-26 17:00:04 +01:00
e1ca1fe8b0 Merge branch 'dev' into vertical-stargates
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
# Conflicts:
#	Jenkinsfile
2022-11-26 16:13:40 +01:00
9ac3c11345 Adds missing distribution management
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-11-26 15:34:34 +01:00
13cdccfc1d Improves Jenkinsfile 2022-11-26 15:15:45 +01:00
6954d46af4 Adds auto-deploy
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-11-26 14:52:18 +01:00
a1f25a794e Merge branch 'master' into vertical-stargates
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2022-11-26 14:39:09 +01:00
31b3423246 Adds the knarcraft git repository
All checks were successful
EpicKnarvik97/Stargate/pipeline/head This commit looks good
2022-11-26 04:05:03 +01:00
a35c07dc9c Updates the JDK used for Jenkins
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-11-22 16:41:55 +01:00
5aed252181 Updates dependencies
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-11-14 00:45:31 +00:00
ef97da9177 Removes KnarLib.setPlugin call
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-11-07 22:27:53 +01:00
a26cc30092 Uses placeholder for plugin version 2022-11-07 10:07:16 +01:00
4fda4c3905 Makes sure to initialize KnarLib properly 2022-11-07 10:01:34 +01:00
2b23e6fc56 Uses KnarLib where possible 2022-11-07 09:57:32 +01:00
c09063c49e Updates README and version to 0.9.4.2
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-10-09 10:46:49 +02:00
b5e2565626 Fixes use of depreciated setWoodType and getWoodType 2022-10-09 10:44:30 +02:00
fbabe7b117 Merge branch 'master' into vertical-stargates
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-06-25 23:25:25 +02:00
5e456a1326 Updates some names and comments for SimpleVectorOperation 2022-06-25 23:24:59 +02:00
11d3dc7a92 Increases hitbox of bungee portals using END_PORTAL to fix server change not triggering
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-06-25 12:11:37 +02:00
1c87d803ff Adds a delayed teleport to really force the player respawn for end_portal Stargate 2022-06-25 11:53:52 +02:00
2076fda4d1 Fixes end portals completely hijacking BungeeCord teleportation 2022-06-25 10:54:19 +02:00
524130c4e0 Avoids some potential NullPointerExceptions related to dynmap integration
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-06-24 03:33:49 +02:00
ce5f3ef52f Avoids a NullPointerException if dynmap is present, but isn't loaded properly 2022-06-24 03:23:59 +02:00
cae34d395b Displays bungee markers properly
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-06-18 16:16:36 +02:00
643a48392b Adds Dynmap integration
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
This adds Dynmap integration for displaying Stargates in Dynmap
API version is changed back to 1.18 as 1.19 is still kind of new
Version is now 0.9.4.1
2022-06-18 16:05:05 +02:00
92c3eadf8f Updates Stargate for Spigot 1.19
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-06-17 12:40:15 +02:00
Michael Smith
4bdcd9992b Fix repository URL 2022-03-25 23:48:40 -07:00
92f452df00 Adds LGPL version to the README
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-03-11 17:16:26 +01:00
6eb7649e0d Clarifies license to prevent any disputes 2022-03-11 17:10:10 +01:00
5c2cbaae58 Merge branch 'master' into dev
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
# Conflicts:
#	README.md
#	pom.xml
#	src/main/resources/plugin.yml
2022-02-17 20:17:11 +01:00
0c69dc8991 Adds the Japanese language file provided by spigot user furplag (0.9.3.7) 2022-02-17 20:08:24 +01:00
68bed24137 Merge branch 'master' of https://git.knarcraft.net/EpicKnarvik97/Stargate
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
 Conflicts:
	README.md
	pom.xml
	src/main/resources/plugin.yml
2022-02-15 18:27:25 +01:00
5c1f9036c2 Merge branch 'dev' of https://git.knarcraft.net/EpicKnarvik97/Stargate into dev
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
 Conflicts:
	README.md
	pom.xml
	src/main/resources/plugin.yml
2022-02-15 18:24:38 +01:00
8488c5abdb Fixes some after-merge problems 2022-02-15 18:21:42 +01:00
e3189e9ab2 Merge branch 'master' into dev
# Conflicts:
#	README.md
#	pom.xml
#	src/main/resources/plugin.yml
2022-02-15 18:16:53 +01:00
8c334ff5f0 Adds simplified chinese, courtesy of YKDZ 2022-02-15 18:15:22 +01:00
4dfce3d325 Updates README and version
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-02-09 18:37:04 +01:00
00462799b9 Fixes the default wait for player after teleport delay 2022-02-09 18:28:47 +01:00
9e78e32db4 Fixes some spacing 2022-02-09 17:20:28 +01:00
28bb6f2109 Improves the generation of the list of available chat colors 2022-02-09 17:19:51 +01:00
61b05bcce9 Improves some code 2022-02-09 16:47:37 +01:00
5f4a90aabb Gives all passengers the same exit velocity as the root vehicle 2022-02-09 16:23:45 +01:00
a481ccf017 Adds some debug messages and makes sure to listen to the vehicle teleport event result 2022-02-09 05:54:57 +01:00
28d84450fb Makes boats keep their boat type when teleported using the CraftBook fix
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-02-08 19:00:44 +01:00
99bceb9165 Updates some test dependencies 2022-02-08 18:44:44 +01:00
a521454020 Updates README and version to 0.9.3.4 2022-02-08 18:43:04 +01:00
6c32de59a8 Makes the delay to prevent client errors configurable 2022-02-08 18:31:26 +01:00
497551d889 Makes the CraftBook remove on eject fix optional 2022-02-08 18:10:02 +01:00
8643a44df6 Moves some code to TeleportHelper 2022-02-08 16:24:47 +01:00
e7b711efcf Makes sure to stop polling the block change thread once it's empty instead of continuing doing nothing 2022-02-07 22:51:09 +01:00
56d59f0d2a Makes sure to check player passengers from the root entity
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-01-29 00:30:26 +01:00
248caee620 Fixes some minor formatting issues 2022-01-28 23:23:33 +01:00
7f08763624 Changes a lot of teleportation code to be able to teleport entities with passengers
Moves some teleportation code to the TeleportHelper class
Changes some teleportation method names
Tries to reduce some code duplication between teleporters
Recursively teleports entities to make sure the teleported entity will arrive in the same shape it entered as
Makes player checks for teleported vehicles account for any number of players riding any entity in the stack
2022-01-28 23:23:09 +01:00
95293204d5 Adds a new StargateTeleportEvent to combine some duplicated code 2022-01-28 22:58:02 +01:00
8a5c094ce1 Updates README with changes and version to 0.9.3.3
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-01-28 00:13:32 +01:00
5e79df9f44 Adds an entity spawn listener for preventing Zombified Piglin spawns 2022-01-28 00:13:00 +01:00
a3ed1058e6 Improves the double click prevention by accounting for heavy concurrent usage and server lag 2022-01-27 22:20:38 +01:00
f70ba24e95 Un-does the outwards offset reduction for teleported entities 2022-01-27 21:56:19 +01:00
f6438eb872 Updates the plugin version to 0.9.3.2 2022-01-27 21:39:13 +01:00
2bf5422b2a Updates README with recent changes 2022-01-27 21:37:59 +01:00
f95ee0b85d Reduces the outwards offset of teleported entities 2022-01-27 21:35:40 +01:00
1e06e0e01d Fixes some rotation problems for teleported passengers 2022-01-27 21:35:04 +01:00
bddf8c55d5 Adds an option for setting the exit velocity of a teleporting player
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-01-27 05:15:43 +01:00
99ee5c6978 Adds the SimpleVectorOperation class which is capable of rotating a RelativeBlockVector in 6 directions 2022-01-27 04:28:36 +01:00
05a5fb2160 Updates version to 0.9.3.1
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-01-27 01:32:49 +01:00
d9f535cd07 Updates README 2022-01-27 00:59:45 +01:00
62952611b8 Ignores the type of air when verifying a stargate's opening 2022-01-27 00:58:42 +01:00
29fdc8f849 Adds a new feature to the README 2022-01-27 00:44:31 +01:00
043c5f2408 Updates README and version to 0.9.3.0 2022-01-26 23:41:05 +01:00
a74d47e999 Makes sure default config values are stored with comments on the first run 2022-01-26 23:37:34 +01:00
e86be3e848 Finishes the work required for changing per-sign colors using the /sg config command 2022-01-26 23:27:31 +01:00
366cd3107e Updates per-sign inverted colors when the main or highlighting color is changed 2022-01-26 20:30:16 +01:00
02f6c6e9fc Updates README with recent changes 2022-01-26 18:17:10 +01:00
97670c9367 Adds the possibility to use the inverted default colors for per-sign colors
Adds code for getting inverted colors if "inverted" is given as the per-sign color
Fixes exceptions thrown when the per-sign config value is malformed
Changes the default to use inverted colors for crimson, dark_oak, spruce and warped signs
Adds methods to get main and highlighting colors from the portal sign drawer in case the given default colors are invalid and fallback colors are necessary instead
2022-01-26 18:02:53 +01:00
2773079a09 Uses the inverted color for a dyed sign's highlighting color 2022-01-26 15:49:07 +01:00
071f1895de Makes sure only the owner and players with stargate.admin.dye can dye stargate signs 2022-01-26 14:57:15 +01:00
4cae54e46f Adds support for dyed stargate signs
A dyed sign's color will now take priority over its main sign color. Black is used as the "default" color, so use black dye to remove dye effects
Adds a new class to keep track of a sign and its colors
Updates a bunch of code to make everything account for the dye color
2022-01-26 14:40:29 +01:00
7d41b75bac Makes string list config values display their default value properly
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-01-26 05:09:34 +01:00
404b4d0543 Changes the comment format for config files 2022-01-26 02:07:07 +01:00
842fd9c452 Fixes a few bugs regarding the new sign color system
Fixes missing STRING_LIST in loadConfig switch
Fixes missing color valid check in loadGateConfig
Fixes typing of PER_SIGN_COLORS
2022-01-26 02:06:42 +01:00
2bb0e8670d Makes a lot of changes necessary for RGB and per-sign color configuration support 2022-01-25 16:46:29 +01:00
acbdcd3ce3 Fixes a hang caused by MockBukkit 2022-01-25 15:34:27 +01:00
948f92f140 Updates Spigot and MockBukkit versions
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2022-01-15 21:59:46 +01:00
5d1d6ffaf0 Removes some unnecessary calculation when converting relative vectors to real vectors 2021-12-03 23:23:19 +01:00
a7dc02eef0 Updates plugin version and README
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-12-02 04:19:44 +01:00
9177773a0e Updates Spigot and Java version 2021-12-02 04:05:27 +01:00
fc5bac937a Changes the max age of gateway blocks to prevent the occasional purple beam
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-26 16:55:22 +01:00
f8ae83bc08 Updates README and version to 0.9.2.4
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-25 14:56:44 +01:00
cfb4910977 Adds a toggle-able admin alert when any admin joins the server 2021-11-25 14:56:05 +01:00
a61a03be33 Adds an update checker for alerting about new updates 2021-11-25 14:54:50 +01:00
131 changed files with 7741 additions and 5048 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,6 @@
target/
.idea/
*.secret
*.db
nbactions.xml
stargate.iml

19
HEADER Normal file
View File

@@ -0,0 +1,19 @@
Stargate - A portal plugin for Bukkit
Copyright (C) 2011 Shaun (sturmeh)
Copyright (C) 2011 Dinnerbone
Copyright (C) 2011-2013 Steven "Drakia" Scott <Contact@TheDgtl.net>
Copyright (C) 2015-2020 Michael Smith (PseudoKnight)
Copyright (C) 2021-2022 Kristian Knarvik (EpicKnarvik97)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

10
Jenkinsfile vendored
View File

@@ -1,7 +1,7 @@
pipeline {
agent any
tools {
jdk 'JDK16'
jdk 'JDK17'
}
stages {
stage('Build') {
@@ -16,10 +16,16 @@ pipeline {
sh 'mvn test'
}
}
stage('Verify') {
steps {
echo 'Verifying...'
sh 'mvn verify -Dmaven.test.skip=true'
}
}
stage('Deploy') {
steps {
echo 'Deploying...'
sh 'mvn verify -Dmaven.test.skip=true'
sh 'mvn deploy -Dmaven.install.skip=true -Dmaven.test.skip=true'
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
}
}

674
LICENSE
View File

@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

915
README.md

File diff suppressed because it is too large Load Diff

185
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>net.knarcraft</groupId>
<artifactId>Stargate</artifactId>
<version>0.9.2.3</version>
<version>0.11.5.12-SNAPSHOT</version>
<licenses>
<license>
@@ -15,70 +15,197 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
<java.version>16</java.version>
</properties>
<repositories>
<repository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>vault-repo</id>
<url>http://nexus.hc.to/content/repositories/pub_releases</url>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>dynmap</id>
<url>https://repo.mikeprimm.com/</url>
</repository>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>minebench-repo</id>
<url>https://repo.minebench.de/</url>
</repository>
<repository>
<id>opencollab-snapshot</id>
<url>https://repo.opencollab.dev/main/</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</repository>
<snapshotRepository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.17.1-R0.1-SNAPSHOT</version>
<version>1.21.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.milkbowl.vault</groupId>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.0-M1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.seeseemelk</groupId>
<artifactId>MockBukkit-v1.17</artifactId>
<version>1.7.0</version>
<scope>test</scope>
<version>1.7.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>19.0.0</version>
<version>26.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>us.dynmap</groupId>
<artifactId>dynmap-api</artifactId>
<version>3.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.knarcraft</groupId>
<artifactId>knarlib</artifactId>
<version>1.2.18</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-base</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>de.themoep</groupId>
<artifactId>minedown</artifactId>
<version>1.7.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.geysermc.geyser</groupId>
<artifactId>api</artifactId>
<version>2.2.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>api</artifactId>
<version>2.2.2-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<version>3.8.1</version>
<configuration>
<source>16</source>
<target>16</target>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>net.knarcraft.stargate.lib.metrics</shadedPattern>
</relocation>
<relocation>
<pattern>net.knarcraft.knarlib</pattern>
<shadedPattern>net.knarcraft.stargate.lib.knarlib</shadedPattern>
</relocation>
<relocation>
<pattern>de.themoep</pattern>
<shadedPattern>net.knarcraft.stargate.lib.minedown</shadedPattern>
</relocation>
<relocation>
<pattern>org.jetbrains.annotations</pattern>
<shadedPattern>net.knarcraft.blacksmith.lib.annotations</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>de.themoep:minedown</artifact>
<includes>
<include>de/themoep/minedown/**</include>
</includes>
</filter>
<filter>
<artifact>org.bstats</artifact>
<includes>
<include>org/bstats/**</include>
</includes>
</filter>
<filter>
<artifact>net.knarcraft:knarlib</artifact>
<includes>
<include>net/knarcraft/knarlib/**</include>
</includes>
</filter>
<filter>
<artifact>org.jetbrains:annotations</artifact>
<includes>
<include>org/jetbrains/annotations/**</include>
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
</project>

View File

@@ -1,34 +1,39 @@
package net.knarcraft.stargate;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.Translator;
import net.knarcraft.knarlib.plugin.ConfigCommentPlugin;
import net.knarcraft.knarlib.util.ConfigHelper;
import net.knarcraft.knarlib.util.UpdateChecker;
import net.knarcraft.stargate.command.CommandStarGate;
import net.knarcraft.stargate.command.StarGateTabCompleter;
import net.knarcraft.stargate.config.EconomyConfig;
import net.knarcraft.stargate.config.MessageSender;
import net.knarcraft.stargate.config.StargateConfig;
import net.knarcraft.stargate.config.StargateGateConfig;
import net.knarcraft.stargate.config.addons.EconomyConfig;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.container.ChunkUnloadRequest;
import net.knarcraft.stargate.listener.BlockEventListener;
import net.knarcraft.stargate.listener.EntityEventListener;
import net.knarcraft.stargate.container.ControlBlockUpdateRequest;
import net.knarcraft.stargate.listener.EnvironmentChangeListener;
import net.knarcraft.stargate.listener.PlayerEventListener;
import net.knarcraft.stargate.listener.PluginEventListener;
import net.knarcraft.stargate.listener.PortalEventListener;
import net.knarcraft.stargate.listener.TeleportEventListener;
import net.knarcraft.stargate.listener.VehicleEventListener;
import net.knarcraft.stargate.listener.WorldEventListener;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.listener.StargateBreakListener;
import net.knarcraft.stargate.listener.StargateCreateDestroyListener;
import net.knarcraft.stargate.listener.StargateTeleportListener;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.thread.BlockChangeThread;
import net.knarcraft.stargate.thread.ChunkUnloadThread;
import net.knarcraft.stargate.thread.ControlBlocksUpdateThread;
import net.knarcraft.stargate.thread.StarGateThread;
import org.bukkit.Server;
import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.file.FileConfiguration;
import net.knarcraft.stargate.utility.BStatsHelper;
import net.knarcraft.stargate.utility.PortalUtil;
import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;
import org.bukkit.scheduler.BukkitScheduler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.LinkedList;
@@ -37,23 +42,47 @@ import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
/*
Stargate - A portal plugin for Bukkit
Copyright (C) 2011 Shaun (sturmeh)
Copyright (C) 2011 Dinnerbone
Copyright (C) 2011-2013 Steven "Drakia" Scott <Contact@TheDgtl.net>
Copyright (C) 2015-2020 Michael Smith (PseudoKnight)
Copyright (C) 2021-2025 Kristian Knarvik (EpicKnarvik97)
The following license notice applies to all source and resource files in the Stargate project:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* The main class of the Stargate plugin
*/
@SuppressWarnings("unused")
public class Stargate extends JavaPlugin {
public class Stargate extends ConfigCommentPlugin {
//Used for changing gate open/closed material.
private static final Queue<BlockChangeRequest> blockChangeRequestQueue = new LinkedList<>();
private static final String CONFIG_FILE_NAME = "config.yml";
private static final Queue<BlockChangeRequest> controlBlockUpdateRequestQueue = new LinkedList<>();
private static final Queue<ControlBlockUpdateRequest> CONTROL_BLOCK_UPDATE_REQUEST_QUEUE = new LinkedList<>();
private static final Queue<ChunkUnloadRequest> chunkUnloadQueue = new PriorityQueue<>();
private static Logger logger;
private static Stargate stargate;
private static String pluginVersion;
private static PluginManager pluginManager;
private static StargateConfig stargateConfig;
private static String updateAvailable = null;
/**
* Empty constructor necessary for Spigot
@@ -74,11 +103,33 @@ public class Stargate extends JavaPlugin {
super(loader, descriptionFile, dataFolder, file);
}
/**
* Stores information about an available update
*
* <p>If a non-null version is given, joining admins will be alerted about the new update.</p>
*
* @param version <p>The version of the new update available</p>
*/
public static void setUpdateAvailable(@NotNull String version) {
updateAvailable = version;
}
/**
* Gets information about an available update
*
* @return <p>The version number if an update is available. Null otherwise</p>
*/
@Nullable
public static String getUpdateAvailable() {
return updateAvailable;
}
/**
* Gets an instance of this plugin
*
* @return <p>An instance of this plugin, or null if not instantiated</p>
*/
@NotNull
public static Stargate getInstance() {
return stargate;
}
@@ -88,28 +139,41 @@ public class Stargate extends JavaPlugin {
*
* @param request <p>The request to add</p>
*/
public static void addBlockChangeRequest(BlockChangeRequest request) {
public static void addControlBlockUpdateRequest(@Nullable BlockChangeRequest request) {
if (request != null) {
blockChangeRequestQueue.add(request);
controlBlockUpdateRequestQueue.add(request);
}
}
/**
* Gets the queue containing block change requests
* Gets the queue containing control block update requests
*
* @return <p>A block change request queue</p>
* @return <p>A control block update request queue</p>
*/
public static Queue<BlockChangeRequest> getBlockChangeRequestQueue() {
return blockChangeRequestQueue;
@NotNull
public static Queue<BlockChangeRequest> getControlBlockUpdateRequestQueue() {
return controlBlockUpdateRequestQueue;
}
/**
* Gets the sender for sending messages to players
* Adds a control block update request to the request queue
*
* @return <p>The sender for sending messages to players</p>
* @param request <p>The request to add</p>
*/
public static MessageSender getMessageSender() {
return stargateConfig.getMessageSender();
public static void addControlBlockUpdateRequest(@Nullable ControlBlockUpdateRequest request) {
if (request != null) {
CONTROL_BLOCK_UPDATE_REQUEST_QUEUE.add(request);
}
}
/**
* Gets the queue containing button update requests
*
* @return <p>A button update request queue</p>
*/
@NotNull
public static Queue<ControlBlockUpdateRequest> getButtonUpdateRequestQueue() {
return CONTROL_BLOCK_UPDATE_REQUEST_QUEUE;
}
/**
@@ -117,6 +181,7 @@ public class Stargate extends JavaPlugin {
*
* @return <p>The object containing gate configuration values</p>
*/
@NotNull
public static StargateGateConfig getGateConfig() {
return stargateConfig.getStargateGateConfig();
}
@@ -126,6 +191,7 @@ public class Stargate extends JavaPlugin {
*
* @return <p>This plugin's version</p>
*/
@NotNull
public static String getPluginVersion() {
return pluginVersion;
}
@@ -135,6 +201,7 @@ public class Stargate extends JavaPlugin {
*
* @return <p>The logger</p>
*/
@NotNull
public static Logger getConsoleLogger() {
return logger;
}
@@ -155,8 +222,8 @@ public class Stargate extends JavaPlugin {
* @param route <p>The class name/route where something happened</p>
* @param message <p>A message describing what happened</p>
*/
public static void debug(String route, String message) {
if (stargateConfig == null || stargateConfig.isDebuggingEnabled()) {
public static void debug(@NotNull String route, @NotNull String message) {
if (stargateConfig == null || stargateConfig.isNotLoaded() || stargateConfig.isDebuggingEnabled()) {
logger.info("[Stargate::" + route + "] " + message);
} else {
logger.log(Level.FINEST, "[Stargate::" + route + "] " + message);
@@ -168,8 +235,8 @@ public class Stargate extends JavaPlugin {
*
* @param message <p>The message to log</p>
*/
public static void logInfo(String message) {
logger.info(getBackupString("prefix") + message);
public static void logInfo(@NotNull String message) {
log(Level.INFO, message);
}
/**
@@ -177,7 +244,7 @@ public class Stargate extends JavaPlugin {
*
* @param message <p>The message to log</p>
*/
public static void logSevere(String message) {
public static void logSevere(@NotNull String message) {
log(Level.SEVERE, message);
}
@@ -186,7 +253,7 @@ public class Stargate extends JavaPlugin {
*
* @param message <p>The message to log</p>
*/
public static void logWarning(String message) {
public static void logWarning(@NotNull String message) {
log(Level.WARNING, message);
}
@@ -196,8 +263,11 @@ public class Stargate extends JavaPlugin {
* @param severity <p>The severity of the event triggering the message</p>
* @param message <p>The message to log</p>
*/
private static void log(Level severity, String message) {
logger.log(severity, getBackupString("prefix") + message);
private static void log(@NotNull Level severity, @NotNull String message) {
if (logger == null) {
logger = Bukkit.getLogger();
}
logger.log(severity, message);
}
/**
@@ -207,6 +277,7 @@ public class Stargate extends JavaPlugin {
*
* @return <p>The folder for storing the portal database</p>
*/
@NotNull
public static String getPortalFolder() {
return stargateConfig.getPortalFolder();
}
@@ -218,6 +289,7 @@ public class Stargate extends JavaPlugin {
*
* @return <p>The folder storing gate files</p>
*/
@NotNull
public static String getGateFolder() {
return stargateConfig.getGateFolder();
}
@@ -227,69 +299,27 @@ public class Stargate extends JavaPlugin {
*
* @return <p>The default network</p>
*/
@NotNull
public static String getDefaultNetwork() {
return stargateConfig.getStargateGateConfig().getDefaultPortalNetwork();
}
/**
* Gets a translated string given its string key
*
* <p>The name/key is the string before the equals sign in the language files</p>
*
* @param name <p>The name/key of the string to get</p>
* @return <p>The full translated string</p>
*/
public static String getString(String name) {
return stargateConfig.getLanguageLoader().getString(name);
}
/**
* Gets a backup string given its string key
*
* <p>The name/key is the string before the equals sign in the language files</p>
* Gets a backup string given its message key
*
* @param name <p>The name/key of the string to get</p>
* @return <p>The full string in the backup language (English)</p>
*/
public static String getBackupString(String name) {
public static @NotNull String getBackupString(@NotNull Message name) {
return stargateConfig.getLanguageLoader().getBackupString(name);
}
/**
* Replaces a list of variables in a string in the order they are given
*
* @param input <p>The input containing the variables</p>
* @param search <p>The variables to replace</p>
* @param values <p>The replacement values</p>
* @return <p>The input string with the search values replaced with the given values</p>
*/
public static String replaceVars(String input, String[] search, String[] values) {
if (search.length != values.length) {
throw new IllegalArgumentException("The number of search values and replace values do not match.");
}
for (int i = 0; i < search.length; i++) {
input = replaceVars(input, search[i], values[i]);
}
return input;
}
/**
* Replaces a variable in a string
*
* @param input <p>The input containing the variables</p>
* @param search <p>The variable to replace</p>
* @param value <p>The replacement value</p>
* @return <p>The input string with the search replaced with value</p>
*/
public static String replaceVars(String input, String search, String value) {
return input.replace(search, value);
}
/**
* Gets this plugin's plugin manager
*
* @return <p>A plugin manager</p>
*/
@NotNull
public static PluginManager getPluginManager() {
return pluginManager;
}
@@ -299,29 +329,43 @@ public class Stargate extends JavaPlugin {
*
* @return <p>The object containing economy config values</p>
*/
@NotNull
public static EconomyConfig getEconomyConfig() {
return stargateConfig.getEconomyConfig();
}
@Override
public void onDisable() {
PortalHandler.closeAllPortals();
PortalUtil.closeAllPortals();
PortalRegistry.clearPortals();
stargateConfig.clearManagedWorlds();
if (stargateConfig != null) {
stargateConfig.clearManagedWorlds();
}
getServer().getScheduler().cancelTasks(this);
}
@Override
public void onEnable() {
Stargate.stargate = this;
Stargate.logger = getLogger();
ConfigHelper.saveDefaults(this);
PluginDescriptionFile pluginDescriptionFile = this.getDescription();
pluginManager = getServer().getPluginManager();
FileConfiguration newConfig = this.getConfig();
logger = Logger.getLogger("Minecraft");
Server server = getServer();
stargate = this;
stargateConfig = new StargateConfig(logger);
stargateConfig.finishSetup();
// Set temporary string formatter before strings are loaded
SGFormatBuilder.setStringFormatter(new StringFormatter(this.getDescription().getName(), new Translator()));
try {
stargateConfig = new StargateConfig(logger);
stargateConfig.finishSetup();
} catch (NoClassDefFoundError exception) {
logSevere("Could not properly load. Class not found: " +
exception.getMessage() + "\nThis is probably because you are using CraftBukkit, or other outdated" +
"Minecraft server software. Minecraft server software based on Spigot or Paper is required. Paper" +
" is recommended, and can be downloaded at: https://papermc.io/downloads/paper");
this.onDisable();
return;
}
pluginVersion = pluginDescriptionFile.getVersion();
@@ -333,7 +377,13 @@ public class Stargate extends JavaPlugin {
//Run necessary threads
runThreads();
this.registerCommands();
registerCommand("stargate", new CommandStarGate(this), new StarGateTabCompleter());
//Check for any available updates
UpdateChecker.checkForUpdate(this, "https://api.spigotmc.org/legacy/update.php?resource=97784",
Stargate::getPluginVersion, Stargate::setUpdateAvailable);
BStatsHelper.initialize(this);
}
/**
@@ -343,6 +393,8 @@ public class Stargate extends JavaPlugin {
BukkitScheduler scheduler = getServer().getScheduler();
scheduler.runTaskTimer(this, new StarGateThread(), 0L, 100L);
scheduler.runTaskTimer(this, new BlockChangeThread(), 0L, 1L);
scheduler.runTaskTimer(this, new ControlBlocksUpdateThread(), 0L,
getStargateConfig().getStargateGateConfig().controlUpdateDelay());
scheduler.runTaskTimer(this, new ChunkUnloadThread(), 0L, 100L);
}
@@ -351,25 +403,10 @@ public class Stargate extends JavaPlugin {
*/
private void registerEventListeners() {
pluginManager.registerEvents(new PlayerEventListener(), this);
pluginManager.registerEvents(new BlockEventListener(), this);
pluginManager.registerEvents(new VehicleEventListener(), this);
pluginManager.registerEvents(new EntityEventListener(), this);
pluginManager.registerEvents(new PortalEventListener(), this);
pluginManager.registerEvents(new WorldEventListener(), this);
pluginManager.registerEvents(new PluginEventListener(this), this);
pluginManager.registerEvents(new TeleportEventListener(), this);
}
/**
* Registers a command for this plugin
*/
private void registerCommands() {
PluginCommand stargateCommand = this.getCommand("stargate");
if (stargateCommand != null) {
stargateCommand.setExecutor(new CommandStarGate());
stargateCommand.setTabCompleter(new StarGateTabCompleter());
}
pluginManager.registerEvents(new StargateCreateDestroyListener(), this);
pluginManager.registerEvents(new StargateTeleportListener(), this);
pluginManager.registerEvents(new EnvironmentChangeListener(this), this);
pluginManager.registerEvents(new StargateBreakListener(), this);
}
/**
@@ -377,6 +414,7 @@ public class Stargate extends JavaPlugin {
*
* @return <p>The chunk unload queue</p>
*/
@NotNull
public static Queue<ChunkUnloadRequest> getChunkUnloadQueue() {
return chunkUnloadQueue;
}
@@ -386,7 +424,7 @@ public class Stargate extends JavaPlugin {
*
* @param request <p>The new chunk unload request to add</p>
*/
public static void addChunkUnloadRequest(ChunkUnloadRequest request) {
public static void addChunkUnloadRequest(@NotNull ChunkUnloadRequest request) {
chunkUnloadQueue.removeIf((item) -> item.getChunkToUnload().equals(request.getChunkToUnload()));
chunkUnloadQueue.add(request);
}
@@ -396,7 +434,9 @@ public class Stargate extends JavaPlugin {
*
* @return <p>The stargate configuration</p>
*/
@NotNull
public static StargateConfig getStargateConfig() {
return stargateConfig;
}
}

View File

@@ -1,12 +1,20 @@
package net.knarcraft.stargate.command;
import de.themoep.minedown.MineDown;
import net.knarcraft.knarlib.util.FileHelper;
import net.knarcraft.stargate.Stargate;
import org.bukkit.ChatColor;
import net.knarcraft.stargate.config.formatting.Message;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* This command represents the plugin's about command
*/
@@ -18,13 +26,22 @@ public class CommandAbout implements CommandExecutor {
ChatColor textColor = ChatColor.GOLD;
ChatColor highlightColor = ChatColor.GREEN;
commandSender.sendMessage(textColor + "Stargate Plugin originally created by " + highlightColor +
"Drakia" + textColor + ", and revived by " + highlightColor + "EpicKnarvik97");
commandSender.sendMessage(textColor + "Go to " + highlightColor +
"https://git.knarcraft.net/EpicKnarvik97/Stargate " + textColor + "for the official repository");
String author = Stargate.getStargateConfig().getLanguageLoader().getString("author");
if (!author.isEmpty())
try (InputStream inputStream = FileHelper.getInputStreamForInternalFile("/messages/about.md")) {
if (inputStream != null) {
List<String> lines = FileHelper.readLines(FileHelper.getBufferedReaderFromInputStream(inputStream));
String aboutMessageString = String.join("\n", lines);
BaseComponent[] component = MineDown.parse(aboutMessageString);
commandSender.spigot().sendMessage(component);
}
} catch (IOException ioException) {
commandSender.sendMessage("Internal error");
}
String author = Stargate.getStargateConfig().getLanguageLoader().getString(Message.AUTHOR);
if (!author.isEmpty()) {
commandSender.sendMessage(textColor + "Language created by " + highlightColor + author);
}
return true;
}

View File

@@ -3,16 +3,27 @@ package net.knarcraft.stargate.command;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.ConfigOption;
import net.knarcraft.stargate.config.ConfigTag;
import net.knarcraft.stargate.config.OptionDataType;
import net.knarcraft.stargate.config.Permission;
import net.knarcraft.stargate.config.addons.DynmapManager;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.portal.PortalSignDrawer;
import org.bukkit.ChatColor;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* This command represents the config command for changing config values
@@ -23,8 +34,8 @@ public class CommandConfig implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (commandSender instanceof Player player) {
if (!player.hasPermission("stargate.admin.config")) {
Stargate.getMessageSender().sendErrorMessage(commandSender, "Permission Denied");
if (!PermissionHelper.hasPermission(player, Permission.CONFIG)) {
new SGFormatBuilder("Permission Denied").error(commandSender);
return true;
}
}
@@ -35,7 +46,11 @@ public class CommandConfig implements CommandExecutor {
return false;
}
if (args.length > 1) {
updateConfigValue(selectedOption, commandSender, args[1]);
if (selectedOption.getDataType() == OptionDataType.STRING_LIST) {
updateListConfigValue(selectedOption, commandSender, args);
} else {
updateConfigValue(selectedOption, commandSender, args[1]);
}
} else {
//Display info and the current value of the given config value
printConfigOptionValue(commandSender, selectedOption);
@@ -55,13 +70,14 @@ public class CommandConfig implements CommandExecutor {
* @param commandSender <p>The command sender that changed the value</p>
* @param value <p>The new value of the config option</p>
*/
private void updateConfigValue(ConfigOption selectedOption, CommandSender commandSender, String value) {
private void updateConfigValue(@NotNull ConfigOption selectedOption, @NotNull CommandSender commandSender,
@NotNull String value) {
FileConfiguration configuration = Stargate.getInstance().getConfig();
//Validate any sign colors
if (ConfigTag.COLOR.isTagged(selectedOption)) {
try {
ChatColor.valueOf(value.toUpperCase());
ChatColor.of(value.toUpperCase());
} catch (IllegalArgumentException | NullPointerException ignored) {
commandSender.sendMessage(ChatColor.RED + "Invalid color given");
return;
@@ -70,14 +86,7 @@ public class CommandConfig implements CommandExecutor {
//Store the config values, accounting for the data type
switch (selectedOption.getDataType()) {
case BOOLEAN -> {
boolean newValue = Boolean.parseBoolean(value);
if (selectedOption == ConfigOption.ENABLE_BUNGEE && newValue != Stargate.getGateConfig().enableBungee()) {
Stargate.getStargateConfig().startStopBungeeListener(newValue);
}
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, newValue);
configuration.set(selectedOption.getConfigNode(), newValue);
}
case BOOLEAN -> updateBooleanConfigValue(selectedOption, value, configuration);
case INTEGER -> {
Integer intValue = getInteger(commandSender, selectedOption, value);
if (intValue == null) {
@@ -87,23 +96,17 @@ public class CommandConfig implements CommandExecutor {
configuration.set(selectedOption.getConfigNode(), intValue);
}
}
case DOUBLE -> {
Double doubleValue = getDouble(commandSender, selectedOption, value);
if (doubleValue == null) {
return;
} else {
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, doubleValue);
configuration.set(selectedOption.getConfigNode(), doubleValue);
}
}
case STRING -> {
if (selectedOption == ConfigOption.GATE_FOLDER || selectedOption == ConfigOption.PORTAL_FOLDER ||
selectedOption == ConfigOption.DEFAULT_GATE_NETWORK) {
if (value.contains("../") || value.contains("..\\")) {
commandSender.sendMessage(ChatColor.RED + "Path traversal characters cannot be used");
return;
}
}
if (ConfigTag.COLOR.isTagged(selectedOption)) {
if (!registerColor(selectedOption, value, commandSender)) {
return;
}
}
if (selectedOption == ConfigOption.LANGUAGE) {
Stargate.getStargateConfig().getLanguageLoader().setChosenLanguage(value);
}
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, value);
updateStringConfigValue(selectedOption, commandSender, value);
configuration.set(selectedOption.getConfigNode(), value);
}
default -> {
@@ -112,10 +115,159 @@ public class CommandConfig implements CommandExecutor {
}
}
saveAndReload(selectedOption, commandSender);
}
/**
* Updates a boolean config value
*
* @param selectedOption <p>The option which should be updated</p>
* @param value <p>The new value of the config option</p>
* @param configuration <p>The configuration file to save to</p>
*/
private void updateBooleanConfigValue(@NotNull ConfigOption selectedOption, @NotNull String value,
@NotNull FileConfiguration configuration) {
boolean newValue = Boolean.parseBoolean(value);
if (selectedOption == ConfigOption.ENABLE_BUNGEE && newValue != Stargate.getGateConfig().enableBungee()) {
Stargate.getStargateConfig().startStopBungeeListener(newValue);
}
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, newValue);
configuration.set(selectedOption.getConfigNode(), newValue);
}
/**
* Updates a string config value
*
* @param selectedOption <p>The option which should be updated</p>
* @param commandSender <p>The command sender that changed the value</p>
* @param value <p>The new value of the config option</p>
*/
private void updateStringConfigValue(@NotNull ConfigOption selectedOption, @NotNull CommandSender commandSender,
@NotNull String value) {
if (selectedOption == ConfigOption.GATE_FOLDER || selectedOption == ConfigOption.PORTAL_FOLDER ||
selectedOption == ConfigOption.DEFAULT_GATE_NETWORK) {
if (value.contains("../") || value.contains("..\\")) {
commandSender.sendMessage(ChatColor.RED + "Path traversal characters cannot be used");
return;
}
}
if (ConfigTag.COLOR.isTagged(selectedOption)) {
if (!registerColor(selectedOption, value, commandSender)) {
return;
}
}
if (selectedOption == ConfigOption.LANGUAGE) {
Stargate.getStargateConfig().getLanguageLoader().setChosenLanguage(value);
}
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, value);
}
/**
* Updates a config value
*
* @param selectedOption <p>The option which should be updated</p>
* @param commandSender <p>The command sender that changed the value</p>
* @param arguments <p>The arguments for the new config option</p>
*/
private void updateListConfigValue(@NotNull ConfigOption selectedOption, @NotNull CommandSender commandSender,
@NotNull String[] arguments) {
FileConfiguration configuration = Stargate.getInstance().getConfig();
if (selectedOption == ConfigOption.PER_SIGN_COLORS) {
if (arguments.length < 4) {
new SGFormatBuilder("Usage: /sg config perSignColors <SIGN_TYPE> <MAIN_COLOR> <HIGHLIGHTING_COLOR>").error(commandSender);
return;
}
String colorString = parsePerSignColorInput(commandSender, arguments);
if (colorString == null) {
return;
}
//Update the per-sign colors according to input
updatePerSignColors(arguments[1], colorString, configuration);
}
saveAndReload(selectedOption, commandSender);
}
/**
* Parses the input given for changing the per-color string
*
* @param commandSender <p>The command sender that triggered the command</p>
* @param arguments <p>The arguments given by the user</p>
* @return <p>The per-sign color string to update with, or null if the input was invalid</p>
*/
@Nullable
private String parsePerSignColorInput(@NotNull CommandSender commandSender, @NotNull String[] arguments) {
//Make sure the sign type is an actual sign
if (Material.matchMaterial(arguments[1] + "_SIGN") == null) {
new SGFormatBuilder("The given sign type is invalid").error(commandSender);
return null;
}
String colorString = arguments[1] + ":";
//Validate the colors given by the user
String[] errorMessage = new String[]{"The given main sign color is invalid!", "The given highlight sign color " +
"is invalid!"};
String[] newColors = new String[2];
for (int i = 0; i < 2; i++) {
if (validatePerSignColor(arguments[i + 2])) {
newColors[i] = arguments[i + 2];
} else {
new SGFormatBuilder(errorMessage[i]).error(commandSender);
return null;
}
}
colorString += String.join(",", newColors);
return colorString;
}
/**
* Updates the per-sign colors with the given input
*
* @param signType <p>The sign type that is updated</p>
* @param colorString <p>The new color string to replace any previous value with</p>
* @param configuration <p>The file configuration to update with the new per-sign colors</p>
*/
private void updatePerSignColors(@NotNull String signType, @NotNull String colorString,
@NotNull FileConfiguration configuration) {
List<String> newColorStrings = new ArrayList<>();
List<?> oldColors = (List<?>) Stargate.getStargateConfig().getConfigOptionsReference().get(
ConfigOption.PER_SIGN_COLORS);
for (Object object : oldColors) {
newColorStrings.add(String.valueOf(object));
}
newColorStrings.removeIf((item) -> item.startsWith(signType));
newColorStrings.add(colorString);
Stargate.getStargateConfig().getConfigOptionsReference().put(ConfigOption.PER_SIGN_COLORS, newColorStrings);
configuration.set(ConfigOption.PER_SIGN_COLORS.getConfigNode(), newColorStrings);
}
/**
* Tries to validate one of the colors given when changing per-sign colors
*
* @param color <p>The color chosen by the user</p>
* @return <p>True if the given color is valid</p>
*/
private boolean validatePerSignColor(@NotNull String color) {
ChatColor newHighlightColor = parseColor(color);
return newHighlightColor != null || color.equalsIgnoreCase("default") ||
color.equalsIgnoreCase("inverted");
}
/**
* Saves the configuration file and reloads as necessary
*
* @param selectedOption <p>The config option that was changed</p>
* @param commandSender <p>The command sender that executed the config command</p>
*/
private void saveAndReload(@NotNull ConfigOption selectedOption, @NotNull CommandSender commandSender) {
//Save the config file and reload if necessary
Stargate.getInstance().saveConfig();
Stargate.getMessageSender().sendSuccessMessage(commandSender, "Config updated");
new SGFormatBuilder("Config updated").success(commandSender);
//Reload whatever is necessary
reloadIfNecessary(commandSender, selectedOption);
@@ -128,7 +280,8 @@ public class CommandConfig implements CommandExecutor {
* @param commandSender <p>The command sender to alert if the color is invalid</p>
* @param value <p>The new option value</p>
*/
private boolean registerColor(ConfigOption selectedOption, String value, CommandSender commandSender) {
private boolean registerColor(@NotNull ConfigOption selectedOption, @NotNull String value,
@NotNull CommandSender commandSender) {
ChatColor parsedColor = parseColor(value);
if (parsedColor == null) {
commandSender.sendMessage(ChatColor.RED + "Invalid color given");
@@ -151,9 +304,10 @@ public class CommandConfig implements CommandExecutor {
* @param value <p>The value to parse</p>
* @return <p>The parsed color or null</p>
*/
private ChatColor parseColor(String value) {
@Nullable
private ChatColor parseColor(@NotNull String value) {
try {
return ChatColor.valueOf(value.toUpperCase());
return ChatColor.of(value.toUpperCase());
} catch (IllegalArgumentException | NullPointerException ignored) {
return null;
}
@@ -167,7 +321,9 @@ public class CommandConfig implements CommandExecutor {
* @param value <p>The value given</p>
* @return <p>An integer, or null if it was invalid</p>
*/
private Integer getInteger(CommandSender commandSender, ConfigOption selectedOption, String value) {
@Nullable
private Integer getInteger(@NotNull CommandSender commandSender, @NotNull ConfigOption selectedOption,
@NotNull String value) {
try {
int intValue = Integer.parseInt(value);
@@ -183,30 +339,67 @@ public class CommandConfig implements CommandExecutor {
}
}
/**
* Gets a double from a string
*
* @param commandSender <p>The command sender that sent the config command</p>
* @param selectedOption <p>The option the command sender is trying to change</p>
* @param value <p>The value given</p>
* @return <p>A double, or null if it was invalid</p>
*/
@Nullable
private Double getDouble(@NotNull CommandSender commandSender, @NotNull ConfigOption selectedOption,
@NotNull String value) {
try {
double doubleValue = Double.parseDouble(value);
if (selectedOption == ConfigOption.EXIT_VELOCITY && doubleValue < 0) {
commandSender.sendMessage(ChatColor.RED + "This config option cannot be negative.");
return null;
}
return doubleValue;
} catch (NumberFormatException exception) {
commandSender.sendMessage(ChatColor.RED + "Invalid number given");
return null;
}
}
/**
* Reloads the config if necessary
*
* @param commandSender <p>The command sender initiating the reload</p>
* @param configOption <p>The changed config option</p>
*/
private void reloadIfNecessary(CommandSender commandSender, ConfigOption configOption) {
private void reloadIfNecessary(@NotNull CommandSender commandSender, @NotNull ConfigOption configOption) {
if (ConfigTag.requiresFullReload(configOption)) {
//Reload everything
Stargate.getStargateConfig().reload(commandSender);
} else if (ConfigTag.requiresPortalReload(configOption)) {
//Just unload and reload the portals
Stargate.getStargateConfig().unloadAllPortals();
Stargate.getStargateConfig().loadAllPortals();
} else if (ConfigTag.requiresLanguageReload(configOption)) {
//Reload the language loader
Stargate.getStargateConfig().getLanguageLoader().reload();
//Re-draw all portal signs
for (Portal portal : PortalRegistry.getAllPortals()) {
portal.drawSign();
} else {
if (ConfigTag.requiresColorReload(configOption)) {
Stargate.getStargateConfig().getStargateGateConfig().loadPerSignColors();
}
if (ConfigTag.requiresPortalReload(configOption)) {
//Just unload and reload the portals
Stargate.getStargateConfig().unloadAllPortals();
Stargate.getStargateConfig().loadAllPortals();
}
if (ConfigTag.requiresLanguageReload(configOption)) {
//Reload the language loader
Stargate.getStargateConfig().getLanguageLoader().reload();
//Re-draw all portal signs
for (Portal portal : PortalRegistry.getAllPortals()) {
portal.drawSign();
}
}
if (ConfigTag.requiresEconomyReload(configOption)) {
//Load or unload Vault and Economy as necessary
Stargate.getStargateConfig().reloadEconomy();
}
if (ConfigTag.requiresDynmapReload(configOption)) {
//Regenerate all Dynmap markers
DynmapManager.addAllPortalMarkers();
}
} else if (ConfigTag.requiresEconomyReload(configOption)) {
//Load or unload Vault and Economy as necessary
Stargate.getStargateConfig().reloadEconomy();
}
}
@@ -216,7 +409,7 @@ public class CommandConfig implements CommandExecutor {
* @param sender <p>The command sender that sent the command</p>
* @param option <p>The config option to print information about</p>
*/
private void printConfigOptionValue(CommandSender sender, ConfigOption option) {
private void printConfigOptionValue(@NotNull CommandSender sender, @NotNull ConfigOption option) {
Object value = Stargate.getStargateConfig().getConfigOptions().get(option);
sender.sendMessage(getOptionDescription(option));
sender.sendMessage(ChatColor.GREEN + "Current value: " + ChatColor.GOLD + value);
@@ -227,8 +420,8 @@ public class CommandConfig implements CommandExecutor {
*
* @param sender <p>The command sender to display the config list to</p>
*/
private void displayConfigValues(CommandSender sender) {
sender.sendMessage(ChatColor.GREEN + Stargate.getBackupString("prefix") + ChatColor.GOLD +
private void displayConfigValues(@NotNull CommandSender sender) {
sender.sendMessage(ChatColor.GREEN + Stargate.getBackupString(Message.PREFIX) + ChatColor.GOLD +
"Config values:");
for (ConfigOption option : ConfigOption.values()) {
sender.sendMessage(getOptionDescription(option));
@@ -241,9 +434,15 @@ public class CommandConfig implements CommandExecutor {
* @param option <p>The option to describe</p>
* @return <p>A string describing the config option</p>
*/
private String getOptionDescription(ConfigOption option) {
@NotNull
private String getOptionDescription(@NotNull ConfigOption option) {
Object defaultValue = option.getDefaultValue();
String stringValue = String.valueOf(defaultValue);
if (option.getDataType() == OptionDataType.STRING_LIST) {
stringValue = "[" + String.join(",", (String[]) defaultValue) + "]";
}
return ChatColor.GOLD + option.getName() + ChatColor.WHITE + " - " + ChatColor.GREEN + option.getDescription() +
ChatColor.DARK_GRAY + " (Default: " + ChatColor.GRAY + option.getDefaultValue() + ChatColor.DARK_GRAY + ")";
ChatColor.DARK_GRAY + " (Default: " + ChatColor.GRAY + stringValue + ChatColor.DARK_GRAY + ")";
}
}

View File

@@ -1,6 +1,9 @@
package net.knarcraft.stargate.command;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.Permission;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.utility.PermissionHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@@ -16,8 +19,8 @@ public class CommandReload implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (commandSender instanceof Player player) {
if (!player.hasPermission("stargate.admin.reload")) {
Stargate.getMessageSender().sendErrorMessage(commandSender, "Permission Denied");
if (!PermissionHelper.hasPermission(player, Permission.RELOAD)) {
new SGFormatBuilder("Permission Denied").error(commandSender);
return true;
}
}

View File

@@ -1,13 +1,15 @@
package net.knarcraft.stargate.command;
import net.knarcraft.stargate.Stargate;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.ChatColor;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
/**
* This command represents any command which starts with stargate
*
@@ -15,6 +17,11 @@ import org.jetbrains.annotations.NotNull;
* the plugin itself, not commands for functions of the plugin.</p>
*/
public class CommandStarGate implements CommandExecutor {
private final Plugin stargate;
public CommandStarGate(Plugin stargate) {
this.stargate = stargate;
}
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@@ -25,14 +32,15 @@ public class CommandStarGate implements CommandExecutor {
} else if (args[0].equalsIgnoreCase("reload")) {
return new CommandReload().onCommand(commandSender, command, s, args);
} else if (args[0].equalsIgnoreCase("config")) {
String[] subArgs = (String[]) ArrayUtils.remove(args, 0);
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
return new CommandConfig().onCommand(commandSender, command, s, subArgs);
}
return false;
} else {
commandSender.sendMessage(ChatColor.GOLD + "Stargate version " +
ChatColor.GREEN + Stargate.getPluginVersion());
commandSender.sendMessage(ChatColor.GREEN + "Stargate version " + ChatColor.GOLD + stargate.getDescription().getVersion()
+ ChatColor.GREEN + " running on " + ChatColor.GOLD + Bukkit.getServer().getVersion());
return true;
}
}
}

View File

@@ -2,7 +2,9 @@ package net.knarcraft.stargate.command;
import net.knarcraft.stargate.config.ConfigOption;
import net.knarcraft.stargate.config.OptionDataType;
import org.bukkit.ChatColor;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
@@ -12,20 +14,34 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static net.knarcraft.knarlib.util.TabCompletionHelper.filterMatchingStartsWith;
/**
* This is the completer for stargates config sub-command (/sg config)
*/
public class ConfigTabCompleter implements TabCompleter {
private List<String> signTypes;
private List<String> booleans;
private List<String> integers;
private List<String> chatColors;
private List<String> languages;
private List<String> extendedColors;
private List<String> doubles;
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (signTypes == null || booleans == null || integers == null || chatColors == null || languages == null) {
initializeAutoCompleteLists();
}
if (args.length > 1) {
ConfigOption selectedOption = ConfigOption.getByName(args[0]);
if (selectedOption == null) {
return new ArrayList<>();
} else if (selectedOption.getDataType() == OptionDataType.STRING_LIST) {
return getPossibleStringListOptionValues(selectedOption, args);
} else {
return getPossibleOptionValues(selectedOption, args[1]);
}
@@ -34,27 +50,10 @@ public class ConfigTabCompleter implements TabCompleter {
for (ConfigOption option : ConfigOption.values()) {
configOptionNames.add(option.getName());
}
return filterMatching(configOptionNames, args[0]);
return filterMatchingStartsWith(configOptionNames, args[0]);
}
}
/**
* Find completable strings which match the text typed by the command's sender
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values which start with the player's typed text</p>
*/
private List<String> filterMatching(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Get possible values for the selected option
*
@@ -62,122 +61,173 @@ public class ConfigTabCompleter implements TabCompleter {
* @param typedText <p>The beginning of the typed text, for filtering matching results</p>
* @return <p>Some or all of the valid values for the option</p>
*/
private List<String> getPossibleOptionValues(ConfigOption selectedOption, String typedText) {
List<String> booleans = new ArrayList<>();
booleans.add("true");
booleans.add("false");
List<String> numbers = new ArrayList<>();
numbers.add("0");
numbers.add("5");
@Nullable
private List<String> getPossibleOptionValues(@NotNull ConfigOption selectedOption,
@NotNull String typedText) {
switch (selectedOption) {
case LANGUAGE:
case LANGUAGE -> {
//Return available languages
return filterMatching(getLanguages(), typedText);
case GATE_FOLDER:
case PORTAL_FOLDER:
case DEFAULT_GATE_NETWORK:
return filterMatchingStartsWith(languages, typedText);
}
case GATE_FOLDER, PORTAL_FOLDER, DEFAULT_GATE_NETWORK -> {
//Just return the default value as most values should be possible
if (typedText.trim().isEmpty()) {
return putStringInList((String) selectedOption.getDefaultValue());
return List.of((String) selectedOption.getDefaultValue());
} else {
return new ArrayList<>();
}
case MAIN_SIGN_COLOR:
case HIGHLIGHT_SIGN_COLOR:
case FREE_GATES_COLOR:
}
case MAIN_SIGN_COLOR, HIGHLIGHT_SIGN_COLOR, FREE_GATES_COLOR -> {
//Return all colors
return filterMatching(getColors(), typedText);
return filterMatchingStartsWith(chatColors, typedText);
}
}
//If the config value is a boolean, show the two boolean values
if (selectedOption.getDataType() == OptionDataType.BOOLEAN) {
return filterMatching(booleans, typedText);
return filterMatchingStartsWith(booleans, typedText);
}
//If the config value is an integer, display some valid numbers
if (selectedOption.getDataType() == OptionDataType.INTEGER) {
if (typedText.trim().isEmpty()) {
return numbers;
return integers;
} else {
return new ArrayList<>();
}
}
//If the config value is a double, display some valid numbers
if (selectedOption.getDataType() == OptionDataType.DOUBLE) {
if (typedText.trim().isEmpty()) {
return doubles;
} else {
return new ArrayList<>();
}
}
return null;
}
/**
* Gets all available languages
* Get possible values for the selected string list option
*
* @return <p>The available languages</p>
* @param selectedOption <p>The selected option</p>
* @param args <p>The arguments given by the user</p>
* @return <p>Some or all of the valid values for the option</p>
*/
private List<String> getLanguages() {
List<String> languages = new ArrayList<>();
@NotNull
private List<String> getPossibleStringListOptionValues(@NotNull ConfigOption selectedOption,
@NotNull String[] args) {
if (selectedOption == ConfigOption.PER_SIGN_COLORS) {
return getPerSignColorCompletion(args);
} else {
return new ArrayList<>();
}
}
/**
* Gets the tab completion values for completing the per-sign color text
*
* @param args <p>The arguments given by the user</p>
* @return <p>The options to give the user</p>
*/
@NotNull
private List<String> getPerSignColorCompletion(@NotNull String[] args) {
if (args.length < 3) {
return filterMatchingStartsWith(signTypes, args[1]);
} else if (args.length < 4) {
return filterMatchingStartsWith(extendedColors, args[2]);
} else if (args.length < 5) {
return filterMatchingStartsWith(extendedColors, args[3]);
}
return new ArrayList<>();
}
/**
* Initializes all lists of auto-completable values
*/
private void initializeAutoCompleteLists() {
booleans = new ArrayList<>();
booleans.add("true");
booleans.add("false");
integers = new ArrayList<>();
integers.add("0");
integers.add("5");
signTypes = new ArrayList<>();
for (Material material : Material.values()) {
if (Tag.STANDING_SIGNS.isTagged(material)) {
signTypes.add(material.toString().replace("_SIGN", ""));
}
}
getColors();
initializeLanguages();
extendedColors = new ArrayList<>(chatColors);
extendedColors.add("default");
extendedColors.add("inverted");
doubles = new ArrayList<>();
doubles.add("5");
doubles.add("1");
doubles.add("0.5");
doubles.add("0.1");
}
/**
* Initializes the list of chat colors
*/
private void getColors() {
chatColors = new ArrayList<>();
for (ChatColor color : getChatColors()) {
chatColors.add(color.getName());
}
}
/**
* Gets available chat colors
*
* @return <p>The available chat colors</p>
*/
@NotNull
private List<ChatColor> getChatColors() {
List<ChatColor> chatColors = new ArrayList<>();
char[] colors = new char[]{'a', 'b', 'c', 'd', 'e', 'f', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
for (char color : colors) {
chatColors.add(ChatColor.getByChar(color));
}
chatColors.add(ChatColor.of("#ed76d9"));
chatColors.add(ChatColor.of("#ffecb7"));
return chatColors;
}
/**
* Initializes the list of all available languages
*/
private void initializeLanguages() {
languages = new ArrayList<>();
languages.add("cs");
languages.add("de");
languages.add("en");
languages.add("es");
languages.add("fr");
languages.add("hu");
languages.add("it");
languages.add("nb-no");
languages.add("ja");
languages.add("nb");
languages.add("nl");
languages.add("nn-no");
languages.add("pt-br");
languages.add("nn");
languages.add("pt");
languages.add("ru");
return languages;
}
/**
* Gets all available colors
*
* @return <p>All available colors</p>
*/
private List<String> getColors() {
List<String> colors = new ArrayList<>();
for (ChatColor color : getChatColors()) {
colors.add(color.name());
}
return colors;
}
/**
* Gets a list of all available chat colors
*
* @return <p>A list of chat colors</p>
*/
private List<ChatColor> getChatColors() {
List<ChatColor> chatColors = new ArrayList<>();
chatColors.add(ChatColor.WHITE);
chatColors.add(ChatColor.BLUE);
chatColors.add(ChatColor.DARK_BLUE);
chatColors.add(ChatColor.DARK_PURPLE);
chatColors.add(ChatColor.LIGHT_PURPLE);
chatColors.add(ChatColor.GOLD);
chatColors.add(ChatColor.GREEN);
chatColors.add(ChatColor.BLACK);
chatColors.add(ChatColor.DARK_GREEN);
chatColors.add(ChatColor.DARK_RED);
chatColors.add(ChatColor.RED);
chatColors.add(ChatColor.AQUA);
chatColors.add(ChatColor.DARK_AQUA);
chatColors.add(ChatColor.DARK_GRAY);
chatColors.add(ChatColor.GRAY);
chatColors.add(ChatColor.YELLOW);
return chatColors;
}
/**
* Puts a single string value into a string list
*
* @param value <p>The string to make into a list</p>
* @return <p>A list containing the string value</p>
*/
private List<String> putStringInList(String value) {
List<String> list = new ArrayList<>();
list.add(value);
return list;
languages.add("sv");
languages.add("tr");
languages.add("uk");
languages.add("zh");
//TODO: Generate this list dynamically by listing the language files in the jar and adding the user's custom
// language files
}
}

View File

@@ -1,6 +1,7 @@
package net.knarcraft.stargate.command;
import org.apache.commons.lang.ArrayUtils;
import net.knarcraft.stargate.config.Permission;
import net.knarcraft.stargate.utility.PermissionHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
@@ -9,6 +10,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -17,8 +19,9 @@ import java.util.List;
public class StarGateTabCompleter implements TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command,
@NotNull String s, @NotNull String[] args) {
@Nullable
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (args.length == 1) {
List<String> commands = getAvailableCommands(commandSender);
List<String> matchingCommands = new ArrayList<>();
@@ -29,7 +32,7 @@ public class StarGateTabCompleter implements TabCompleter {
}
return matchingCommands;
} else if (args.length > 1 && args[0].equalsIgnoreCase("config")) {
String[] subArgs = (String[]) ArrayUtils.remove(args, 0);
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
return new ConfigTabCompleter().onTabComplete(commandSender, command, s, subArgs);
} else {
return new ArrayList<>();
@@ -42,13 +45,14 @@ public class StarGateTabCompleter implements TabCompleter {
* @param commandSender <p>The command sender to get available commands for</p>
* @return <p>The commands available to the command sender</p>
*/
private List<String> getAvailableCommands(CommandSender commandSender) {
@NotNull
private List<String> getAvailableCommands(@NotNull CommandSender commandSender) {
List<String> commands = new ArrayList<>();
commands.add("about");
if (!(commandSender instanceof Player player) || player.hasPermission("stargate.admin.reload")) {
if (!(commandSender instanceof Player player) || PermissionHelper.hasPermission(player, Permission.RELOAD)) {
commands.add("reload");
}
if (!(commandSender instanceof Player player) || player.hasPermission("stargate.admin.config")) {
if (!(commandSender instanceof Player player) || PermissionHelper.hasPermission(player, Permission.CONFIG)) {
commands.add("config");
}
return commands;

View File

@@ -1,5 +1,8 @@
package net.knarcraft.stargate.config;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A ConfigOption represents one of the available config options
*/
@@ -50,6 +53,14 @@ public enum ConfigOption {
*/
HIGHLIGHT_SIGN_COLOR("gates.cosmetic.highlightSignColor", "The text color used for highlighting stargate signs", "WHITE"),
/**
* The colors to use for each type of sign
*/
PER_SIGN_COLORS("gates.cosmetic.perSignColors", "The per-sign color specification", new String[]{
"'ACACIA:default,default'", "'BIRCH:default,default'", "'CRIMSON:inverted,inverted'", "'DARK_OAK:inverted,inverted'",
"'JUNGLE:default,default'", "'OAK:default,default'", "'SPRUCE:inverted,inverted'", "'WARPED:inverted,inverted'",
"'BAMBOO:default,default'", "'CHERRY:default,default'"}),
/**
* Whether to destroy portals when any blocks are broken by explosions
*/
@@ -98,6 +109,20 @@ public enum ConfigOption {
HANDLE_LEASHED_CREATURES("gates.functionality.handleLeashedCreatures",
"Whether to enable players to teleport a creature on a leash", true),
/**
* Whether to enable a fix that makes teleportation of minecarts/boats work even with craftbook's vehicle removal
*/
ENABLE_CRAFT_BOOK_REMOVE_ON_EJECT_FIX("gates.functionality.enableCraftBookRemoveOnEjectFix",
"Whether to enable a fix that causes loss of NBT data, but allows vehicle teleportation to work " +
"when CraftBook's remove minecart/boat on eject setting is enabled", false),
/**
* The delay between teleporting a vehicle and adding the player as passenger
*/
WAIT_FOR_PLAYER_AFTER_TELEPORT_DELAY("advanced.waitForPlayerAfterTeleportDelay",
"The amount of ticks to wait before adding a player as passenger of a vehicle. On slow servers, " +
"a value of 6 is required to avoid client glitches after teleporting on a vehicle.", 6),
/**
* Whether to enable economy support for taking payment from players creating/destroying/using stargates
*/
@@ -129,6 +154,11 @@ public enum ConfigOption {
CHARGE_FREE_DESTINATION("economy.chargeFreeDestination",
"Whether to require payment if the destination is free, but the entrance stargate is not", true),
/**
* The account to transfer all paid fees to
*/
TAX_ACCOUNT("economy.taxAccount", "The UUID of the account all fees are paid to (except for money to the Stargate owner)", ""),
/**
* Whether to mark free gates with a different color
*/
@@ -147,8 +177,35 @@ public enum ConfigOption {
/**
* Whether to enable debug output for debugging permissions
*/
PERMISSION_DEBUG("debugging.permissionDebug", "Whether to enable permission debugging output", false);
PERMISSION_DEBUG("debugging.permissionDebug", "Whether to enable permission debugging output", false),
/**
* Whether to alert admins about new updates
*/
ADMIN_UPDATE_ALERT("adminUpdateAlert", "Whether to alert admins about new plugin updates", true),
/**
* The velocity of players exiting a stargate, relative to the entry velocity
*/
EXIT_VELOCITY("gates.exitVelocity", "The velocity of players exiting stargates, relative to the entry velocity", 0.1D),
/**
* Whether to enable showing Stargates in Dynmap
*/
ENABLE_DYNMAP("dynmap.enableDynmap", "Whether to display Stargates in Dynmap's map", true),
/**
* Whether to hide Dynmap icons by default
*/
DYNMAP_ICONS_DEFAULT_HIDDEN("dynmap.dynmapIconsHiddenByDefault",
"Whether to hide Stargate's Dynmap icons by default, requiring the user to enable them.", true),
/**
* The amount of ticks to wait when processing the Stargate control update queue
*/
CONTROL_UPDATE_QUEUE_DELAY("gates.integrity.controlUpdateDelay",
"The delay between each time a Stargate's controls are updated after startup", 3),
;
private final String configNode;
private final String description;
@@ -162,19 +219,23 @@ public enum ConfigOption {
* @param description <p>The description of what this config option does</p>
* @param defaultValue <p>The default value of this config option</p>
*/
ConfigOption(String configNode, String description, Object defaultValue) {
ConfigOption(@NotNull String configNode, @NotNull String description, @NotNull Object defaultValue) {
this.configNode = configNode;
this.description = description;
this.defaultValue = defaultValue;
if (defaultValue instanceof String) {
if (defaultValue instanceof String[]) {
this.dataType = OptionDataType.STRING_LIST;
} else if (defaultValue instanceof String) {
this.dataType = OptionDataType.STRING;
} else if (defaultValue instanceof Boolean) {
this.dataType = OptionDataType.BOOLEAN;
} else if (defaultValue instanceof Integer) {
this.dataType = OptionDataType.INTEGER;
} else if (defaultValue instanceof Double) {
this.dataType = OptionDataType.DOUBLE;
} else {
throw new IllegalArgumentException("Unknown config data type encountered.");
throw new IllegalArgumentException("Unknown config data type encountered: " + defaultValue);
}
}
@@ -184,7 +245,7 @@ public enum ConfigOption {
* @param name <p>The name of the config option to get</p>
* @return <p>The corresponding config option, or null if the name is invalid</p>
*/
public static ConfigOption getByName(String name) {
public static @Nullable ConfigOption getByName(@NotNull String name) {
for (ConfigOption option : ConfigOption.values()) {
if (option.getName().equalsIgnoreCase(name)) {
return option;
@@ -198,7 +259,7 @@ public enum ConfigOption {
*
* @return <p>The name of this config option</p>
*/
public String getName() {
public @NotNull String getName() {
if (!this.configNode.contains(".")) {
return this.configNode;
}
@@ -211,7 +272,7 @@ public enum ConfigOption {
*
* @return <p>The data type used</p>
*/
public OptionDataType getDataType() {
public @NotNull OptionDataType getDataType() {
return this.dataType;
}
@@ -220,7 +281,7 @@ public enum ConfigOption {
*
* @return <p>This config option's config node</p>
*/
public String getConfigNode() {
public @NotNull String getConfigNode() {
return this.configNode;
}
@@ -229,7 +290,7 @@ public enum ConfigOption {
*
* @return <p>The description of this config option</p>
*/
public String getDescription() {
public @NotNull String getDescription() {
return this.description;
}
@@ -238,7 +299,7 @@ public enum ConfigOption {
*
* @return <p>This config option's default value</p>
*/
public Object getDefaultValue() {
public @NotNull Object getDefaultValue() {
return this.defaultValue;
}

View File

@@ -1,23 +1,38 @@
package net.knarcraft.stargate.config;
import java.util.Arrays;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* A config tag groups config values by a property
*/
public enum ConfigTag {
COLOR(new ConfigOption[]{ConfigOption.FREE_GATES_COLOR, ConfigOption.MAIN_SIGN_COLOR, ConfigOption.HIGHLIGHT_SIGN_COLOR}),
FOLDER(new ConfigOption[]{ConfigOption.GATE_FOLDER, ConfigOption.PORTAL_FOLDER});
/**
* Color-related configuration options
*/
COLOR(Set.of(ConfigOption.FREE_GATES_COLOR, ConfigOption.MAIN_SIGN_COLOR, ConfigOption.HIGHLIGHT_SIGN_COLOR,
ConfigOption.PER_SIGN_COLORS)),
private final ConfigOption[] taggedOptions;
/**
* Folder-altering configuration options
*/
FOLDER(Set.of(ConfigOption.GATE_FOLDER, ConfigOption.PORTAL_FOLDER)),
/**
* Dynmap-related configuration options
*/
DYNMAP(Set.of(ConfigOption.ENABLE_DYNMAP, ConfigOption.DYNMAP_ICONS_DEFAULT_HIDDEN));
private final Set<ConfigOption> taggedOptions;
/**
* Instantiates a new config tag
*
* @param taggedOptions <p>The config options included in this tag</p>
*/
ConfigTag(ConfigOption[] taggedOptions) {
ConfigTag(@NotNull Set<ConfigOption> taggedOptions) {
this.taggedOptions = taggedOptions;
}
@@ -27,8 +42,18 @@ public enum ConfigTag {
* @param option <p>The config option to check</p>
* @return <p>True of the config option is tagged</p>
*/
public boolean isTagged(ConfigOption option) {
return Arrays.stream(taggedOptions).anyMatch((item) -> item == option);
public boolean isTagged(@NotNull ConfigOption option) {
return taggedOptions.contains(option);
}
/**
* Checks whether a given config option requires a "reload of colors" to take effect
*
* @param configOption <p>The config option to check</p>
* @return <p>True if changing the config option requires a "reload of colors" to take effect</p>
*/
public static boolean requiresColorReload(@NotNull ConfigOption configOption) {
return (COLOR.isTagged(configOption) && configOption != ConfigOption.FREE_GATES_COLOR);
}
/**
@@ -37,17 +62,27 @@ public enum ConfigTag {
* @param option <p>The config option to check</p>
* @return <p>True if changing the config option requires a full reload to take effect</p>
*/
public static boolean requiresFullReload(ConfigOption option) {
public static boolean requiresFullReload(@NotNull ConfigOption option) {
return FOLDER.isTagged(option);
}
/**
* Checks whether a given config option requires a re-load of all Dynmap markers
*
* @param configOption <p>The config option to check</p>
* @return <p>True if changing the config option requires a reload of all dynmap markers</p>
*/
public static boolean requiresDynmapReload(@NotNull ConfigOption configOption) {
return DYNMAP.isTagged(configOption);
}
/**
* Checks whether a given config option requires a portal reload to take effect
*
* @param option <p>The config option to check</p>
* @return <p>True if changing the config option requires a portal reload to take effect</p>
*/
public static boolean requiresPortalReload(ConfigOption option) {
public static boolean requiresPortalReload(@NotNull ConfigOption option) {
return COLOR.isTagged(option) || FOLDER.isTagged(option) || option == ConfigOption.VERIFY_PORTALS;
}
@@ -57,7 +92,7 @@ public enum ConfigTag {
* @param option <p>The config option to check</p>
* @return <p>True if the language loader requires a reload</p>
*/
public static boolean requiresLanguageReload(ConfigOption option) {
public static boolean requiresLanguageReload(@NotNull ConfigOption option) {
return option == ConfigOption.LANGUAGE;
}
@@ -67,7 +102,7 @@ public enum ConfigTag {
* @param option <p>The config option to check</p>
* @return <p>True if economy requires a reload</p>
*/
public static boolean requiresEconomyReload(ConfigOption option) {
public static boolean requiresEconomyReload(@NotNull ConfigOption option) {
return option == ConfigOption.USE_ECONOMY;
}

View File

@@ -1,61 +0,0 @@
package net.knarcraft.stargate.config;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
/**
* The message sender is responsible sending messages to players with correct coloring and formatting
*/
public final class MessageSender {
private final LanguageLoader languageLoader;
/**
* Instantiates a new message sender
*
* @param languageLoader <p>The language loader to get translated strings from</p>
*/
public MessageSender(LanguageLoader languageLoader) {
this.languageLoader = languageLoader;
}
/**
* Sends an error message to a player
*
* @param player <p>The player to send the message to</p>
* @param message <p>The message to send</p>
*/
public void sendErrorMessage(CommandSender player, String message) {
sendMessage(player, message, true);
}
/**
* Sends a success message to a player
*
* @param player <p>The player to send the message to</p>
* @param message <p>The message to send</p>
*/
public void sendSuccessMessage(CommandSender player, String message) {
sendMessage(player, message, false);
}
/**
* Sends a message to a player
*
* @param sender <p>The player to send the message to</p>
* @param message <p>The message to send</p>
* @param error <p>Whether the message sent is an error</p>
*/
private void sendMessage(CommandSender sender, String message, boolean error) {
if (message.isEmpty()) {
return;
}
message = ChatColor.translateAlternateColorCodes('&', message);
if (error) {
sender.sendMessage(ChatColor.RED + languageLoader.getString("prefix") + ChatColor.WHITE + message);
} else {
sender.sendMessage(ChatColor.GREEN + languageLoader.getString("prefix") + ChatColor.WHITE + message);
}
}
}

View File

@@ -6,16 +6,28 @@ package net.knarcraft.stargate.config;
public enum OptionDataType {
/**
* The data type for the option is a String
* The data type if the option is a String
*/
STRING,
/**
* The data type for the option is a Boolean
* The data type if the option is a Boolean
*/
BOOLEAN,
/**
* The data type for the option is an Integer
* The data type if the option is a string list
*/
INTEGER
STRING_LIST,
/**
* The data type if the option is an Integer
*/
INTEGER,
/**
* The data type if the option is a double
*/
DOUBLE
}

View File

@@ -0,0 +1,68 @@
package net.knarcraft.stargate.config;
import org.jetbrains.annotations.NotNull;
/**
* A representation of all Stargate permissions
*/
public enum Permission {
ADMIN("admin"),
CONFIG("admin.config"),
RELOAD("admin.reload"),
CREATE_BUNGEE("admin.bungee"),
DYE_SIGN("admin.dye"),
SEE_HIDDEN("admin.hidden"),
USE_PRIVATE("admin.private"),
FREE_USAGE("free.use"),
FREE_DESTRUCTION("free.destroy"),
FREE_CREATION("free.create"),
ACCESS_SERVER("server"),
ACCESS_NETWORK("network"),
ACCESS_WORLD("world"),
CREATE_GATE("create.gate"),
CREATE_NETWORK("create.network"),
CREATE_PERSONAL("create.personal"),
DESTROY_NETWORK("destroy.network"),
DESTROY_PERSONAL("destroy.personal"),
OPTION_HIDDEN("option.hidden"),
OPTION_ALWAYS_ON("option.alwayson"),
OPTIONS_PRIVATE("options.private"),
OPTIONS_FREE("options.free"),
OPTIONS_BACKWARDS("options.backwards"),
OPTIONS_SHOW("options.show"),
OPTIONS_NO_NETWORK("options.nonetwork"),
OPTIONS_RANDOM("options.random"),
OPTIONS_BUNGEE("options.bungee"),
OPTIONS_QUIET("options.quiet"),
OPTIONS_INVISIBLE("options.invisible"),
;
private final String node;
/**
* Instantiates a new permission
*
* @param node <p>The permission node</p>
*/
Permission(@NotNull String node) {
this.node = node;
}
/**
* Gets the permission node of this permission
*
* @return <p>The node of this permission</p>
*/
@NotNull
public String getNode() {
return "stargate." + this.node;
}
}

View File

@@ -1,23 +1,32 @@
package net.knarcraft.stargate.config;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.Translator;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ConfigHelper;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.addons.DynmapManager;
import net.knarcraft.stargate.config.addons.EconomyConfig;
import net.knarcraft.stargate.config.formatting.LanguageLoader;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.listener.BungeeCordListener;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.portal.property.gate.GateHandler;
import net.knarcraft.stargate.thread.BlockChangeThread;
import net.knarcraft.stargate.utility.FileHelper;
import net.knarcraft.stargate.utility.BungeeHelper;
import net.knarcraft.stargate.utility.PortalFileHelper;
import net.knarcraft.stargate.utility.PortalUtil;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.messaging.Messenger;
import org.dynmap.DynmapAPI;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -25,6 +34,8 @@ import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The stargate config is responsible for keeping track of all configuration values
@@ -36,7 +47,6 @@ public final class StargateConfig {
private final HashSet<String> managedWorlds = new HashSet<>();
private StargateGateConfig stargateGateConfig;
private MessageSender messageSender;
private final LanguageLoader languageLoader;
private EconomyConfig economyConfig;
private final Logger logger;
@@ -45,6 +55,7 @@ public final class StargateConfig {
private String gateFolder;
private String portalFolder;
private String languageName = "en";
private boolean isLoaded = false;
private final Map<ConfigOption, Object> configOptions;
@@ -53,7 +64,7 @@ public final class StargateConfig {
*
* @param logger <p>The logger to use for logging errors</p>
*/
public StargateConfig(Logger logger) {
public StargateConfig(@NotNull Logger logger) {
this.logger = logger;
configOptions = new HashMap<>();
@@ -71,6 +82,7 @@ public final class StargateConfig {
*
* @return <p>A reference to the config options map</p>
*/
@NotNull
public Map<ConfigOption, Object> getConfigOptionsReference() {
return configOptions;
}
@@ -90,17 +102,48 @@ public final class StargateConfig {
languageLoader.setChosenLanguage(languageName);
languageLoader.reload();
messageSender = new MessageSender(languageLoader);
// Update prefix of the format builder
SGFormatBuilder.setStringFormatter(getStringFormatter());
if (isDebuggingEnabled()) {
languageLoader.debug();
}
this.createMissingFolders();
this.loadGates();
this.createMissingFolders();
this.loadAllPortals();
//Set up vault economy if vault has been loaded
setupVaultEconomy();
//Set up dynmap
try {
DynmapAPI dynmapAPI = (DynmapAPI) Bukkit.getPluginManager().getPlugin("dynmap");
if (dynmapAPI != null) {
try {
DynmapManager.initialize(dynmapAPI);
DynmapManager.addAllPortalMarkers();
} catch (NullPointerException ignored) {
logger.warning("Dynmap started in an invalid state. Check your log/console for dynmap-related " +
"problems. Dynmap integration cannot be initialized.");
}
}
} catch (NoClassDefFoundError error) {
logger.warning("Dynmap seems to be unavailable, even though its API is registered. Dynmap " +
"integration is disabled.");
DynmapManager.disable();
}
this.isLoaded = true;
}
/**
* Gets whether this configuration has been fully loaded
*
* @return <p>True if not fully loaded</p>
*/
public boolean isNotLoaded() {
return !this.isLoaded;
}
/**
@@ -108,6 +151,7 @@ public final class StargateConfig {
*
* @return <p>The loaded config options</p>
*/
@NotNull
public Map<ConfigOption, Object> getConfigOptions() {
return new HashMap<>(configOptions);
}
@@ -119,6 +163,7 @@ public final class StargateConfig {
*
* @return <p>The open portals queue</p>
*/
@NotNull
public Queue<Portal> getOpenPortalsQueue() {
return openPortalsQueue;
}
@@ -130,6 +175,7 @@ public final class StargateConfig {
*
* @return <p>The active portals queue</p>
*/
@NotNull
public Queue<Portal> getActivePortalsQueue() {
return activePortalsQueue;
}
@@ -152,11 +198,30 @@ public final class StargateConfig {
return (boolean) configOptions.get(ConfigOption.PERMISSION_DEBUG);
}
/**
* Gets whether Dynmap integration is disabled
*
* @return <p>Whether Dynmap integration is disabled</p>
*/
public boolean isDynmapDisabled() {
return !((boolean) configOptions.get(ConfigOption.ENABLE_DYNMAP));
}
/**
* Gets whether Dynmap icons should be hidden by default
*
* @return <p>Whether Dynmap icons should be hidden by default</p>
*/
public boolean hideDynmapIcons() {
return (boolean) configOptions.get(ConfigOption.DYNMAP_ICONS_DEFAULT_HIDDEN);
}
/**
* Gets the object containing economy config values
*
* @return <p>The object containing economy config values</p>
*/
@NotNull
public EconomyConfig getEconomyConfig() {
return this.economyConfig;
}
@@ -166,16 +231,16 @@ public final class StargateConfig {
*
* @param sender <p>The sender of the reload request</p>
*/
public void reload(CommandSender sender) {
public void reload(@NotNull CommandSender sender) {
//Unload all saved data
unload();
//Perform all block change requests to prevent mismatch if a gate's open-material changes. Changing the
// closed-material still requires a restart.
BlockChangeRequest firstElement = Stargate.getBlockChangeRequestQueue().peek();
BlockChangeRequest firstElement = Stargate.getControlBlockUpdateRequestQueue().peek();
while (firstElement != null) {
BlockChangeThread.pollQueue();
firstElement = Stargate.getBlockChangeRequestQueue().peek();
firstElement = Stargate.getControlBlockUpdateRequestQueue().peek();
}
//Store the old enable bungee state in case it changes
@@ -189,7 +254,13 @@ public final class StargateConfig {
startStopBungeeListener(stargateGateConfig.enableBungee());
}
messageSender.sendErrorMessage(sender, languageLoader.getString("reloaded"));
//Reload portal markers
DynmapManager.addAllPortalMarkers();
// Update prefix of the format builder
SGFormatBuilder.setStringFormatter(getStringFormatter());
new SGFormatBuilder(Message.RELOADED).error(sender);
}
/**
@@ -213,7 +284,7 @@ public final class StargateConfig {
}
//Force all portals to close
closeAllOpenPortals();
PortalHandler.closeAllPortals();
PortalUtil.closeAllPortals();
//Clear queues and lists
activePortalsQueue.clear();
@@ -236,6 +307,7 @@ public final class StargateConfig {
*
* @return <p>The managed worlds</p>
*/
@NotNull
public Set<String> getManagedWorlds() {
return new HashSet<>(managedWorlds);
}
@@ -245,7 +317,7 @@ public final class StargateConfig {
*
* @param worldName <p>The name of the world to manage</p>
*/
public void addManagedWorld(String worldName) {
public void addManagedWorld(@NotNull String worldName) {
managedWorlds.add(worldName);
}
@@ -254,7 +326,7 @@ public final class StargateConfig {
*
* @param worldName <p>The name of the world to stop managing</p>
*/
public void removeManagedWorld(String worldName) {
public void removeManagedWorld(@NotNull String worldName) {
managedWorlds.remove(worldName);
}
@@ -291,7 +363,21 @@ public final class StargateConfig {
if (start) {
messenger.registerOutgoingPluginChannel(Stargate.getInstance(), bungeeChannel);
messenger.registerIncomingPluginChannel(Stargate.getInstance(), bungeeChannel, new BungeeCordListener());
messenger.registerIncomingPluginChannel(Stargate.getInstance(), bungeeChannel, (channel, unused, message) -> {
//Ignore plugin messages if some other plugin message is received
if (!channel.equals(BungeeHelper.getBungeeChannel())) {
return;
}
//Try to read the plugin message
String receivedMessage = BungeeHelper.readPluginMessage(message);
if (receivedMessage == null) {
return;
}
//Use the message to initiate teleportation
BungeeHelper.handleTeleportMessage(receivedMessage);
});
} else {
messenger.unregisterIncomingPluginChannel(Stargate.getInstance(), bungeeChannel);
messenger.unregisterOutgoingPluginChannel(Stargate.getInstance(), bungeeChannel);
@@ -319,6 +405,15 @@ public final class StargateConfig {
}
}
/**
* Gets whether admins should be alerted about new plugin updates
*
* @return <p>Whether admins should be alerted about new updates</p>
*/
public boolean alertAdminsAboutUpdates() {
return (boolean) configOptions.get(ConfigOption.ADMIN_UPDATE_ALERT);
}
/**
* Loads all config values
*/
@@ -327,9 +422,11 @@ public final class StargateConfig {
FileConfiguration newConfig = Stargate.getInstance().getConfig();
boolean isMigrating = false;
if (newConfig.getString("lang") != null || newConfig.getString("economy.freeGatesGreen") != null) {
migrateConfig(newConfig);
if (newConfig.getString("lang") != null || newConfig.getString("economy.taxAccount") == null) {
ConfigHelper.migrateConfig(Stargate.getInstance());
isMigrating = true;
Stargate.getInstance().reloadConfig();
newConfig = Stargate.getInstance().getConfig();
}
//Copy missing default values if any values are missing
@@ -342,12 +439,11 @@ public final class StargateConfig {
//Load the option using its correct data type
switch (option.getDataType()) {
case STRING -> {
String value = newConfig.getString(configNode);
optionValue = value != null ? value.trim() : "";
}
case BOOLEAN -> optionValue = newConfig.getBoolean(configNode);
case INTEGER -> optionValue = newConfig.getInt(configNode);
case STRING_LIST -> optionValue = newConfig.getStringList(configNode);
case STRING -> optionValue = newConfig.getString(configNode, (String) option.getDefaultValue()).trim();
case BOOLEAN -> optionValue = newConfig.getBoolean(configNode, (boolean) option.getDefaultValue());
case INTEGER -> optionValue = newConfig.getInt(configNode, (int) option.getDefaultValue());
case DOUBLE -> optionValue = newConfig.getDouble(configNode, (double) option.getDefaultValue());
default -> throw new IllegalArgumentException("Invalid config data type encountered");
}
configOptions.put(option, optionValue);
@@ -358,10 +454,24 @@ public final class StargateConfig {
//Get important folders from the config
portalFolder = (String) configOptions.get(ConfigOption.PORTAL_FOLDER);
if (portalFolder.isEmpty()) {
portalFolder = dataFolderPath + "/portals/";
} else {
portalFolder = replacePluginFolderPath(portalFolder);
}
Stargate.debug("StargateConfig::loadConfig", "Portal folder is " + portalFolder);
gateFolder = (String) configOptions.get(ConfigOption.GATE_FOLDER);
if (gateFolder.isEmpty()) {
gateFolder = dataFolderPath + "/gates/";
} else {
gateFolder = replacePluginFolderPath(gateFolder);
}
Stargate.debug("StargateConfig::loadConfig", "Gate folder is " + gateFolder);
//If users have an outdated config, assume they also need to update their default gates
if (isMigrating) {
this.createMissingFolders();
GateHandler.writeDefaultGatesToFolder(gateFolder);
}
@@ -374,11 +484,29 @@ public final class StargateConfig {
Stargate.getInstance().saveConfig();
}
/**
* Replaces "plugins/Stargate" in a folder path, and replaces it with the full path relative to the data folder
*
* @param input <p>The input string to replace in</p>
* @return <p>The replaced path, or the input if not applicable</p>
*/
@NotNull
private String replacePluginFolderPath(@NotNull String input) {
Pattern pattern = Pattern.compile("(?i)^plugins[\\\\/]Stargate");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
return dataFolderPath + matcher.replaceAll("");
} else {
return input;
}
}
/**
* Gets the object containing configuration values regarding gates
*
* @return <p>Gets the gate config</p>
*/
@NotNull
public StargateGateConfig getStargateGateConfig() {
return stargateGateConfig;
}
@@ -391,53 +519,15 @@ public final class StargateConfig {
Stargate.logInfo(String.format("Loaded %s gate layouts", GateHandler.getGateCount()));
}
/**
* Changes all configuration values from the old name to the new name
*
* @param newConfig <p>The config to read from and write to</p>
*/
private void migrateConfig(FileConfiguration newConfig) {
//Save the old config just in case something goes wrong
try {
newConfig.save(dataFolderPath + "/config.yml.old");
} catch (IOException e) {
Stargate.debug("Stargate::migrateConfig", "Unable to save old backup and do migration");
e.printStackTrace();
return;
}
//Read all available config migrations
Map<String, String> migrationFields;
try {
migrationFields = FileHelper.readKeyValuePairs(FileHelper.getBufferedReaderFromInputStream(
FileHelper.getInputStreamForInternalFile("/config-migrations.txt")));
} catch (IOException e) {
Stargate.debug("Stargate::migrateConfig", "Unable to load config migration file");
e.printStackTrace();
return;
}
//Replace old config names with the new ones
for (String key : migrationFields.keySet()) {
if (newConfig.contains(key)) {
String newPath = migrationFields.get(key);
Object oldValue = newConfig.get(key);
if (!newPath.trim().isEmpty()) {
newConfig.set(newPath, oldValue);
}
newConfig.set(key, null);
}
}
}
/**
* Loads economy from Vault
*/
private void setupVaultEconomy() {
EconomyConfig economyConfig = getEconomyConfig();
if (economyConfig.setupEconomy(Stargate.getPluginManager()) && economyConfig.getEconomy() != null) {
if (economyConfig.setupEconomy(Stargate.getPluginManager()) && economyConfig.getEconomy() != null &&
economyConfig.getVault() != null) {
String vaultVersion = economyConfig.getVault().getDescription().getVersion();
Stargate.logInfo(Stargate.replaceVars(Stargate.getString("vaultLoaded"), "%version%", vaultVersion));
Stargate.logInfo(new SGFormatBuilder(Message.VAULT_LOADED).replace("%version%", vaultVersion).toString());
}
}
@@ -457,18 +547,24 @@ public final class StargateConfig {
* Creates missing folders
*/
private void createMissingFolders() {
File newPortalDir = new File(portalFolder);
if (!newPortalDir.exists()) {
if (!newPortalDir.mkdirs()) {
logger.severe("Unable to create portal directory");
}
}
createMissingFolder(new File(gateFolder), "Unable to create gate directory");
createMissingFolder(new File(portalFolder), "Unable to create portal directory");
File newFile = new File(portalFolder, Stargate.getInstance().getServer().getWorlds().get(0).getName() +
".db");
if (!newFile.exists() && !newFile.getParentFile().exists()) {
if (!newFile.getParentFile().mkdirs()) {
logger.severe("Unable to create portal database folder: " + newFile.getParentFile().getPath());
}
if (!newFile.exists() && !newFile.getParentFile().exists() && !newFile.getParentFile().mkdirs()) {
logger.severe("Unable to create portal database folder: " + newFile.getParentFile().getPath());
}
}
/**
* Creates the given folder if it's missing
*
* @param folder <p>The folder to create</p>
* @param errorMessage <p>The error message to display if unable to create the folder</p>
*/
private void createMissingFolder(File folder, String errorMessage) {
if (!folder.exists() && !folder.mkdirs()) {
logger.severe(errorMessage);
}
}
@@ -477,6 +573,7 @@ public final class StargateConfig {
*
* @return <p>The portal folder</p>
*/
@NotNull
public String getPortalFolder() {
return portalFolder;
}
@@ -488,25 +585,57 @@ public final class StargateConfig {
*
* @return <p>The folder storing gate files</p>
*/
@NotNull
public String getGateFolder() {
return gateFolder;
}
/**
* Gets the sender for sending messages to players
*
* @return <p>The sender for sending messages to players</p>
*/
public MessageSender getMessageSender() {
return messageSender;
}
/**
* Gets the language loader containing translated strings
*
* @return <p>The language loader</p>
*/
@NotNull
public LanguageLoader getLanguageLoader() {
return languageLoader;
}
/**
* Gets the string formatter to use
*/
@NotNull
private StringFormatter getStringFormatter() {
// In order to allow automatic customization of prefix color, parse it properly
String rawPrefix = getLanguageLoader().getString(Message.PREFIX);
String colorPattern = "(?:[&§][a-fA-F0-9klmnor]|&?#[0-9a-fA-F]{6}|§x(?:§[a-fA-F0-9]){6})*";
Pattern pattern = Pattern.compile("(" + colorPattern + "\\[" + colorPattern + ")(\\w+)(" +
colorPattern + "]" + colorPattern + ")");
return getStringFormatter(rawPrefix, pattern);
}
/**
* Gets the string formatter to use
*
* @param rawPrefix <p>The formatter prefix to parse</p>
* @param pattern <p>The pattern to use for parsing</p>
*/
private static @NotNull StringFormatter getStringFormatter(String rawPrefix, Pattern pattern) {
String prefix = rawPrefix;
String namePrefix = "[";
String nameSuffix = "]";
Matcher matcher = pattern.matcher(rawPrefix);
if (matcher.find()) {
namePrefix = matcher.group(1).trim();
prefix = matcher.group(2).trim();
nameSuffix = matcher.group(3).trim();
}
StringFormatter stringFormatter = new StringFormatter(prefix, new Translator());
stringFormatter.setColorConversion(ColorConversion.RGB);
stringFormatter.setNamePrefix(namePrefix);
stringFormatter.setNameSuffix(nameSuffix);
return stringFormatter;
}
}

View File

@@ -1,15 +1,23 @@
package net.knarcraft.stargate.config;
import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.PortalSignDrawer;
import org.bukkit.ChatColor;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Color;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The Stargate gate config keeps track of all global config values related to gates
*/
public final class StargateGateConfig {
private static final int activeTime = 10;
private static final int openTime = 10;
private final Map<ConfigOption, Object> configOptions;
@@ -19,7 +27,7 @@ public final class StargateGateConfig {
*
* @param configOptions <p>The loaded config options to use</p>
*/
public StargateGateConfig(Map<ConfigOption, Object> configOptions) {
public StargateGateConfig(@NotNull Map<ConfigOption, Object> configOptions) {
this.configOptions = configOptions;
loadGateConfig();
}
@@ -93,6 +101,17 @@ public final class StargateGateConfig {
return (boolean) configOptions.get(ConfigOption.HANDLE_CREATURE_TRANSPORTATION);
}
/**
* Gets the delay to wait between each update of a Stargate's control block
*
* <p>This only affects the queued control updates during startup. It does not affect normal gameplay.</p>
*
* @return <p>The amount of ticks to delay control updates by</p>
*/
public int controlUpdateDelay() {
return (int) configOptions.get(ConfigOption.CONTROL_UPDATE_QUEUE_DELAY);
}
/**
* Gets whether vehicles containing a creature, but not a player should be handled
*
@@ -119,6 +138,29 @@ public final class StargateGateConfig {
return (boolean) configOptions.get(ConfigOption.HANDLE_LEASHED_CREATURES);
}
/**
* Gets whether the CraftBook vehicle removal fix is enabled
*
* <p>If enabled, minecarts and boats should be re-created instead of teleported.</p>
*
* @return <p>True if the CraftBook vehicle removal fix is enabled</p>
*/
public boolean enableCraftBookRemoveOnEjectFix() {
return (boolean) configOptions.get(ConfigOption.ENABLE_CRAFT_BOOK_REMOVE_ON_EJECT_FIX);
}
/**
* Gets the delay to use before adding a player as passenger of a teleported vehicle
*
* @return <p>The delay to use before adding a player as passenger of a teleported vehicle</p>
*/
public int waitForPlayerAfterTeleportDelay() {
if ((int) configOptions.get(ConfigOption.WAIT_FOR_PLAYER_AFTER_TELEPORT_DELAY) < 2) {
configOptions.put(ConfigOption.WAIT_FOR_PLAYER_AFTER_TELEPORT_DELAY, 6);
}
return (int) configOptions.get(ConfigOption.WAIT_FOR_PLAYER_AFTER_TELEPORT_DELAY);
}
/**
* Gets whether the list of destinations within a network should be sorted
*
@@ -173,13 +215,105 @@ public final class StargateGateConfig {
return (String) configOptions.get(ConfigOption.DEFAULT_GATE_NETWORK);
}
/**
* Gets the exit velocity of players using stargates, relative to the entry velocity
*
* @return <p>The relative exit velocity</p>
*/
public double getExitVelocity() {
return (double) configOptions.get(ConfigOption.EXIT_VELOCITY);
}
/**
* Loads all config values related to gates
*/
private void loadGateConfig() {
//Load the sign colors
loadSignColor((String) configOptions.get(ConfigOption.MAIN_SIGN_COLOR),
(String) configOptions.get(ConfigOption.HIGHLIGHT_SIGN_COLOR));
String mainSignColor = (String) configOptions.get(ConfigOption.MAIN_SIGN_COLOR);
String highlightSignColor = (String) configOptions.get(ConfigOption.HIGHLIGHT_SIGN_COLOR);
loadPerSignColor(mainSignColor, highlightSignColor);
loadPerSignColors();
}
/**
* Loads the per-sign colors specified in the config file
*/
public void loadPerSignColors() {
List<?> perSignColors = (List<?>) configOptions.get(ConfigOption.PER_SIGN_COLORS);
ChatColor[] defaultColors = new ChatColor[]{PortalSignDrawer.getMainColor(), PortalSignDrawer.getHighlightColor()};
List<Map<Material, ChatColor>> colorMaps = new ArrayList<>();
colorMaps.add(new HashMap<>());
colorMaps.add(new HashMap<>());
for (Object signColorSpecification : perSignColors) {
parsePerSignColors(signColorSpecification, defaultColors, colorMaps);
}
PortalSignDrawer.setPerSignMainColors(colorMaps.get(0));
PortalSignDrawer.setPerSignHighlightColors(colorMaps.get(1));
}
/**
* Parses a per-sign color specification object and stores the result
*
* @param signColorSpecification <p>The sign color specification to parse</p>
* @param defaultColors <p>The specified default colors</p>
* @param colorMaps <p>The list of color maps to save the resulting colors to</p>
*/
private void parsePerSignColors(@NotNull Object signColorSpecification, @NotNull ChatColor[] defaultColors,
@NotNull List<Map<Material, ChatColor>> colorMaps) {
String[] specificationData = String.valueOf(signColorSpecification).split(":");
Material[] signMaterials = new Material[]{Material.matchMaterial(specificationData[0] + "_SIGN"),
Material.matchMaterial(specificationData[0] + "_WALL_SIGN")};
if (specificationData.length != 2) {
Stargate.logWarning("You have an invalid per-sign line in your config.yml file. Please fix it!");
return;
}
String[] colors = specificationData[1].split(",");
if (colors.length != 2) {
Stargate.logWarning("You have an invalid per-sign line in your config.yml file. Please fix it!");
return;
}
for (int colorIndex = 0; colorIndex < 2; colorIndex++) {
if (colors[colorIndex].equalsIgnoreCase("default")) {
continue;
}
loadPerSignColor(colors, colorIndex, defaultColors, signMaterials, colorMaps);
}
}
/**
* Loads a per-sign color
*
* @param colors <p>The colors specified in the config file</p>
* @param colorIndex <p>The index of the color to load</p>
* @param defaultColors <p>The specified default colors</p>
* @param signMaterials <p>The materials to load this color for</p>
* @param colorMaps <p>The list of color maps to save the resulting color to</p>
*/
private void loadPerSignColor(@NotNull String[] colors, int colorIndex, @NotNull ChatColor[] defaultColors,
@NotNull Material[] signMaterials, @NotNull List<Map<Material, ChatColor>> colorMaps) {
ChatColor parsedColor;
if (colors[colorIndex].equalsIgnoreCase("inverted")) {
//Convert from ChatColor to awt.Color to Bukkit.Color then invert and convert to ChatColor
java.awt.Color color = defaultColors[colorIndex].getColor();
parsedColor = ColorHelper.fromColor(ColorHelper.invert(Color.fromRGB(color.getRed(), color.getGreen(),
color.getBlue())));
} else {
try {
parsedColor = ChatColor.of(colors[colorIndex]);
} catch (IllegalArgumentException | NullPointerException exception) {
Stargate.logWarning("You have specified an invalid per-sign color in your config.yml. Custom color for \"" +
signMaterials[0] + "\" disabled");
return;
}
}
if (parsedColor != null) {
for (Material signMaterial : signMaterials) {
colorMaps.get(colorIndex).put(signMaterial, parsedColor);
}
}
}
/**
@@ -187,14 +321,22 @@ public final class StargateGateConfig {
*
* @param mainSignColor <p>A string representing the main sign color</p>
*/
private void loadSignColor(String mainSignColor, String highlightSignColor) {
private void loadPerSignColor(@NotNull String mainSignColor, @NotNull String highlightSignColor) {
try {
PortalSignDrawer.setMainColor(ChatColor.valueOf(mainSignColor.toUpperCase()));
PortalSignDrawer.setHighlightColor(ChatColor.valueOf(highlightSignColor.toUpperCase()));
PortalSignDrawer.setMainColor(ChatColor.of(mainSignColor.toUpperCase()));
} catch (IllegalArgumentException | NullPointerException exception) {
Stargate.logWarning("You have specified an invalid color in your config.yml. Defaulting to BLACK and WHITE");
Stargate.logWarning("You have specified an invalid main sign color in your config.yml (" + mainSignColor +
"). Defaulting to BLACK");
PortalSignDrawer.setMainColor(ChatColor.BLACK);
}
try {
PortalSignDrawer.setHighlightColor(ChatColor.of(highlightSignColor.toUpperCase()));
} catch (IllegalArgumentException | NullPointerException exception) {
Stargate.logWarning("You have specified an invalid highlighting sign color in your config.yml (" +
highlightSignColor + "). Defaulting to WHITE");
PortalSignDrawer.setHighlightColor(ChatColor.WHITE);
}
}
}

View File

@@ -0,0 +1,160 @@
package net.knarcraft.stargate.config.addons;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalRegistry;
import org.bukkit.Location;
import org.bukkit.World;
import org.dynmap.DynmapAPI;
import org.dynmap.markers.GenericMarker;
import org.dynmap.markers.Marker;
import org.dynmap.markers.MarkerIcon;
import org.dynmap.markers.MarkerSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A manager for dealing with everything Dynmap
*/
public final class DynmapManager {
private static MarkerSet markerSet;
private static MarkerIcon portalIcon;
private DynmapManager() {
}
/**
* Initializes the dynmap manager
*
* @param dynmapAPI <p>A reference</p>
* @throws NullPointerException <p>If dynmap has an invalid state</p>
*/
public static void initialize(@Nullable DynmapAPI dynmapAPI) throws NullPointerException {
if (dynmapAPI == null || !dynmapAPI.markerAPIInitialized() || dynmapAPI.getMarkerAPI() == null) {
markerSet = null;
portalIcon = null;
} else {
markerSet = dynmapAPI.getMarkerAPI().createMarkerSet("stargate", "Stargate", null, false);
if (markerSet != null) {
markerSet.setHideByDefault(Stargate.getStargateConfig().hideDynmapIcons());
}
portalIcon = dynmapAPI.getMarkerAPI().getMarkerIcon("portal");
}
}
/**
* Adds all portal markers for all current portals
*/
public static void addAllPortalMarkers() {
if (markerSet == null || Stargate.getStargateConfig().isDynmapDisabled()) {
//Remove any existing markers if dynmap has been disabled after startup
if (markerSet != null) {
markerSet.getMarkers().forEach(GenericMarker::deleteMarker);
}
return;
}
markerSet.setHideByDefault(Stargate.getStargateConfig().hideDynmapIcons());
//Remove all existing markers for a clean start
markerSet.getMarkers().forEach(GenericMarker::deleteMarker);
for (Portal portal : PortalRegistry.getAllPortals()) {
addPortalMarker(portal);
}
}
/**
* Adds a portal marker for the given portal
*
* @param portal <p>The portal to add a marker for</p>
*/
public static void addPortalMarker(@NotNull Portal portal) {
if (markerSet == null || Stargate.getStargateConfig().isDynmapDisabled()) {
return;
}
World world = portal.getLocation().getWorld();
if (portal.getOptions().isHidden() || world == null) {
return;
}
Location location;
@Nullable RelativeBlockVector exit = portal.getGate().getLayout().getExit();
if (exit == null) {
location = portal.getLocation().getTopLeft();
} else {
location = portal.getBlockAt(exit);
}
String markerId = getPortalMarkerId(portal);
if (markerSet.findMarker(markerId) != null) {
Stargate.debug("DynmapManager::addPortalMarker", "Skipped marker creation, as the portal " +
"marker " + markerId + " already exists");
return;
}
Marker marker = markerSet.createMarker(markerId, portal.getName(), world.getName(),
location.getX(), location.getY(), location.getZ(), portalIcon, false);
if (marker == null) {
Stargate.logWarning(String.format(
"""
Unable to create marker for portal
Portal marker id: %s
Portal name: %s
Portal world: %s
Portal location: %s,%s,%s""",
markerId, portal.getName(), world.getName(), location.getX(), location.getY(),
location.getZ()));
return;
}
String networkPrompt;
if (portal.getOptions().isBungee()) {
networkPrompt = "Server";
} else {
networkPrompt = "Network";
}
String markerDescription = String.format("<b>Name:</b> %s<br /><b>%s:</b> %s<br /><b>Destination:</b> " +
"%s<br /><b>Owner:</b> %s<br />", portal.getName(), networkPrompt, portal.getNetwork(),
portal.getDestinationName(), portal.getOwner().getName());
marker.setDescription(markerDescription);
marker.setLabel(portal.getName(), true);
if (portalIcon != null) {
marker.setMarkerIcon(portalIcon);
}
}
/**
* Removes the portal marker for the given portal
*
* @param portal <p>The portal to remove the marker for</p>
*/
public static void removePortalMarker(@NotNull Portal portal) {
if (markerSet == null || Stargate.getStargateConfig().isDynmapDisabled()) {
return;
}
Marker marker = markerSet.findMarker(getPortalMarkerId(portal));
if (marker != null) {
marker.deleteMarker();
}
}
/**
* Disables Dynmap integration
*/
public static void disable() {
markerSet = null;
portalIcon = null;
}
/**
* Gets the id used for the given portal's marker
*
* @param portal <p>The portal to get a marker id for</p>
* @return <p></p>
*/
private static String getPortalMarkerId(@NotNull Portal portal) {
return portal.getNetwork() + "-:-" + portal.getName();
}
}

View File

@@ -1,16 +1,22 @@
package net.knarcraft.stargate.config;
package net.knarcraft.stargate.config.addons;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.ConfigOption;
import net.knarcraft.stargate.config.Permission;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.portal.PortalSignDrawer;
import net.knarcraft.stargate.portal.property.gate.Gate;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.md_5.bungee.api.ChatColor;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
@@ -29,11 +35,11 @@ public final class EconomyConfig {
*
* @param configOptions <p>The loaded config options to read</p>
*/
public EconomyConfig(Map<ConfigOption, Object> configOptions) {
public EconomyConfig(@NotNull Map<ConfigOption, Object> configOptions) {
this.configOptions = configOptions;
try {
String freeColor = (String) configOptions.get(ConfigOption.FREE_GATES_COLOR);
PortalSignDrawer.setFreeColor(ChatColor.valueOf(freeColor.toUpperCase()));
PortalSignDrawer.setFreeColor(ChatColor.of(freeColor.toUpperCase()));
} catch (IllegalArgumentException | NullPointerException ignored) {
PortalSignDrawer.setFreeColor(ChatColor.DARK_GREEN);
}
@@ -62,6 +68,7 @@ public final class EconomyConfig {
*
* @return <p>An economy object, or null if economy is disabled or not initialized</p>
*/
@Nullable
public Economy getEconomy() {
return economy;
}
@@ -71,6 +78,7 @@ public final class EconomyConfig {
*
* @return <p>An instance of the Vault plugin, or null if Vault is not loaded</p>
*/
@Nullable
public Plugin getVault() {
return vault;
}
@@ -132,6 +140,16 @@ public final class EconomyConfig {
return (Integer) configOptions.get(ConfigOption.DESTROY_COST);
}
/**
* Gets the account all taxes are paid to
*
* @return <p>The account all taxes are paid to</p>
*/
@Nullable
public String getTaxAccount() {
return (String) configOptions.get(ConfigOption.TAX_ACCOUNT);
}
/**
* Checks whether the given player can afford the given fee
*
@@ -149,6 +167,7 @@ public final class EconomyConfig {
* @param amount <p>The amount to display</p>
* @return <p>A formatted text string describing the amount</p>
*/
@NotNull
public String format(int amount) {
if (isEconomyEnabled()) {
return economy.format(amount);
@@ -163,7 +182,7 @@ public final class EconomyConfig {
* @param pluginManager <p>The plugin manager to get plugins from</p>
* @return <p>True if economy was enabled</p>
*/
public boolean setupEconomy(PluginManager pluginManager) {
public boolean setupEconomy(@NotNull PluginManager pluginManager) {
if (!isEconomyEnabled()) {
return false;
}
@@ -177,10 +196,10 @@ public final class EconomyConfig {
this.vault = vault;
return true;
} else {
Stargate.logInfo(Stargate.getString("ecoLoadError"));
Stargate.logInfo(new SGFormatBuilder(Message.ECONOMY_LOAD_ERROR).toString());
}
} else {
Stargate.logInfo(Stargate.getString("vaultLoadError"));
Stargate.logInfo(new SGFormatBuilder(Message.VAULT_LOAD_ERROR).toString());
}
configOptions.put(ConfigOption.USE_ECONOMY, false);
return false;
@@ -202,8 +221,8 @@ public final class EconomyConfig {
* @param gate <p>The gate type used</p>
* @return <p>The cost of creating the gate</p>
*/
public int getCreateCost(Player player, Gate gate) {
if (isFree(player, "create")) {
public int getCreateCost(@NotNull Player player, @NotNull Gate gate) {
if (isFree(player, Permission.FREE_CREATION)) {
return 0;
} else {
return gate.getCreateCost();
@@ -217,8 +236,8 @@ public final class EconomyConfig {
* @param gate <p>The gate type used</p>
* @return <p>The cost of destroying the gate</p>
*/
public int getDestroyCost(Player player, Gate gate) {
if (isFree(player, "destroy")) {
public int getDestroyCost(@NotNull Player player, @NotNull Gate gate) {
if (isFree(player, Permission.FREE_DESTRUCTION)) {
return 0;
} else {
return gate.getDestroyCost();
@@ -228,12 +247,12 @@ public final class EconomyConfig {
/**
* Determines if a player can do a gate action for free
*
* @param player <p>The player to check</p>
* @param permissionNode <p>The free.permissionNode necessary to allow free gate {action}</p>
* @param player <p>The player to check</p>
* @param permission <p>The permission necessary to allow free gate {action}</p>
* @return <p></p>
*/
private boolean isFree(Player player, String permissionNode) {
return !useEconomy() || PermissionHelper.hasPermission(player, "stargate.free." + permissionNode);
private boolean isFree(@NotNull Player player, @NotNull Permission permission) {
return !useEconomy() || PermissionHelper.hasPermission(player, permission.getNode());
}
}

View File

@@ -1,16 +1,18 @@
package net.knarcraft.stargate.config;
package net.knarcraft.stargate.config.formatting;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.FileHelper;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.utility.FileHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.EnumMap;
import java.util.Map;
import java.util.Set;
/**
* This class is responsible for loading all strings which are translated into several languages
@@ -18,9 +20,9 @@ import java.util.Set;
public final class LanguageLoader {
private final String languageFolder;
private final Map<String, String> loadedBackupStrings;
private final Map<Message, String> loadedBackupStrings;
private String chosenLanguage;
private Map<String, String> loadedStringTranslations;
private Map<Message, String> loadedStringTranslations;
/**
* Instantiates a new language loader
@@ -29,7 +31,7 @@ public final class LanguageLoader {
*
* @param languageFolder <p>The folder containing the language files</p>
*/
public LanguageLoader(String languageFolder) {
public LanguageLoader(@NotNull String languageFolder) {
this.languageFolder = languageFolder;
File testFile = new File(languageFolder, "en.txt");
if (!testFile.exists()) {
@@ -44,7 +46,7 @@ public final class LanguageLoader {
loadedBackupStrings = load("en", inputStream);
} else {
loadedBackupStrings = null;
Stargate.getConsoleLogger().severe("[stargate] Error loading backup language. " +
Stargate.logSevere("Error loading backup language. " +
"There may be missing text in-game");
}
}
@@ -59,32 +61,34 @@ public final class LanguageLoader {
}
/**
* Gets the string to display given its name/key
* Gets the string to display given its message key
*
* @param name <p>The name/key of the string to display</p>
* @return <p>The string in the user's preferred language</p>
* @param message <p>The message to display</p>
* @return <p>The message in the user's preferred language</p>
*/
public String getString(String name) {
@NotNull
public String getString(@NotNull Message message) {
String value = null;
if (loadedStringTranslations != null) {
value = loadedStringTranslations.get(name);
value = loadedStringTranslations.get(message);
}
if (value == null) {
value = getBackupString(name);
value = getBackupString(message);
}
return value;
}
/**
* Gets the string to display given its name/key
* Gets the string to display given its message key
*
* @param name <p>The name/key of the string to display</p>
* @param message <p>The message to display</p>
* @return <p>The string in the backup language (English)</p>
*/
public String getBackupString(String name) {
@NotNull
public String getBackupString(@NotNull Message message) {
String value = null;
if (loadedBackupStrings != null) {
value = loadedBackupStrings.get(name);
value = loadedBackupStrings.get(message);
}
if (value == null) {
return "";
@@ -97,7 +101,7 @@ public final class LanguageLoader {
*
* @param chosenLanguage <p>The new plugin language</p>
*/
public void setChosenLanguage(String chosenLanguage) {
public void setChosenLanguage(@NotNull String chosenLanguage) {
this.chosenLanguage = chosenLanguage;
}
@@ -106,21 +110,21 @@ public final class LanguageLoader {
*
* @param language <p>The language to update</p>
*/
private void updateLanguage(String language) {
Map<String, String> currentLanguageValues = load(language);
private void updateLanguage(@NotNull String language) {
Map<Message, String> currentLanguageValues = load(language);
InputStream inputStream = getClass().getResourceAsStream("/lang/" + language + ".txt");
InputStream inputStream = FileHelper.getInputStreamForInternalFile("/lang/" + language + ".txt");
if (inputStream == null) {
Stargate.logInfo(String.format("The language %s is not available. Falling back to english, You can add a " +
"custom language by creating a new text file in the lang directory.", language));
Stargate.logInfo(String.format("Unable to find internal language file for %s. This will normally not " +
"cause any problems, except newly added translatable strings won't be automatically added", language));
Stargate.debug("LanguageLoader::updateLanguage", String.format("Unable to load /lang/%s.txt", language));
return;
}
try {
readChangedLanguageStrings(inputStream, language, currentLanguageValues);
} catch (IOException ex) {
ex.printStackTrace();
} catch (IOException exception) {
Stargate.logSevere("Unable to read language strings! Message: " + exception.getMessage());
}
}
@@ -132,11 +136,12 @@ public final class LanguageLoader {
* @param currentLanguageValues <p>The current values of the loaded/processed language</p>
* @throws IOException <p>if unable to read a language file</p>
*/
private void readChangedLanguageStrings(InputStream inputStream, String language, Map<String,
String> currentLanguageValues) throws IOException {
private void readChangedLanguageStrings(@NotNull InputStream inputStream, @NotNull String language,
@Nullable Map<Message, String> currentLanguageValues) throws IOException {
//Get language values
BufferedReader bufferedReader = FileHelper.getBufferedReaderFromInputStream(inputStream);
Map<String, String> internalLanguageValues = FileHelper.readKeyValuePairs(bufferedReader);
Map<Message, String> internalLanguageValues = fromStringMap(FileHelper.readKeyValuePairs(bufferedReader,
"=", ColorConversion.NORMAL));
//If currentLanguageValues is null; the chosen language has not been used before
if (currentLanguageValues == null) {
@@ -147,11 +152,14 @@ public final class LanguageLoader {
//If a key is not found in the language file, add the one in the internal file. Must update the external file
if (!internalLanguageValues.keySet().equals(currentLanguageValues.keySet())) {
Map<String, String> newLanguageValues = new HashMap<>();
Map<Message, String> newLanguageValues = new EnumMap<>(Message.class);
boolean updateNecessary = false;
for (String key : internalLanguageValues.keySet()) {
for (Map.Entry<Message, String> entry : internalLanguageValues.entrySet()) {
Message key = entry.getKey();
String value = entry.getValue();
if (currentLanguageValues.get(key) == null) {
newLanguageValues.put(key, internalLanguageValues.get(key));
newLanguageValues.put(key, value);
//Found at least one value in the internal file not in the external file. Need to update
updateNecessary = true;
} else {
@@ -175,20 +183,20 @@ public final class LanguageLoader {
* @param customLanguageStrings <p>Any custom language strings not recognized</p>
* @throws IOException <p>If unable to write to the language file</p>
*/
private void updateLanguageFile(String language, Map<String, String> languageStrings,
Map<String, String> customLanguageStrings) throws IOException {
private void updateLanguageFile(@NotNull String language, @NotNull Map<Message, String> languageStrings,
@Nullable Map<Message, String> customLanguageStrings) throws IOException {
BufferedWriter bufferedWriter = FileHelper.getBufferedWriterFromString(languageFolder + language + ".txt");
//Output normal Language data
for (String key : languageStrings.keySet()) {
bufferedWriter.write(key + "=" + languageStrings.get(key));
for (Map.Entry<Message, String> entry : languageStrings.entrySet()) {
bufferedWriter.write(entry.getKey() + "=" + entry.getValue());
bufferedWriter.newLine();
}
bufferedWriter.newLine();
//Output any custom language strings the user had
if (customLanguageStrings != null) {
for (String key : customLanguageStrings.keySet()) {
bufferedWriter.write(key + "=" + customLanguageStrings.get(key));
for (Map.Entry<Message, String> entry : customLanguageStrings.entrySet()) {
bufferedWriter.write(entry.getKey() + "=" + entry.getValue());
bufferedWriter.newLine();
}
}
@@ -201,7 +209,8 @@ public final class LanguageLoader {
* @param lang <p>The language to load</p>
* @return <p>A mapping between loaded string indexes and the strings to display</p>
*/
private Map<String, String> load(String lang) {
@Nullable
private Map<Message, String> load(@NotNull String lang) {
return load(lang, null);
}
@@ -212,8 +221,8 @@ public final class LanguageLoader {
* @param inputStream <p>An optional input stream to use. Defaults to using a file input stream</p>
* @return <p>A mapping between loaded string indexes and the strings to display</p>
*/
private Map<String, String> load(String lang, InputStream inputStream) {
Map<String, String> strings;
@Nullable
private Map<Message, String> load(@NotNull String lang, @Nullable InputStream inputStream) {
BufferedReader bufferedReader;
try {
if (inputStream == null) {
@@ -221,14 +230,13 @@ public final class LanguageLoader {
} else {
bufferedReader = FileHelper.getBufferedReaderFromInputStream(inputStream);
}
strings = FileHelper.readKeyValuePairs(bufferedReader);
} catch (Exception e) {
return fromStringMap(FileHelper.readKeyValuePairs(bufferedReader, "=", ColorConversion.NORMAL));
} catch (Exception exception) {
if (Stargate.getStargateConfig().isDebuggingEnabled()) {
Stargate.getConsoleLogger().info("[Stargate] Unable to load language " + lang);
Stargate.logInfo("Unable to load language " + lang);
}
return null;
}
return strings;
}
/**
@@ -236,20 +244,41 @@ public final class LanguageLoader {
*/
public void debug() {
if (loadedStringTranslations != null) {
Set<String> keys = loadedStringTranslations.keySet();
for (String key : keys) {
Stargate.debug("LanguageLoader::Debug::loadedStringTranslations", key + " => " +
loadedStringTranslations.get(key));
for (Map.Entry<Message, String> entry : loadedStringTranslations.entrySet()) {
Stargate.debug("LanguageLoader::Debug::loadedStringTranslations", entry.getKey() +
" => " + entry.getValue());
}
}
if (loadedBackupStrings == null) {
return;
}
Set<String> keys = loadedBackupStrings.keySet();
for (String key : keys) {
Stargate.debug("LanguageLoader::Debug::loadedBackupStrings", key + " => " +
loadedBackupStrings.get(key));
for (Map.Entry<Message, String> entry : loadedBackupStrings.entrySet()) {
Stargate.debug("LanguageLoader::Debug::loadedBackupStrings", entry.getKey() + " => " +
entry.getValue());
}
}
/**
* Converts a map from string key to message into a map from message key to message
*
* @param configurationStrings <p>The map to convert</p>
* @return <p>The converted map</p>
*/
@NotNull
private Map<Message, String> fromStringMap(@NotNull Map<String, String> configurationStrings) {
Map<Message, String> output = new EnumMap<>(Message.class);
for (Map.Entry<String, String> entry : configurationStrings.entrySet()) {
Message message = Message.getFromKey(entry.getKey());
if (message == null) {
Stargate.logWarning("Found unrecognized language key " + entry.getKey());
continue;
}
output.put(message, entry.getValue());
}
return output;
}
}

View File

@@ -0,0 +1,251 @@
package net.knarcraft.stargate.config.formatting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Translated messages displayed to players
*/
public enum Message {
/**
* The prefix displayed in front of all messages shown in the chat
*/
PREFIX("prefix"),
/**
* The message displayed when a player is teleported
*/
TELEPORTED("teleportMsg"),
/**
* The message displayed when a player destroys a Stargate
*/
DESTROYED("destroyMsg"),
/**
* The message displayed when the currently selected Stargate destination is invalid
*/
INVALID_DESTINATION("invalidMsg"),
/**
* The message displayed when the destination portal is busy with another player
*/
DESTINATION_BLOCKED("blockMsg"),
/**
* The message displayed when the Stargate has no destinations available to the player
*/
NO_DESTINATION("destEmpty"),
/**
* The message displayed when a player is denied access to any action
*/
ACCESS_DENIED("denyMsg"),
/**
* The message displayed when the plugin is reloaded
*/
RELOADED("reloaded"),
/**
* The message displayed when a player has some currency deducted from their account
*/
ECONOMY_DEDUCTED("ecoDeduct"),
/**
* The message displayed when a player has some currency refunded to their account
*/
ECONOMY_REFUNDED("ecoRefund"),
/**
* The message displayed when a player obtains some currency to their account (from portal usage)
*/
ECONOMY_OBTAINED("ecoObtain"),
/**
* The message displayed when the player has an insufficient amount of currency to perform an action
*/
ECONOMY_INSUFFICIENT("ecoInFunds"),
/**
* The message displayed when economy fails to load
*/
ECONOMY_LOAD_ERROR("ecoLoadError"),
/**
* The message displayed when Vault fails to load
*/
VAULT_LOAD_ERROR("vaultLoadError"),
/**
* The message displayed when Vault successfully loads
*/
VAULT_LOADED("vaultLoaded"),
/**
* The message displayed when a Stargate is successfully created
*/
CREATED("createMsg"),
/**
* The message displayed when a player is denied from creating a Stargate on the selected network
*/
CREATION_NETWORK_DENIED("createNetDeny"),
/**
* The message displayed when a player is denied from creating a Stargate of the given gate type
*/
CREATION_GATE_DENIED("createGateDeny"),
/**
* The message displayed when a Stargate is created on the player's personal network
*/
CREATION_PERSONAL("createPersonal"),
/**
* The message displayed when the name of a Stargate is too short or too long
*/
CREATION_NAME_LENGTH("createNameLength"),
/**
* The message displayed when another Stargate on the network has the same name as the new Stargate
*/
CREATION_NAME_COLLISION("createExists"),
/**
* The message displayed when the specified network is full
*/
CREATION_NETWORK_FULL("createFull"),
/**
* The message displayed when a player is denied from creating a Stargate in the current world
*/
CREATION_WORLD_DENIED("createWorldDeny"),
/**
* The message displayed when a gate is physically conflicting with another
*/
CREATION_CONFLICT("createConflict"),
/**
* The right-click prompt displayed on Stargate signs
*/
SIGN_RIGHT_CLICK("signRightClick"),
/**
* The to use prompt displayed on Stargate signs
*/
SIGN_TO_USE("signToUse"),
/**
* The random string displayed on Stargate signs
*/
SIGN_RANDOM("signRandom"),
/**
* The disconnected string displayed on Stargate signs
*/
SIGN_DISCONNECTED("signDisconnected"),
/**
* The invalid gate string displayed on Stargate signs
*/
SIGN_INVALID("signInvalidGate"),
/**
* The message displayed if trying to create a bungee gate when bungee is disabled
*/
BUNGEE_DISABLED("bungeeDisabled"),
/**
* The message displayed when a player is denied from creating a bungee Stargate
*/
BUNGEE_CREATION_DENIED("bungeeDeny"),
/**
* The message displayed if a Stargate is missing the destination, the network or both
*/
BUNGEE_MISSING_INFO("bungeeEmpty"),
/**
* The teleportation prompt shown on bungee signs
*/
BUNGEE_SIGN("bungeeSign"),
/**
* The format of the title of the portal info shown in chat
*/
PORTAL_INFO_TITLE("portalInfoTitle"),
/**
* The format of the name of the portal info shown in chat
*/
PORTAL_INFO_NAME("portalInfoName"),
/**
* The format of the destination of the portal info shown in chat
*/
PORTAL_INFO_DESTINATION("portalInfoDestination"),
/**
* The format of the network of the portal info shown in chat
*/
PORTAL_INFO_NETWORK("portalInfoNetwork"),
/**
* The format of the server of the portal info shown in chat
*/
PORTAL_INFO_SERVER("portalInfoServer"),
/**
* The author that created the loaded translation
*/
AUTHOR("author"),
;
private final String key;
/**
* Instantiates a new message
*
* @param key <p>The key of the message in the language files</p>
*/
Message(@NotNull String key) {
this.key = key;
}
/**
* Gets the language file key for this message
*
* @return <p>This message's key</p>
*/
@NotNull
public String getKey() {
return this.key;
}
/**
* Gets the message corresponding to the given key
*
* @param key <p>The key to get a message from</p>
* @return <p>The message, or null if not found</p>
*/
@Nullable
public static Message getFromKey(@NotNull String key) {
for (Message message : Message.values()) {
if (message.getKey().equalsIgnoreCase(key)) {
return message;
}
}
return null;
}
@Override
@NotNull
public String toString() {
return this.getKey();
}
}

View File

@@ -0,0 +1,40 @@
package net.knarcraft.stargate.config.formatting;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.knarcraft.stargate.Stargate;
import org.jetbrains.annotations.NotNull;
/**
* A customized format builder for automatic translation of Stargate messages
*/
public class SGFormatBuilder extends FormatBuilder {
/**
* Instantiates a new format builder
*/
public SGFormatBuilder() {
super();
}
/**
* Instantiates a new format builder
*
* <p>If the input is a list, it will be joined using the default delimiter: ",".</p>
*
* @param input <p>The input to use as the initial string of this format builder</p>
* @throws IllegalStateException <p>If the string formatter has not been set, and the input is a translatable message</p>
*/
public <K> SGFormatBuilder(@NotNull K input) throws IllegalStateException {
super(input);
}
@Override
@NotNull
protected <K> String asString(@NotNull K input, @NotNull String delimiter) {
if (input instanceof Message message) {
return Stargate.getStargateConfig().getLanguageLoader().getString(message);
}
return super.asString(input, delimiter);
}
}

View File

@@ -0,0 +1,49 @@
package net.knarcraft.stargate.config.material;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* A specifier for a Bukkit material
*/
public class BukkitMaterialSpecifier implements MaterialSpecifier {
private final Material material;
/**
* Instantiates a new material specifier
*
* @param material <p>The material to specify</p>
*/
public BukkitMaterialSpecifier(@NotNull Material material) {
this.material = material;
}
@Override
@NotNull
public String asString() {
return this.material.name();
}
@Override
@NotNull
public Set<Material> asMaterials() {
return Set.of(this.material);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof BukkitMaterialSpecifier bukkitMaterialSpecifier)) {
return false;
}
return this.material == bukkitMaterialSpecifier.material;
}
@Override
public int hashCode() {
return material.hashCode();
}
}

View File

@@ -0,0 +1,49 @@
package net.knarcraft.stargate.config.material;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* A specifier for a Bukkit material tag
*/
public class BukkitTagSpecifier implements MaterialSpecifier {
private final Tag<Material> tag;
/**
* Instantiates a new tag specifier
*
* @param tag <p>The tag to specify</p>
*/
public BukkitTagSpecifier(@NotNull Tag<Material> tag) {
this.tag = tag;
}
@Override
public @NotNull String asString() {
return "#" + this.tag.getKey().toString().replaceFirst("minecraft:", "");
}
@Override
public @NotNull Set<Material> asMaterials() {
return this.tag.getValues();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof BukkitTagSpecifier bukkitMaterialSpecifier)) {
return false;
}
return this.tag == bukkitMaterialSpecifier.tag;
}
@Override
public int hashCode() {
return tag.hashCode();
}
}

View File

@@ -0,0 +1,29 @@
package net.knarcraft.stargate.config.material;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* An interface describing a specifier for one or more Bukkit materials
*/
public interface MaterialSpecifier {
/**
* Gets the string representation of the material specifier
*
* <p>This is used when saving the value to a gate file</p>
*/
@NotNull
String asString();
/**
* Gets all the materials the specifier specifies
*
* <p>This is used when registering gate materials</p>
*/
@NotNull
Set<Material> asMaterials();
}

View File

@@ -2,13 +2,16 @@ package net.knarcraft.stargate.container;
import org.bukkit.Axis;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a request for changing a block into another material
*/
public class BlockChangeRequest {
private final BlockLocation blockLocation;
private final Block blockLocation;
private final Material newMaterial;
private final Axis newAxis;
@@ -19,7 +22,7 @@ public class BlockChangeRequest {
* @param material <p>The new material to change the block to</p>
* @param axis <p>The new axis to orient the block along</p>
*/
public BlockChangeRequest(BlockLocation blockLocation, Material material, Axis axis) {
public BlockChangeRequest(@NotNull Block blockLocation, @NotNull Material material, @Nullable Axis axis) {
this.blockLocation = blockLocation;
newMaterial = material;
newAxis = axis;
@@ -30,7 +33,8 @@ public class BlockChangeRequest {
*
* @return <p>The location of the block</p>
*/
public BlockLocation getBlockLocation() {
@NotNull
public Block getBlockLocation() {
return blockLocation;
}
@@ -39,6 +43,7 @@ public class BlockChangeRequest {
*
* @return <p>The material to change the block into</p>
*/
@NotNull
public Material getMaterial() {
return newMaterial;
}
@@ -48,6 +53,7 @@ public class BlockChangeRequest {
*
* @return <p>The axis to orient the block along</p>
*/
@Nullable
public Axis getAxis() {
return newAxis;
}

View File

@@ -5,11 +5,9 @@ import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.type.Sign;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This class represents a block location
@@ -20,8 +18,6 @@ import org.bukkit.util.Vector;
*/
public class BlockLocation extends Location {
private BlockLocation parent = null;
/**
* Creates a new block location
*
@@ -30,7 +26,7 @@ public class BlockLocation extends Location {
* @param y <p>The y coordinate of the block</p>
* @param z <p>The z coordinate of the block</p>
*/
public BlockLocation(World world, int x, int y, int z) {
public BlockLocation(@NotNull World world, int x, int y, int z) {
super(world, x, y, z);
}
@@ -39,7 +35,7 @@ public class BlockLocation extends Location {
*
* @param block <p>The block to get the location of</p>
*/
public BlockLocation(Block block) {
public BlockLocation(@NotNull Block block) {
super(block.getWorld(), block.getX(), block.getY(), block.getZ());
}
@@ -49,7 +45,7 @@ public class BlockLocation extends Location {
* @param world <p>The world the block exists in</p>
* @param string <p>A comma separated list of x, y and z coordinates as integers</p>
*/
public BlockLocation(World world, String string) {
public BlockLocation(@NotNull World world, @NotNull String string) {
super(world, Integer.parseInt(string.split(",")[0]), Integer.parseInt(string.split(",")[1]),
Integer.parseInt(string.split(",")[2]));
}
@@ -62,6 +58,7 @@ public class BlockLocation extends Location {
* @param z <p>The number of blocks to move in the z-direction</p>
* @return <p>A new block location</p>
*/
@NotNull
public BlockLocation makeRelativeBlockLocation(int x, int y, int z) {
return (BlockLocation) this.clone().add(x, y, z);
}
@@ -75,6 +72,7 @@ public class BlockLocation extends Location {
* @param yaw <p>The number of blocks to move in the z-direction</p>
* @return <p>A new location</p>
*/
@NotNull
public Location makeRelativeLocation(double x, double y, double z, float yaw) {
Location newLocation = this.clone();
newLocation.setYaw(yaw);
@@ -89,9 +87,10 @@ public class BlockLocation extends Location {
* @param yaw <p>The yaw pointing outwards from a portal (in the relative vector's out direction)</p>
* @return <p>A location relative to this location</p>
*/
public BlockLocation getRelativeLocation(RelativeBlockVector relativeVector, double yaw) {
Vector realVector = DirectionHelper.getCoordinateVectorFromRelativeVector(relativeVector.getRight(),
relativeVector.getDown(), relativeVector.getOut(), yaw);
@NotNull
public BlockLocation getRelativeLocation(@NotNull RelativeBlockVector relativeVector, double yaw) {
Vector realVector = DirectionHelper.getCoordinateVectorFromRelativeVector(relativeVector.right(),
relativeVector.down(), relativeVector.out(), yaw);
return makeRelativeBlockLocation(realVector.getBlockX(), realVector.getBlockY(), realVector.getBlockZ());
}
@@ -107,6 +106,7 @@ public class BlockLocation extends Location {
* @param portalYaw <p>The yaw when looking out from the portal</p>
* @return A new location relative to this block location
*/
@NotNull
public Location getRelativeLocation(double right, double down, double out, float portalYaw) {
Vector realVector = DirectionHelper.getCoordinateVectorFromRelativeVector(right, down, out, portalYaw);
return makeRelativeLocation(0.5 + realVector.getBlockX(), realVector.getBlockY(),
@@ -118,6 +118,7 @@ public class BlockLocation extends Location {
*
* @return <p>The block's material type</p>
*/
@NotNull
public Material getType() {
return this.getBlock().getType();
}
@@ -127,7 +128,7 @@ public class BlockLocation extends Location {
*
* @param type <p>The block's new material type</p>
*/
public void setType(Material type) {
public void setType(@NotNull Material type) {
this.getBlock().setType(type);
}
@@ -136,54 +137,13 @@ public class BlockLocation extends Location {
*
* @return <p>The location representing this block location</p>
*/
@NotNull
public Location getLocation() {
return this.clone();
}
/**
* Gets this block location's parent block
*
* <p>The parent block is the block the item at this block location is attached to. Usually this is the block a
* sign or wall sign is attached to.</p>
*
* @return <p>This block location's parent block</p>
*/
public Block getParent() {
if (parent == null) {
findParent();
}
if (parent == null) {
return null;
}
return parent.getBlock();
}
/**
* Tries to find the parent block location
*
* <p>If this block location is a sign, the parent is the block location of the block the sign is connected to.</p>
*/
private void findParent() {
int offsetX = 0;
int offsetY = 0;
int offsetZ = 0;
BlockData blockData = getBlock().getBlockData();
if (blockData instanceof Directional) {
//Get the offset of the block "behind" this block
BlockFace facing = ((Directional) blockData).getFacing().getOppositeFace();
offsetX = facing.getModX();
offsetZ = facing.getModZ();
} else if (blockData instanceof Sign) {
//Get offset the block beneath the sign
offsetY = -1;
} else {
return;
}
parent = this.makeRelativeBlockLocation(offsetX, offsetY, offsetZ);
}
@Override
@NotNull
public String toString() {
return String.valueOf(this.getBlockX()) + ',' + this.getBlockY() + ',' + this.getBlockZ();
}
@@ -203,7 +163,7 @@ public class BlockLocation extends Location {
}
@Override
public boolean equals(Object object) {
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}

View File

@@ -17,7 +17,7 @@ public class ChunkUnloadRequest implements Comparable<ChunkUnloadRequest> {
* @param chunkToUnload <p>The chunk to request the unloading of</p>
* @param timeUntilUnload <p>The time in milliseconds to wait before unloading the chunk</p>
*/
public ChunkUnloadRequest(Chunk chunkToUnload, Long timeUntilUnload) {
public ChunkUnloadRequest(@NotNull Chunk chunkToUnload, @NotNull Long timeUntilUnload) {
this.chunkToUnload = chunkToUnload;
long systemNanoTime = System.nanoTime();
this.unloadNanoTime = systemNanoTime + (timeUntilUnload * 1000000);
@@ -28,6 +28,7 @@ public class ChunkUnloadRequest implements Comparable<ChunkUnloadRequest> {
*
* @return <p>The chunk to unload</p>
*/
@NotNull
public Chunk getChunkToUnload() {
return this.chunkToUnload;
}
@@ -37,11 +38,13 @@ public class ChunkUnloadRequest implements Comparable<ChunkUnloadRequest> {
*
* @return <p>The system nano time denoting when the chunk is to be unloaded</p>
*/
@NotNull
public Long getUnloadNanoTime() {
return this.unloadNanoTime;
}
@Override
@NotNull
public String toString() {
return "{" + chunkToUnload + ", " + unloadNanoTime + "}";
}

View File

@@ -0,0 +1,12 @@
package net.knarcraft.stargate.container;
import net.knarcraft.stargate.portal.Portal;
import org.jetbrains.annotations.NotNull;
/**
* A request for updating a portal's control blocks
*
* @param portal <p>The portal to update the control blocks for</p>
*/
public record ControlBlockUpdateRequest(@NotNull Portal portal) {
}

View File

@@ -1,60 +1,16 @@
package net.knarcraft.stargate.container;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* This class represents a player teleporting from the end to the over-world using an artificial end portal
* This class represents a teleportation from the end to the over-world using an artificial end portal
*
* <p>This is necessary because a player entering an end portal in the end is a special case. Instead of being
* teleported, the player is respawned. Because of this, the teleportation needs to be saved and later used to hijack
* the position of where the player is to respawn.</p>
*
* @param exitPortal <p>The portal the player should exit from when arriving in the over-world</p>
*/
public class FromTheEndTeleportation {
private final Player teleportingPlayer;
private final Portal exitPortal;
/**
* Instantiates a new teleportation from the end
*
* @param teleportingPlayer <p>The teleporting player</p>
* @param exitPortal <p>The portal to exit from</p>
*/
public FromTheEndTeleportation(Player teleportingPlayer, Portal exitPortal) {
this.teleportingPlayer = teleportingPlayer;
this.exitPortal = exitPortal;
}
/**
* Gets the teleporting player
*
* @return <p>The teleporting player</p>
*/
public Player getPlayer() {
return this.teleportingPlayer;
}
/**
* Gets the portal to exit from
*
* @return <p>The portal to exit from</p>
*/
public Portal getExit() {
return this.exitPortal;
}
@Override
public int hashCode() {
return teleportingPlayer.hashCode();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof FromTheEndTeleportation otherTeleportation)) {
return false;
}
return teleportingPlayer.equals(otherTeleportation.teleportingPlayer);
}
public record FromTheEndTeleportation(@NotNull Portal exitPortal) {
}

View File

@@ -1,5 +1,9 @@
package net.knarcraft.stargate.container;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This stores a block location as a vector relative to a position
*
@@ -7,67 +11,57 @@ package net.knarcraft.stargate.container;
* top-left block of a gate (top-left when looking at the side with the sign). The right is therefore the distance
* from the top-left corner towards the top-right corner. Down is the distance from the top-left corner towards the
* bottom-left corner. Out is the distance outward from the gate.</p>
*
* <p>Relative block vectors start from a top-left corner. A yaw is used to orient a relative block vector in the
* "real world". In terms of a gate layout, the origin is 0,0. Right is towards the end of the line. Down is to the
* next line. Out is towards the observer.</p>
*
* @param right <p>The distance rightward relative to the origin</p>
* @param down <p>The distance downward relative to the origin</p>
* @param out <p>The distance outward relative to the origin</p>
*/
public class RelativeBlockVector {
private final int right;
private final int down;
private final int out;
public record RelativeBlockVector(int right, int down, int out) {
/**
* A specifier for one of the relative block vector's three properties
* Adds the given value to this relative block vector's "right" property
*
* @param valueToAdd <p>The value to add</p>
* @return <p>The new resulting vector</p>
*/
public enum Property {
/**
* Specifies the relative block vector's right property
*/
RIGHT,
/**
* Specifies the relative block vector's down property
*/
DOWN,
/**
* Specifies the relative block vector's out property
*/
OUT
@NotNull
public RelativeBlockVector addRight(int valueToAdd) {
return new RelativeBlockVector(this.right + valueToAdd, this.down, this.out);
}
/**
* Instantiates a new relative block vector
* Adds the given value to this relative block vector's "down" property
*
* <p>Relative block vectors start from a top-left corner. A yaw is used to orient a relative block vector in the
* "real world".
* In terms of a gate layout, the origin is 0,0. Right is towards the end of the line. Down is to the
* next line. Out is towards the observer.</p>
*
* @param right <p>The distance rightward relative to the origin</p>
* @param down <p>The distance downward relative to the origin</p>
* @param out <p>The distance outward relative to the origin</p>
* @param valueToAdd <p>The value to add</p>
* @return <p>The new resulting vector</p>
*/
public RelativeBlockVector(int right, int down, int out) {
this.right = right;
this.down = down;
this.out = out;
@NotNull
public RelativeBlockVector addDown(int valueToAdd) {
return new RelativeBlockVector(this.right, this.down + valueToAdd, this.out);
}
/**
* Adds a value to one of the properties of this relative block vector
* Adds the given value to this relative block vector's "out" property
*
* @param propertyToAddTo <p>The property to add to</p>
* @param valueToAdd <p>The value to add to the property (negative to move in the opposite direction)</p>
* @return <p>A new relative block vector with the property altered</p>
* @param valueToAdd <p>The value to add</p>
* @return <p>The new resulting vector</p>
*/
public RelativeBlockVector addToVector(Property propertyToAddTo, int valueToAdd) {
switch (propertyToAddTo) {
case RIGHT:
return new RelativeBlockVector(this.right + valueToAdd, this.down, this.out);
case DOWN:
return new RelativeBlockVector(this.right, this.down + valueToAdd, this.out);
case OUT:
return new RelativeBlockVector(this.right, this.down, this.out + valueToAdd);
default:
throw new IllegalArgumentException("Invalid relative block vector property given");
}
@NotNull
public RelativeBlockVector addOut(int valueToAdd) {
return new RelativeBlockVector(this.right, this.down, this.out + valueToAdd);
}
/**
* Gets a relative vector in the real space representing this relative block vector
*
* @return <p>A vector representing this relative block vector</p>
*/
public Vector toVector() {
return new Vector(this.right, -this.down, this.out);
}
/**
@@ -75,44 +69,19 @@ public class RelativeBlockVector {
*
* @return <p>This vector, but inverted</p>
*/
@NotNull
public RelativeBlockVector invert() {
return new RelativeBlockVector(-this.right, -this.down, -this.out);
}
/**
* Gets the distance to the right relative to the origin
*
* @return <p>The distance to the right relative to the origin</p>
*/
public int getRight() {
return right;
}
/**
* Gets the distance downward relative to the origin
*
* @return <p>The distance downward relative to the origin</p>
*/
public int getDown() {
return down;
}
/**
* Gets the distance outward relative to the origin
*
* @return <p>The distance outward relative to the origin</p>
*/
public int getOut() {
return out;
}
@Override
@NotNull
public String toString() {
return String.format("(right = %d, down = %d, out = %d)", right, down, out);
}
@Override
public boolean equals(Object other) {
public boolean equals(@Nullable Object other) {
if (other == this) {
return true;
}

View File

@@ -0,0 +1,72 @@
package net.knarcraft.stargate.container;
import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.stargate.utility.SignHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.DyeColor;
import org.bukkit.block.Sign;
import org.jetbrains.annotations.NotNull;
/**
* A class that keeps track of the sign colors for a given sign
*/
public class SignData {
private final Sign sign;
private final ChatColor mainSignColor;
private final ChatColor highlightSignColor;
private final DyeColor dyedColor;
/**
* Instantiates a new sign colors object
*
* @param sign <p>The sign the colors belong to</p>
* @param mainSignColor <p>The main color to use for the sign</p>
* @param highlightSignColor <p>The highlighting color to use for the sign</p>
*/
public SignData(@NotNull Sign sign, @NotNull ChatColor mainSignColor, @NotNull ChatColor highlightSignColor) {
this.sign = sign;
this.mainSignColor = mainSignColor;
this.highlightSignColor = highlightSignColor;
this.dyedColor = SignHelper.getDye(sign);
}
/**
* Gets the sign of this sign colors object
*
* @return <p>The sign of this sign colors object</p>
*/
@NotNull
public Sign getSign() {
return sign;
}
/**
* Gets the main color of the sign
*
* @return <p>The main color of the sign</p>
*/
@NotNull
public ChatColor getMainSignColor() {
if (dyedColor != DyeColor.BLACK) {
return ColorHelper.fromColor(dyedColor.getColor());
} else {
return mainSignColor;
}
}
/**
* Gets the highlighting color of the sign
*
* @return <p>The highlighting color of the sign</p>
*/
@NotNull
public ChatColor getHighlightSignColor() {
if (dyedColor != DyeColor.BLACK) {
return ColorHelper.fromColor(ColorHelper.invert(dyedColor.getColor()));
} else {
return highlightSignColor;
}
}
}

View File

@@ -24,7 +24,7 @@ public class StargateAccessEvent extends StargatePlayerEvent {
* @param portal <p>The portal involved in the event</p>
* @param deny <p>Whether the stargate access should be denied</p>
*/
public StargateAccessEvent(Player player, Portal portal, boolean deny) {
public StargateAccessEvent(@NotNull Player player, @NotNull Portal portal, boolean deny) {
super(portal, player);
this.deny = deny;
@@ -53,6 +53,7 @@ public class StargateAccessEvent extends StargatePlayerEvent {
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}

View File

@@ -28,7 +28,8 @@ public class StargateActivateEvent extends StargatePlayerEvent {
* @param destinations <p>The destinations available to the player using the portal</p>
* @param destination <p>The currently selected destination</p>
*/
public StargateActivateEvent(Portal portal, Player player, List<String> destinations, String destination) {
public StargateActivateEvent(@NotNull Portal portal, @NotNull Player player, @NotNull List<String> destinations,
@NotNull String destination) {
super(portal, player);
this.destinations = destinations;
@@ -40,6 +41,7 @@ public class StargateActivateEvent extends StargatePlayerEvent {
*
* @return <p>The destinations available for the portal</p>
*/
@NotNull
public List<String> getDestinations() {
return destinations;
}
@@ -49,7 +51,7 @@ public class StargateActivateEvent extends StargatePlayerEvent {
*
* @param destinations <p>The new list of available destinations</p>
*/
public void setDestinations(List<String> destinations) {
public void setDestinations(@NotNull List<String> destinations) {
this.destinations = destinations;
}
@@ -58,6 +60,7 @@ public class StargateActivateEvent extends StargatePlayerEvent {
*
* @return <p>The selected destination</p>
*/
@NotNull
public String getDestination() {
return destination;
}
@@ -67,7 +70,7 @@ public class StargateActivateEvent extends StargatePlayerEvent {
*
* @param destination <p>The new selected destination</p>
*/
public void setDestination(String destination) {
public void setDestination(@NotNull String destination) {
this.destination = destination;
}
@@ -76,6 +79,7 @@ public class StargateActivateEvent extends StargatePlayerEvent {
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}

View File

@@ -22,7 +22,7 @@ public class StargateCloseEvent extends StargateEvent {
* @param portal <p>The portal to close</p>
* @param force <p>Whether to force the gate to close, even if set as always-on</p>
*/
public StargateCloseEvent(Portal portal, boolean force) {
public StargateCloseEvent(@NotNull Portal portal, boolean force) {
super(portal);
this.force = force;
@@ -51,6 +51,7 @@ public class StargateCloseEvent extends StargateEvent {
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}

View File

@@ -29,7 +29,8 @@ public class StargateCreateEvent extends StargatePlayerEvent {
* @param denyReason <p>The reason stargate creation was denied</p>
* @param cost <p>The cost of creating the new star gate</p>
*/
public StargateCreateEvent(Player player, Portal portal, String[] lines, boolean deny, String denyReason, int cost) {
public StargateCreateEvent(@NotNull Player player, @NotNull Portal portal, @NotNull String[] lines, boolean deny,
@NotNull String denyReason, int cost) {
super(portal, player);
this.lines = lines;
this.deny = deny;
@@ -44,6 +45,7 @@ public class StargateCreateEvent extends StargatePlayerEvent {
* @return <p>The text on the given line</p>
* @throws IndexOutOfBoundsException <p>If given a line index less than zero or above three</p>
*/
@NotNull
public String getLine(int index) throws IndexOutOfBoundsException {
return lines[index];
}
@@ -71,6 +73,7 @@ public class StargateCreateEvent extends StargatePlayerEvent {
*
* @return <p>The reason the stargate creation was denied</p>
*/
@NotNull
public String getDenyReason() {
return denyReason;
}
@@ -80,7 +83,7 @@ public class StargateCreateEvent extends StargatePlayerEvent {
*
* @param denyReason <p>The new reason why the stargate creation was denied</p>
*/
public void setDenyReason(String denyReason) {
public void setDenyReason(@NotNull String denyReason) {
this.denyReason = denyReason;
}
@@ -107,6 +110,7 @@ public class StargateCreateEvent extends StargatePlayerEvent {
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}

View File

@@ -20,7 +20,7 @@ public class StargateDeactivateEvent extends StargateEvent {
*
* @param portal <p>The portal which was deactivated</p>
*/
public StargateDeactivateEvent(Portal portal) {
public StargateDeactivateEvent(@NotNull Portal portal) {
super(portal);
}
@@ -29,6 +29,7 @@ public class StargateDeactivateEvent extends StargateEvent {
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}

View File

@@ -27,7 +27,8 @@ public class StargateDestroyEvent extends StargatePlayerEvent {
* @param denyMsg <p>The message to display if the event is denied</p>
* @param cost <p>The cost of destroying the portal</p>
*/
public StargateDestroyEvent(Portal portal, Player player, boolean deny, String denyMsg, int cost) {
public StargateDestroyEvent(@NotNull Portal portal, @NotNull Player player, boolean deny, @NotNull String denyMsg,
int cost) {
super(portal, player);
this.deny = deny;
this.denyReason = denyMsg;
@@ -57,6 +58,7 @@ public class StargateDestroyEvent extends StargatePlayerEvent {
*
* @return <p>The reason the event was denied</p>
*/
@NotNull
public String getDenyReason() {
return denyReason;
}
@@ -66,7 +68,7 @@ public class StargateDestroyEvent extends StargatePlayerEvent {
*
* @param denyReason <p>The reason the event was denied</p>
*/
public void setDenyReason(String denyReason) {
public void setDenyReason(@NotNull String denyReason) {
this.denyReason = denyReason;
}
@@ -93,6 +95,7 @@ public class StargateDestroyEvent extends StargatePlayerEvent {
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}

View File

@@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
* <p>This event can be used to overwrite the location the entity is teleported to.</p>
*/
@SuppressWarnings("unused")
public class StargateEntityPortalEvent extends StargateEvent {
public class StargateEntityPortalEvent extends StargateEvent implements StargateTeleportEvent {
private static final HandlerList handlers = new HandlerList();
final Entity travellingEntity;
@@ -27,7 +27,8 @@ public class StargateEntityPortalEvent extends StargateEvent {
* @param destination <p>The destination the entity should exit from</p>
* @param exit <p>The exit location of the destination portal the entity will be teleported to</p>
*/
public StargateEntityPortalEvent(Entity travellingEntity, Portal portal, Portal destination, Location exit) {
public StargateEntityPortalEvent(@NotNull Entity travellingEntity, @NotNull Portal portal,
@NotNull Portal destination, @NotNull Location exit) {
super(portal);
this.travellingEntity = travellingEntity;
@@ -40,6 +41,7 @@ public class StargateEntityPortalEvent extends StargateEvent {
*
* @return <p>The non-player teleporting</p>
*/
@NotNull
public Entity getEntity() {
return travellingEntity;
}
@@ -49,6 +51,7 @@ public class StargateEntityPortalEvent extends StargateEvent {
*
* @return <p>The destination portal</p>
*/
@NotNull
public Portal getDestination() {
return destination;
}
@@ -58,6 +61,8 @@ public class StargateEntityPortalEvent extends StargateEvent {
*
* @return <p>Location of the exit point</p>
*/
@Override
@NotNull
public Location getExit() {
return exit;
}
@@ -67,7 +72,7 @@ public class StargateEntityPortalEvent extends StargateEvent {
*
* @param location <p>The new location of the entity's exit point</p>
*/
public void setExit(Location location) {
public void setExit(@NotNull Location location) {
this.exit = location;
}
@@ -76,6 +81,7 @@ public class StargateEntityPortalEvent extends StargateEvent {
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}

View File

@@ -3,6 +3,7 @@ package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.jetbrains.annotations.NotNull;
/**
* An abstract event describing any stargate event
@@ -18,7 +19,7 @@ public abstract class StargateEvent extends Event implements Cancellable {
*
* @param portal <p>The portal involved in this stargate event</p>
*/
StargateEvent(Portal portal) {
StargateEvent(@NotNull Portal portal) {
this.portal = portal;
this.cancelled = false;
}
@@ -28,6 +29,7 @@ public abstract class StargateEvent extends Event implements Cancellable {
*
* @return <p>The portal involved in this stargate event</p>
*/
@NotNull
public Portal getPortal() {
return portal;
}

View File

@@ -4,6 +4,7 @@ import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This event should be called whenever a player opens a stargate
@@ -23,7 +24,7 @@ public class StargateOpenEvent extends StargatePlayerEvent {
* @param portal <p>The opened portal</p>
* @param force <p>Whether to force the portal open</p>
*/
public StargateOpenEvent(Player player, Portal portal, boolean force) {
public StargateOpenEvent(@Nullable Player player, @NotNull Portal portal, boolean force) {
super(portal, player);
this.force = force;
@@ -52,6 +53,7 @@ public class StargateOpenEvent extends StargatePlayerEvent {
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}

View File

@@ -2,6 +2,8 @@ package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* An abstract event describing any stargate event where a player is involved
@@ -16,7 +18,7 @@ public abstract class StargatePlayerEvent extends StargateEvent {
*
* @param portal <p>The portal involved in this stargate event</p>
*/
StargatePlayerEvent(Portal portal, Player player) {
StargatePlayerEvent(@NotNull Portal portal, @Nullable Player player) {
super(portal);
this.player = player;
}
@@ -26,6 +28,7 @@ public abstract class StargatePlayerEvent extends StargateEvent {
*
* @return <p>The player creating the star gate</p>
*/
@Nullable
public Player getPlayer() {
return player;
}

View File

@@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
* <p>This event can be used to overwrite the location the player is teleported to.</p>
*/
@SuppressWarnings("unused")
public class StargatePlayerPortalEvent extends StargatePlayerEvent {
public class StargatePlayerPortalEvent extends StargatePlayerEvent implements StargateTeleportEvent {
private static final HandlerList handlers = new HandlerList();
private final Portal destination;
@@ -26,7 +26,8 @@ public class StargatePlayerPortalEvent extends StargatePlayerEvent {
* @param destination <p>The destination the player should exit from</p>
* @param exit <p>The exit location of the destination portal the user will be teleported to</p>
*/
public StargatePlayerPortalEvent(Player player, Portal portal, Portal destination, Location exit) {
public StargatePlayerPortalEvent(@NotNull Player player, @NotNull Portal portal, @NotNull Portal destination,
@NotNull Location exit) {
super(portal, player);
this.destination = destination;
@@ -38,6 +39,7 @@ public class StargatePlayerPortalEvent extends StargatePlayerEvent {
*
* @return <p>The destination portal</p>
*/
@NotNull
public Portal getDestination() {
return destination;
}
@@ -47,6 +49,8 @@ public class StargatePlayerPortalEvent extends StargatePlayerEvent {
*
* @return <p>Location of the exit point</p>
*/
@Override
@NotNull
public Location getExit() {
return exit;
}
@@ -56,7 +60,7 @@ public class StargatePlayerPortalEvent extends StargatePlayerEvent {
*
* @param location <p>The new location of the player's exit point</p>
*/
public void setExit(Location location) {
public void setExit(@NotNull Location location) {
this.exit = location;
}
@@ -65,6 +69,7 @@ public class StargatePlayerPortalEvent extends StargatePlayerEvent {
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}

View File

@@ -0,0 +1,20 @@
package net.knarcraft.stargate.event;
import org.bukkit.Location;
import org.bukkit.event.Cancellable;
import org.jetbrains.annotations.NotNull;
/**
* A generic teleportation event
*/
public interface StargateTeleportEvent extends Cancellable {
/**
* Return the location of the players exit point
*
* @return <p>Location of the exit point</p>
*/
@NotNull
Location getExit();
}

View File

@@ -1,280 +0,0 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.event.StargateDestroyEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalCreator;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.utility.EconomyHelper;
import net.knarcraft.stargate.utility.MaterialHelper;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.knarcraft.stargate.utility.PortalFileHelper;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.entity.Player;
import org.bukkit.entity.Snowman;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPistonEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.EntityBlockFormEvent;
import org.bukkit.event.block.SignChangeEvent;
import java.util.List;
/**
* This class is responsible for listening to relevant block events related to creating and breaking portals
*/
@SuppressWarnings("unused")
public class BlockEventListener implements Listener {
/**
* Detects snowmen ruining portals
*
* <p>If entrance protection or portal verification is enabled, the snowman will be prevented from placing snow in
* the portal entrance.</p>
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onBlockFormedByEntity(EntityBlockFormEvent event) {
if (event.isCancelled() || (!Stargate.getGateConfig().protectEntrance() &&
!Stargate.getGateConfig().verifyPortals())) {
return;
}
//We are only interested in snowman events
if (!(event.getEntity() instanceof Snowman)) {
return;
}
//Cancel the event if a snowman is trying to place snow in the portal's entrance
if (PortalHandler.getByEntrance(event.getBlock()) != null) {
event.setCancelled(true);
}
}
/**
* Detects sign changes to detect if the user is creating a new gate
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onSignChange(SignChangeEvent event) {
if (event.isCancelled()) {
return;
}
Player player = event.getPlayer();
Block block = event.getBlock();
//Ignore normal signs
if (!(block.getBlockData() instanceof WallSign)) {
return;
}
final Portal portal = new PortalCreator(event, player).createPortal();
//Not creating a gate, just placing a sign
if (portal == null) {
return;
}
//Remove the sign if the no sign option is enabled
if (portal.getOptions().hasNoSign()) {
Material replaceMaterial = PortalFileHelper.decideRemovalMaterial(portal.getSignLocation(), portal);
BlockChangeRequest request = new BlockChangeRequest(portal.getSignLocation(), replaceMaterial, null);
Stargate.addBlockChangeRequest(request);
}
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("createMsg"));
Stargate.debug("onSignChange", "Initialized stargate: " + portal.getName());
Stargate.getInstance().getServer().getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(),
portal::drawSign, 1);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockPlace(BlockPlaceEvent event) {
if (event.isCancelled() || !Stargate.getGateConfig().protectEntrance()) {
return;
}
Block block = event.getBlock();
Player player = event.getPlayer();
Portal portal = PortalHandler.getByEntrance(block);
if (portal != null) {
//Prevent blocks from being placed in the entrance, if protectEntrance is enabled, as breaking the block
// would destroy the portal
event.setCancelled(true);
}
}
/**
* Detects block breaking to detect if the user is destroying a gate
*
* @param event <p>The triggered event</p>
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockBreak(BlockBreakEvent event) {
if (event.isCancelled()) {
return;
}
Block block = event.getBlock();
Player player = event.getPlayer();
//Decide if a portal is broken
Portal portal = PortalHandler.getByBlock(block);
if (portal == null && Stargate.getGateConfig().protectEntrance()) {
portal = PortalHandler.getByEntrance(block);
}
if (portal == null) {
return;
}
boolean deny = false;
String denyMessage = "";
//Decide if the user can destroy the portal
if (!PermissionHelper.canDestroyPortal(player, portal)) {
denyMessage = Stargate.getString("denyMsg");
deny = true;
Stargate.logInfo(String.format("%s tried to destroy gate", player.getName()));
}
int cost = Stargate.getEconomyConfig().getDestroyCost(player, portal.getGate());
//Create and call a StarGateDestroyEvent
StargateDestroyEvent destroyEvent = new StargateDestroyEvent(portal, player, deny, denyMessage, cost);
Stargate.getInstance().getServer().getPluginManager().callEvent(destroyEvent);
if (destroyEvent.isCancelled()) {
event.setCancelled(true);
return;
}
//Destroy denied
if (destroyEvent.getDeny()) {
if (!destroyEvent.getDenyReason().trim().isEmpty()) {
Stargate.getMessageSender().sendErrorMessage(player, destroyEvent.getDenyReason());
}
event.setCancelled(true);
return;
}
//Take care of payment transactions
if (!handleEconomyPayment(destroyEvent, player, portal, event)) {
return;
}
PortalRegistry.unregisterPortal(portal, true);
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("destroyMsg"));
}
/**
* Handles economy payment for breaking the portal
*
* @param destroyEvent <p>The destroy event</p>
* @param player <p>The player which triggered the event</p>
* @param portal <p>The broken portal</p>
* @param event <p>The break event</p>
* @return <p>True if the payment was successful. False if the event was cancelled</p>
*/
private boolean handleEconomyPayment(StargateDestroyEvent destroyEvent, Player player, Portal portal,
BlockBreakEvent event) {
int cost = destroyEvent.getCost();
if (cost != 0) {
String portalName = portal.getName();
//Cannot pay
if (!EconomyHelper.chargePlayerIfNecessary(player, cost)) {
Stargate.debug("onBlockBreak", "Insufficient Funds");
EconomyHelper.sendInsufficientFundsMessage(portalName, player, cost);
event.setCancelled(true);
return false;
}
//Tell the player they've paid or deceived money
if (cost > 0) {
EconomyHelper.sendDeductMessage(portalName, player, cost);
} else {
EconomyHelper.sendRefundMessage(portalName, player, cost);
}
}
return true;
}
/**
* Prevents any block physics events which may damage parts of the portal
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onBlockPhysics(BlockPhysicsEvent event) {
Block block = event.getBlock();
Portal portal = null;
if (block.getType() == Material.NETHER_PORTAL) {
portal = PortalHandler.getByEntrance(block);
} else if (MaterialHelper.isButtonCompatible(block.getType())) {
portal = PortalHandler.getByControl(block);
}
if (portal != null) {
event.setCancelled(true);
}
}
/**
* Cancels any block move events which may cause a block to enter the opening of a portal
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onBlockFromTo(BlockFromToEvent event) {
Portal portal = PortalHandler.getByEntrance(event.getBlock());
if (portal != null) {
event.setCancelled((event.getBlock().getY() == event.getToBlock().getY()));
}
}
/**
* Cancels any piston extend events if the target block is part of a portal
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onPistonExtend(BlockPistonExtendEvent event) {
cancelPistonEvent(event, event.getBlocks());
}
/**
* Cancels any piston retract events if the target block is part of a portal
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onPistonRetract(BlockPistonRetractEvent event) {
if (!event.isSticky()) {
return;
}
cancelPistonEvent(event, event.getBlocks());
}
/**
* Cancels a piston event if it would destroy a portal
*
* @param event <p>The event to cancel</p>
* @param blocks <p>The blocks included in the event</p>
*/
private void cancelPistonEvent(BlockPistonEvent event, List<Block> blocks) {
for (Block block : blocks) {
Portal portal = PortalHandler.getByBlock(block);
if (portal != null) {
event.setCancelled(true);
return;
}
}
}
}

View File

@@ -1,42 +0,0 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.utility.BungeeHelper;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
/**
* This listener teleports a user if a valid message is received from BungeeCord
*
* <p>Specifically, if a string starts with SGBungee encoded to be readable by readUTF followed by
* [PlayerUUID]delimiter[DestinationPortal] is received on the BungeeCord channel, this listener will teleport the
* player to the destination portal.</p>
*/
public class BungeeCordListener implements PluginMessageListener {
/**
* Receives plugin messages
*
* @param channel <p>The channel the message was received on</p>
* @param unused <p>Unused.</p>
* @param message <p>The message received from the plugin</p>
*/
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player unused, byte[] message) {
//Ignore plugin messages if some other plugin message is received
if (!channel.equals(BungeeHelper.getBungeeChannel())) {
return;
}
//Try to read the plugin message
String receivedMessage = BungeeHelper.readPluginMessage(message);
if (receivedMessage == null) {
return;
}
//Use the message to initiate teleportation
BungeeHelper.handleTeleportMessage(receivedMessage);
}
}

View File

@@ -1,67 +0,0 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.utility.EntityHelper;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityPortalEvent;
/**
* This listener listens for any relevant events on portal entities
*/
@SuppressWarnings("unused")
public class EntityEventListener implements Listener {
/**
* This event handler prevents sending entities to the normal nether instead of the stargate target
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onPortalEvent(EntityPortalEvent event) {
if (event.isCancelled()) {
return;
}
Entity entity = event.getEntity();
//Cancel normal portal event is near a stargate
if (PortalHandler.getByAdjacentEntrance(event.getFrom(), EntityHelper.getEntityMaxSizeInt(entity)) != null) {
event.setCancelled(true);
}
}
/**
* This method catches any explosion events
*
* <p>If destroyed by explosions is enabled, any portals destroyed by the explosion will be unregistered. If not,
* the explosion will be cancelled.</p>
*
* @param event <p>The triggered explosion event</p>
*/
@EventHandler
public void onEntityExplode(EntityExplodeEvent event) {
if (event.isCancelled()) {
return;
}
for (Block block : event.blockList()) {
Portal portal = PortalHandler.getByBlock(block);
if (portal == null) {
continue;
}
if (Stargate.getGateConfig().destroyedByExplosion()) {
PortalRegistry.unregisterPortal(portal, true);
} else {
event.setCancelled(true);
break;
}
}
}
}

View File

@@ -0,0 +1,96 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.StargateConfig;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.utility.PortalFileHelper;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
/**
* This listener listens for any plugins/worlds being enabled or disabled
*/
public class EnvironmentChangeListener implements Listener {
private final Stargate stargate;
/**
* Instantiates a new plugin event listener
*
* @param stargate <p>A reference to the stargate plugin to </p>
*/
public EnvironmentChangeListener(@NotNull Stargate stargate) {
this.stargate = stargate;
}
/**
* This event listens for and announces that the vault plugin was detected and enabled
*
* <p>Each time this event is called, the economy handler will try to enable vault</p>
*
* @param ignored <p>The actual event called. This is currently not used</p>
*/
@EventHandler
public void onPluginEnable(@NotNull PluginEnableEvent ignored) {
if (Stargate.getEconomyConfig().setupEconomy(stargate.getServer().getPluginManager())) {
Plugin vault = Stargate.getEconomyConfig().getVault();
if (vault != null) {
String vaultVersion = vault.getDescription().getVersion();
Stargate.logInfo(new SGFormatBuilder(Message.VAULT_LOADED).replace("%version%", vaultVersion).toString());
}
}
}
/**
* This event listens for the vault plugin being disabled and notifies the console
*
* @param event <p>The event caused by disabling a plugin</p>
*/
@EventHandler
public void onPluginDisable(@NotNull PluginDisableEvent event) {
if (event.getPlugin().equals(Stargate.getEconomyConfig().getVault())) {
Stargate.logInfo("Vault plugin lost.");
}
}
/**
* This listener listens for the loading of a world and loads all gates from the world if not already loaded
*
* @param event <p>The triggered world load event</p>
*/
@EventHandler
public void onWorldLoad(@NotNull WorldLoadEvent event) {
StargateConfig config = Stargate.getStargateConfig();
if (!config.getManagedWorlds().contains(event.getWorld().getName()) &&
PortalFileHelper.loadAllPortals(event.getWorld())) {
config.addManagedWorld(event.getWorld().getName());
}
}
/**
* This listener listens for the unloading of a world
*
* @param event <p>The triggered world unload event</p>
*/
@EventHandler
public void onWorldUnload(@NotNull WorldUnloadEvent event) {
Stargate.debug("onWorldUnload", "Reloading all Stargates");
World world = event.getWorld();
String worldName = world.getName();
StargateConfig config = Stargate.getStargateConfig();
if (config.getManagedWorlds().contains(worldName)) {
config.removeManagedWorld(worldName);
PortalRegistry.clearPortals(world);
}
}
}

View File

@@ -1,45 +1,44 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.knarcraft.knarlib.util.UpdateChecker;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.MessageSender;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.config.Permission;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalActivator;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
import net.knarcraft.stargate.portal.teleporter.Teleporter;
import net.knarcraft.stargate.portal.teleporter.VehicleTeleporter;
import net.knarcraft.stargate.utility.BungeeHelper;
import net.knarcraft.stargate.utility.MaterialHelper;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.knarcraft.stargate.utility.PortalUtil;
import net.knarcraft.stargate.utility.UUIDMigrationHelper;
import org.bukkit.ChatColor;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* This listener listens to any player-related events related to stargates
*/
@SuppressWarnings("unused")
public class PlayerEventListener implements Listener {
private static long eventTime;
private static PlayerInteractEvent previousEvent;
private static final Map<Player, Long> previousEventTimes = new HashMap<>();
/**
* This event handler handles detection of any player teleporting through a bungee gate
@@ -47,22 +46,31 @@ public class PlayerEventListener implements Listener {
* @param event <p>The event to check for a teleporting player</p>
*/
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
Player player = event.getPlayer();
//Migrate player name to UUID if necessary
UUIDMigrationHelper.migrateUUID(event.getPlayer());
UUIDMigrationHelper.migrateUUID(player);
//Notify joining admins about the available update
String availableUpdate = Stargate.getUpdateAvailable();
if (availableUpdate != null && Stargate.getStargateConfig().alertAdminsAboutUpdates() &&
PermissionHelper.hasPermission(player, Permission.ADMIN)) {
String updateMessage = UpdateChecker.getUpdateAvailableString(availableUpdate, Stargate.getPluginVersion());
new SGFormatBuilder(updateMessage).error(player);
}
if (!Stargate.getGateConfig().enableBungee()) {
return;
}
Player player = event.getPlayer();
//Check if the player is waiting to be teleported to a stargate
String destination = BungeeHelper.removeFromQueue(player.getUniqueId());
if (destination == null) {
Stargate.debug("PlayerJoin", "No bungee request found in queue");
return;
}
Portal portal = PortalHandler.getBungeePortal(destination);
Portal portal = PortalUtil.getBungeePortal(destination);
if (portal == null) {
Stargate.debug("PlayerJoin", "Error fetching destination portal: " + destination);
return;
@@ -71,118 +79,13 @@ public class PlayerEventListener implements Listener {
new PlayerTeleporter(portal, player).teleport(portal, null);
}
/**
* This event handler detects if a player moves into a portal
*
* @param event <p>The player move event which was triggered</p>
*/
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if (event.isCancelled() || event.getTo() == null) {
return;
}
BlockLocation fromLocation = new BlockLocation(event.getFrom().getBlock());
BlockLocation toLocation = new BlockLocation(event.getTo().getBlock());
Player player = event.getPlayer();
//Check whether the event needs to be considered
if (!isRelevantMoveEvent(event, player, fromLocation, toLocation)) {
return;
}
Portal entrancePortal = PortalHandler.getByEntrance(toLocation);
Portal destination = entrancePortal.getPortalActivator().getDestination(player);
Entity playerVehicle = player.getVehicle();
//If the player is in a vehicle, but vehicle handling is disabled, just ignore the player
if (playerVehicle == null || (playerVehicle instanceof LivingEntity &&
Stargate.getGateConfig().handleVehicles())) {
teleportPlayer(playerVehicle, player, entrancePortal, destination, event);
}
}
/**
* Teleports a player, also teleports the player's vehicle if it's a living entity
*
* @param playerVehicle <p>The vehicle the player is currently sitting in</p>
* @param player <p>The player which moved</p>
* @param entrancePortal <p>The entrance the player entered</p>
* @param destination <p>The destination of the entrance portal</p>
* @param event <p>The move event causing the teleportation to trigger</p>
*/
private void teleportPlayer(Entity playerVehicle, Player player, Portal entrancePortal, Portal destination,
PlayerMoveEvent event) {
if (playerVehicle instanceof LivingEntity) {
//Make sure any horses are properly tamed
if (playerVehicle instanceof AbstractHorse horse && !horse.isTamed()) {
horse.setTamed(true);
horse.setOwner(player);
}
//Teleport the player's vehicle
new VehicleTeleporter(destination, (Vehicle) playerVehicle).teleport(entrancePortal);
} else {
//Just teleport the player like normal
new PlayerTeleporter(destination, player).teleport(entrancePortal, event);
}
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("teleportMsg"));
}
entrancePortal.getPortalOpener().closePortal(false);
}
/**
* Checks whether a player move event is relevant for this plugin
*
* @param event <p>The player move event to check</p>
* @param player <p>The player which moved</p>
* @param fromLocation <p>The location the player is moving from</p>
* @param toLocation <p>The location the player is moving to</p>
* @return <p>True if the event is relevant</p>
*/
private boolean isRelevantMoveEvent(PlayerMoveEvent event, Player player, BlockLocation fromLocation,
BlockLocation toLocation) {
//Check to see if the player moved to another block
if (fromLocation.equals(toLocation)) {
return false;
}
//Check if the player moved from a portal
Portal entrancePortal = PortalHandler.getByEntrance(toLocation);
if (entrancePortal == null) {
return false;
}
Portal destination = entrancePortal.getPortalActivator().getDestination(player);
//Catch always open portals without a valid destination to prevent the user for being teleported and denied
if (!entrancePortal.getOptions().isBungee() && destination == null) {
return false;
}
//Decide if the anything stops the player from teleport
if (PermissionHelper.playerCannotTeleport(entrancePortal, destination, player, event)) {
return false;
}
//Decide if the user should be teleported to another bungee server
if (entrancePortal.getOptions().isBungee()) {
if (BungeeHelper.bungeeTeleport(player, entrancePortal, event) && !entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("teleportMsg"));
}
return false;
}
//Make sure to check if the player has any leashed creatures, even though leashed teleportation is disabled
return Teleporter.noLeashedCreaturesPreventTeleportation(player);
}
/**
* This event handler detects if a player clicks a button or a sign
*
* @param event <p>The player interact event which was triggered</p>
*/
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
Player player = event.getPlayer();
Block block = event.getClickedBlock();
@@ -191,6 +94,10 @@ public class PlayerEventListener implements Listener {
}
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
if (event.getHand() == null) {
return;
}
// Handle right-click of a sign, button or other
handleRightClickBlock(event, player, block, event.getHand());
} else if (event.getAction() == Action.LEFT_CLICK_BLOCK && block.getBlockData() instanceof WallSign) {
//Handle left click of a wall sign
@@ -206,12 +113,18 @@ public class PlayerEventListener implements Listener {
* @param block <p>The block that was clicked</p>
* @param leftClick <p>Whether the player performed a left click as opposed to a right click</p>
*/
private void handleSignClick(PlayerInteractEvent event, Player player, Block block, boolean leftClick) {
Portal portal = PortalHandler.getByBlock(block);
private void handleSignClick(@NotNull PlayerInteractEvent event, @NotNull Player player, @NotNull Block block,
boolean leftClick) {
Portal portal = PortalUtil.getByBlock(block);
if (portal == null) {
return;
}
//Allow players with permissions to apply dye to signs
if (dyeSign(event, player, portal)) {
return;
}
event.setUseInteractedBlock(Event.Result.DENY);
if (leftClick) {
//Cancel event in creative mode to prevent breaking the sign
@@ -239,6 +152,39 @@ public class PlayerEventListener implements Listener {
}
}
/**
* Tries to take care of a sign dye interaction
*
* @param event <p>The triggered player interaction event</p>
* @param player <p>The involved player</p>
* @param portal <p>The involved portal</p>
* @return <p>True if a sign was dyed</p>
*/
private boolean dyeSign(@NotNull PlayerInteractEvent event, @NotNull Player player, @NotNull Portal portal) {
EquipmentSlot hand = event.getHand();
// Check if the player is allowed to dye the sign
if (hand == null || (!PermissionHelper.hasPermission(player, Permission.DYE_SIGN) &&
!portal.isOwner(player))) {
return false;
}
// Check if the player is holding an item
ItemStack item = player.getInventory().getItem(hand);
if (item == null) {
return false;
}
String itemName = item.getType().toString();
// Check if the player's item can be used to dye the sign
if (itemName.endsWith("DYE") || itemName.endsWith("INK_SAC")) {
event.setUseInteractedBlock(Event.Result.ALLOW);
Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), portal::drawSign, 1);
return true;
} else {
return false;
}
}
/**
* Check if a player should be denied from accessing (using) a portal
*
@@ -246,12 +192,12 @@ public class PlayerEventListener implements Listener {
* @param portal <p>The portal the player is trying to use</p>
* @return <p>True if the player should be denied</p>
*/
private boolean cannotAccessPortal(Player player, Portal portal) {
private boolean cannotAccessPortal(@NotNull Player player, @NotNull Portal portal) {
boolean deny = PermissionHelper.cannotAccessNetwork(player, portal.getCleanNetwork());
if (PermissionHelper.portalAccessDenied(player, portal, deny)) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
if (!portal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ACCESS_DENIED).error(player);
}
return true;
}
@@ -266,19 +212,20 @@ public class PlayerEventListener implements Listener {
* @param block <p>The block the player clicked</p>
* @param hand <p>The hand the player used to interact with the stargate</p>
*/
private void handleRightClickBlock(PlayerInteractEvent event, Player player, Block block, EquipmentSlot hand) {
private void handleRightClickBlock(@NotNull PlayerInteractEvent event, @NotNull Player player, @NotNull Block block,
@NotNull EquipmentSlot hand) {
if (block.getBlockData() instanceof WallSign) {
handleSignClick(event, player, block, false);
return;
}
//Prevent a double click caused by a Spigot bug
if (clickIsBug(event, block)) {
if (clickIsBug(event.getPlayer())) {
return;
}
if (MaterialHelper.isButtonCompatible(block.getType())) {
Portal portal = PortalHandler.getByBlock(block);
Portal portal = PortalUtil.getByBlock(block);
if (portal == null) {
return;
}
@@ -299,7 +246,7 @@ public class PlayerEventListener implements Listener {
} else {
//Display information about the portal if it has no sign
ItemStack heldItem = player.getInventory().getItem(hand);
if (heldItem.getType().isAir() || !heldItem.getType().isBlock()) {
if (heldItem != null && (heldItem.getType().isAir() || !heldItem.getType().isBlock())) {
displayPortalInfo(block, player);
}
}
@@ -308,32 +255,29 @@ public class PlayerEventListener implements Listener {
/**
* Displays information about a clicked portal
*
* <p>This will only display portal info if the portal has no sign and is not silent.</p>
* <p>This will only display portal info if the portal has no sign and is not quiet.</p>
*
* @param block <p>The clicked block</p>
* @param player <p>The player that clicked the block</p>
*/
private void displayPortalInfo(Block block, Player player) {
Portal portal = PortalHandler.getByBlock(block);
private void displayPortalInfo(@NotNull Block block, @NotNull Player player) {
Portal portal = PortalUtil.getByBlock(block);
if (portal == null) {
return;
}
//Display portal information as a portal without a sign does not display any
if (portal.getOptions().hasNoSign() && (!portal.getOptions().isSilent() || player.isSneaking())) {
MessageSender sender = Stargate.getMessageSender();
sender.sendSuccessMessage(player, ChatColor.GOLD + Stargate.getString("portalInfoTitle"));
sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoName"),
"%name%", portal.getName()));
sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoDestination"),
"%destination%", portal.getDestinationName()));
if (portal.getOptions().hasNoSign() && (!portal.getOptions().isQuiet() || player.isSneaking())) {
FormatBuilder builder = new SGFormatBuilder();
builder.append(ChatColor.GOLD).append(Message.PORTAL_INFO_TITLE).append("\n").
append(Message.PORTAL_INFO_NAME).replace("%name%", portal.getName()).append("\n").
append(Message.PORTAL_INFO_DESTINATION).replace("%destination%", portal.getDestinationName()).append("\n");
if (portal.getOptions().isBungee()) {
sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoServer"),
"%server%", portal.getNetwork()));
builder.append(Message.PORTAL_INFO_SERVER).replace("%server%", portal.getNetwork());
} else {
sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoNetwork"),
"%network%", portal.getNetwork()));
builder.append(Message.PORTAL_INFO_NETWORK).replace("%network%", portal.getNetwork());
}
builder.displayRaw(player);
}
}
@@ -344,19 +288,16 @@ public class PlayerEventListener implements Listener {
* immediately, or causing portal information printing twice. This fix should detect the bug without breaking
* clicking once the bug is fixed.</p>
*
* @param event <p>The event causing the right click</p>
* @param block <p>The block to check</p>
* @param player <p>The player performing the right-click</p>
* @return <p>True if the click is a bug and should be cancelled</p>
*/
private boolean clickIsBug(PlayerInteractEvent event, Block block) {
if (previousEvent != null &&
event.getPlayer() == previousEvent.getPlayer() && eventTime + 15 > System.currentTimeMillis()) {
previousEvent = null;
eventTime = 0;
private boolean clickIsBug(@NotNull Player player) {
Long previousEventTime = previousEventTimes.get(player);
if (previousEventTime != null && previousEventTime + 50 > System.currentTimeMillis()) {
previousEventTimes.put(player, null);
return true;
}
previousEvent = event;
eventTime = System.currentTimeMillis();
previousEventTimes.put(player, System.currentTimeMillis());
return false;
}

View File

@@ -1,52 +0,0 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
/**
* This listener listens for any plugins being enabled or disabled to catch the loading of vault
*/
@SuppressWarnings("unused")
public class PluginEventListener implements Listener {
private final Stargate stargate;
/**
* Instantiates a new plugin event listener
*
* @param stargate <p>A reference to the stargate plugin to </p>
*/
public PluginEventListener(Stargate stargate) {
this.stargate = stargate;
}
/**
* This event listens for and announces that the vault plugin was detected and enabled
*
* <p>Each time this event is called, the economy handler will try to enable vault</p>
*
* @param ignored <p>The actual event called. This is currently not used</p>
*/
@EventHandler
public void onPluginEnable(PluginEnableEvent ignored) {
if (Stargate.getEconomyConfig().setupEconomy(stargate.getServer().getPluginManager())) {
String vaultVersion = Stargate.getEconomyConfig().getVault().getDescription().getVersion();
Stargate.logInfo(Stargate.replaceVars(Stargate.getString("vaultLoaded"), "%version%", vaultVersion));
}
}
/**
* This event listens for the vault plugin being disabled and notifies the console
*
* @param event <p>The event caused by disabling a plugin</p>
*/
@EventHandler
public void onPluginDisable(PluginDisableEvent event) {
if (event.getPlugin().equals(Stargate.getEconomyConfig().getVault())) {
Stargate.logInfo("Vault plugin lost.");
}
}
}

View File

@@ -1,104 +0,0 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.FromTheEndTeleportation;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
import net.knarcraft.stargate.utility.PermissionHelper;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityPortalEnterEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.world.PortalCreateEvent;
import java.util.ArrayList;
import java.util.List;
/**
* Listens for and cancels relevant portal events
*/
public class PortalEventListener implements Listener {
private static final List<FromTheEndTeleportation> playersFromTheEnd = new ArrayList<>();
/**
* Listens for and aborts vanilla portal creation caused by stargate creation
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onPortalCreation(PortalCreateEvent event) {
if (event.isCancelled()) {
return;
}
//Unnecessary nether portal creation is only triggered by nether pairing
if (event.getReason() == PortalCreateEvent.CreateReason.NETHER_PAIR) {
//If an entity is standing in a Stargate entrance, it can be assumed that the creation is a mistake
Entity entity = event.getEntity();
if (entity != null && PortalHandler.getByAdjacentEntrance(entity.getLocation()) != null) {
Stargate.debug("PortalEventListener::onPortalCreation",
"Cancelled nether portal create event");
event.setCancelled(true);
}
}
}
/**
* Listen for entities entering an artificial end portal
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onEntityPortalEnter(EntityPortalEnterEvent event) {
Location location = event.getLocation();
World world = location.getWorld();
Entity entity = event.getEntity();
//Hijack normal portal teleportation if teleporting from a stargate
if (entity instanceof Player player && location.getBlock().getType() == Material.END_PORTAL && world != null &&
world.getEnvironment() == World.Environment.THE_END) {
Portal portal = PortalHandler.getByAdjacentEntrance(location);
if (portal == null) {
return;
}
//Remove any old player teleportations in case weird things happen
playersFromTheEnd.removeIf((teleportation -> teleportation.getPlayer() == player));
//Decide if the anything stops the player from teleporting
if (PermissionHelper.playerCannotTeleport(portal, portal.getPortalActivator().getDestination(), player, null)) {
//Teleport the player back to the portal they came in, just in case
playersFromTheEnd.add(new FromTheEndTeleportation(player, portal));
}
playersFromTheEnd.add(new FromTheEndTeleportation(player, portal.getPortalActivator().getDestination()));
}
}
/**
* Listen for the respawn event to catch players teleporting from the end in an artificial end portal
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent event) {
Player respawningPlayer = event.getPlayer();
int playerIndex = playersFromTheEnd.indexOf(new FromTheEndTeleportation(respawningPlayer, null));
if (playerIndex == -1) {
return;
}
FromTheEndTeleportation teleportation = playersFromTheEnd.get(playerIndex);
playersFromTheEnd.remove(playerIndex);
Portal exitPortal = teleportation.getExit();
//Overwrite respawn location to respawn in front of the portal
event.setRespawnLocation(new PlayerTeleporter(exitPortal, respawningPlayer).getExit(respawningPlayer,
respawningPlayer.getLocation()));
//Properly close the portal to prevent it from staying in a locked state until it times out
exitPortal.getPortalOpener().closePortal(false);
}
}

View File

@@ -0,0 +1,267 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.utility.PortalUtil;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.Directional;
import org.bukkit.event.Cancellable;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBurnEvent;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockFadeEvent;
import org.bukkit.event.block.BlockFertilizeEvent;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockIgniteEvent;
import org.bukkit.event.block.BlockMultiPlaceEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.EntityBlockFormEvent;
import org.bukkit.event.block.LeavesDecayEvent;
import org.bukkit.event.block.SpongeAbsorbEvent;
import org.bukkit.event.block.TNTPrimeEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityBreakDoorEvent;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityPlaceEvent;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
import org.bukkit.event.world.PortalCreateEvent;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A listener for any events that might cause a Stargate to be altered or break
*/
public class StargateBreakListener implements Listener {
/**
* Cancels blocks from being placed in the Stargate's entrance
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBlockPlace(@NotNull BlockPlaceEvent event) {
if (!Stargate.getGateConfig().protectEntrance()) {
return;
}
Block block = event.getBlock();
Portal portal = PortalUtil.getByEntrance(block);
if (portal != null) {
//Prevent blocks from being placed in the entrance, if protectEntrance is enabled, as breaking the block
// would destroy the portal
event.setCancelled(true);
}
}
/**
* This method catches any explosion events
*
* <p>If destroyed by explosions is enabled, any portals destroyed by the explosion will be unregistered. If not,
* the explosion will be cancelled.</p>
*
* @param event <p>The triggered explosion event</p>
*/
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onEntityExplode(@NotNull EntityExplodeEvent event) {
for (Block block : event.blockList()) {
Portal portal = PortalUtil.getByBlock(block);
if (portal == null) {
continue;
}
if (Stargate.getGateConfig().destroyedByExplosion()) {
PortalRegistry.unregisterPortal(portal, true);
} else {
event.setCancelled(true);
break;
}
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onCreatureSpawn(@NotNull CreatureSpawnEvent event) {
//Prevent Zombified Piglins and other creatures form spawning at stargates
if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.NETHER_PORTAL &&
PortalUtil.getByEntrance(event.getLocation()) != null) {
event.setCancelled(true);
Stargate.debug("EntitySpawnListener", "Prevented creature from spawning at Stargate");
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPistonExtend(BlockPistonExtendEvent event) {
cancelPistonIfNeeded(event.getBlocks(), event.getDirection(), event);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPistonRetract(@NotNull BlockPistonRetractEvent event) {
cancelPistonIfNeeded(event.getBlocks(), event.getDirection(), event);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockExplode(@NotNull BlockExplodeEvent event) {
cancelIfAtEntrance(event, event.blockList());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockFromTo(@NotNull BlockFromToEvent event) {
cancelIfAtEntrance(event, event.getToBlock());
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockFormEvent(@NotNull BlockFormEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockPhysics(@NotNull BlockPhysicsEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockBurn(@NotNull BlockBurnEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockIgnite(@NotNull BlockIgniteEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockFade(@NotNull BlockFadeEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockFertilize(@NotNull BlockFertilizeEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockMultiPlace(@NotNull BlockMultiPlaceEvent event) {
cancelIfAtEntrance(event, getBlocksFromBlockStates(event.getReplacedBlockStates()));
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onEntityBlockForm(@NotNull EntityBlockFormEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onLeavesDecay(@NotNull LeavesDecayEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onSpongeAbsorb(@NotNull SpongeAbsorbEvent event) {
cancelIfAtEntrance(event, getBlocksFromBlockStates(event.getBlocks()));
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onEntityChangeBlock(@NotNull EntityChangeBlockEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onEntityBreakDoor(@NotNull EntityBreakDoorEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPortalCreate(@NotNull PortalCreateEvent event) {
cancelIfAtEntrance(event, getBlocksFromBlockStates(event.getBlocks()));
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onEntityPlace(@NotNull EntityPlaceEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerBucketEmpty(@NotNull PlayerBucketEmptyEvent event) {
cancelIfAtEntrance(event, event.getBlock());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockDispense(@NotNull BlockDispenseEvent event) {
if (event.getBlock().getBlockData() instanceof Directional dispenser) {
cancelIfAtEntrance(event, event.getBlock().getRelative(dispenser.getFacing()));
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onTNTPrime(@NotNull TNTPrimeEvent tntPrimeEvent) {
cancelIfAtEntrance(tntPrimeEvent, tntPrimeEvent.getBlock());
}
/**
* Cancels the given event if it involves a Stargate's entrance
*
* @param event <p>The event to cancel</p>
* @param block <p>The block in question</p>
*/
private boolean cancelIfAtEntrance(@NotNull Cancellable event, @NotNull Block block) {
if (PortalUtil.getByEntrance(block) != null || PortalUtil.getByControl(block) != null ||
PortalUtil.getByBlock(block) != null) {
event.setCancelled(true);
return true;
} else {
return false;
}
}
/**
* Cancels the given event if it involves a Stargate's entrance
*
* @param event <p>The event to cancel</p>
* @param blocks <p>The blocks in question</p>
*/
private void cancelIfAtEntrance(@NotNull Cancellable event, @NotNull List<Block> blocks) {
for (Block block : blocks) {
if (cancelIfAtEntrance(event, block)) {
return;
}
}
}
/**
* Gets a list of blocks from a list of block states
*
* @param blockStates <p>The block states to convert into blocks</p>
* @return <p>The corresponding blocks</p>
*/
@NotNull
private List<Block> getBlocksFromBlockStates(@NotNull List<BlockState> blockStates) {
return blockStates.stream().map(BlockState::getBlock).toList();
}
/**
* Cancels the movement of a piston if it would interfere with a Stargate
*
* @param blocks <p>The blocks involved in the move</p>
* @param blockFace <p>The block face of the piston</p>
* @param event <p>The event to possibly cancel</p>
*/
private void cancelPistonIfNeeded(@NotNull List<Block> blocks, @NotNull BlockFace blockFace,
@NotNull Cancellable event) {
cancelIfAtEntrance(event, blocks);
if (event.isCancelled()) {
return;
}
List<Block> movedBlocks = blocks.stream().map(block -> block.getRelative(blockFace)).toList();
cancelIfAtEntrance(event, movedBlocks);
}
}

View File

@@ -0,0 +1,160 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.event.StargateDestroyEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalCreator;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.utility.EconomyHelper;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.knarcraft.stargate.utility.PortalFileHelper;
import net.knarcraft.stargate.utility.PortalUtil;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.WallSign;
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.BlockBreakEvent;
import org.bukkit.event.block.SignChangeEvent;
import org.jetbrains.annotations.NotNull;
/**
* This class is responsible for listening to relevant block events related to creating and breaking portals
*/
public class StargateCreateDestroyListener implements Listener {
/**
* Detects sign changes to detect if the user is creating a new gate
*
* @param event <p>The triggered event</p>
*/
@EventHandler(ignoreCancelled = true)
public void onSignChange(@NotNull SignChangeEvent event) {
Player player = event.getPlayer();
Block block = event.getBlock();
//Ignore normal signs
if (!(block.getBlockData() instanceof WallSign)) {
return;
}
final Portal portal = new PortalCreator(event, player).createPortal();
//Not creating a gate, just placing a sign
if (portal == null) {
return;
}
//Remove the sign if the no sign option is enabled
if (portal.getOptions().hasNoSign()) {
Material replaceMaterial = PortalFileHelper.decideRemovalMaterial(portal.getLocation().getSignBlock(), portal);
BlockChangeRequest request = new BlockChangeRequest(portal.getLocation().getSignBlock(), replaceMaterial, null);
Stargate.addControlBlockUpdateRequest(request);
}
new SGFormatBuilder(Message.CREATED).success(player);
Stargate.debug("onSignChange", "Initialized stargate: " + portal.getName());
Stargate.getInstance().getServer().getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(),
portal::drawSign, 1);
}
/**
* Detects block breaking to detect if the user is destroying a gate
*
* @param event <p>The triggered event</p>
*/
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBlockBreak(@NotNull BlockBreakEvent event) {
Block block = event.getBlock();
Player player = event.getPlayer();
//Decide if a portal is broken
Portal portal = PortalUtil.getByBlock(block);
if (portal == null && Stargate.getGateConfig().protectEntrance()) {
portal = PortalUtil.getByEntrance(block);
}
if (portal == null) {
return;
}
boolean deny = false;
String denyMessage = "";
// Block breaking the button from breaking the entire Stargate
if (portal.getStructure().getButton() != null && portal.getStructure().getButton().equals(
new BlockLocation(event.getBlock()))) {
event.setCancelled(true);
return;
}
//Decide if the user can destroy the portal
if (!PermissionHelper.canDestroyPortal(player, portal)) {
denyMessage = new SGFormatBuilder(Message.ACCESS_DENIED).toString();
deny = true;
Stargate.logInfo(String.format("%s tried to destroy gate", player.getName()));
}
int cost = Stargate.getEconomyConfig().getDestroyCost(player, portal.getGate());
//Create and call a StarGateDestroyEvent
StargateDestroyEvent destroyEvent = new StargateDestroyEvent(portal, player, deny, denyMessage, cost);
Stargate.getInstance().getServer().getPluginManager().callEvent(destroyEvent);
if (destroyEvent.isCancelled()) {
event.setCancelled(true);
return;
}
//Destroy denied
if (destroyEvent.getDeny()) {
if (!destroyEvent.getDenyReason().trim().isEmpty()) {
new SGFormatBuilder(destroyEvent.getDenyReason()).error(player);
}
event.setCancelled(true);
return;
}
//Take care of payment transactions
if (!handleEconomyPayment(destroyEvent, player, portal, event)) {
return;
}
PortalRegistry.unregisterPortal(portal, true);
new SGFormatBuilder(Message.DESTROYED).success(player);
}
/**
* Handles economy payment for breaking the portal
*
* @param destroyEvent <p>The destroy event</p>
* @param player <p>The player which triggered the event</p>
* @param portal <p>The broken portal</p>
* @param event <p>The break event</p>
* @return <p>True if the payment was successful. False if the event was cancelled</p>
*/
private boolean handleEconomyPayment(@NotNull StargateDestroyEvent destroyEvent, @NotNull Player player,
@NotNull Portal portal, @NotNull BlockBreakEvent event) {
int cost = destroyEvent.getCost();
if (cost != 0) {
String portalName = portal.getName();
//Cannot pay
if (!EconomyHelper.chargePlayerIfNecessary(player, cost)) {
Stargate.debug("onBlockBreak", "Insufficient Funds");
EconomyHelper.sendInsufficientFundsMessage(portalName, player, cost);
event.setCancelled(true);
return false;
}
//Tell the player they've paid or deceived money
if (cost > 0) {
EconomyHelper.sendDeductMessage(portalName, player, cost);
} else {
EconomyHelper.sendRefundMessage(portalName, player, cost);
}
}
return true;
}
}

View File

@@ -0,0 +1,530 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.FromTheEndTeleportation;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
import net.knarcraft.stargate.portal.teleporter.VehicleTeleporter;
import net.knarcraft.stargate.utility.BungeeHelper;
import net.knarcraft.stargate.utility.EconomyHelper;
import net.knarcraft.stargate.utility.EntityHelper;
import net.knarcraft.stargate.utility.MaterialHelper;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.knarcraft.stargate.utility.PortalUtil;
import net.knarcraft.stargate.utility.TeleportHelper;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityPortalEnterEvent;
import org.bukkit.event.entity.EntityPortalEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.util.Vector;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.geyser.api.GeyserApi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This listener listens for the vehicle move event to teleport vehicles through portals
*/
public class StargateTeleportListener implements Listener {
private static final Map<Player, FromTheEndTeleportation> playersFromTheEnd = new HashMap<>();
private boolean hasGeyser = true;
private boolean hasFloodgate = true;
/**
* This event handler handles some special teleportation events
*
* <p>This event cancels nether portal, end gateway and end portal teleportation if the user teleported from a
* stargate entrance. This prevents the user from just teleporting to the nether or the end with portals using
* the special teleportation blocks.</p>
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onPlayerTeleport(@NotNull PlayerTeleportEvent event) {
PlayerTeleportEvent.TeleportCause cause = event.getCause();
//Block normal portal teleportation if teleporting from a stargate
if (!event.isCancelled() && (cause == PlayerTeleportEvent.TeleportCause.NETHER_PORTAL ||
cause == PlayerTeleportEvent.TeleportCause.END_GATEWAY ||
cause == PlayerTeleportEvent.TeleportCause.END_PORTAL)
&& PortalUtil.getByAdjacentEntrance(event.getFrom()) != null) {
event.setCancelled(true);
}
}
/**
* This event handler prevents sending entities to the normal nether instead of the stargate target
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onPortalEvent(@NotNull EntityPortalEvent event) {
if (event.isCancelled()) {
return;
}
Entity entity = event.getEntity();
//Cancel normal portal event is near a stargate
if (PortalUtil.getByAdjacentEntrance(event.getFrom(), EntityHelper.getEntityMaxSizeInt(entity)) != null) {
event.setCancelled(true);
}
}
/**
* Check for a vehicle moving through a portal
*
* @param event <p>The triggered move event</p>
*/
@EventHandler
public void onVehicleMove(@NotNull VehicleMoveEvent event) {
if (!Stargate.getGateConfig().handleVehicles()) {
return;
}
List<Entity> passengers = event.getVehicle().getPassengers();
Vehicle vehicle = event.getVehicle();
Portal entrancePortal;
int entitySize = EntityHelper.getEntityMaxSizeInt(vehicle);
if (EntityHelper.getEntityMaxSize(vehicle) > 1) {
entrancePortal = PortalUtil.getByAdjacentEntrance(event.getTo(), entitySize - 1);
} else {
entrancePortal = PortalUtil.getByEntrance(event.getTo());
}
//Return if the portal cannot be teleported through
if (entrancePortal == null || !entrancePortal.isOpen() || entrancePortal.getOptions().isBungee()) {
return;
}
teleportVehicle(passengers, entrancePortal, vehicle);
}
/**
* This event handler detects if a player moves into a portal
*
* @param event <p>The player move event which was triggered</p>
*/
@EventHandler
public void onPlayerMove(@NotNull PlayerMoveEvent event) {
if (event.isCancelled() || event.getTo() == null) {
return;
}
BlockLocation fromLocation = new BlockLocation(event.getFrom().getBlock());
BlockLocation toLocation = new BlockLocation(event.getTo().getBlock());
Player player = event.getPlayer();
//Check whether the event needs to be considered
if (!isRelevantMoveEvent(event, player, fromLocation, toLocation)) {
return;
}
Portal entrancePortal = PortalUtil.getByEntrance(toLocation);
//Check an additional block away in case the portal is a bungee portal using END_PORTAL
if (entrancePortal == null) {
entrancePortal = PortalUtil.getByAdjacentEntrance(toLocation);
// This should never realistically be null
if (entrancePortal == null) {
return;
}
}
Portal destination = entrancePortal.getPortalActivator().getDestination(player);
if (destination == null) {
return;
}
Entity playerVehicle = player.getVehicle();
//If the player is in a vehicle, but vehicle handling is disabled, just ignore the player
if (playerVehicle == null || (playerVehicle instanceof LivingEntity &&
Stargate.getGateConfig().handleVehicles())) {
teleportPlayer(playerVehicle, player, entrancePortal, destination, event);
}
}
/**
* Listen for entities entering an artificial end portal
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onEntityPortalEnter(@NotNull EntityPortalEnterEvent event) {
Location location = event.getLocation();
World world = location.getWorld();
Entity entity = event.getEntity();
//Hijack normal portal teleportation if teleporting from a stargate, and teleporting from an end portal in the
// end
if (!(entity instanceof Player player) || location.getBlock().getType() != Material.END_PORTAL ||
world == null || world.getEnvironment() != World.Environment.THE_END) {
return;
}
Portal portal = PortalUtil.getByAdjacentEntrance(location);
if (portal == null) {
return;
}
Stargate.debug("PortalEventListener::onEntityPortalEnter",
"Found player " + player + " entering END_PORTAL " + portal);
//Decide if the anything stops the player from teleporting
if (PermissionHelper.playerCannotTeleport(portal, portal.getPortalActivator().getDestination(),
player, null) || portal.getOptions().isBungee()) {
//Teleport the player back to the portal they came in, just in case
playersFromTheEnd.put(player, new FromTheEndTeleportation(portal));
Stargate.debug("PortalEventListener::onEntityPortalEnter",
"Sending player back to the entrance");
} else {
Portal destination = portal.getPortalActivator().getDestination();
if (destination != null) {
playersFromTheEnd.put(player, new FromTheEndTeleportation(destination));
Stargate.debug("PortalEventListener::onEntityPortalEnter",
"Sending player to destination");
}
}
}
/**
* Listen for the respawn event to catch players teleporting from the end in an artificial end portal
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onRespawn(@NotNull PlayerRespawnEvent event) {
Player respawningPlayer = event.getPlayer();
FromTheEndTeleportation teleportation = playersFromTheEnd.remove(respawningPlayer);
if (teleportation == null) {
return;
}
Portal exitPortal = teleportation.exitPortal();
//Overwrite respawn location to respawn in front of the portal
PlayerTeleporter teleporter = new PlayerTeleporter(exitPortal, respawningPlayer);
Location respawnLocation = teleporter.getExit();
event.setRespawnLocation(respawnLocation);
//Try and force the player if for some reason the changing of respawn location isn't properly handled
Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), () ->
respawningPlayer.teleport(respawnLocation), 1);
//Properly close the portal to prevent it from staying in a locked state until it times out
exitPortal.getPortalOpener().closePortal(false);
Stargate.debug("PortalEventListener::onRespawn", "Overwriting respawn for " + respawningPlayer +
" to " + respawnLocation.getWorld() + ":" + respawnLocation);
}
/**
* Teleports a vehicle through a stargate
*
* @param passengers <p>The passengers inside the vehicle</p>
* @param entrancePortal <p>The portal the vehicle is entering</p>
* @param vehicle <p>The vehicle passing through</p>
*/
private static void teleportVehicle(@NotNull List<Entity> passengers, @NotNull Portal entrancePortal,
@NotNull Vehicle vehicle) {
String route = "VehicleEventListener::teleportVehicle";
if (!passengers.isEmpty() && TeleportHelper.containsPlayer(passengers)) {
Stargate.debug(route, "Found passenger vehicle");
teleportPlayerAndVehicle(entrancePortal, vehicle);
} else {
Stargate.debug(route, "Found vehicle without players");
Portal destinationPortal = entrancePortal.getPortalActivator().getDestination();
if (destinationPortal == null) {
Stargate.debug(route, "Unable to find portal destination");
return;
}
Stargate.debug("vehicleTeleport", destinationPortal.getLocation().getWorld() + " " +
destinationPortal.getLocation().getSignBlock());
new VehicleTeleporter(destinationPortal, vehicle).teleportEntity(entrancePortal);
}
}
/**
* Teleports a player and the vehicle the player sits in
*
* @param entrancePortal <p>The portal the minecart entered</p>
* @param vehicle <p>The vehicle to teleport</p>
*/
private static void teleportPlayerAndVehicle(@NotNull Portal entrancePortal, @NotNull Vehicle vehicle) {
Entity rootEntity = vehicle;
while (rootEntity.getVehicle() != null) {
rootEntity = rootEntity.getVehicle();
}
List<Player> players = TeleportHelper.getPlayers(rootEntity.getPassengers());
Portal destinationPortal = getDestinationPortal(players, entrancePortal);
//Cancel the teleport if no players activated the portal, or if any players are denied access
boolean cancelTeleportation = false;
for (Player player : players) {
if (destinationPortal == null) {
cancelTeleportation = true;
if (!entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.INVALID_DESTINATION).error(player);
}
} else if (!TeleportHelper.playerCanTeleport(player, entrancePortal, destinationPortal)) {
cancelTeleportation = true;
}
}
if (cancelTeleportation || destinationPortal == null) {
return;
}
//Take payment from all players
if (!takePayment(players, entrancePortal, destinationPortal)) {
return;
}
// Perform the teleportation
teleportPlayerAndVehicle(players, vehicle, entrancePortal, destinationPortal);
}
/**
* Performs the teleportation of one or more players in a vehicle
*
* @param players <p>The players to be teleported</p>
* @param vehicle <p>The vehicle that triggered the teleportation</p>
* @param entrancePortal <p>The portal the player(s) and vehicle entered from</p>
* @param destinationPortal <p>The portal the player(s) and vehicle are teleporting to</p>
*/
private static void teleportPlayerAndVehicle(@NotNull List<Player> players, @NotNull Vehicle vehicle,
@NotNull Portal entrancePortal, @NotNull Portal destinationPortal) {
//Teleport the vehicle and inform the user if the vehicle was teleported
boolean teleported = new VehicleTeleporter(destinationPortal, vehicle).teleportEntity(entrancePortal);
if (!teleported) {
return;
}
if (!entrancePortal.getOptions().isQuiet()) {
for (Player player : players) {
new SGFormatBuilder(Message.TELEPORTED).success(player);
}
}
entrancePortal.getPortalOpener().closePortal(false);
}
/**
* Tries to get the destination portal selected by one of the players included in the teleportation
*
* @param players <p>The players to be teleported</p>
* @param entrancePortal <p>The portal the players are entering</p>
* @return <p>The destination portal, or null if not found</p>
*/
@Nullable
private static Portal getDestinationPortal(@NotNull List<Player> players, @NotNull Portal entrancePortal) {
for (Player player : players) {
//The entrance portal must be open for one player for the teleportation to happen
if (!entrancePortal.getPortalOpener().isOpenFor(player)) {
continue;
}
//Check if any of the players has selected the destination
Portal possibleDestinationPortal = entrancePortal.getPortalActivator().getDestination(player);
if (possibleDestinationPortal != null) {
return possibleDestinationPortal;
}
}
return null;
}
/**
* Takes payment for the given players
*
* @param players <p>The players to take payment from</p>
* @param entrancePortal <p>The portal the players are travelling from</p>
* @param destinationPortal <p>The portal the players are travelling to</p>
* @return <p>True if payment was successfully taken, false otherwise</p>
*/
private static boolean takePayment(@NotNull List<Player> players, @NotNull Portal entrancePortal,
@NotNull Portal destinationPortal) {
for (Player player : players) {
//To prevent the case where the first passenger pays and then the second passenger is denied, this has to be
// run after it has been confirmed that all passengers are able to pay. Also note that some players might
// not have to pay, and thus the cost check has to be in the loop,
int cost = EconomyHelper.getUseCost(player, entrancePortal, destinationPortal);
if (cost > 0) {
if (EconomyHelper.cannotPayTeleportFee(entrancePortal, player, cost)) {
return false;
}
}
}
return true;
}
/**
* Teleports a player, also teleports the player's vehicle if it's a living entity
*
* @param playerVehicle <p>The vehicle the player is currently sitting in</p>
* @param player <p>The player which moved</p>
* @param entrancePortal <p>The entrance the player entered</p>
* @param destination <p>The destination of the entrance portal</p>
* @param event <p>The move event causing the teleportation to trigger</p>
*/
private void teleportPlayer(@Nullable Entity playerVehicle, @NotNull Player player, @NotNull Portal entrancePortal,
@NotNull Portal destination, @NotNull PlayerMoveEvent event) {
if (playerVehicle instanceof LivingEntity) {
//Make sure any horses are properly tamed
if (playerVehicle instanceof AbstractHorse horse && !horse.isTamed()) {
horse.setTamed(true);
horse.setOwner(player);
}
//Teleport the player's vehicle
player.setVelocity(new Vector());
new VehicleTeleporter(destination, (Vehicle) playerVehicle).teleportEntity(entrancePortal);
} else {
//Just teleport the player like normal
new PlayerTeleporter(destination, player).teleportPlayer(entrancePortal, event);
}
if (!entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.TELEPORTED).success(player);
}
entrancePortal.getPortalOpener().closePortal(false);
}
/**
* Checks whether a player move event is relevant for this plugin
*
* @param event <p>The player move event to check</p>
* @param player <p>The player which moved</p>
* @param fromLocation <p>The location the player is moving from</p>
* @param toLocation <p>The location the player is moving to</p>
* @return <p>True if the event is relevant</p>
*/
private boolean isRelevantMoveEvent(@NotNull PlayerMoveEvent event, Player player,
@NotNull BlockLocation fromLocation, @NotNull BlockLocation toLocation) {
//Check to see if the player moved to another block
if (fromLocation.equals(toLocation)) {
return false;
}
//Get the portal the player entered, if any
Portal entrancePortal = getEnteredPortal(toLocation, player);
if (entrancePortal == null) {
return false;
}
Portal destination = entrancePortal.getPortalActivator().getDestination(player);
//Catch always open portals without a valid destination to prevent the user for being teleported and denied
if (!entrancePortal.getOptions().isBungee() && destination == null) {
return false;
}
//Decide if the anything stops the player from teleport
if (PermissionHelper.playerCannotTeleport(entrancePortal, destination, player, event)) {
return false;
}
//Decide if the user should be teleported to another bungee server
if (entrancePortal.getOptions().isBungee()) {
if (BungeeHelper.bungeeTeleport(player, entrancePortal, event) && !entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.TELEPORTED).success(player);
}
return false;
}
//Make sure to check if the player has any leashed creatures, even though leashed teleportation is disabled
return TeleportHelper.noLeashedCreaturesPreventTeleportation(player);
}
/**
* Gets the portal a player entered
*
* @param toLocation <p>The location the player moved to</p>
* @param player <p>The player that moved</p>
* @return <p>The portal the player entered, or null if no portal was entered</p>
*/
private Portal getEnteredPortal(@NotNull BlockLocation toLocation, @NotNull Player player) {
Portal entrancePortal = PortalUtil.getByEntrance(toLocation);
// Return if in an entrance
if (entrancePortal != null) {
return entrancePortal;
}
//Check an additional block away for special cases like BungeeCord portals using END_PORTAL as its material
entrancePortal = PortalUtil.getByAdjacentEntrance(toLocation);
if (entrancePortal == null) {
return null;
}
// If END_GATEWAY and END_PORTAL cannot appear, skip further checks
Set<Material> entranceMaterials = MaterialHelper.specifiersToMaterials(entrancePortal.getGate().getPortalOpenMaterials());
if (!entranceMaterials.contains(Material.END_GATEWAY) && !entranceMaterials.contains(Material.END_PORTAL)) {
return null;
}
// Get the real materials in the entrance, as END_GATEWAY or END_PORTAL may be available, but not chosen
Set<Material> materialsInEntrance = new HashSet<>();
for (BlockLocation location : entrancePortal.getStructure().getEntrances()) {
materialsInEntrance.add(location.getType());
}
// Abort if not a special case
if ((!materialsInEntrance.contains(Material.END_GATEWAY) || !isGeyserPlayer(player)) &&
(!entrancePortal.getOptions().isBungee() || !materialsInEntrance.contains(Material.END_PORTAL))) {
return null;
}
return entrancePortal;
}
/**
* Checks whether the given player is connected through Geyser
*
* @param player <p>The player to check</p>
* @return <p>True if the player is connected through Geyser</p>
*/
private boolean isGeyserPlayer(@NotNull Player player) {
// Prevent unnecessary checking for non-geyser and floodgate servers
if (!hasGeyser && !hasFloodgate) {
return false;
}
// Use Geyser API to get connection status
if (hasGeyser) {
try {
return GeyserApi.api().connectionByUuid(player.getUniqueId()) != null;
} catch (NoClassDefFoundError error1) {
hasGeyser = false;
}
}
// Use Floodgate API to get connection status
if (hasFloodgate) {
try {
return FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId());
} catch (NoClassDefFoundError error2) {
hasFloodgate = false;
}
}
return false;
}
}

View File

@@ -1,36 +0,0 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.portal.PortalHandler;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerTeleportEvent;
/**
* This listener listens to teleportation-related events
*/
@SuppressWarnings("unused")
public class TeleportEventListener implements Listener {
/**
* This event handler handles some special teleportation events
*
* <p>This event cancels nether portal, end gateway and end portal teleportation if the user teleported from a
* stargate entrance. This prevents the user from just teleporting to the nether or the end with portals using
* the special teleportation blocks.</p>
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent event) {
PlayerTeleportEvent.TeleportCause cause = event.getCause();
//Block normal portal teleportation if teleporting from a stargate
if (!event.isCancelled() && (cause == PlayerTeleportEvent.TeleportCause.NETHER_PORTAL ||
cause == PlayerTeleportEvent.TeleportCause.END_GATEWAY ||
cause == PlayerTeleportEvent.TeleportCause.END_PORTAL)
&& PortalHandler.getByAdjacentEntrance(event.getFrom()) != null) {
event.setCancelled(true);
}
}
}

View File

@@ -1,180 +0,0 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.teleporter.Teleporter;
import net.knarcraft.stargate.portal.teleporter.VehicleTeleporter;
import net.knarcraft.stargate.utility.EconomyHelper;
import net.knarcraft.stargate.utility.EntityHelper;
import net.knarcraft.stargate.utility.PermissionHelper;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import java.util.List;
/**
* This listener listens for the vehicle move event to teleport vehicles through portals
*/
@SuppressWarnings("unused")
public class VehicleEventListener implements Listener {
/**
* Check for a vehicle moving through a portal
*
* @param event <p>The triggered move event</p>
*/
@EventHandler
public void onVehicleMove(VehicleMoveEvent event) {
if (!Stargate.getGateConfig().handleVehicles()) {
return;
}
List<Entity> passengers = event.getVehicle().getPassengers();
Vehicle vehicle = event.getVehicle();
Portal entrancePortal;
int entitySize = EntityHelper.getEntityMaxSizeInt(vehicle);
if (EntityHelper.getEntityMaxSize(vehicle) > 1) {
entrancePortal = PortalHandler.getByAdjacentEntrance(event.getTo(), entitySize - 1);
} else {
entrancePortal = PortalHandler.getByEntrance(event.getTo());
}
//Return if the portal cannot be teleported through
if (entrancePortal == null || !entrancePortal.isOpen() || entrancePortal.getOptions().isBungee()) {
return;
}
teleportVehicle(passengers, entrancePortal, vehicle);
}
/**
* Teleports a vehicle through a stargate
*
* @param passengers <p>The passengers inside the vehicle</p>
* @param entrancePortal <p>The portal the vehicle is entering</p>
* @param vehicle <p>The vehicle passing through</p>
*/
private static void teleportVehicle(List<Entity> passengers, Portal entrancePortal, Vehicle vehicle) {
String route = "VehicleEventListener::teleportVehicle";
if (!passengers.isEmpty() && passengers.get(0) instanceof Player) {
Stargate.debug(route, "Found passenger vehicle");
teleportPlayerAndVehicle(entrancePortal, vehicle, passengers);
} else {
Stargate.debug(route, "Found empty vehicle");
Portal destinationPortal = entrancePortal.getPortalActivator().getDestination();
if (destinationPortal == null) {
Stargate.debug(route, "Unable to find portal destination");
return;
}
Stargate.debug("vehicleTeleport", destinationPortal.getWorld() + " " +
destinationPortal.getSignLocation());
new VehicleTeleporter(destinationPortal, vehicle).teleport(entrancePortal);
}
}
/**
* Teleports a player and the vehicle the player sits in
*
* @param entrancePortal <p>The portal the minecart entered</p>
* @param vehicle <p>The vehicle to teleport</p>
* @param passengers <p>Any entities sitting in the minecart</p>
*/
private static void teleportPlayerAndVehicle(Portal entrancePortal, Vehicle vehicle, List<Entity> passengers) {
Player player = (Player) passengers.get(0);
//On the assumption that a non-player cannot sit in the driver's seat and since some portals can only be open
// to one player at a time, we only need to check if the portal is open to the driver.
if (!entrancePortal.getPortalOpener().isOpenFor(player)) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
return;
}
//If no destination exists, the teleportation cannot happen
Portal destinationPortal = entrancePortal.getPortalActivator().getDestination(player);
if (destinationPortal == null) {
return;
}
//Make sure all player passengers are allowed to, and can afford to, enter the portal
for (Entity entity : passengers) {
if (entity instanceof Player && !playerCanTeleport((Player) entity, entrancePortal, destinationPortal)) {
return;
}
}
//To prevent the case where the first passenger pays and then the second passenger is denied, this has to be
// run after it has been confirmed that all passengers are able to pay
int cost = EconomyHelper.getUseCost(player, entrancePortal, destinationPortal);
if (cost > 0) {
if (!takePlayerPayment(passengers, entrancePortal, cost)) {
return;
}
}
//Teleport the vehicle and inform the user if the vehicle was teleported
boolean teleported = new VehicleTeleporter(destinationPortal, vehicle).teleport(entrancePortal);
if (teleported) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("teleportMsg"));
}
entrancePortal.getPortalOpener().closePortal(false);
}
}
/**
* Takes payment from all player passengers
*
* @param passengers <p>All passengers in the teleporting vehicle</p>
* @param entrancePortal <p>The portal the vehicle is entering from</p>
* @param cost <p>The cost each player has to pay</p>
* @return <p>True if all player passengers paid successfully</p>
*/
private static boolean takePlayerPayment(List<Entity> passengers, Portal entrancePortal, int cost) {
for (Entity entity : passengers) {
//If the passenger is a player, make it pay
if (entity instanceof Player && EconomyHelper.cannotPayTeleportFee(entrancePortal, (Player) entity, cost)) {
return false;
}
}
return true;
}
/**
* Checks whether the given player is allowed to and can afford to teleport
*
* @param player <p>The player trying to teleport</p>
* @param entrancePortal <p>The portal the player is entering</p>
* @param destinationPortal <p>The portal the player is to exit from</p>
* @return <p>True if the player is allowed to teleport and is able to pay necessary fees</p>
*/
private static boolean playerCanTeleport(Player player, Portal entrancePortal, Portal destinationPortal) {
//Make sure the user can access the portal
if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destinationPortal)) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
entrancePortal.getPortalOpener().closePortal(false);
return false;
}
//Check if the player is able to afford the teleport fee
int cost = EconomyHelper.getUseCost(player, entrancePortal, destinationPortal);
boolean canAffordFee = cost <= 0 || Stargate.getEconomyConfig().canAffordFee(player, cost);
if (!canAffordFee) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("ecoInFunds"));
}
return false;
}
return Teleporter.noLeashedCreaturesPreventTeleportation(player);
}
}

View File

@@ -1,49 +0,0 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.StargateConfig;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.utility.PortalFileHelper;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
/**
* This listener listens for the loading and unloading of worlds to load and unload stargates
*/
@SuppressWarnings("unused")
public class WorldEventListener implements Listener {
/**
* This listener listens for the loading of a world and loads all gates from the world if not already loaded
*
* @param event <p>The triggered world load event</p>
*/
@EventHandler
public void onWorldLoad(WorldLoadEvent event) {
StargateConfig config = Stargate.getStargateConfig();
if (!config.getManagedWorlds().contains(event.getWorld().getName()) &&
PortalFileHelper.loadAllPortals(event.getWorld())) {
config.addManagedWorld(event.getWorld().getName());
}
}
/**
* This listener listens for the unloading of a world
*
* @param event <p>The triggered world unload event</p>
*/
@EventHandler
public void onWorldUnload(WorldUnloadEvent event) {
Stargate.debug("onWorldUnload", "Reloading all Stargates");
World world = event.getWorld();
String worldName = world.getName();
StargateConfig config = Stargate.getStargateConfig();
if (config.getManagedWorlds().contains(worldName)) {
config.removeManagedWorld(worldName);
PortalRegistry.clearPortals(world);
}
}
}

View File

@@ -1,16 +1,17 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.portal.property.PortalLocation;
import net.knarcraft.stargate.portal.property.PortalOption;
import net.knarcraft.stargate.portal.property.PortalOptions;
import net.knarcraft.stargate.portal.property.PortalOwner;
import net.knarcraft.stargate.portal.property.PortalStrings;
import net.knarcraft.stargate.portal.property.PortalStructure;
import net.knarcraft.stargate.portal.property.gate.Gate;
import org.bukkit.ChatColor;
import org.bukkit.World;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
@@ -39,22 +40,21 @@ public class Portal {
*
* @param portalLocation <p>Object containing locations of all relevant blocks</p>
* @param button <p>The location of the portal's open button</p>
* @param destination <p>The destination defined on the sign's destination line. "" for non-fixed gates</p>
* @param name <p>The name of the portal defined on the sign's first line</p>
* @param network <p>The network the portal belongs to, defined on the sign's third</p>
* @param portalStrings <p>The portal's string values, such as name, network and destination</p>
* @param gate <p>The gate type to use for this portal</p>
* @param portalOwner <p>The portal's owner</p>
* @param options <p>A map containing all possible portal options, with true for the ones enabled</p>
*/
public Portal(PortalLocation portalLocation, BlockLocation button, String destination, String name, String network,
Gate gate, PortalOwner portalOwner, Map<PortalOption, Boolean> options) {
public Portal(@NotNull PortalLocation portalLocation, @Nullable Block button,
@NotNull PortalStrings portalStrings, @NotNull Gate gate, @NotNull PortalOwner portalOwner,
@NotNull Map<PortalOption, Boolean> options) {
this.location = portalLocation;
this.network = network;
this.name = name;
this.network = portalStrings.network();
this.name = portalStrings.name();
this.portalOwner = portalOwner;
this.options = new PortalOptions(options, destination.length() > 0);
this.options = new PortalOptions(options, !portalStrings.destination().isEmpty());
this.signDrawer = new PortalSignDrawer(this);
this.portalOpener = new PortalOpener(this, destination);
this.portalOpener = new PortalOpener(this, portalStrings.destination());
this.structure = new PortalStructure(this, gate, button);
this.portalActivator = portalOpener.getPortalActivator();
this.cleanName = cleanString(name);
@@ -84,6 +84,7 @@ public class Portal {
*
* @return <p>This portal's location data</p>
*/
@NotNull
public PortalLocation getLocation() {
return this.location;
}
@@ -96,6 +97,7 @@ public class Portal {
*
* @return <p>This portal's structure</p>
*/
@NotNull
public PortalStructure getStructure() {
return this.structure;
}
@@ -108,6 +110,7 @@ public class Portal {
*
* @return <p>This portal's activator</p>
*/
@NotNull
public PortalActivator getPortalActivator() {
return this.portalActivator;
}
@@ -124,6 +127,7 @@ public class Portal {
*
* @return <p>This portal's portal options</p>
*/
@NotNull
public PortalOptions getOptions() {
return this.options;
}
@@ -142,6 +146,7 @@ public class Portal {
*
* @return <p>The player currently using this portal</p>
*/
@Nullable
public Player getActivePlayer() {
return portalActivator.getActivePlayer();
}
@@ -151,6 +156,7 @@ public class Portal {
*
* @return <p>The network this portal belongs to</p>
*/
@NotNull
public String getNetwork() {
return network;
}
@@ -160,6 +166,7 @@ public class Portal {
*
* @return <p>The clean network name</p>
*/
@NotNull
public String getCleanNetwork() {
return cleanNetwork;
}
@@ -181,6 +188,7 @@ public class Portal {
*
* @return <p>The name of this portal</p>
*/
@NotNull
public String getName() {
return name;
}
@@ -190,6 +198,7 @@ public class Portal {
*
* @return <p>The clean name of this portal</p>
*/
@NotNull
public String getCleanName() {
return cleanName;
}
@@ -201,6 +210,7 @@ public class Portal {
*
* @return <p>This portal's portal opener</p>
*/
@NotNull
public PortalOpener getPortalOpener() {
return portalOpener;
}
@@ -210,6 +220,7 @@ public class Portal {
*
* @return <p>The name of this portal's destination portal</p>
*/
@NotNull
public String getDestinationName() {
return portalOpener.getPortalActivator().getDestinationName();
}
@@ -219,6 +230,7 @@ public class Portal {
*
* @return <p>The gate type used by this portal</p>
*/
@NotNull
public Gate getGate() {
return structure.getGate();
}
@@ -230,6 +242,7 @@ public class Portal {
*
* @return <p>This portal's owner</p>
*/
@NotNull
public PortalOwner getOwner() {
return portalOwner;
}
@@ -240,7 +253,7 @@ public class Portal {
* @param player <p>The player to check</p>
* @return <p>True if the player is the owner of this portal</p>
*/
public boolean isOwner(Player player) {
public boolean isOwner(@NotNull Player player) {
if (this.portalOwner.getUUID() != null) {
return player.getUniqueId().compareTo(this.portalOwner.getUUID()) == 0;
} else {
@@ -248,68 +261,22 @@ public class Portal {
}
}
/**
* Gets the world this portal belongs to
*
* @return <p>The world this portal belongs to</p>
*/
public World getWorld() {
return location.getWorld();
}
/**
* Gets the location of this portal's sign
*
* @return <p>The location of this portal's sign</p>
*/
public BlockLocation getSignLocation() {
return this.location.getSignLocation();
}
/**
* Gets the rotation (yaw) of this portal
*
* <p>The yaw is used to calculate all kinds of directions. See DirectionHelper to see how the yaw is used to
* calculate to/from other direction types.</p>
*
* @return <p>The rotation (yaw) of this portal</p>
*/
public float getYaw() {
return this.location.getYaw();
}
/**
* Gets the location of the top-left block of the portal
*
* @return <p>The location of the top-left portal block</p>
*/
public BlockLocation getTopLeft() {
return this.location.getTopLeft();
}
/**
* Gets the block at the given location relative to this portal's top-left block
*
* @param vector <p>The relative block vector explaining the position of the block</p>
* @return <p>The block at the given relative position</p>
*/
public BlockLocation getBlockAt(RelativeBlockVector vector) {
return getTopLeft().getRelativeLocation(vector, getYaw());
}
/**
* Cleans a string by removing color codes, lower-casing and replacing spaces with underscores
*
* @param string <p>The string to clean</p>
* @return <p>The clean string</p>
*/
public static String cleanString(String string) {
@NotNull
public static String cleanString(@NotNull String string) {
// TODO: Replace special characters such as : and .
return ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', string)).toLowerCase();
}
@Override
@NotNull
public String toString() {
return String.format("Portal [id=%s, network=%s name=%s, type=%s]", getSignLocation(), network, name,
return String.format("Portal [id=%s, network=%s name=%s, type=%s]", this.location.getSignBlock(), network, name,
structure.getGate().getFilename());
}
@@ -323,7 +290,7 @@ public class Portal {
}
@Override
public boolean equals(Object object) {
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
@@ -345,4 +312,5 @@ public class Portal {
return cleanNetwork.equalsIgnoreCase(other.cleanNetwork);
}
}
}

View File

@@ -1,14 +1,19 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.event.StargateActivateEvent;
import net.knarcraft.stargate.event.StargateDeactivateEvent;
import net.knarcraft.stargate.utility.ListHelper;
import net.knarcraft.stargate.utility.PortalUtil;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
/**
* The portal activator activates/de-activates portals and keeps track of a portal's destinations
@@ -33,7 +38,7 @@ public class PortalActivator {
* @param portalOpener <p>The portal opener to trigger when the activation causes the portal to open</p>
* @param destination <p>The fixed destination specified on the portal's sign</p>
*/
public PortalActivator(Portal portal, PortalOpener portalOpener, String destination) {
public PortalActivator(@NotNull Portal portal, @NotNull PortalOpener portalOpener, @NotNull String destination) {
this.portal = portal;
this.opener = portalOpener;
this.destination = destination;
@@ -44,6 +49,7 @@ public class PortalActivator {
*
* @return <p>The player this activator's portal is currently activated for</p>
*/
@NotNull
public Player getActivePlayer() {
return activePlayer;
}
@@ -53,6 +59,7 @@ public class PortalActivator {
*
* @return <p>The available portal destinations</p>
*/
@NotNull
public List<String> getDestinations() {
return new ArrayList<>(this.destinations);
}
@@ -63,20 +70,21 @@ public class PortalActivator {
* @param player <p>Used for random gates to determine which destinations are available</p>
* @return <p>The destination portal the player should teleport to</p>
*/
public Portal getDestination(Player player) {
@Nullable
public Portal getDestination(@Nullable Player player) {
String portalNetwork = portal.getCleanNetwork();
if (portal.getOptions().isRandom()) {
//Find possible destinations
List<String> destinations = PortalHandler.getDestinations(portal, player, portalNetwork);
if (destinations.size() == 0) {
List<String> destinations = PortalUtil.getDestinations(portal, player, portalNetwork);
if (destinations.isEmpty()) {
return null;
}
//Get one random destination
String destination = destinations.get((new Random()).nextInt(destinations.size()));
return PortalHandler.getByName(Portal.cleanString(destination), portalNetwork);
String randomDestination = ListHelper.getRandom(destinations);
return PortalUtil.getByName(randomDestination, portalNetwork);
} else {
//Just return the normal fixed destination
return PortalHandler.getByName(Portal.cleanString(destination), portalNetwork);
return PortalUtil.getByName(destination, portalNetwork);
}
}
@@ -88,6 +96,7 @@ public class PortalActivator {
*
* @return <p>The portal destination</p>
*/
@Nullable
public Portal getDestination() {
return getDestination(null);
}
@@ -97,7 +106,7 @@ public class PortalActivator {
*
* @param destination <p>The new destination of this portal activator's portal</p>
*/
public void setDestination(Portal destination) {
public void setDestination(@NotNull Portal destination) {
setDestination(destination.getName());
}
@@ -106,7 +115,7 @@ public class PortalActivator {
*
* @param destination <p>The new destination of this portal activator's portal</p>
*/
public void setDestination(String destination) {
public void setDestination(@NotNull String destination) {
this.destination = destination;
}
@@ -115,6 +124,7 @@ public class PortalActivator {
*
* @return <p>The name of the selected destination</p>
*/
@NotNull
public String getDestinationName() {
return destination;
}
@@ -125,7 +135,7 @@ public class PortalActivator {
* @param player <p>The player to activate the portal for</p>
* @return <p>True if the portal was activated</p>
*/
boolean activate(Player player) {
public boolean activate(@NotNull Player player) {
//Clear previous destination data
this.destination = "";
this.destinations.clear();
@@ -137,7 +147,7 @@ public class PortalActivator {
activePlayer = player;
String network = portal.getCleanNetwork();
destinations = PortalHandler.getDestinations(portal, player, network);
destinations = PortalUtil.getDestinations(portal, player, network);
//Sort destinations if enabled
if (Stargate.getGateConfig().sortNetworkDestinations()) {
@@ -162,7 +172,7 @@ public class PortalActivator {
* @param player <p>The player trying to activate this activator's portal</p>
* @return <p>True if the portal was activated. False otherwise</p>
*/
private boolean triggerStargateActivationEvent(Player player) {
private boolean triggerStargateActivationEvent(@NotNull Player player) {
StargateActivateEvent event = new StargateActivateEvent(portal, player, destinations, destination);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
@@ -209,7 +219,7 @@ public class PortalActivator {
* @return <p>Whether this portal activator's portal is active</p>
*/
public boolean isActive() {
return portal.getOptions().isFixed() || (destinations.size() > 0);
return portal.getOptions().isFixed() || (!destinations.isEmpty());
}
/**
@@ -217,7 +227,7 @@ public class PortalActivator {
*
* @param player <p>The player to cycle the gate for</p>
*/
public void cycleDestination(Player player) {
public void cycleDestination(@NotNull Player player) {
cycleDestination(player, 1);
}
@@ -227,7 +237,7 @@ public class PortalActivator {
* @param player <p>The player cycling destinations</p>
* @param direction <p>The direction of the cycle (+1 for next, -1 for previous)</p>
*/
public void cycleDestination(Player player, int direction) {
public void cycleDestination(@NotNull Player player, int direction) {
//Only allow going exactly one step in either direction
if (direction != 1 && direction != -1) {
throw new IllegalArgumentException("The destination direction must be 1 or -1.");
@@ -241,15 +251,17 @@ public class PortalActivator {
}
activate = true;
Stargate.debug("cycleDestination", "Network Size: " +
PortalHandler.getNetwork(portal.getCleanNetwork()).size());
List<String> portalsInNetwork = PortalUtil.getNetwork(portal.getCleanNetwork());
if (portalsInNetwork != null) {
Stargate.debug("cycleDestination", "Network Size: " + portalsInNetwork.size());
}
Stargate.debug("cycleDestination", "Player has access to: " + destinations.size());
}
//If no destinations are available, just tell the player and quit
if (destinations.size() == 0) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("destEmpty"));
if (destinations.isEmpty()) {
if (!portal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.NO_DESTINATION).error(player);
}
return;
}

View File

@@ -1,23 +1,31 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.event.StargateCreateEvent;
import net.knarcraft.stargate.portal.property.PortalLocation;
import net.knarcraft.stargate.portal.property.PortalOption;
import net.knarcraft.stargate.portal.property.PortalOptions;
import net.knarcraft.stargate.portal.property.PortalOwner;
import net.knarcraft.stargate.portal.property.PortalStrings;
import net.knarcraft.stargate.portal.property.gate.Gate;
import net.knarcraft.stargate.portal.property.gate.GateHandler;
import net.knarcraft.stargate.utility.DirectionHelper;
import net.knarcraft.stargate.utility.EconomyHelper;
import net.knarcraft.stargate.utility.ListHelper;
import net.knarcraft.stargate.utility.MaterialHelper;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.knarcraft.stargate.utility.PortalFileHelper;
import net.knarcraft.stargate.utility.PortalUtil;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.util.BlockVector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
@@ -39,7 +47,7 @@ public class PortalCreator {
* @param event <p>The sign change event which initialized the creation</p>
* @param player <p>The player creating the portal</p>
*/
public PortalCreator(SignChangeEvent event, Player player) {
public PortalCreator(@NotNull SignChangeEvent event, @NotNull Player player) {
this.event = event;
this.player = player;
}
@@ -49,53 +57,66 @@ public class PortalCreator {
*
* @return <p>The created portal</p>
*/
@Nullable
public Portal createPortal() {
BlockLocation signLocation = new BlockLocation(event.getBlock());
Block signControlBlock = signLocation.getParent();
String route = "PortalCreator::createPortal";
Block signLocation = event.getBlock();
Block signControlBlock = DirectionHelper.getParent(signLocation);
//Return early if the sign is not placed on a block, or the block is not a control block
if (signControlBlock == null || GateHandler.getGatesByControlBlock(signControlBlock).length == 0) {
Stargate.debug("createPortal", "Control block not registered");
if (signControlBlock == null || GateHandler.getGatesByControlBlock(signControlBlock).isEmpty()) {
Stargate.debug(route, "Control block not registered");
return null;
}
//The control block is already part of another portal
if (PortalHandler.getByBlock(signControlBlock) != null) {
Stargate.debug("createPortal", "idParent belongs to existing stargate");
if (PortalUtil.getByBlock(signControlBlock) != null) {
Stargate.debug(route, "idParent belongs to existing stargate");
return null;
}
//Get necessary information from the gate's sign
String portalName = PortalHandler.filterName(event.getLine(0));
String destinationName = PortalHandler.filterName(event.getLine(1));
String network = PortalHandler.filterName(event.getLine(2));
String options = PortalHandler.filterName(event.getLine(3)).toLowerCase();
@NotNull String portalName = PortalUtil.filterName(event.getLine(0));
@NotNull String destinationName = PortalUtil.filterName(event.getLine(1));
@NotNull String network = PortalUtil.filterName(event.getLine(2));
@NotNull String options = PortalUtil.filterName(event.getLine(3)).toLowerCase();
PortalStrings portalStrings = new PortalStrings(portalName, network, destinationName);
//Get portal options available to the player creating the portal
Map<PortalOption, Boolean> portalOptions = PortalHandler.getPortalOptions(player, destinationName, options);
Map<PortalOption, Boolean> portalOptions = PortalUtil.getPortalOptions(player, destinationName, options);
//Get the yaw
float yaw = DirectionHelper.getYawFromLocationDifference(signControlBlock.getLocation(),
signLocation.getLocation());
//Get the direction the button should be facing
BlockFace buttonFacing = DirectionHelper.getBlockFaceFromYaw(yaw);
PortalLocation portalLocation = new PortalLocation();
portalLocation.setButtonFacing(buttonFacing).setYaw(yaw).setSignLocation(signLocation);
BlockFace facing = DirectionHelper.getFacing(signLocation);
if (facing == null) {
facing = DirectionHelper.getBlockFaceFromLocationDifference(signControlBlock.getLocation(),
signLocation.getLocation());
}
Stargate.debug("createPortal", "Finished getting all portal info");
PortalLocation portalLocation = new PortalLocation(signLocation, facing);
Stargate.debug(route, "Finished getting all portal info");
return createPortal(portalStrings, portalOptions, portalLocation);
}
@Nullable
private Portal createPortal(@NotNull PortalStrings portalStrings, @NotNull Map<PortalOption, Boolean> portalOptions,
@NotNull PortalLocation portalLocation) {
String route = "PortalCreator::createPortal";
//Try and find a gate matching the new portal
Gate gate = PortalHandler.findMatchingGate(portalLocation, player.getWorld());
if ((gate == null) || (portalLocation.getButtonVector() == null)) {
Stargate.debug("createPortal", "Could not find matching gate layout");
Gate gate = PortalUtil.findMatchingGate(portalLocation);
if ((gate == null) || (portalLocation.getButtonBlock() == null)) {
Stargate.debug(route, "Could not find matching gate layout");
return null;
}
//If the portal is a bungee portal and invalid, abort here
if (!PortalHandler.isValidBungeePortal(portalOptions, player, destinationName, network)) {
Stargate.debug("createPortal", "Portal is an invalid bungee portal");
if (!PortalUtil.isValidBungeePortal(portalOptions, player, portalStrings.destination(),
portalStrings.network())) {
Stargate.debug(route, "Portal is an invalid bungee portal");
return null;
}
@@ -104,65 +125,102 @@ public class PortalCreator {
for (PortalOption option : portalOptions.keySet()) {
builder.append(option.getCharacterRepresentation()).append(" = ").append(portalOptions.get(option)).append(" ");
}
Stargate.debug("createPortal", builder.toString());
//Use default network if a proper alternative is not set
if (!portalOptions.get(PortalOption.BUNGEE) && (network.length() < 1 || network.length() >
getMaxNameNetworkLength())) {
network = Stargate.getDefaultNetwork();
}
Stargate.debug(route, builder.toString());
boolean deny = false;
String denyMessage = "";
if (!(boolean) portalOptions.get(PortalOption.BUNGEE)) {
String networkName = getNetworkName(portalStrings);
if (networkName == null) {
deny = true;
denyMessage = new SGFormatBuilder(Message.CREATION_NETWORK_DENIED).toString();
} else {
portalStrings = new PortalStrings(portalStrings.name(), networkName, portalStrings.destination());
}
}
// Check whether the player can create a portal with the specified gate in the specified world
if (!deny) {
denyMessage = canCreatePortal(portalOptions.get(PortalOption.BUNGEE), portalStrings.network(), gate,
portalStrings.destination());
if (denyMessage != null) {
deny = true;
} else {
denyMessage = "";
}
}
//Check if a conflict exists
if (conflictsWithExistingPortal(gate, portalLocation, player)) {
return null;
}
PortalOwner owner = new PortalOwner(player);
this.portal = new Portal(portalLocation, null, portalStrings, gate, owner, portalOptions);
return validatePortal(denyMessage, event.getLines(), deny);
}
/**
* Gets the network name to use for the new portal
*
* @param portalStrings <p>The string values for the new portal</p>
* @return <p>The new network name, or null if the player does not have the necessary permission for any networks</p>
*/
@Nullable
private String getNetworkName(@NotNull PortalStrings portalStrings) {
String network = portalStrings.network();
String route = "PortalCreator::getNetworkName";
//Use default network if a proper alternative is not set
if (portalStrings.network().isEmpty() || portalStrings.network().length() > getMaxNameNetworkLength()) {
network = Stargate.getDefaultNetwork();
}
//Check if the player can create portals on this network. If not, create a personal portal
if (!portalOptions.get(PortalOption.BUNGEE) && !PermissionHelper.canCreateNetworkGate(player, network)) {
Stargate.debug("createPortal", "Player doesn't have create permissions on network. Trying personal");
if (!PermissionHelper.canCreateNetworkGate(player, network)) {
Stargate.debug(route, "Player doesn't have create permissions on network. Trying personal");
if (PermissionHelper.canCreatePersonalPortal(player)) {
network = player.getName();
if (network.length() > getMaxNameNetworkLength()) {
network = network.substring(0, getMaxNameNetworkLength());
}
Stargate.debug("createPortal", "Creating personal portal");
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createPersonal"));
Stargate.debug(route, "Creating personal portal");
new SGFormatBuilder(Message.CREATION_PERSONAL).error(player);
return network;
} else {
Stargate.debug("createPortal", "Player does not have access to network");
deny = true;
denyMessage = Stargate.getString("createNetDeny");
Stargate.debug(route, "Player does not have access to network");
return null;
}
}
return network;
}
@Nullable
private String canCreatePortal(boolean bungee, @NotNull String network,
@NotNull Gate gate, @NotNull String destinationName) {
//Check if the player can create this gate layout
String gateName = gate.getFilename();
gateName = gateName.substring(0, gateName.indexOf('.'));
if (!deny && !PermissionHelper.canCreatePortal(player, gateName)) {
Stargate.debug("createPortal", "Player does not have access to gate layout");
deny = true;
denyMessage = Stargate.getString("createGateDeny");
if (!PermissionHelper.canCreatePortal(player, gateName)) {
Stargate.debug("PortalCreator::canCreatePortal", "Player does not have access to gate layout");
return new SGFormatBuilder(Message.CREATION_GATE_DENIED).toString();
}
//Check if the user can create portals to this world.
if (!portalOptions.get(PortalOption.BUNGEE) && !deny && destinationName.length() > 0) {
Portal portal = PortalHandler.getByName(destinationName, network);
if (portal != null) {
String world = portal.getWorld().getName();
if (!bungee && !destinationName.isEmpty()) {
Portal destinationPortal = PortalUtil.getByName(destinationName, network);
if (destinationPortal != null) {
String world = destinationPortal.getLocation().getWorld().getName();
if (PermissionHelper.cannotAccessWorld(player, world)) {
Stargate.debug("canCreateNetworkGate", "Player does not have access to destination world");
deny = true;
denyMessage = Stargate.getString("createWorldDeny");
Stargate.debug("PortalCreator::canCreatePortal", "Player does not have access to destination world");
return new SGFormatBuilder(Message.CREATION_WORLD_DENIED).toString();
}
}
}
//Check if a conflict exists
if (conflictsWithExistingPortal(gate, portalLocation.getTopLeft(), yaw, player)) {
return null;
}
PortalOwner owner = new PortalOwner(player);
this.portal = new Portal(portalLocation, null, destinationName, portalName, network, gate, owner,
portalOptions);
return validatePortal(denyMessage, event.getLines(), deny);
return null;
}
/**
@@ -173,7 +231,8 @@ public class PortalCreator {
* @param deny <p>Whether the portal creation has already been denied</p>
* @return <p>The portal or null if its creation was denied</p>
*/
public Portal validatePortal(String denyMessage, String[] lines, boolean deny) {
@Nullable
public Portal validatePortal(@NotNull String denyMessage, @NotNull String[] lines, boolean deny) {
PortalLocation portalLocation = portal.getLocation();
Gate gate = portal.getStructure().getGate();
PortalOptions portalOptions = portal.getOptions();
@@ -193,7 +252,7 @@ public class PortalCreator {
//Tell the user why it was denied from creating the portal
if (stargateCreateEvent.getDeny()) {
if (!stargateCreateEvent.getDenyReason().trim().isEmpty()) {
Stargate.getMessageSender().sendErrorMessage(player, stargateCreateEvent.getDenyReason());
new SGFormatBuilder(stargateCreateEvent.getDenyReason()).error(player);
}
return null;
}
@@ -207,19 +266,19 @@ public class PortalCreator {
//Add button if the portal is not always on
if (!portalOptions.isAlwaysOn()) {
PortalFileHelper.generatePortalButton(portal, portalLocation.getButtonFacing());
PortalFileHelper.generatePortalButton(portal, portalLocation.getFacing());
}
//Register the new portal
PortalHandler.registerPortal(portal);
PortalUtil.registerPortal(portal);
updateNewPortalOpenState(destinationName);
//Update portals pointing at this one if it's not a bungee portal
if (!portal.getOptions().isBungee()) {
PortalHandler.updatePortalsPointingAtNewPortal(portal);
PortalUtil.updatePortalsPointingAtNewPortal(portal);
}
PortalFileHelper.saveAllPortals(portal.getWorld());
PortalFileHelper.saveAllPortals(portal.getLocation().getWorld());
return portal;
}
@@ -231,35 +290,37 @@ public class PortalCreator {
* @param portalName <p>The name of the newly created portal</p>
* @return <p>True if the portal is completely valid</p>
*/
private boolean checkIfNewPortalIsValid(int cost, String portalName) {
private boolean checkIfNewPortalIsValid(int cost, @NotNull String portalName) {
String route = "PortalCreator::checkIfNewPortalIsValid";
//Check if the portal name can fit on the sign with padding (>name<)
if (portal.getCleanName().length() < 1 || portal.getCleanName().length() > getMaxNameNetworkLength()) {
Stargate.debug("createPortal", String.format("Name length error. %s is too long.",
if (portal.getCleanName().isEmpty() || portal.getCleanName().length() > getMaxNameNetworkLength()) {
Stargate.debug(route, String.format("Name length error. %s is too long.",
portal.getCleanName()));
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createNameLength"));
new SGFormatBuilder(Message.CREATION_NAME_LENGTH).error(player);
return false;
}
if (portal.getOptions().isBungee()) {
//Check if the bungee portal's name has been duplicated
if (PortalHandler.getBungeePortals().get(portal.getCleanName()) != null) {
Stargate.debug("createPortal::Bungee", "Gate name duplicate");
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createExists"));
if (PortalRegistry.getBungeePortal(portal.getCleanName()) != null) {
Stargate.debug(route, "Gate name duplicate");
new SGFormatBuilder(Message.CREATION_NAME_COLLISION).error(player);
return false;
}
} else {
//Check if the portal name has been duplicated on the network
if (PortalHandler.getByName(portal.getCleanName(), portal.getCleanNetwork()) != null) {
Stargate.debug("createPortal", "Gate name duplicate");
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createExists"));
if (PortalUtil.getByName(portal.getCleanName(), portal.getCleanNetwork()) != null) {
Stargate.debug(route, "Gate name duplicate");
new SGFormatBuilder(Message.CREATION_NAME_COLLISION).error(player);
return false;
}
//Check if the number of portals in the network has been surpassed
List<String> networkList = PortalHandler.getAllPortalNetworks().get(portal.getCleanNetwork());
List<String> networkList = PortalUtil.getNetwork(portal.getCleanNetwork());
int maxGates = Stargate.getGateConfig().maxGatesEachNetwork();
if (maxGates > 0 && networkList != null && networkList.size() >= maxGates) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createFull"));
new SGFormatBuilder(Message.CREATION_NETWORK_FULL).error(player);
return false;
}
}
@@ -268,7 +329,7 @@ public class PortalCreator {
//Deduct the required fee from the player
if (!EconomyHelper.chargePlayerIfNecessary(player, cost)) {
EconomyHelper.sendInsufficientFundsMessage(portalName, player, cost);
Stargate.debug("createPortal", "Insufficient Funds");
Stargate.debug(route, "Insufficient Funds");
return false;
} else {
EconomyHelper.sendDeductMessage(portalName, player, cost);
@@ -282,14 +343,14 @@ public class PortalCreator {
*
* @param destinationName <p>The name of the destination portal. Only used if set as always on</p>
*/
private void updateNewPortalOpenState(String destinationName) {
private void updateNewPortalOpenState(@NotNull String destinationName) {
portal.drawSign();
if (portal.getOptions().isRandom() || portal.getOptions().isBungee()) {
//Open the implicitly always on portal
portal.getPortalOpener().openPortal(true);
} else if (portal.getOptions().isAlwaysOn()) {
//For a normal always-on portal, open both the portal and the destination
Portal destinationPortal = PortalHandler.getByName(destinationName, portal.getCleanNetwork());
Portal destinationPortal = PortalUtil.getByName(destinationName, portal.getCleanNetwork());
if (destinationPortal != null) {
portal.getPortalOpener().openPortal(true);
destinationPortal.drawSign();
@@ -297,8 +358,12 @@ public class PortalCreator {
} else {
//Update the block type for the portal's opening to the closed block as the closed block can be anything,
// not just air or water
for (BlockLocation entrance : portal.getStructure().getEntrances()) {
entrance.setType(portal.getGate().getPortalClosedBlock());
@NotNull List<Material> possibleMaterials = MaterialHelper.specifiersToMaterials(
portal.getGate().getPortalClosedMaterials()).stream().toList();
Material closedType = ListHelper.getRandom(possibleMaterials);
for (Block entrance : portal.getStructure().getEntrances()) {
entrance.setType(closedType);
}
}
}
@@ -306,18 +371,19 @@ public class PortalCreator {
/**
* Checks whether the new portal conflicts with an existing portal
*
* @param gate <p>The gate type of the new portal</p>
* @param topLeft <p>The top-left block of the new portal</p>
* @param yaw <p>The yaw when looking directly outwards from the portal</p>
* @param player <p>The player creating the new portal</p>
* @param gate <p>The gate type of the new portal</p>
* @param portalLocation <p>The location of the portal to check</p>
* @param player <p>The player creating the new portal</p>
* @return <p>True if a conflict was found. False otherwise</p>
*/
private static boolean conflictsWithExistingPortal(Gate gate, BlockLocation topLeft, double yaw, Player player) {
for (RelativeBlockVector borderVector : gate.getLayout().getBorder()) {
BlockLocation borderBlockLocation = topLeft.getRelativeLocation(borderVector, yaw);
if (PortalHandler.getByBlock(borderBlockLocation.getBlock()) != null) {
Stargate.debug("createPortal", "Gate conflicts with existing gate");
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createConflict"));
private static boolean conflictsWithExistingPortal(@NotNull Gate gate, @NotNull PortalLocation portalLocation,
@NotNull Player player) {
for (BlockVector borderVector : gate.getLayout().getBorder()) {
Block borderBlockLocation = portalLocation.getRelative(borderVector);
if (PortalUtil.getByBlock(borderBlockLocation) != null) {
Stargate.debug("PortalCreator::conflictsWithExistingPortal",
"Gate conflicts with existing gate");
new SGFormatBuilder(Message.CREATION_CONFLICT).error(player);
return true;
}
}

View File

@@ -1,15 +1,23 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.material.BukkitTagSpecifier;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.event.StargateCloseEvent;
import net.knarcraft.stargate.event.StargateOpenEvent;
import net.knarcraft.stargate.portal.property.PortalOptions;
import net.knarcraft.stargate.utility.ListHelper;
import net.knarcraft.stargate.utility.MaterialHelper;
import org.bukkit.Axis;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.data.Orientable;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* The portal opener is responsible for opening and closing a portal
@@ -28,7 +36,7 @@ public class PortalOpener {
* @param portal <p>The portal this portal opener should open</p>
* @param destination <p>The fixed destination defined on the portal's sign</p>
*/
public PortalOpener(Portal portal, String destination) {
public PortalOpener(@NotNull Portal portal, @NotNull String destination) {
this.portal = portal;
this.portalActivator = new PortalActivator(portal, this, destination);
}
@@ -56,6 +64,7 @@ public class PortalOpener {
*
* @return <p>The portal activator belonging to this portal opener</p>
*/
@NotNull
public PortalActivator getPortalActivator() {
return this.portalActivator;
}
@@ -75,7 +84,7 @@ public class PortalOpener {
* @param openFor <p>The player to open the portal for</p>
* @param force <p>Whether to force the portal open, even if it's already open for some player</p>
*/
public void openPortal(Player openFor, boolean force) {
public void openPortal(@Nullable Player openFor, boolean force) {
//Call the StargateOpenEvent to allow the opening to be cancelled
StargateOpenEvent event = new StargateOpenEvent(openFor, portal, force);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
@@ -84,13 +93,16 @@ public class PortalOpener {
}
//Get the material to change the opening to
Material openType = portal.getGate().getPortalOpenBlock();
@NotNull List<Material> possibleMaterials = MaterialHelper.specifiersToMaterials(
portal.getGate().getPortalOpenMaterials()).stream().toList();
Material openType = ListHelper.getRandom(possibleMaterials);
//Adjust orientation if applicable
Axis axis = (openType.createBlockData() instanceof Orientable) ? portal.getLocation().getRotationAxis() : null;
Axis axis = (openType.createBlockData() instanceof Orientable) ? portal.getLocation().getVectorOperation().getNormalAxis() : null;
//Change the entrance blocks to the correct type
for (BlockLocation inside : portal.getStructure().getEntrances()) {
Stargate.addBlockChangeRequest(new BlockChangeRequest(inside, openType, axis));
for (Block inside : portal.getStructure().getEntrances()) {
Stargate.addControlBlockUpdateRequest(new BlockChangeRequest(inside, openType, axis));
}
//Update the portal state to make is actually open
@@ -102,7 +114,7 @@ public class PortalOpener {
*
* @param openFor <p>The player to open this portal opener's portal for</p>
*/
private void updatePortalOpenState(Player openFor) {
private void updatePortalOpenState(@Nullable Player openFor) {
//Update the open state of this portal
isOpen = true;
triggeredTime = System.currentTimeMillis() / 1000;
@@ -134,8 +146,8 @@ public class PortalOpener {
//Set the destination portal to this opener's portal
destination.getPortalActivator().setDestination(portal);
//Update the destination's sign if it's verified
if (destination.getStructure().isVerified()) {
//Update the destination's sign if it exists
if (new BukkitTagSpecifier(Tag.WALL_SIGNS).asMaterials().contains(destination.getLocation().getSignBlock().getType())) {
destination.drawSign();
}
}
@@ -165,9 +177,15 @@ public class PortalOpener {
}
//Close the portal by requesting the opening blocks to change
Material closedType = portal.getGate().getPortalClosedBlock();
for (BlockLocation entrance : portal.getStructure().getEntrances()) {
Stargate.addBlockChangeRequest(new BlockChangeRequest(entrance, closedType, null));
@NotNull List<Material> possibleMaterials = MaterialHelper.specifiersToMaterials(
portal.getGate().getPortalClosedMaterials()).stream().toList();
Material closedType = ListHelper.getRandom(possibleMaterials);
//Adjust orientation if applicable
Axis axis = (closedType.createBlockData() instanceof Orientable) ? portal.getLocation().getVectorOperation().getNormalAxis() : null;
for (Block entrance : portal.getStructure().getEntrances()) {
Stargate.addControlBlockUpdateRequest(new BlockChangeRequest(entrance, closedType, axis));
}
//Update the portal state to make it actually closed
@@ -207,7 +225,7 @@ public class PortalOpener {
* @param player <p>The player to check portal state for</p>
* @return <p>True if this portal opener's portal is open to the given player</p>
*/
public boolean isOpenFor(Player player) {
public boolean isOpenFor(@Nullable Player player) {
//If closed, it's closed for everyone
if (!isOpen) {
return false;

View File

@@ -1,9 +1,15 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.config.addons.DynmapManager;
import net.knarcraft.stargate.config.material.BukkitTagSpecifier;
import net.knarcraft.stargate.utility.PortalFileHelper;
import net.knarcraft.stargate.utility.PortalUtil;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
@@ -15,9 +21,9 @@ import java.util.Map;
*/
public class PortalRegistry {
private static final Map<BlockLocation, Portal> lookupBlocks = new HashMap<>();
private static final Map<BlockLocation, Portal> lookupEntrances = new HashMap<>();
private static final Map<BlockLocation, Portal> lookupControls = new HashMap<>();
private static final Map<Block, Portal> lookupBlocks = new HashMap<>();
private static final Map<Block, Portal> lookupEntrances = new HashMap<>();
private static final Map<Block, Portal> lookupControls = new HashMap<>();
private static final Map<String, Map<String, Portal>> portalLookupByNetwork = new HashMap<>();
private static final Map<String, List<String>> allPortalNetworks = new HashMap<>();
@@ -43,11 +49,11 @@ public class PortalRegistry {
*
* @param world <p>The world containing the portals to clear</p>
*/
public static void clearPortals(World world) {
public static void clearPortals(@NotNull World world) {
//Storing the portals to clear is necessary to avoid a concurrent modification exception
List<Portal> portalsToRemove = new ArrayList<>();
allPortals.forEach((portal) -> {
if (portal.getWorld().equals(world)) {
if (portal.getLocation().getWorld().equals(world)) {
portalsToRemove.add(portal);
}
});
@@ -60,7 +66,7 @@ public class PortalRegistry {
*
* @param portalsToRemove <p>A list of portals to remove</p>
*/
private static void clearPortals(List<Portal> portalsToRemove) {
private static void clearPortals(@NotNull List<Portal> portalsToRemove) {
//Store the names of the portals to remove as some maps require the name, not the object
List<String> portalNames = new ArrayList<>();
portalsToRemove.forEach((portal) -> portalNames.add(portal.getCleanName()));
@@ -86,44 +92,58 @@ public class PortalRegistry {
*
* @return <p>A copy of the list of all portals</p>
*/
@NotNull
public static List<Portal> getAllPortals() {
return new ArrayList<>(allPortals);
}
/**
* Gets a copy of the lookup map for finding a portal by its frame
* Gets a portal that the given frame block belongs to
*
* @return <p>A copy of the frame block lookup map</p>
* @param blockLocation <p>The location that might be a frame block</p>
* @return <p>The portal the frame block belongs to, or null</p>
*/
public static Map<BlockLocation, Portal> getLookupBlocks() {
return new HashMap<>(lookupBlocks);
@Nullable
public static Portal getPortalFromFrame(@NotNull Block blockLocation) {
return lookupBlocks.get(blockLocation);
}
/**
* Gets a copy of the lookup map for finding a portal by its control block
* Gets the portal that the given control block belongs to
*
* @return <p>A copy of the control block lookup map</p>
* @param blockLocation <p>The location that might be a portal control block</p>
* @return <p>The portal the control block belongs to, or null</p>
*/
public static Map<BlockLocation, Portal> getLookupControls() {
return new HashMap<>(lookupControls);
@Nullable
public static Portal getPortalFromControl(@NotNull Block blockLocation) {
return lookupControls.get(blockLocation);
}
/**
* Gets a copy of the lookup map for finding all portals in a network
* Gets the portal identified by the given network name and portal name
*
* @return <p>A copy of the network portal lookup map</p>
* @param networkName <p>The name of the network the portal belongs to</p>
* @param portalName <p>The name of the portal</p>
* @return <p>The portal, or null if no such network and/or portal exists</p>
*/
public static Map<String, Map<String, Portal>> getPortalLookupByNetwork() {
return new HashMap<>(portalLookupByNetwork);
@Nullable
public static Portal getPortalInNetwork(@NotNull String networkName, @NotNull String portalName) {
Map<String, Portal> portalsInNetwork = portalLookupByNetwork.get(Portal.cleanString(networkName));
if (portalsInNetwork == null) {
return null;
}
return portalsInNetwork.get(Portal.cleanString(portalName));
}
/**
* Gets a copy of all portal entrances available for lookup
* Gets a portal from the location of a possible entrance
*
* @return <p>A copy of all entrances to portal mappings</p>
* @param blockLocation <p>A location that might be a portal's entrance</p>
* @return <p>A portal, or null</p>
*/
public static Map<BlockLocation, Portal> getLookupEntrances() {
return new HashMap<>(lookupEntrances);
@Nullable
public static Portal getPortalFromEntrance(@NotNull Block blockLocation) {
return lookupEntrances.get(blockLocation);
}
/**
@@ -131,17 +151,20 @@ public class PortalRegistry {
*
* @return <p>A copy of all portal networks</p>
*/
@NotNull
public static Map<String, List<String>> getAllPortalNetworks() {
return new HashMap<>(allPortalNetworks);
}
/**
* Gets a copy of all bungee portals
* Gets the BungeeCord portal with the given name
*
* @return <p>A copy of all bungee portals</p>
* @param portalName <p>The name of the portal to get</p>
* @return <p>The portal, or null</p>
*/
public static Map<String, Portal> getBungeePortals() {
return new HashMap<>(bungeePortals);
@Nullable
public static Portal getBungeePortal(@NotNull String portalName) {
return bungeePortals.get(Portal.cleanString(portalName));
}
/**
@@ -150,7 +173,8 @@ public class PortalRegistry {
* @param network <p>The network to get portals from</p>
* @return <p>A list of portal names</p>
*/
public static List<String> getNetwork(String network) {
@Nullable
public static List<String> getNetwork(@NotNull String network) {
return allPortalNetworks.get(network.toLowerCase());
}
@@ -160,7 +184,7 @@ public class PortalRegistry {
* @param portal <p>The portal to un-register</p>
* @param removeAll <p>Whether to remove the portal from the list of all portals</p>
*/
public static void unregisterPortal(Portal portal, boolean removeAll) {
public static void unregisterPortal(@NotNull Portal portal, boolean removeAll) {
Stargate.debug("Unregister", "Unregistering gate " + portal.getName());
portal.getPortalActivator().deactivate();
portal.getPortalOpener().closePortal(true);
@@ -168,30 +192,7 @@ public class PortalRegistry {
String portalName = portal.getCleanName();
String networkName = portal.getCleanNetwork();
//Remove portal from lookup blocks
for (BlockLocation block : portal.getStructure().getFrame()) {
lookupBlocks.remove(block);
}
//Remove registered info about the lookup controls and blocks
lookupBlocks.remove(portal.getSignLocation());
lookupControls.remove(portal.getSignLocation());
BlockLocation button = portal.getStructure().getButton();
if (button != null) {
lookupBlocks.remove(button);
lookupControls.remove(button);
}
//Remove entrances
for (BlockLocation entrance : portal.getStructure().getEntrances()) {
lookupEntrances.remove(entrance);
}
//Remove the portal from the list of all portals
if (removeAll) {
allPortals.remove(portal);
}
clearLookupMaps(portal, removeAll);
if (portal.getOptions().isBungee()) {
//Remove the bungee listing
@@ -203,9 +204,9 @@ public class PortalRegistry {
//Update all portals in the same network with this portal as its destination
for (String originName : allPortalNetworks.get(networkName)) {
Portal origin = PortalHandler.getByName(originName, portal.getCleanNetwork());
Portal origin = PortalUtil.getByName(originName, portal.getCleanNetwork());
if (origin == null || !origin.getDestinationName().equalsIgnoreCase(portalName) ||
!origin.getStructure().isVerified()) {
!new BukkitTagSpecifier(Tag.WALL_SIGNS).asMaterials().contains(origin.getLocation().getSignBlock().getType())) {
continue;
}
//Update the portal's sign
@@ -222,8 +223,42 @@ public class PortalRegistry {
//Mark the portal's sign as unregistered
new PortalSignDrawer(portal).drawUnregisteredSign();
PortalFileHelper.saveAllPortals(portal.getWorld());
PortalFileHelper.saveAllPortals(portal.getLocation().getWorld());
portal.setRegistered(false);
DynmapManager.removePortalMarker(portal);
}
/**
* Clears the given portal's presence from lookup maps
*
* @param portal <p>The portal to clear</p>
* @param removeAll <p>Whether to remove the portal from the list of all portals</p>
*/
private static void clearLookupMaps(@NotNull Portal portal, boolean removeAll) {
//Remove portal from lookup blocks
for (Block block : portal.getStructure().getFrame()) {
lookupBlocks.remove(block);
}
//Remove registered info about the lookup controls and blocks
lookupBlocks.remove(portal.getLocation().getSignBlock());
lookupControls.remove(portal.getLocation().getSignBlock());
Block button = portal.getStructure().getButton();
if (button != null) {
lookupBlocks.remove(button);
lookupControls.remove(button);
}
//Remove entrances
for (Block entrance : portal.getStructure().getEntrances()) {
lookupEntrances.remove(entrance);
}
//Remove the portal from the list of all portals
if (removeAll) {
allPortals.remove(portal);
}
}
/**
@@ -231,8 +266,8 @@ public class PortalRegistry {
*
* @param portal <p>The portal to register</p>
*/
static void registerPortal(Portal portal) {
portal.getOptions().setFixed(portal.getDestinationName().length() > 0 || portal.getOptions().isRandom() ||
public static void registerPortal(@NotNull Portal portal) {
portal.getOptions().setFixed(!portal.getDestinationName().isEmpty() || portal.getOptions().isRandom() ||
portal.getOptions().isBungee());
String portalName = portal.getCleanName();
@@ -267,28 +302,29 @@ public class PortalRegistry {
}
//Register all frame blocks to the lookup list
for (BlockLocation block : portal.getStructure().getFrame()) {
for (Block block : portal.getStructure().getFrame()) {
lookupBlocks.put(block, portal);
}
//Register the sign and button to the lookup lists
if (!portal.getOptions().hasNoSign()) {
lookupBlocks.put(portal.getSignLocation(), portal);
lookupControls.put(portal.getSignLocation(), portal);
lookupBlocks.put(portal.getLocation().getSignBlock(), portal);
lookupControls.put(portal.getLocation().getSignBlock(), portal);
}
BlockLocation button = portal.getStructure().getButton();
Block button = portal.getStructure().getButton();
if (button != null) {
lookupBlocks.put(button, portal);
lookupControls.put(button, portal);
}
//Register entrances to the lookup list
for (BlockLocation entrance : portal.getStructure().getEntrances()) {
for (Block entrance : portal.getStructure().getEntrances()) {
lookupEntrances.put(entrance, portal);
}
allPortals.add(portal);
portal.setRegistered(true);
DynmapManager.addPortalMarker(portal);
}
}

View File

@@ -1,12 +1,25 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.container.SignData;
import net.knarcraft.stargate.portal.property.PortalLocation;
import net.knarcraft.stargate.utility.PermissionHelper;
import org.bukkit.ChatColor;
import net.knarcraft.stargate.utility.PortalUtil;
import net.knarcraft.stargate.utility.SignHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Objects;
/**
* The portal sign drawer draws the sing of a given portal
@@ -18,13 +31,15 @@ public class PortalSignDrawer {
private static ChatColor freeColor;
private static ChatColor mainColor;
private static ChatColor highlightColor;
private static Map<Material, ChatColor> perSignMainColors;
private static Map<Material, ChatColor> perSignHighlightColors;
/**
* Instantiates a new portal sign drawer
*
* @param portal <p>The portal whose sign this portal sign drawer is responsible for drawing</p>
*/
public PortalSignDrawer(Portal portal) {
public PortalSignDrawer(@NotNull Portal portal) {
this.portal = portal;
}
@@ -35,7 +50,7 @@ public class PortalSignDrawer {
*
* @param newHighlightColor <p>The new highlight color</p>
*/
public static void setHighlightColor(ChatColor newHighlightColor) {
public static void setHighlightColor(@NotNull ChatColor newHighlightColor) {
highlightColor = newHighlightColor;
}
@@ -46,7 +61,7 @@ public class PortalSignDrawer {
*
* @param newMainColor <p>The new main sign color</p>
*/
public static void setMainColor(ChatColor newMainColor) {
public static void setMainColor(@NotNull ChatColor newMainColor) {
mainColor = newMainColor;
}
@@ -55,10 +70,48 @@ public class PortalSignDrawer {
*
* @param freeColor <p>The new color to use for marking free stargates</p>
*/
public static void setFreeColor(ChatColor freeColor) {
public static void setFreeColor(@NotNull ChatColor freeColor) {
PortalSignDrawer.freeColor = freeColor;
}
/**
* Sets the per-sign main colors
*
* @param signMainColors <p>The per-sign main colors</p>
*/
public static void setPerSignMainColors(@NotNull Map<Material, ChatColor> signMainColors) {
PortalSignDrawer.perSignMainColors = signMainColors;
}
/**
* Sets the per-sign highlight colors
*
* @param signHighlightColors <p>The per-sign highlight colors</p>
*/
public static void setPerSignHighlightColors(@NotNull Map<Material, ChatColor> signHighlightColors) {
PortalSignDrawer.perSignHighlightColors = signHighlightColors;
}
/**
* Gets the currently used main sign color
*
* @return <p>The currently used main sign color</p>
*/
@NotNull
public static ChatColor getMainColor() {
return mainColor;
}
/**
* Gets the currently used highlighting sign color
*
* @return <p>The currently used highlighting sign color</p>
*/
@NotNull
public static ChatColor getHighlightColor() {
return highlightColor;
}
/**
* Draws the sign of the portal this sign drawer is responsible for
*/
@@ -68,7 +121,7 @@ public class PortalSignDrawer {
return;
}
drawSign(sign);
drawSign(new SignData(sign, getMainColor(sign.getType()), getHighlightColor(sign.getType())));
}
/**
@@ -76,8 +129,9 @@ public class PortalSignDrawer {
*
* @return <p>The sign of this sign drawer's portal</p>
*/
@Nullable
private Sign getSign() {
Block signBlock = portal.getSignLocation().getBlock();
Block signBlock = portal.getLocation().getSignBlock();
BlockState state = signBlock.getState();
if (!(state instanceof Sign sign)) {
if (!portal.getOptions().hasNoSign()) {
@@ -93,40 +147,65 @@ public class PortalSignDrawer {
/**
* Draws the sign of the portal this sign drawer is responsible for
*
* @param sign <p>The sign re-draw</p>
* @param signData <p>All necessary sign information</p>
*/
private void drawSign(Sign sign) {
//Clear sign
clearSign(sign);
setLine(sign, 0, highlightColor + "-" + mainColor + fixColor(portal.getName()) + highlightColor + "-");
private void drawSign(@NotNull SignData signData) {
Sign sign = signData.getSign();
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
// Initialize all lines as empty to prevent null
String[] lines = new String[4];
lines[0] = "";
lines[1] = "";
lines[2] = "";
lines[3] = "";
setLine(signData, 0, highlightColor + "-" + mainColor + translateAllColorCodes(portal.getName()) +
highlightColor + "-", lines);
if (!portal.getPortalActivator().isActive()) {
//Default sign text
drawInactiveSign(sign);
drawInactiveSign(signData, lines);
} else {
if (portal.getOptions().isBungee()) {
//Bungee sign
drawBungeeSign(sign);
drawBungeeSign(signData, lines);
} else if (portal.getOptions().isFixed()) {
//Sign pointing at one other portal
drawFixedSign(sign);
drawFixedSign(signData, lines);
} else {
//Networking stuff
drawNetworkSign(sign);
drawNetworkSign(signData, lines);
}
}
sign.update();
updateSign(sign, lines);
}
/**
* Clears all lines of a sign, but does not update the sign
* Updates a sign, if necessary
*
* @param sign <p>The sign to clear</p>
* @param sign <p>The sign to update</p>
* @param lines <p>The sign's new lines</p>
*/
private void clearSign(Sign sign) {
for (int index = 0; index <= 3; index++) {
sign.setLine(index, "");
private void updateSign(@NotNull Sign sign, @NotNull String[] lines) {
boolean updateNecessary = false;
String[] oldLines = SignHelper.getLines(sign);
for (int i = 0; i < 4; i++) {
if (!oldLines[i].equals(lines[i])) {
updateNecessary = true;
break;
}
}
if (updateNecessary) {
for (int i = 0; i < 4; i++) {
SignHelper.setSignLine(sign, i, lines[i]);
}
sign.update();
}
}
@@ -138,17 +217,22 @@ public class PortalSignDrawer {
if (sign == null) {
return;
}
clearSign(sign);
sign.setLine(0, fixColor(portal.getName()));
for (int index = 0; index <= 3; index++) {
SignHelper.setSignLine(sign, index, "");
}
SignHelper.setSignLine(sign, 0, translateAllColorCodes(portal.getName()));
sign.update();
}
/**
* Draws a sign with choose-able network locations
*
* @param sign <p>The sign to re-draw</p>
* @param signData <p>All necessary sign information</p>
* @param output <p>The output list to write to</p>
*/
private void drawNetworkSign(Sign sign) {
private void drawNetworkSign(@NotNull SignData signData, @NotNull String[] output) {
PortalActivator destinations = portal.getPortalActivator();
int maxIndex = destinations.getDestinations().size() - 1;
int signLineIndex = 0;
@@ -158,86 +242,98 @@ public class PortalSignDrawer {
//Last, and not only entry. Draw the entry two back
if ((destinationIndex == maxIndex) && (maxIndex > 1)) {
drawNetworkSignLine(freeGatesColored, sign, ++signLineIndex, destinationIndex - 2);
drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex - 2, output);
}
//Not first entry. Draw the previous entry
if (destinationIndex > 0) {
drawNetworkSignLine(freeGatesColored, sign, ++signLineIndex, destinationIndex - 1);
drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex - 1, output);
}
//Draw the chosen entry (line 2 or 3)
drawNetworkSignChosenLine(freeGatesColored, sign, ++signLineIndex);
drawNetworkSignChosenLine(signData, freeGatesColored, ++signLineIndex, output);
//Has another entry and space on the sign
if ((maxIndex >= destinationIndex + 1)) {
drawNetworkSignLine(freeGatesColored, sign, ++signLineIndex, destinationIndex + 1);
drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex + 1, output);
}
//Has another entry and space on the sign
if ((maxIndex >= destinationIndex + 2) && (++signLineIndex <= 3)) {
drawNetworkSignLine(freeGatesColored, sign, signLineIndex, destinationIndex + 2);
drawNetworkSignLine(signData, freeGatesColored, signLineIndex, destinationIndex + 2, output);
}
}
/**
* Draws the chosen destination on one sign line
*
* @param signData <p>All necessary sign information</p>
* @param freeGatesColored <p>Whether to display free gates in a different color</p>
* @param sign <p>The sign to draw on</p>
* @param signLineIndex <p>The line to draw on</p>
* @param output <p>The output list to write to</p>
*/
private void drawNetworkSignChosenLine(boolean freeGatesColored, Sign sign, int signLineIndex) {
private void drawNetworkSignChosenLine(@NotNull SignData signData, boolean freeGatesColored, int signLineIndex,
@NotNull String[] output) {
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
if (freeGatesColored) {
Portal destination = PortalHandler.getByName(portal.getDestinationName(), portal.getNetwork());
boolean free = PermissionHelper.isFree(portal.getActivePlayer(), portal, destination);
Portal destination = PortalUtil.getByName(portal.getDestinationName(), portal.getNetwork());
boolean free = PermissionHelper.isFree(Objects.requireNonNull(portal.getActivePlayer()), portal, destination);
ChatColor nameColor = (free ? freeColor : highlightColor);
setLine(sign, signLineIndex, nameColor + ">" + (free ? freeColor : mainColor) +
fixColor(portal.getDestinationName()) + nameColor + "<");
setLine(signData, signLineIndex, nameColor + ">" + (free ? freeColor : mainColor) +
translateAllColorCodes(portal.getDestinationName()) + nameColor + "<", output);
} else {
setLine(sign, signLineIndex, highlightColor + ">" + mainColor +
fixColor(portal.getDestinationName()) + highlightColor + "<");
setLine(signData, signLineIndex, highlightColor + ">" + mainColor +
translateAllColorCodes(portal.getDestinationName()) + highlightColor + "<", output);
}
}
/**
* Sets a line on a sign, adding the chosen sign color
*
* @param sign <p>The sign to update</p>
* @param index <p>The index of the sign line to change</p>
* @param text <p>The new text on the sign</p>
* @param signData <p>All necessary sign information</p>
* @param index <p>The index of the sign line to change</p>
* @param text <p>The new text on the sign</p>
* @param output <p>The output list to write to</p>
*/
public void setLine(Sign sign, int index, String text) {
sign.setLine(index, mainColor + text);
public void setLine(@NotNull SignData signData, int index, @NotNull String text, @NotNull String[] output) {
ChatColor mainColor = signData.getMainSignColor();
output[index] = mainColor + text;
}
/**
* Draws one network destination on one sign line
*
* @param signData <p>All necessary sign information</p>
* @param freeGatesColored <p>Whether to display free gates in a different color</p>
* @param sign <p>The sign to draw on</p>
* @param signLineIndex <p>The line to draw on</p>
* @param destinationIndex <p>The index of the destination to draw</p>
* @param output <p>The output list to write to</p>
*/
private void drawNetworkSignLine(boolean freeGatesColored, Sign sign, int signLineIndex, int destinationIndex) {
private void drawNetworkSignLine(@NotNull SignData signData, boolean freeGatesColored, int signLineIndex,
int destinationIndex, @NotNull String[] output) {
ChatColor mainColor = signData.getMainSignColor();
PortalActivator destinations = portal.getPortalActivator();
String destinationName = destinations.getDestinations().get(destinationIndex);
if (freeGatesColored) {
Portal destination = PortalHandler.getByName(destinationName, portal.getNetwork());
boolean free = PermissionHelper.isFree(portal.getActivePlayer(), portal, destination);
setLine(sign, signLineIndex, (free ? freeColor : mainColor) + fixColor(destinationName));
Portal destination = PortalUtil.getByName(destinationName, portal.getNetwork());
boolean free = PermissionHelper.isFree(Objects.requireNonNull(portal.getActivePlayer()), portal, destination);
setLine(signData, signLineIndex, (free ? freeColor : mainColor) + translateAllColorCodes(destinationName), output);
} else {
setLine(sign, signLineIndex, mainColor + fixColor(destinationName));
setLine(signData, signLineIndex, mainColor + translateAllColorCodes(destinationName), output);
}
}
/**
* Draws the sign of a BungeeCord portal
*
* @param sign <p>The sign to re-draw</p>
* @param signData <p>All necessary sign information</p>
* @param output <p>The output list to write to</p>
*/
private void drawBungeeSign(Sign sign) {
setLine(sign, 1, Stargate.getString("bungeeSign"));
setLine(sign, 2, highlightColor + ">" + mainColor + fixColor(portal.getDestinationName()) +
highlightColor + "<");
setLine(sign, 3, highlightColor + "[" + mainColor + fixColor(portal.getNetwork()) +
highlightColor + "]");
private void drawBungeeSign(@NotNull SignData signData, @NotNull String[] output) {
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
setLine(signData, 1, new SGFormatBuilder(Message.BUNGEE_SIGN).toString(), output);
setLine(signData, 2, highlightColor + ">" + mainColor +
translateAllColorCodes(portal.getDestinationName()) + highlightColor + "<", output);
setLine(signData, 3, highlightColor + "[" + mainColor + translateAllColorCodes(portal.getNetwork()) +
highlightColor + "]", output);
}
/**
@@ -245,43 +341,48 @@ public class PortalSignDrawer {
*
* <p>The sign for an in-active portal should display the right-click prompt and the network.</p>
*
* @param sign <p>The sign to re-draw</p>
* @param signData <p>All necessary sign information</p>
* @param output <p>The output list to write to</p>
*/
private void drawInactiveSign(Sign sign) {
setLine(sign, 1, Stargate.getString("signRightClick"));
setLine(sign, 2, Stargate.getString("signToUse"));
private void drawInactiveSign(@NotNull SignData signData, @NotNull String[] output) {
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
setLine(signData, 1, new SGFormatBuilder(Message.SIGN_RIGHT_CLICK).toString(), output);
setLine(signData, 2, new SGFormatBuilder(Message.SIGN_TO_USE).toString(), output);
if (!portal.getOptions().isNoNetwork()) {
setLine(sign, 3, highlightColor + "(" + mainColor + fixColor(portal.getNetwork()) +
highlightColor + ")");
setLine(signData, 3, highlightColor + "(" + mainColor + translateAllColorCodes(portal.getNetwork()) +
highlightColor + ")", output);
} else {
setLine(sign, 3, "");
setLine(signData, 3, "", output);
}
}
/**
* Draws a sign pointing to a fixed location
*
* @param sign <p>The sign to re-draw</p>
* @param signData <p>All necessary sign information</p>
* @param output <p>The output list to write to</p>
*/
private void drawFixedSign(Sign sign) {
Portal destinationPortal = PortalHandler.getByName(Portal.cleanString(portal.getDestinationName()),
portal.getCleanNetwork());
String destinationName = portal.getOptions().isRandom() ? Stargate.getString("signRandom") :
private void drawFixedSign(@NotNull SignData signData, @NotNull String[] output) {
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
Portal destinationPortal = PortalUtil.getByName(portal.getDestinationName(), portal.getCleanNetwork());
String destinationName = portal.getOptions().isRandom() ? new SGFormatBuilder(Message.SIGN_RANDOM).toString() :
(destinationPortal != null ? destinationPortal.getName() : portal.getDestinationName());
setLine(sign, 1, highlightColor + ">" + mainColor + fixColor(destinationName) + highlightColor + "<");
setLine(signData, 1, highlightColor + ">" + mainColor + translateAllColorCodes(destinationName) +
highlightColor + "<", output);
if (portal.getOptions().isNoNetwork()) {
setLine(sign, 2, "");
setLine(signData, 2, "", output);
} else {
setLine(sign, 2, highlightColor + "(" + mainColor + fixColor(portal.getNetwork()) +
highlightColor + ")");
setLine(signData, 2, highlightColor + "(" + mainColor +
translateAllColorCodes(portal.getNetwork()) + highlightColor + ")", output);
}
Portal destination = PortalHandler.getByName(Portal.cleanString(portal.getDestinationName()),
portal.getNetwork());
Portal destination = PortalUtil.getByName(portal.getDestinationName(), portal.getNetwork());
if (destination == null && !portal.getOptions().isRandom()) {
setLine(sign, 3, errorColor + Stargate.getString("signDisconnected"));
setLine(signData, 3, errorColor + new SGFormatBuilder(Message.SIGN_DISCONNECTED).toString(), output);
} else {
setLine(sign, 3, "");
setLine(signData, 3, "", output);
}
}
@@ -292,25 +393,59 @@ public class PortalSignDrawer {
* @param gateName <p>The name of the invalid gate type</p>
* @param lineIndex <p>The index of the line the invalid portal was found at</p>
*/
public static void markPortalWithInvalidGate(PortalLocation portalLocation, String gateName, int lineIndex) {
BlockState blockState = portalLocation.getSignLocation().getBlock().getState();
public static void markPortalWithInvalidGate(@NotNull PortalLocation portalLocation, @NotNull String gateName,
int lineIndex) {
BlockState blockState = portalLocation.getSignBlock().getState();
if (!(blockState instanceof Sign sign)) {
return;
}
sign.setLine(3, errorColor + Stargate.getString("signInvalidGate"));
SignHelper.setSignLine(sign, 3, errorColor + new SGFormatBuilder(Message.SIGN_INVALID).toString());
sign.update();
Stargate.logInfo(String.format("Gate layout on line %d does not exist [%s]", lineIndex, gateName));
}
/**
* Fixes coloring of signs as the & character isn't translated on all servers
* Gets the main color to use for the given sign type
*
* @param text <p>The text to fix the coloring of</p>
* @return <p>The text with the coloring fixed</p>
* @param signType <p>The sign type to get the main color for</p>
* @return <p>The main color for the given sign type</p>
*/
private String fixColor(String text) {
return ChatColor.translateAlternateColorCodes('&', text);
@NotNull
private ChatColor getMainColor(@NotNull Material signType) {
ChatColor signColor = perSignMainColors.get(signType);
if (signColor == null) {
return mainColor;
} else {
return signColor;
}
}
/**
* Gets the highlight color to use for the given sign type
*
* @param signType <p>The sign type to get the highlight color for</p>
* @return <p>The highlight color for the given sign type</p>
*/
@NotNull
private ChatColor getHighlightColor(@NotNull Material signType) {
ChatColor signColor = perSignHighlightColors.get(signType);
if (signColor == null) {
return highlightColor;
} else {
return signColor;
}
}
/**
* Translates all normal and RGB color codes in the given input
*
* @param input <p>The input to translate color codes for</p>
* @return <p>The input with color codes converted translated from & to §</p>
*/
@NotNull
private String translateAllColorCodes(@NotNull String input) {
return ColorHelper.translateColorCodes(input, ColorConversion.RGB);
}
}

View File

@@ -1,10 +1,12 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import org.bukkit.Axis;
import net.knarcraft.stargate.transformation.SimpleVectorOperation;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.util.BlockVector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Keeps track of location related data for a portal
@@ -12,28 +14,61 @@ import org.bukkit.block.BlockFace;
@SuppressWarnings("UnusedReturnValue")
public class PortalLocation {
private BlockLocation topLeft;
private float yaw;
private BlockLocation signLocation;
private RelativeBlockVector buttonVector;
private BlockFace buttonFacing;
private @Nullable Block topLeft;
private @NotNull BlockFace facing;
private @NotNull Block signBlock;
private @Nullable Block buttonBlock;
private final @NotNull World world;
private final SimpleVectorOperation vectorOperation;
/**
* Instantiates a new portal location
*
* @param topLeft <p>The top-left block of the portal</p>
* @param facing <p>The direction the portal is facing</p>
* @param signBlock <p>The block the sign is attached to</p>
* @param buttonBlock <p>The block of the portal, or null if the portal doesn't need a button</p>
*/
public PortalLocation(@NotNull Block topLeft, @NotNull BlockFace facing, @NotNull Block signBlock,
@Nullable Block buttonBlock) {
this.world = topLeft.getWorld();
this.topLeft = topLeft;
this.facing = facing;
this.signBlock = signBlock;
this.buttonBlock = buttonBlock;
this.vectorOperation = new SimpleVectorOperation(facing);
}
/**
* Instantiates a new portal location
*
* @param signBlock <p>The block the sign is attached to</p>
* @param facing <p>The direction the portal is facing</p>
*/
public PortalLocation(@NotNull Block signBlock, @NotNull BlockFace facing) {
this.world = signBlock.getWorld();
this.signBlock = signBlock;
this.facing = facing;
this.vectorOperation = new SimpleVectorOperation(facing);
}
/**
* Gets the top-left block of the portal
*
* @return <p>The top-left block of the portal</p>
*/
public BlockLocation getTopLeft() {
@Nullable
public Block getTopLeft() {
return topLeft;
}
/**
* Gets the yaw for looking outwards from the portal
* Gets the block face the portal is facing towards
*
* @return <p>The portal's yaw</p>
* @return <p>The portal's facing direction</p>
*/
public float getYaw() {
return yaw;
public BlockFace getFacing() {
return facing;
}
/**
@@ -41,8 +76,9 @@ public class PortalLocation {
*
* @return <p>The location of the portal's sign</p>
*/
public BlockLocation getSignLocation() {
return signLocation;
@NotNull
public Block getSignBlock() {
return signBlock;
}
/**
@@ -50,27 +86,9 @@ public class PortalLocation {
*
* @return <p>The relative location of the portal's button</p>
*/
public RelativeBlockVector getButtonVector() {
return buttonVector;
}
/**
* Gets the block face determining the button's direction
*
* @return <p>The button's block face</p>
*/
public BlockFace getButtonFacing() {
return buttonFacing;
}
/**
* Gets the rotation axis, which is the axis along which the gate is placed
* <p>The portal's rotation axis is the cross axis of the button's axis</p>
*
* @return <p>The portal's rotation axis</p>
*/
public Axis getRotationAxis() {
return getYaw() == 0.0F || getYaw() == 180.0F ? Axis.X : Axis.Z;
@Nullable
public Block getButtonBlock() {
return buttonBlock;
}
/**
@@ -78,8 +96,19 @@ public class PortalLocation {
*
* @return <p>The world this portal resides in</p>
*/
@NotNull
public World getWorld() {
return topLeft.getWorld();
return this.world;
}
/**
* Gets the vector operation to use in order to convert this portal's relative vectors to the real space
*
* @return <p>The vector operation</p>
*/
@NotNull
public SimpleVectorOperation getVectorOperation() {
return this.vectorOperation;
}
/**
@@ -91,55 +120,63 @@ public class PortalLocation {
* @param topLeft <p>The new top-left block of the portal's square structure</p>
* @return <p>The portal location Object</p>
*/
public PortalLocation setTopLeft(BlockLocation topLeft) {
@NotNull
public PortalLocation setTopLeft(@NotNull Block topLeft) {
this.topLeft = topLeft;
return this;
}
/**
* Sets the portal's yaw
* Sets the portal's facing direction
*
* <p>The portal's yaw is the yaw a player would get when looking directly out from the portal</p>
* <p>The portal's facing direction is the direction a player would get when looking directly out from the portal</p>
*
* @param yaw <p>The portal's new yaw</p>
* @param facing <p>The portal's new facing direction</p>
* @return <p>The portal location Object</p>
*/
public PortalLocation setYaw(float yaw) {
this.yaw = yaw;
@NotNull
public PortalLocation setFacing(@NotNull BlockFace facing) {
this.facing = facing;
return this;
}
/**
* Sets the location of the portal's sign
*
* @param signLocation <p>The new sign location</p>
* @param signBlock <p>The new sign location</p>
* @return <p>The portal location Object</p>
*/
public PortalLocation setSignLocation(BlockLocation signLocation) {
this.signLocation = signLocation;
@NotNull
public PortalLocation setSignBlock(@NotNull Block signBlock) {
this.signBlock = signBlock;
return this;
}
/**
* Sets the relative location of the portal's button
*
* @param buttonVector <p>The new relative button location</p>
* @param buttonBlock <p>The new relative button location</p>
* @return <p>The portal location Object</p>
*/
public PortalLocation setButtonVector(RelativeBlockVector buttonVector) {
this.buttonVector = buttonVector;
@NotNull
public PortalLocation setButtonBlock(@Nullable Block buttonBlock) {
this.buttonBlock = buttonBlock;
return this;
}
/**
* Sets the block face for the direction the portal button is facing
* Gets a block relative to this portal's top-left block
*
* @param buttonFacing <p>The new block face of the portal's button</p>
* @return <p>The portal location Object</p>
* @param vector <p>The relative vector pointing at the block</p>
* @return <p>The block</p>
* @throws IllegalStateException <p>If the top-left location is not set</p>
*/
public PortalLocation setButtonFacing(BlockFace buttonFacing) {
this.buttonFacing = buttonFacing;
return this;
@NotNull
public Block getRelative(@NotNull BlockVector vector) throws IllegalStateException {
if (this.topLeft == null) {
throw new IllegalStateException("Top-left is not set");
}
return this.vectorOperation.getRealLocationBlock(this.topLeft, vector);
}
}

View File

@@ -1,5 +1,8 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.config.Permission;
import org.jetbrains.annotations.NotNull;
/**
* Each enum value represents one option a portal can have/use
*/
@@ -8,71 +11,71 @@ public enum PortalOption {
/**
* This option allows a portal to be hidden from others
*/
HIDDEN('h', "stargate.option.hidden", 11),
HIDDEN('h', Permission.OPTION_HIDDEN, 11),
/**
* This option allows a portal that's always on and does not need to be activated or opened each time
*/
ALWAYS_ON('a', "stargate.option.alwayson", 12),
ALWAYS_ON('a', Permission.OPTION_ALWAYS_ON, 12),
/**
* This option allows a portal that's private to the stargate's owner
*/
PRIVATE('p', "stargate.option.private", 13),
PRIVATE('p', Permission.OPTIONS_PRIVATE, 13),
/**
* This option allows a portal that's free even if stargates usually are not
*/
FREE('f', "stargate.option.free", 15),
FREE('f', Permission.OPTIONS_FREE, 15),
/**
* This option allows a portal where players exit through the back of the portal
*/
BACKWARDS('b', "stargate.option.backwards", 16),
BACKWARDS('b', Permission.OPTIONS_BACKWARDS, 16),
/**
* This option shows the gate in the network list even if it's always on
*/
SHOW('s', "stargate.option.show", 17),
SHOW('s', Permission.OPTIONS_SHOW, 17),
/**
* This option hides the network name on the sign
*/
NO_NETWORK('n', "stargate.option.nonetwork", 18),
NO_NETWORK('n', Permission.OPTIONS_NO_NETWORK, 18),
/**
* This option allows a portal where players teleport to a random exit portal in the network
*/
RANDOM('r', "stargate.option.random", 19),
RANDOM('r', Permission.OPTIONS_RANDOM, 19),
/**
* This option allows a portal to teleport to another server connected through BungeeCord
*/
BUNGEE('u', "stargate.admin.bungee", 20),
BUNGEE('u', Permission.OPTIONS_BUNGEE, 20),
/**
* This option allows a portal which does not display a teleportation message, for better immersion
*/
SILENT('i', "stargate.option.silent", 21),
QUIET('q', Permission.OPTIONS_QUIET, 21),
/**
* This option causes a fixed portal's sign to be removed after creation
*/
NO_SIGN('e', "stargate.option.nosign", 22);
INVISIBLE('v', Permission.OPTIONS_INVISIBLE, 22);
private final char characterRepresentation;
private final String permissionString;
private final Permission permission;
private final int saveIndex;
/**
* Instantiates a new portal options
*
* @param characterRepresentation <p>The character representation used on the sign to allow this option</p>
* @param permissionString <p>The permission necessary to use this option</p>
* @param permission <p>The permission necessary to use this option</p>
*/
PortalOption(final char characterRepresentation, String permissionString, int saveIndex) {
PortalOption(final char characterRepresentation, @NotNull Permission permission, int saveIndex) {
this.characterRepresentation = characterRepresentation;
this.permissionString = permissionString;
this.permission = permission;
this.saveIndex = saveIndex;
}
@@ -90,8 +93,9 @@ public enum PortalOption {
*
* @return <p>The permission necessary for this option</p>
*/
public String getPermissionString() {
return this.permissionString;
@NotNull
public Permission getPermission() {
return this.permission;
}
/**

View File

@@ -1,6 +1,7 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.Stargate;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
@@ -18,7 +19,7 @@ public class PortalOptions {
* @param options <p>All options to keep track of</p>
* @param hasDestination <p>Whether the portal has a fixed destination</p>
*/
public PortalOptions(Map<PortalOption, Boolean> options, boolean hasDestination) {
public PortalOptions(@NotNull Map<PortalOption, Boolean> options, boolean hasDestination) {
this.options = options;
isFixed = hasDestination || this.isRandom() || this.isBungee();
@@ -34,7 +35,7 @@ public class PortalOptions {
}
if (this.hasNoSign() && !this.isFixed) {
this.options.put(PortalOption.NO_SIGN, false);
this.options.put(PortalOption.INVISIBLE, false);
Stargate.debug("PortalOptions", "Gate marked with no sign, but not fixed. Setting NoSign = false");
}
}
@@ -171,15 +172,15 @@ public class PortalOptions {
}
/**
* Gets whether this portal is silent
* Gets whether this portal is QUIET
*
* <p>A silent portal does not output anything to the chat when teleporting. This option is mainly useful to keep
* <p>A quiet portal does not output anything to the chat when teleporting. This option is mainly useful to keep
* the immersion during teleportation (for role-playing servers or similar).</p>
*
* @return <p>Whether this portal is silent</p>
* @return <p>Whether this portal is quiet</p>
*/
public boolean isSilent() {
return this.options.get(PortalOption.SILENT);
public boolean isQuiet() {
return this.options.get(PortalOption.QUIET);
}
/**
@@ -190,7 +191,17 @@ public class PortalOptions {
* @return <p>Whether this portal has no sign</p>
*/
public boolean hasNoSign() {
return this.options.get(PortalOption.NO_SIGN);
return this.options.get(PortalOption.INVISIBLE);
}
/**
* Checks whether the given portal option is true
*
* @param option <p>The option to check</p>
* @return <p>True if enabled for the portal</p>
*/
public boolean checkOption(@NotNull PortalOption option) {
return this.options.get(option);
}
}

View File

@@ -3,7 +3,8 @@ package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.Stargate;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
@@ -20,7 +21,7 @@ public class PortalOwner {
*
* @param ownerIdentifier <p>A UUID, or a username for legacy support</p>
*/
public PortalOwner(String ownerIdentifier) {
public PortalOwner(@NotNull String ownerIdentifier) {
parseIdentifier(ownerIdentifier);
}
@@ -29,7 +30,7 @@ public class PortalOwner {
*
* @param player <p>The player which is the owner of the portal</p>
*/
public PortalOwner(Player player) {
public PortalOwner(@NotNull OfflinePlayer player) {
this.ownerUUID = player.getUniqueId();
this.ownerName = player.getName();
}
@@ -39,6 +40,7 @@ public class PortalOwner {
*
* @return <p>The UUID of this owner, or null if a UUID is not available</p>
*/
@Nullable
public UUID getUUID() {
return ownerUUID;
}
@@ -51,7 +53,7 @@ public class PortalOwner {
*
* @param uniqueId <p>The new unique id for the portal owner</p>
*/
public void setUUID(UUID uniqueId) {
public void setUUID(@NotNull UUID uniqueId) {
if (ownerUUID == null) {
ownerUUID = uniqueId;
} else {
@@ -64,7 +66,14 @@ public class PortalOwner {
*
* @return <p>The name of this owner</p>
*/
@NotNull
public String getName() {
if (this.ownerUUID != null && this.ownerName == null) {
this.ownerName = fetchName();
if (this.ownerName == null) {
this.ownerName = "UNKNOWN!";
}
}
return ownerName;
}
@@ -76,6 +85,7 @@ public class PortalOwner {
*
* @return <p>The owner's identifier</p>
*/
@NotNull
public String getIdentifier() {
if (ownerUUID != null) {
return ownerUUID.toString();
@@ -93,16 +103,14 @@ public class PortalOwner {
*
* @param ownerIdentifier <p>The identifier for a portal's owner</p>
*/
private void parseIdentifier(String ownerIdentifier) {
private void parseIdentifier(@NotNull String ownerIdentifier) {
UUID ownerUUID = null;
String ownerName;
String ownerName = null;
if (ownerIdentifier.length() > 16) {
//If more than 16 characters, the string cannot be a username, so it's probably a UUID
try {
ownerUUID = UUID.fromString(ownerIdentifier);
OfflinePlayer offlineOwner = Bukkit.getServer().getOfflinePlayer(ownerUUID);
ownerName = offlineOwner.getName();
} catch (IllegalArgumentException ex) {
} catch (IllegalArgumentException exception) {
//Invalid as UUID and username, so just keep it as owner name and hope the server owner fixes it
ownerName = ownerIdentifier;
Stargate.debug("loadAllPortals", "Invalid stargate owner string: " + ownerIdentifier);
@@ -115,4 +123,14 @@ public class PortalOwner {
this.ownerUUID = ownerUUID;
}
/**
* Gets the name of a player
*
* @return <p>The player to get the name of</p>
*/
@Nullable
private String fetchName() {
return Bukkit.getServer().getOfflinePlayer(ownerUUID).getName();
}
}

View File

@@ -0,0 +1,13 @@
package net.knarcraft.stargate.portal.property;
import org.jetbrains.annotations.NotNull;
/**
* A record of a portal's string values
*
* @param name <p>The name of the portal</p>
* @param network <p>The name of the network the portal belongs to</p>
* @param destination <p>The name of the portal's destination</p>
*/
public record PortalStrings(@NotNull String name, @NotNull String network, @NotNull String destination) {
}

View File

@@ -1,10 +1,12 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.property.gate.Gate;
import org.bukkit.block.Block;
import org.bukkit.util.BlockVector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The portal structure is responsible for the physical properties of a portal
@@ -16,10 +18,9 @@ public class PortalStructure {
private final Portal portal;
private final Gate gate;
private BlockLocation button;
private BlockLocation[] frame;
private BlockLocation[] entrances;
private boolean verified;
private Block button;
private Block[] frame;
private Block[] entrances;
/**
* Instantiates a new portal structure
@@ -28,10 +29,9 @@ public class PortalStructure {
* @param gate <p>The gate type used by this portal structure</p>
* @param button <p>The real location of the portal's button</p>
*/
public PortalStructure(Portal portal, Gate gate, BlockLocation button) {
public PortalStructure(@NotNull Portal portal, @NotNull Gate gate, @Nullable Block button) {
this.portal = portal;
this.gate = gate;
this.verified = false;
this.button = button;
}
@@ -40,6 +40,7 @@ public class PortalStructure {
*
* @return <p>The gate used by this portal structure</p>
*/
@NotNull
public Gate getGate() {
return gate;
}
@@ -49,7 +50,8 @@ public class PortalStructure {
*
* @return <p>The location of this portal's button</p>
*/
public BlockLocation getButton() {
@Nullable
public Block getButton() {
return button;
}
@@ -58,39 +60,10 @@ public class PortalStructure {
*
* @param button <p>The location of this portal's button</p>
*/
public void setButton(BlockLocation button) {
public void setButton(@NotNull Block button) {
this.button = button;
}
/**
* Verifies that all control blocks in this portal follows its gate template
*
* @return <p>True if all control blocks were verified</p>
*/
public boolean isVerified() {
boolean verified = true;
if (!Stargate.getGateConfig().verifyPortals()) {
return true;
}
for (RelativeBlockVector control : gate.getLayout().getControls()) {
verified = verified && portal.getBlockAt(control).getBlock().getType().equals(gate.getControlBlock());
}
this.verified = verified;
return verified;
}
/**
* Gets the result of the last portal verification
*
* @return <p>True if this portal was verified</p>
*/
public boolean wasVerified() {
if (!Stargate.getGateConfig().verifyPortals()) {
return true;
}
return verified;
}
/**
* Checks if all blocks in a gate matches the gate template
*
@@ -98,7 +71,7 @@ public class PortalStructure {
*/
public boolean checkIntegrity() {
if (Stargate.getGateConfig().verifyPortals()) {
return gate.matches(portal.getTopLeft(), portal.getYaw());
return gate.matches(portal.getLocation().getTopLeft(), portal.getLocation().getFacing());
} else {
return true;
}
@@ -113,10 +86,11 @@ public class PortalStructure {
* @param vectors <p>The relative block vectors to convert</p>
* @return <p>A list of block locations</p>
*/
private BlockLocation[] relativeBlockVectorsToBlockLocations(RelativeBlockVector[] vectors) {
BlockLocation[] locations = new BlockLocation[vectors.length];
@NotNull
private Block[] relativeBlockVectorsToBlockLocations(@NotNull BlockVector[] vectors) {
Block[] locations = new Block[vectors.length];
for (int i = 0; i < vectors.length; i++) {
locations[i] = portal.getBlockAt(vectors[i]);
locations[i] = portal.getLocation().getRelative(vectors[i]);
}
return locations;
}
@@ -126,7 +100,8 @@ public class PortalStructure {
*
* @return <p>The locations of this portal's entrances</p>
*/
public BlockLocation[] getEntrances() {
@NotNull
public Block[] getEntrances() {
if (entrances == null) {
//Get the locations of the entrances once, and only if necessary as it's an expensive operation
entrances = relativeBlockVectorsToBlockLocations(gate.getLayout().getEntrances());
@@ -139,7 +114,8 @@ public class PortalStructure {
*
* @return <p>The locations of this portal's frame</p>
*/
public BlockLocation[] getFrame() {
@NotNull
public Block[] getFrame() {
if (frame == null) {
//Get the locations of the frame blocks once, and only if necessary as it's an expensive operation
frame = relativeBlockVectorsToBlockLocations(gate.getLayout().getBorder());

View File

@@ -1,14 +1,23 @@
package net.knarcraft.stargate.portal.property.gate;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.config.material.BukkitMaterialSpecifier;
import net.knarcraft.stargate.config.material.MaterialSpecifier;
import net.knarcraft.stargate.transformation.SimpleVectorOperation;
import net.knarcraft.stargate.utility.MaterialHelper;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.util.BlockVector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -20,11 +29,13 @@ public class Gate {
private final String filename;
private final GateLayout layout;
private final Map<Character, Material> characterMaterialMap;
private final Map<Character, List<MaterialSpecifier>> characterMaterialMap;
//Gate materials
private final Material portalOpenBlock;
private final Material portalClosedBlock;
private final Material portalButton;
private final List<MaterialSpecifier> portalOpenMaterials;
private final List<MaterialSpecifier> portalClosedMaterials;
private final List<MaterialSpecifier> portalButtonMaterials;
//Economy information
private final int useCost;
private final int createCost;
@@ -34,30 +45,29 @@ public class Gate {
/**
* Instantiates a new gate
*
* @param filename <p>The name of the gate file, including extension</p>
* @param layout <p>The gate layout defined in the gate file</p>
* @param characterMaterialMap <p>The material types the different layout characters represent</p>
* @param portalOpenBlock <p>The material to set the opening to when the portal is open</p>
* @param portalClosedBlock <p>The material to set the opening to when the portal is closed</p>
* @param portalButton <p>The material to use for the portal button</p>
* @param useCost <p>The cost of using a portal with this gate layout (-1 to disable)</p>
* @param createCost <p>The cost of creating a portal with this gate layout (-1 to disable)</p>
* @param destroyCost <p>The cost of destroying a portal with this gate layout (-1 to disable)</p>
* @param toOwner <p>Whether any payment should go to the owner of the gate, as opposed to just disappearing</p>
* @param filename <p>The name of the gate file, including extension</p>
* @param layout <p>The gate layout defined in the gate file</p>
* @param characterMaterialsMap <p>The material types the different layout characters represent</p>
* @param portalOpenMaterials <p>The material to set the opening to when the portal is open</p>
* @param portalClosedMaterials <p>The material to set the opening to when the portal is closed</p>
* @param portalButtonMaterials <p>The material to use for the portal button</p>
* @param gateCosts <p>The costs and other economy information for the gate</p>
*/
public Gate(String filename, GateLayout layout, Map<Character, Material> characterMaterialMap, Material portalOpenBlock,
Material portalClosedBlock, Material portalButton, int useCost, int createCost, int destroyCost,
boolean toOwner) {
public Gate(@NotNull String filename, @NotNull GateLayout layout,
@NotNull Map<Character, List<MaterialSpecifier>> characterMaterialsMap,
@NotNull List<MaterialSpecifier> portalOpenMaterials,
@NotNull List<MaterialSpecifier> portalClosedMaterials,
@NotNull List<MaterialSpecifier> portalButtonMaterials, @NotNull GateCosts gateCosts) {
this.filename = filename;
this.layout = layout;
this.characterMaterialMap = characterMaterialMap;
this.portalOpenBlock = portalOpenBlock;
this.portalClosedBlock = portalClosedBlock;
this.portalButton = portalButton;
this.useCost = useCost;
this.createCost = createCost;
this.destroyCost = destroyCost;
this.toOwner = toOwner;
this.characterMaterialMap = characterMaterialsMap;
this.portalOpenMaterials = portalOpenMaterials;
this.portalClosedMaterials = portalClosedMaterials;
this.portalButtonMaterials = portalButtonMaterials;
this.useCost = gateCosts.useCost();
this.createCost = gateCosts.createCost();
this.destroyCost = gateCosts.destroyCost();
this.toOwner = gateCosts.toOwner();
}
/**
@@ -65,6 +75,7 @@ public class Gate {
*
* @return <p>This gate's layout</p>
*/
@NotNull
public GateLayout getLayout() {
return layout;
}
@@ -74,7 +85,8 @@ public class Gate {
*
* @return <p>The character to material map</p>
*/
public Map<Character, Material> getCharacterMaterialMap() {
@NotNull
public Map<Character, List<MaterialSpecifier>> getCharacterMaterialMap() {
return new HashMap<>(characterMaterialMap);
}
@@ -83,7 +95,8 @@ public class Gate {
*
* @return <p>The material type used for control blocks</p>
*/
public Material getControlBlock() {
@NotNull
public List<MaterialSpecifier> getControlBlockMaterials() {
return characterMaterialMap.get(GateHandler.getControlBlockCharacter());
}
@@ -92,6 +105,7 @@ public class Gate {
*
* @return <p>The filename of this gate's file</p>
*/
@NotNull
public String getFilename() {
return filename;
}
@@ -101,8 +115,9 @@ public class Gate {
*
* @return <p>The block type to use for the opening when open</p>
*/
public Material getPortalOpenBlock() {
return portalOpenBlock;
@NotNull
public List<MaterialSpecifier> getPortalOpenMaterials() {
return portalOpenMaterials;
}
/**
@@ -110,8 +125,9 @@ public class Gate {
*
* @return <p>The block type to use for the opening when closed</p>
*/
public Material getPortalClosedBlock() {
return portalClosedBlock;
@NotNull
public List<MaterialSpecifier> getPortalClosedMaterials() {
return portalClosedMaterials;
}
/**
@@ -119,8 +135,9 @@ public class Gate {
*
* @return <p>The material to use for a portal's button if using this gate type</p>
*/
public Material getPortalButton() {
return portalButton;
@NotNull
public List<MaterialSpecifier> getPortalButtonMaterials() {
return portalButtonMaterials;
}
/**
@@ -137,6 +154,7 @@ public class Gate {
*
* @return <p>The cost of creating a portal with this gate</p>
*/
@NotNull
public Integer getCreateCost() {
return createCost < 0 ? Stargate.getEconomyConfig().getDefaultCreateCost() : createCost;
}
@@ -146,6 +164,7 @@ public class Gate {
*
* @return <p>The cost of destroying a portal with this gate</p>
*/
@NotNull
public Integer getDestroyCost() {
return destroyCost < 0 ? Stargate.getEconomyConfig().getDefaultDestroyCost() : destroyCost;
}
@@ -155,6 +174,7 @@ public class Gate {
*
* @return <p>Whether portal payments go to the owner</p>
*/
@NotNull
public Boolean getToOwner() {
return toOwner;
}
@@ -163,11 +183,11 @@ public class Gate {
* Checks whether a portal's gate matches this gate type
*
* @param topLeft <p>The top-left block of the portal's gate</p>
* @param yaw <p>The yaw when looking directly outwards</p>
* @param facing <p>The direction the portal is facing</p>
* @return <p>True if this gate matches the portal</p>
*/
public boolean matches(BlockLocation topLeft, double yaw) {
return matches(topLeft, yaw, false);
public boolean matches(@NotNull Block topLeft, @NotNull BlockFace facing) {
return matches(topLeft, facing, false);
}
/**
@@ -178,43 +198,44 @@ public class Gate {
* containing AIR or WATER will cause the gate to not match.</p>
*
* @param topLeft <p>The top-left block of the portal's gate</p>
* @param yaw <p>The yaw when looking directly outwards</p>
* @param facing <p>The direction the portal is facing</p>
* @param onCreate <p>Whether this is used in the context of creating a new gate</p>
* @return <p>True if this gate matches the portal</p>
*/
public boolean matches(BlockLocation topLeft, double yaw, boolean onCreate) {
return verifyGateEntrancesMatch(topLeft, yaw, onCreate) && verifyGateBorderMatches(topLeft, yaw);
public boolean matches(@NotNull Block topLeft, @NotNull BlockFace facing, boolean onCreate) {
return verifyGateEntrancesMatch(topLeft, facing, onCreate) && verifyGateBorderMatches(topLeft, facing);
}
/**
* Verifies that all border blocks of a portal matches this gate type
*
* @param topLeft <p>The top-left block of the portal</p>
* @param yaw <p>The yaw when looking directly outwards from the portal</p>
* @param topLeft <p>The top-left block of the portal's gate</p>
* @param facing <p>The direction the portal is facing</p>
* @return <p>True if all border blocks of the gate match the layout</p>
*/
private boolean verifyGateBorderMatches(BlockLocation topLeft, double yaw) {
Map<Character, Material> characterMaterialMap = new HashMap<>(this.characterMaterialMap);
for (RelativeBlockVector borderVector : layout.getBorder()) {
int rowIndex = borderVector.getRight();
int lineIndex = borderVector.getDown();
private boolean verifyGateBorderMatches(@NotNull Block topLeft, @NotNull BlockFace facing) {
for (BlockVector borderVector : layout.getBorder()) {
int rowIndex = borderVector.getBlockX();
int lineIndex = borderVector.getBlockY();
Character key = layout.getLayout()[lineIndex][rowIndex];
Material materialInLayout = characterMaterialMap.get(key);
Material materialAtLocation = topLeft.getRelativeLocation(borderVector, yaw).getType();
List<MaterialSpecifier> materialInLayout = characterMaterialMap.get(key);
Material type = new SimpleVectorOperation(facing).getRealLocationBlock(topLeft, borderVector).getType();
if (materialInLayout == null) {
if (materialInLayout != null) {
if (!MaterialHelper.specifiersToMaterials(materialInLayout).contains(type)) {
Stargate.debug("Gate::Matches", String.format("Block Type Mismatch: %s != %s",
type, MaterialHelper.specifiersToMaterials(materialInLayout)));
return false;
}
} else {
/* This generally should not happen with proper checking, but just in case a material character is not
* recognized, but still allowed in previous checks, verify the gate as long as all such instances of
* the character correspond to the same material in the physical gate. All subsequent gates will also
* need to match the first verified gate. */
characterMaterialMap.put(key, materialAtLocation);
this.characterMaterialMap.put(key, List.of(new BukkitMaterialSpecifier(type)));
Stargate.debug("Gate::Matches", String.format("Missing layout material in %s. Using %s from the" +
" physical portal.", getFilename(), materialAtLocation));
} else if (materialAtLocation != materialInLayout) {
Stargate.debug("Gate::Matches", String.format("Block Type Mismatch: %s != %s",
materialAtLocation, materialInLayout));
return false;
" physical portal.", getFilename(), type));
}
}
return true;
@@ -223,23 +244,24 @@ public class Gate {
/**
* Verifies that all entrances of a portal gate matches this gate type
*
* @param topLeft <p>The top-left block of this portal</p>
* @param yaw <p>The yaw when looking directly outwards</p>
* @param topLeft <p>The top-left block of the portal's gate</p>
* @param facing <p>The direction the portal is facing</p>
* @param onCreate <p>Whether this is used in the context of creating a new gate</p>
* @return <p>Whether this is used in the context of creating a new gate</p>
*/
private boolean verifyGateEntrancesMatch(BlockLocation topLeft, double yaw, boolean onCreate) {
private boolean verifyGateEntrancesMatch(@NotNull Block topLeft, @NotNull BlockFace facing, boolean onCreate) {
Stargate.debug("verifyGateEntrancesMatch", String.valueOf(topLeft));
for (RelativeBlockVector entranceVector : layout.getEntrances()) {
for (BlockVector entranceVector : layout.getEntrances()) {
Stargate.debug("verifyGateEntrancesMatch", String.valueOf(entranceVector));
Material type = topLeft.getRelativeLocation(entranceVector, yaw).getType();
Material type = new SimpleVectorOperation(facing).getRealLocationBlock(topLeft, entranceVector).getType();
//Ignore entrance if it's air or water, and we're creating a new gate
if (onCreate && (type == Material.AIR || type == Material.WATER)) {
if (onCreate && (type.isAir() || type == Material.WATER)) {
continue;
}
if (type != portalClosedBlock && type != portalOpenBlock) {
if (!MaterialHelper.specifiersToMaterials(portalClosedMaterials).contains(type) &&
!MaterialHelper.specifiersToMaterials(portalOpenMaterials).contains(type)) {
Stargate.debug("Gate::Matches", "Entrance/Exit Material Mismatch: " + type);
return false;
}
@@ -254,20 +276,20 @@ public class Gate {
*
* @param gateFolder <p>The folder to save the gate file in</p>
*/
public void save(String gateFolder) {
public void save(@NotNull String gateFolder) {
try {
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(gateFolder + filename));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(new File(gateFolder, filename)));
//Save main material names
writeConfig(bufferedWriter, "portal-open", portalOpenBlock.name());
writeConfig(bufferedWriter, "portal-closed", portalClosedBlock.name());
writeConfig(bufferedWriter, "button", portalButton.name());
writeConfig(bufferedWriter, "portal-open", MaterialHelper.specifiersToString(portalOpenMaterials));
writeConfig(bufferedWriter, "portal-closed", MaterialHelper.specifiersToString(portalClosedMaterials));
writeConfig(bufferedWriter, "button", MaterialHelper.specifiersToString(portalButtonMaterials));
//Save the values necessary for economy
saveEconomyValues(bufferedWriter);
//Store material types to use for frame blocks
saveFrameBlockTypes(bufferedWriter);
saveFrameBlockType(bufferedWriter);
bufferedWriter.newLine();
@@ -275,8 +297,8 @@ public class Gate {
layout.saveLayout(bufferedWriter);
bufferedWriter.close();
} catch (IOException ex) {
Stargate.logSevere(String.format("Could not save Gate %s - %s", filename, ex.getMessage()));
} catch (IOException exception) {
Stargate.logSevere(String.format("Could not save Gate %s - %s", filename, exception.getMessage()));
}
}
@@ -286,7 +308,7 @@ public class Gate {
* @param bufferedWriter <p>The buffered writer to write to</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void saveEconomyValues(BufferedWriter bufferedWriter) throws IOException {
private void saveEconomyValues(@NotNull BufferedWriter bufferedWriter) throws IOException {
//Write use cost if not disabled
if (useCost != -1) {
writeConfig(bufferedWriter, "usecost", useCost);
@@ -308,26 +330,37 @@ public class Gate {
* @param bufferedWriter <p>The buffered writer to write to</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void saveFrameBlockTypes(BufferedWriter bufferedWriter) throws IOException {
for (Map.Entry<Character, Material> entry : characterMaterialMap.entrySet()) {
Character type = entry.getKey();
Material value = entry.getValue();
private void saveFrameBlockType(@NotNull BufferedWriter bufferedWriter) throws IOException {
for (Map.Entry<Character, List<MaterialSpecifier>> entry : this.characterMaterialMap.entrySet()) {
Character key = entry.getKey();
//Skip characters not part of the frame
if (type.equals(GateHandler.getAnythingCharacter()) ||
type.equals(GateHandler.getEntranceCharacter()) ||
type.equals(GateHandler.getExitCharacter())) {
if (key.equals(GateHandler.getAnythingCharacter()) ||
key.equals(GateHandler.getEntranceCharacter()) ||
key.equals(GateHandler.getExitCharacter())) {
continue;
}
bufferedWriter.append(type);
bufferedWriter.append('=');
if (value != null) {
bufferedWriter.append(value.toString());
}
bufferedWriter.newLine();
saveFrameBlockType(key, MaterialHelper.specifiersToString(entry.getValue()), bufferedWriter);
}
}
/**
* Saves a type of block used for the gate frame/border using a buffered writer
*
* @param key <p>The character key to store</p>
* @param value <p>The string value to store</p>
* @param bufferedWriter <p>The buffered writer to write to</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void saveFrameBlockType(@NotNull Character key, @Nullable String value,
@NotNull BufferedWriter bufferedWriter) throws IOException {
bufferedWriter.append(key.toString());
bufferedWriter.append('=');
if (value != null) {
bufferedWriter.append(value);
}
bufferedWriter.newLine();
}
/**
* Writes a formatted string to a buffered writer
*
@@ -336,7 +369,8 @@ public class Gate {
* @param value <p>The config value to save</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void writeConfig(BufferedWriter bufferedWriter, String key, Object value) throws IOException {
private void writeConfig(@NotNull BufferedWriter bufferedWriter, @NotNull String key,
@NotNull Object value) throws IOException {
//Figure out the correct formatting to use
String format = "%s=";
if (value instanceof Boolean) {

View File

@@ -0,0 +1,12 @@
package net.knarcraft.stargate.portal.property.gate;
/**
* The costs assigned to a gate
*
* @param useCost <p>The cost for using (entering) the gate</p>
* @param createCost <p>The cost for creating a portal with the gate type</p>
* @param destroyCost <p>The cost for destroying a portal with the gate type</p>
* @param toOwner <p>Whether the use cost is paid to the gate's owner</p>
*/
public record GateCosts(int useCost, int createCost, int destroyCost, boolean toOwner) {
}

View File

@@ -1,20 +1,26 @@
package net.knarcraft.stargate.portal.property.gate;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.utility.GateReader;
import net.knarcraft.stargate.config.material.BukkitMaterialSpecifier;
import net.knarcraft.stargate.config.material.MaterialSpecifier;
import net.knarcraft.stargate.utility.MaterialHelper;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.function.Predicate;
import static net.knarcraft.stargate.utility.GateReader.generateLayoutMatrix;
import static net.knarcraft.stargate.utility.GateReader.readGateConfig;
@@ -34,8 +40,9 @@ public class GateHandler {
private static final Material defaultPortalBlockClosed = Material.AIR;
private static final Material defaultButton = Material.STONE_BUTTON;
private static final HashMap<String, Gate> gates = new HashMap<>();
private static final HashMap<Material, List<Gate>> controlBlocks = new HashMap<>();
private static final Map<String, Gate> gates = new HashMap<>();
private static final Map<Material, List<Gate>> controlBlocks = new HashMap<>();
private static final Map<String, List<Gate>> controlBlockTags = new HashMap<>();
private GateHandler() {
@@ -46,6 +53,7 @@ public class GateHandler {
*
* @return <p>The character used for blocks that are not part of the gate</p>
*/
@NotNull
public static Character getAnythingCharacter() {
return ANYTHING;
}
@@ -55,6 +63,7 @@ public class GateHandler {
*
* @return <p>The character used for defining the entrance</p>
*/
@NotNull
public static Character getEntranceCharacter() {
return ENTRANCE;
}
@@ -64,6 +73,7 @@ public class GateHandler {
*
* @return <p>The character used for defining the exit</p>
*/
@NotNull
public static Character getExitCharacter() {
return EXIT;
}
@@ -74,6 +84,7 @@ public class GateHandler {
*
* @return <p>The character used for defining control blocks</p>
*/
@NotNull
public static Character getControlBlockCharacter() {
return CONTROL_BLOCK;
}
@@ -83,16 +94,16 @@ public class GateHandler {
*
* @param gate <p>The gate to register</p>
*/
private static void registerGate(Gate gate) {
private static void registerGate(@NotNull Gate gate) {
gates.put(gate.getFilename(), gate);
Material blockID = gate.getControlBlock();
if (!controlBlocks.containsKey(blockID)) {
controlBlocks.put(blockID, new ArrayList<>());
Set<Material> blockTypes = MaterialHelper.specifiersToMaterials(gate.getControlBlockMaterials());
for (Material material : blockTypes) {
if (!controlBlocks.containsKey(material)) {
controlBlocks.put(material, new ArrayList<>());
}
controlBlocks.get(material).add(gate);
}
controlBlocks.get(blockID).add(gate);
}
/**
@@ -101,7 +112,8 @@ public class GateHandler {
* @param file <p>The file containing the gate data</p>
* @return <p>The loaded gate, or null if unable to load the gate</p>
*/
private static Gate loadGate(File file) {
@Nullable
private static Gate loadGate(@NotNull File file) {
try (Scanner scanner = new Scanner(file)) {
return loadGate(file.getName(), file.getParent(), scanner);
} catch (Exception exception) {
@@ -118,19 +130,20 @@ public class GateHandler {
* @param scanner <p>The scanner to use for reading the gate data</p>
* @return <p>The loaded gate or null if unable to load the gate</p>
*/
private static Gate loadGate(String fileName, String parentFolder, Scanner scanner) {
@Nullable
private static Gate loadGate(@NotNull String fileName, @NotNull String parentFolder,
@NotNull Scanner scanner) {
List<List<Character>> design = new ArrayList<>();
Map<Character, Material> characterMaterialMap = new HashMap<>();
Map<Character, List<MaterialSpecifier>> characterMaterialMap = new HashMap<>();
Map<String, String> config = new HashMap<>();
Set<Material> frameTypes = new HashSet<>();
//Initialize character to material map
characterMaterialMap.put(ENTRANCE, Material.AIR);
characterMaterialMap.put(EXIT, Material.AIR);
characterMaterialMap.put(ANYTHING, Material.AIR);
characterMaterialMap.put(ENTRANCE, List.of(new BukkitMaterialSpecifier(Material.AIR)));
characterMaterialMap.put(EXIT, List.of(new BukkitMaterialSpecifier(Material.AIR)));
characterMaterialMap.put(ANYTHING, List.of(new BukkitMaterialSpecifier(Material.AIR)));
//Read the file into appropriate lists and maps
int columns = readGateFile(scanner, characterMaterialMap, fileName, design, frameTypes, config);
int columns = readGateFile(scanner, characterMaterialMap, fileName, design, config);
if (columns < 0) {
return null;
}
@@ -143,7 +156,7 @@ public class GateHandler {
}
//Update gate file in case the format has changed between versions
gate.save(parentFolder + "/");
gate.save(parentFolder);
return gate;
}
@@ -156,23 +169,26 @@ public class GateHandler {
* @param characterMaterialMap <p>A map between layout characters and the material to use</p>
* @return <p>A new gate, or null if the config is invalid</p>
*/
private static Gate createGate(Map<String, String> config, String fileName, Character[][] layout,
Map<Character, Material> characterMaterialMap) {
@Nullable
private static Gate createGate(@NotNull Map<String, String> config, @NotNull String fileName,
@NotNull Character[][] layout,
@NotNull Map<Character, List<MaterialSpecifier>> characterMaterialMap) {
//Read relevant material types
Material portalOpenBlock = readGateConfig(config, fileName, "portal-open", defaultPortalBlockOpen);
Material portalClosedBlock = readGateConfig(config, fileName, "portal-closed", defaultPortalBlockClosed);
Material portalButton = readGateConfig(config, fileName, "button", defaultButton);
List<MaterialSpecifier> portalOpenBlock = readGateConfig(config, fileName, "portal-open", defaultPortalBlockOpen);
List<MaterialSpecifier> portalClosedBlock = readGateConfig(config, fileName, "portal-closed", defaultPortalBlockClosed);
List<MaterialSpecifier> portalButton = readGateConfig(config, fileName, "button", defaultButton);
//Read economy values
int useCost = GateReader.readGateConfig(config, fileName, "usecost");
int createCost = GateReader.readGateConfig(config, fileName, "createcost");
int destroyCost = GateReader.readGateConfig(config, fileName, "destroycost");
int useCost = readGateConfig(config, fileName, "usecost");
int createCost = readGateConfig(config, fileName, "createcost");
int destroyCost = readGateConfig(config, fileName, "destroycost");
boolean toOwner = (config.containsKey("toowner") ? Boolean.parseBoolean(config.get("toowner")) :
Stargate.getEconomyConfig().sendPaymentToOwner());
GateCosts gateCosts = new GateCosts(useCost, createCost, destroyCost, toOwner);
//Create the new gate
Gate gate = new Gate(fileName, new GateLayout(layout), characterMaterialMap, portalOpenBlock, portalClosedBlock,
portalButton, useCost, createCost, destroyCost, toOwner);
portalButton, gateCosts);
if (!validateGate(gate, fileName)) {
return null;
@@ -187,7 +203,7 @@ public class GateHandler {
* @param fileName <p>The filename of the loaded gate file</p>
* @return <p>True if the gate is valid. False otherwise</p>
*/
private static boolean validateGate(Gate gate, String fileName) {
private static boolean validateGate(@NotNull Gate gate, @NotNull String fileName) {
String failString = String.format("Could not load Gate %s", fileName) + " - %s";
if (gate.getLayout().getControls().length != 2) {
@@ -195,23 +211,28 @@ public class GateHandler {
return false;
}
if (!MaterialHelper.isButtonCompatible(gate.getPortalButton())) {
if (gate.getLayout().getExit() == null) {
Stargate.logSevere(String.format(failString, "Gates must have one specified exit point"));
return false;
}
if (checkMaterialPredicateFail(gate.getPortalButtonMaterials(), MaterialHelper::isButtonCompatible)) {
Stargate.logSevere(String.format(failString, "Gate button must be a type of button."));
return false;
}
if (!gate.getPortalOpenBlock().isBlock()) {
if (checkMaterialPredicateFail(gate.getPortalOpenMaterials(), Material::isBlock)) {
Stargate.logSevere(String.format(failString, "Gate open block must be a type of block."));
return false;
}
if (!gate.getPortalClosedBlock().isBlock()) {
if (checkMaterialPredicateFail(gate.getPortalClosedMaterials(), Material::isBlock)) {
Stargate.logSevere(String.format(failString, "Gate closed block must be a type of block."));
return false;
}
for (Material material : gate.getCharacterMaterialMap().values()) {
if (!material.isBlock()) {
for (List<MaterialSpecifier> materialSpecifiers : gate.getCharacterMaterialMap().values()) {
if (checkMaterialPredicateFail(materialSpecifiers, Material::isBlock)) {
Stargate.logSevere(String.format(failString, "Every gate border block must be a type of block."));
return false;
}
@@ -220,12 +241,31 @@ public class GateHandler {
return true;
}
/**
* Checks whether a predicate is true for a list of material specifiers
*
* @param materialSpecifiers <p>The material specifiers to test</p>
* @param predicate <p>The predicate to test</p>
* @return <p>True if the predicate failed for any specified materials</p>
*/
private static boolean checkMaterialPredicateFail(@NotNull List<MaterialSpecifier> materialSpecifiers,
@NotNull Predicate<Material> predicate) {
Set<Material> closedMaterials = MaterialHelper.specifiersToMaterials(materialSpecifiers);
for (Material material : closedMaterials) {
if (!predicate.test(material)) {
return true;
}
}
return false;
}
/**
* Loads all gates inside the given folder
*
* @param gateFolder <p>The folder containing the gates</p>
*/
public static void loadGates(String gateFolder) {
public static void loadGates(@NotNull String gateFolder) {
File directory = new File(gateFolder);
File[] files;
@@ -258,11 +298,12 @@ public class GateHandler {
*
* @param gateFolder <p>The folder containing gate config files</p>
*/
public static void writeDefaultGatesToFolder(String gateFolder) {
public static void writeDefaultGatesToFolder(@NotNull String gateFolder) {
loadGateFromJar("nethergate.gate", gateFolder);
loadGateFromJar("watergate.gate", gateFolder);
loadGateFromJar("endgate.gate", gateFolder);
loadGateFromJar("squarenetherglowstonegate.gate", gateFolder);
loadGateFromJar("wool.gate", gateFolder);
}
/**
@@ -271,7 +312,7 @@ public class GateHandler {
* @param gateFile <p>The name of the gate file</p>
* @param gateFolder <p>The folder containing gates</p>
*/
private static void loadGateFromJar(String gateFile, String gateFolder) {
private static void loadGateFromJar(@NotNull String gateFile, @NotNull String gateFolder) {
//Get an input stream for the internal file
InputStream gateFileStream = Gate.class.getResourceAsStream("/gates/" + gateFile);
if (gateFileStream != null) {
@@ -293,7 +334,8 @@ public class GateHandler {
* @param block <p>The control block to check</p>
* @return <p>A list of gates using the given control block</p>
*/
public static Gate[] getGatesByControlBlock(Block block) {
@NotNull
public static List<Gate> getGatesByControlBlock(@NotNull Block block) {
return getGatesByControlBlock(block.getType());
}
@@ -306,12 +348,24 @@ public class GateHandler {
* @param type <p>The type of the control block to check</p>
* @return <p>A list of gates using the given material for control block</p>
*/
public static Gate[] getGatesByControlBlock(Material type) {
Gate[] result = new Gate[0];
List<Gate> lookup = controlBlocks.get(type);
@NotNull
public static List<Gate> getGatesByControlBlock(@NotNull Material type) {
List<Gate> result = new ArrayList<>();
List<Gate> fromId = controlBlocks.get(type);
List<Gate> fromTag = null;
for (String tagString : controlBlockTags.keySet()) {
Tag<Material> tag = Bukkit.getTag(Tag.REGISTRY_BLOCKS, NamespacedKey.minecraft(tagString.replaceFirst(
"minecraft:", "")), Material.class);
if (tag != null && tag.isTagged(type)) {
fromTag = controlBlockTags.get(tag.getKey().toString());
}
}
if (lookup != null) {
result = lookup.toArray(result);
if (fromId != null) {
result.addAll(fromId);
}
if (fromTag != null) {
result.addAll(fromTag);
}
return result;
@@ -323,7 +377,8 @@ public class GateHandler {
* @param fileName <p>The filename of the gate to get</p>
* @return <p>The gate with the given filename</p>
*/
public static Gate getGateByName(String fileName) {
@Nullable
public static Gate getGateByName(@NotNull String fileName) {
return gates.get(fileName);
}
@@ -342,6 +397,7 @@ public class GateHandler {
public static void clearGates() {
gates.clear();
controlBlocks.clear();
controlBlockTags.clear();
}
}

View File

@@ -1,6 +1,8 @@
package net.knarcraft.stargate.portal.property.gate;
import net.knarcraft.stargate.container.RelativeBlockVector;
import org.bukkit.util.BlockVector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedWriter;
import java.io.IOException;
@@ -17,18 +19,18 @@ import java.util.List;
public class GateLayout {
private final Character[][] layout;
private final List<RelativeBlockVector> exits = new ArrayList<>();
private RelativeBlockVector[] entrances = new RelativeBlockVector[0];
private RelativeBlockVector[] border = new RelativeBlockVector[0];
private RelativeBlockVector[] controls = new RelativeBlockVector[0];
private RelativeBlockVector exitBlock = null;
private final List<BlockVector> exits = new ArrayList<>();
private BlockVector[] entrances = new BlockVector[0];
private BlockVector[] border = new BlockVector[0];
private BlockVector[] controls = new BlockVector[0];
private BlockVector exitBlock = null;
/**
* Instantiates a new gate layout
*
* @param layout <p>A character matrix describing the layout</p>
*/
public GateLayout(Character[][] layout) {
public GateLayout(@NotNull Character[][] layout) {
this.layout = layout;
readLayout();
}
@@ -38,6 +40,7 @@ public class GateLayout {
*
* @return <p>The character array describing this layout</p>
*/
@NotNull
public Character[][] getLayout() {
return this.layout;
}
@@ -49,7 +52,8 @@ public class GateLayout {
*
* @return <p>The locations of entrances for this gate</p>
*/
public RelativeBlockVector[] getEntrances() {
@NotNull
public BlockVector[] getEntrances() {
return entrances;
}
@@ -61,7 +65,8 @@ public class GateLayout {
*
* @return <p>The locations of border blocks for this gate</p>
*/
public RelativeBlockVector[] getBorder() {
@NotNull
public BlockVector[] getBorder() {
return border;
}
@@ -70,7 +75,8 @@ public class GateLayout {
*
* @return <p>The exit block defined in the layout</p>
*/
public RelativeBlockVector getExit() {
@Nullable
public BlockVector getExit() {
return exitBlock;
}
@@ -82,7 +88,8 @@ public class GateLayout {
*
* @return <p>All possible exits</p>
*/
public List<RelativeBlockVector> getExits() {
@NotNull
public List<BlockVector> getExits() {
return exits;
}
@@ -94,7 +101,8 @@ public class GateLayout {
*
* @return <p>The locations of the control blocks for this gate</p>
*/
public RelativeBlockVector[] getControls() {
@NotNull
public BlockVector[] getControls() {
return controls;
}
@@ -104,7 +112,7 @@ public class GateLayout {
* @param bufferedWriter <p>The buffered writer to write to</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
public void saveLayout(BufferedWriter bufferedWriter) throws IOException {
public void saveLayout(@NotNull BufferedWriter bufferedWriter) throws IOException {
for (Character[] line : this.layout) {
for (Character character : line) {
bufferedWriter.append(character);
@@ -119,9 +127,9 @@ public class GateLayout {
* <p>This methods reads the layout and stores exits, entrances, border blocks and control blocks.</p>
*/
private void readLayout() {
List<RelativeBlockVector> entranceList = new ArrayList<>();
List<RelativeBlockVector> borderList = new ArrayList<>();
List<RelativeBlockVector> controlList = new ArrayList<>();
List<BlockVector> entranceList = new ArrayList<>();
List<BlockVector> borderList = new ArrayList<>();
List<BlockVector> controlList = new ArrayList<>();
readLayout(controlList, entranceList, borderList);
@@ -137,8 +145,9 @@ public class GateLayout {
* @param entranceList <p>The list of entrances to save to</p>
* @param borderList <p>The list of border blocks to save to</p>
*/
private void readLayout(List<RelativeBlockVector> controlList, List<RelativeBlockVector> entranceList,
List<RelativeBlockVector> borderList) {
private void readLayout(@NotNull List<BlockVector> controlList,
@NotNull List<BlockVector> entranceList,
@NotNull List<BlockVector> borderList) {
//Store the lowest opening for each column
int[] exitDepths = new int[layout[0].length];
@@ -157,7 +166,7 @@ public class GateLayout {
for (int x = 0; x < exitDepths.length; x++) {
//Ignore invalid exits
if (exitDepths[x] > 0) {
this.exits.add(new RelativeBlockVector(x, exitDepths[x], 0));
this.exits.add(new BlockVector(x, exitDepths[x], 0));
}
}
}
@@ -173,26 +182,27 @@ public class GateLayout {
* @param entranceList <p>The list of entrances to save to</p>
* @param borderList <p>The list of border blocks to save to</p>
*/
private void parseLayoutCharacter(Character key, int columnIndex, int rowIndex, int[] exitDepths,
List<RelativeBlockVector> controlList, List<RelativeBlockVector> entranceList,
List<RelativeBlockVector> borderList) {
private void parseLayoutCharacter(@NotNull Character key, int columnIndex, int rowIndex, int[] exitDepths,
@NotNull List<BlockVector> controlList,
@NotNull List<BlockVector> entranceList,
@NotNull List<BlockVector> borderList) {
//Add control blocks to the control block list
if (key.equals(GateHandler.getControlBlockCharacter())) {
controlList.add(new RelativeBlockVector(columnIndex, rowIndex, 0));
controlList.add(new BlockVector(columnIndex, rowIndex, 0));
}
if (isOpening(key)) {
//Register entrance
entranceList.add(new RelativeBlockVector(columnIndex, rowIndex, 0));
entranceList.add(new BlockVector(columnIndex, rowIndex, 0));
//Overwrite the lowest exit location for this column/x-coordinate
exitDepths[columnIndex] = rowIndex;
//Register exit if found
if (key.equals(GateHandler.getExitCharacter())) {
this.exitBlock = new RelativeBlockVector(columnIndex, rowIndex, 0);
this.exitBlock = new BlockVector(columnIndex, rowIndex, 0);
}
} else if (!key.equals(GateHandler.getAnythingCharacter())) {
//Register border block
borderList.add(new RelativeBlockVector(columnIndex, rowIndex, 0));
borderList.add(new BlockVector(columnIndex, rowIndex, 0));
}
}
@@ -202,7 +212,7 @@ public class GateLayout {
* @param character <p>The character to check</p>
* @return <p>True if the character represents an opening</p>
*/
private boolean isOpening(Character character) {
private boolean isOpening(@NotNull Character character) {
return character.equals(GateHandler.getEntranceCharacter()) || character.equals(GateHandler.getExitCharacter());
}

View File

@@ -1,10 +1,9 @@
package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.event.StargateEntityPortalEvent;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;
/**
* The portal teleporter takes care of the actual portal teleportation for any entities
@@ -16,10 +15,10 @@ public class EntityTeleporter extends Teleporter {
/**
* Instantiates a new portal teleporter
*
* @param portal <p>The portal which is the target of the teleportation</p>
* @param targetPortal <p>The portal which is the target of the teleportation</p>
*/
public EntityTeleporter(Portal portal, Entity teleportingEntity) {
super(portal);
public EntityTeleporter(@NotNull Portal targetPortal, @NotNull Entity teleportingEntity) {
super(targetPortal, teleportingEntity);
this.teleportingEntity = teleportingEntity;
}
@@ -29,44 +28,8 @@ public class EntityTeleporter extends Teleporter {
* @param origin <p>The portal the entity is teleporting from</p>
* @return <p>True if the entity was teleported. False otherwise</p>
*/
public boolean teleport(Portal origin) {
Location traveller = teleportingEntity.getLocation();
Location exit = getExit(teleportingEntity, traveller);
//Rotate the entity to face out from the portal
adjustRotation(exit);
//Call the StargateEntityPortalEvent to allow plugins to change destination
if (!origin.equals(portal)) {
exit = triggerEntityPortalEvent(origin, exit);
if (exit == null) {
return false;
}
}
//Load chunks to make sure not to teleport to the void
loadChunks();
teleportingEntity.teleport(exit);
return true;
public boolean teleportEntity(@NotNull Portal origin) {
return teleport(origin, new StargateEntityPortalEvent(teleportingEntity, origin, portal, exit));
}
/**
* Triggers the entity portal event to allow plugins to change the exit location
*
* @param origin <p>The origin portal teleported from</p>
* @param exit <p>The exit location to teleport the entity to</p>
* @return <p>The location the entity should be teleported to, or null if the event was cancelled</p>
*/
protected Location triggerEntityPortalEvent(Portal origin, Location exit) {
StargateEntityPortalEvent stargateEntityPortalEvent = new StargateEntityPortalEvent(teleportingEntity, origin,
portal, exit);
Stargate.getInstance().getServer().getPluginManager().callEvent(stargateEntityPortalEvent);
//Teleport is cancelled. Teleport the entity back to where it came from just for sanity's sake
if (stargateEntityPortalEvent.isCancelled()) {
new EntityTeleporter(origin, teleportingEntity).teleport(origin);
return null;
}
return stargateEntityPortalEvent.getExit();
}
}

View File

@@ -3,9 +3,17 @@ package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.event.StargatePlayerPortalEvent;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.Location;
import net.knarcraft.stargate.utility.DirectionHelper;
import net.knarcraft.stargate.utility.TeleportHelper;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* The portal teleporter takes care of the actual portal teleportation for any players
@@ -17,11 +25,11 @@ public class PlayerTeleporter extends Teleporter {
/**
* Instantiates a new player teleporter
*
* @param portal <p>The portal which is the target of the teleportation</p>
* @param player <p>The teleporting player</p>
* @param targetPortal <p>The portal which is the target of the teleportation</p>
* @param player <p>The teleporting player</p>
*/
public PlayerTeleporter(Portal portal, Player player) {
super(portal);
public PlayerTeleporter(@NotNull Portal targetPortal, @NotNull Player player) {
super(targetPortal, player);
this.player = player;
}
@@ -31,53 +39,42 @@ public class PlayerTeleporter extends Teleporter {
* @param origin <p>The portal the player teleports from</p>
* @param event <p>The player move event triggering the event</p>
*/
public void teleport(Portal origin, PlayerMoveEvent event) {
Location traveller = player.getLocation();
Location exit = getExit(player, traveller);
//Rotate the player to face out from the portal
adjustRotation(exit);
public void teleportPlayer(@NotNull Portal origin, @Nullable PlayerMoveEvent event) {
double velocity = player.getVelocity().length();
List<Entity> passengers = player.getPassengers();
//Call the StargatePlayerPortalEvent to allow plugins to change destination
if (!origin.equals(portal)) {
exit = triggerPlayerPortalEvent(origin, exit, event);
exit = triggerPortalEvent(origin, new StargatePlayerPortalEvent(player, origin, portal, exit));
if (exit == null) {
return;
}
}
//Calculate the exit velocity of the player
Vector newVelocityDirection = DirectionHelper.getDirectionVectorFromYaw(portal.getLocation().getYaw());
Vector newVelocity = newVelocityDirection.multiply(velocity * Stargate.getGateConfig().getExitVelocity());
//Load chunks to make sure not to teleport to the void
loadChunks();
//Teleport any creatures leashed by the player in a 15-block range
teleportLeashedCreatures(player, origin);
TeleportHelper.teleportLeashedCreatures(player, origin, portal);
if (player.eject()) {
TeleportHelper.handleEntityPassengers(passengers, player, origin, portal, exit.getDirection(), newVelocity);
}
//If no event is passed in, assume it's a teleport, and act as such
if (event == null) {
player.teleport(exit);
} else {
//The new method to teleport in a move event is set the "to" field.
//Set the exit location of the event
event.setTo(exit);
}
}
/**
* Triggers the player portal event to allow plugins to change the exit location
*
* @param origin <p>The origin portal teleported from</p>
* @param exit <p>The exit location to teleport the player to</p>
* @param event <p>The player move event which triggered the teleportation</p>
* @return <p>The location the player should be teleported to, or null if the event was cancelled</p>
*/
private Location triggerPlayerPortalEvent(Portal origin, Location exit, PlayerMoveEvent event) {
StargatePlayerPortalEvent stargatePlayerPortalEvent = new StargatePlayerPortalEvent(player, origin, portal, exit);
Stargate.getInstance().getServer().getPluginManager().callEvent(stargatePlayerPortalEvent);
//Teleport is cancelled. Teleport the player back to where it came from
if (stargatePlayerPortalEvent.isCancelled()) {
new PlayerTeleporter(origin, player).teleport(origin, event);
return null;
}
return stargatePlayerPortalEvent.getExit();
//Set the velocity of the teleported player after the teleportation is finished
Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), () -> player.setVelocity(newVelocity), 1);
}
}

View File

@@ -1,23 +1,30 @@
package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.ChunkUnloadRequest;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.event.StargateTeleportEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.transformation.SimpleVectorOperation;
import net.knarcraft.stargate.utility.DirectionHelper;
import net.knarcraft.stargate.utility.EntityHelper;
import net.knarcraft.stargate.utility.TeleportHelper;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Slab;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Creature;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -31,71 +38,117 @@ public abstract class Teleporter {
* The portal the entity is teleporting to
*/
protected final Portal portal;
/**
* The scheduler to use for delaying tasks
*/
protected final BukkitScheduler scheduler;
/**
* The exit location any entities will be teleported to
*/
protected Location exit;
/**
* The entity being teleported by this teleporter
*/
protected final Entity teleportedEntity;
/**
* Instantiates a new portal teleporter
*
* @param portal <p>The portal which is the target of the teleportation</p>
* @param portal <p>The portal which is the target of the teleportation</p>
* @param teleportedEntity <p>The entity teleported by this teleporter</p>
*/
public Teleporter(Portal portal) {
protected Teleporter(@NotNull Portal portal, @NotNull Entity teleportedEntity) {
this.portal = portal;
this.scheduler = Stargate.getInstance().getServer().getScheduler();
this.teleportedEntity = teleportedEntity;
this.exit = getExit(teleportedEntity);
}
/**
* Teleports an entity
*
* @param origin <p>The portal the entity teleported from</p>
* @param stargateTeleportEvent <p>The event to call to make sure the teleportation is valid</p>
* @return <p>True if the teleportation was successfully performed</p>
*/
public boolean teleport(@NotNull Portal origin, @Nullable StargateTeleportEvent stargateTeleportEvent) {
List<Entity> passengers = teleportedEntity.getPassengers();
//Call the StargateEntityPortalEvent to allow plugins to change destination
if (!origin.equals(portal) && stargateTeleportEvent != null) {
exit = triggerPortalEvent(origin, stargateTeleportEvent);
if (exit == null) {
return false;
}
}
//Load chunks to make sure not to teleport to the void
loadChunks();
if (teleportedEntity.eject()) {
TeleportHelper.handleEntityPassengers(passengers, teleportedEntity, origin, portal, exit.getDirection(),
new Vector());
}
teleportedEntity.teleport(exit);
return true;
}
/**
* Gets the exit location of this teleporter
*
* @return <p>The exit location of this teleporter</p>
*/
@NotNull
public Location getExit() {
return exit.clone();
}
/**
* Triggers the entity portal event to allow plugins to change the exit location
*
* @param origin <p>The origin portal teleported from</p>
* @param stargateTeleportEvent <p>The exit location to teleport the entity to</p>
* @return <p>The location the entity should be teleported to, or null if the event was cancelled</p>
*/
@Nullable
protected Location triggerPortalEvent(@NotNull Portal origin,
@NotNull StargateTeleportEvent stargateTeleportEvent) {
Stargate.getInstance().getServer().getPluginManager().callEvent((Event) stargateTeleportEvent);
//Teleport is cancelled. Teleport the entity back to where it came from just for sanity's sake
if (stargateTeleportEvent.isCancelled()) {
new EntityTeleporter(origin, teleportedEntity).teleportEntity(origin);
return null;
}
return stargateTeleportEvent.getExit();
}
/**
* Adjusts the rotation of the exit to make the teleporting entity face directly out from the portal
*
* @param exit <p>The location the entity will exit from</p>
*/
protected void adjustRotation(Location exit) {
protected void adjustExitLocationRotation(@NotNull Location exit) {
int adjust = 0;
if (portal.getOptions().isBackwards()) {
adjust = 180;
}
float newYaw = (portal.getYaw() + adjust) % 360;
float newYaw = (portal.getLocation().getYaw() + adjust) % 360;
Stargate.debug("Portal::adjustRotation", "Setting exit yaw to " + newYaw);
exit.setYaw(newYaw);
exit.setDirection(DirectionHelper.getDirectionVectorFromYaw(newYaw));
}
/**
* Gets the exit location for a given entity and current location
*
* @param entity <p>The entity to teleport (used to determine distance from portal to avoid suffocation)</p>
* @param traveller <p>The location of the entity travelling</p>
* @return <p>The location the entity should be teleported to.</p>
* Loads the chunks outside the portal's entrance
*/
public Location getExit(Entity entity, Location traveller) {
Location exitLocation = null;
RelativeBlockVector relativeExit = portal.getGate().getLayout().getExit();
if (relativeExit != null) {
BlockLocation exit = portal.getBlockAt(relativeExit);
//Move one block out to prevent exiting inside the portal
float portalYaw = portal.getYaw();
if (portal.getOptions().isBackwards()) {
portalYaw += 180;
}
exitLocation = exit.getRelativeLocation(0D, 0D, 1, portalYaw);
if (entity != null) {
double entitySize = EntityHelper.getEntityMaxSize(entity);
//Prevent exit suffocation for players riding horses or similar
if (entitySize > 1) {
exitLocation = preventExitSuffocation(relativeExit, exitLocation, entity);
}
}
} else {
Stargate.logWarning(String.format("Missing destination point in .gate file %s",
portal.getGate().getFilename()));
protected void loadChunks() {
for (Chunk chunk : getChunksToLoad()) {
chunk.addPluginChunkTicket(Stargate.getInstance());
//Allow the chunk to unload after 10 seconds
Stargate.addChunkUnloadRequest(new ChunkUnloadRequest(chunk, 10000L));
}
//Adjust pitch and height
return adjustExitLocation(traveller, exitLocation);
}
/**
@@ -106,7 +159,9 @@ public abstract class Teleporter {
* @param entity <p>The travelling entity</p>
* @return <p>A location which won't suffocate the entity inside the portal</p>
*/
private Location preventExitSuffocation(RelativeBlockVector relativeExit, Location exitLocation, Entity entity) {
@NotNull
private Location preventExitSuffocation(@NotNull RelativeBlockVector relativeExit,
@NotNull Location exitLocation, @NotNull Entity entity) {
//Go left to find start of opening
RelativeBlockVector openingLeft = getPortalExitEdge(relativeExit, -1);
@@ -114,15 +169,15 @@ public abstract class Teleporter {
RelativeBlockVector openingRight = getPortalExitEdge(relativeExit, 1);
//Get the width to check if the entity fits
int openingWidth = openingRight.getRight() - openingLeft.getRight() + 1;
int existingOffset = relativeExit.getRight() - openingLeft.getRight();
int openingWidth = openingRight.right() - openingLeft.right() + 1;
int existingOffset = relativeExit.right() - openingLeft.right();
double newOffset = (openingWidth - existingOffset) / 2D;
//Remove the half offset for better centering
if (openingWidth > 1) {
newOffset -= 0.5;
}
exitLocation = DirectionHelper.moveLocation(exitLocation, newOffset, 0, 0, portal.getYaw());
exitLocation = DirectionHelper.moveLocation(exitLocation, newOffset, 0, 0, portal.getLocation().getYaw());
//Move large entities further from the portal
return moveExitLocationOutwards(exitLocation, entity);
@@ -135,13 +190,14 @@ public abstract class Teleporter {
* @param entity <p>The entity to adjust the exit location for</p>
* @return <p>The adjusted exit location</p>
*/
private Location moveExitLocationOutwards(Location exitLocation, Entity entity) {
@NotNull
private Location moveExitLocationOutwards(@NotNull Location exitLocation, @NotNull Entity entity) {
double entitySize = EntityHelper.getEntityMaxSize(entity);
int entityBoxSize = EntityHelper.getEntityMaxSizeInt(entity);
if (entitySize > 1) {
double entityOffset;
if (portal.getOptions().isAlwaysOn()) {
entityOffset = entityBoxSize / 2D;
entityOffset = (entityBoxSize / 2D);
} else {
entityOffset = (entitySize / 2D) - 1;
}
@@ -150,7 +206,7 @@ public abstract class Teleporter {
if (entity instanceof AbstractHorse) {
entityOffset += 1;
}
exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, entityOffset, portal.getYaw());
exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, entityOffset, portal.getLocation().getYaw());
}
return exitLocation;
}
@@ -162,12 +218,13 @@ public abstract class Teleporter {
* @param direction <p>The direction to move (+1 for right, -1 for left)</p>
* @return <p>The right or left edge of the opening</p>
*/
private RelativeBlockVector getPortalExitEdge(RelativeBlockVector relativeExit, int direction) {
@NotNull
private RelativeBlockVector getPortalExitEdge(@NotNull RelativeBlockVector relativeExit, int direction) {
RelativeBlockVector openingEdge = relativeExit;
do {
RelativeBlockVector possibleOpening = new RelativeBlockVector(openingEdge.getRight() + direction,
openingEdge.getDown(), openingEdge.getOut());
RelativeBlockVector possibleOpening = new RelativeBlockVector(openingEdge.right() + direction,
openingEdge.down(), openingEdge.out());
if (portal.getGate().getLayout().getExits().contains(possibleOpening)) {
openingEdge = possibleOpening;
} else {
@@ -185,41 +242,64 @@ public abstract class Teleporter {
* slab check is necessary to prevent the player from clipping through the slab and spawning beneath it. The water
* check is necessary when teleporting boats to prevent it from becoming a submarine.</p>
*
* @param traveller <p>The location of the travelling entity</p>
* @param entity <p>The travelling entity</p>
* @param exitLocation <p>The exit location generated</p>
* @return <p>The location the travelling entity should be teleported to</p>
*/
private Location adjustExitLocation(Location traveller, Location exitLocation) {
@NotNull
private Location adjustExitLocationHeight(@NotNull Entity entity, @Nullable Location exitLocation) {
if (exitLocation != null) {
BlockData blockData = exitLocation.getBlock().getBlockData();
if ((blockData instanceof Bisected bisected && bisected.getHalf() == Bisected.Half.BOTTOM) ||
(blockData instanceof Slab slab && slab.getType() == Slab.Type.BOTTOM)) {
//Prevent traveller from spawning inside a slab
Stargate.debug("adjustExitLocation", "Added a block to get above a slab");
exitLocation.add(0, 1, 0);
} else if (blockData.getMaterial() == Material.WATER) {
//If there's water outside, go one up to allow for boat teleportation
Stargate.debug("adjustExitLocation", "Added a block to get above a block of water");
(blockData instanceof Slab slab && slab.getType() == Slab.Type.BOTTOM) ||
blockData.getMaterial() == Material.WATER) {
//Prevent traveller from spawning inside a slab, or a boat from spawning inside water
Stargate.debug("adjustExitLocation", "Added a block to get above a slab or a block of water");
exitLocation.add(0, 1, 0);
}
exitLocation.setPitch(traveller.getPitch());
return exitLocation;
} else {
Stargate.logWarning("Unable to generate exit location");
return traveller;
return entity.getLocation();
}
}
/**
* Loads the chunks outside the portal's entrance
* Gets the exit location for a given entity and current location
*
* @param entity <p>The entity to teleport (used to determine distance from portal to avoid suffocation)</p>
* @return <p>The location the entity should be teleported to.</p>
*/
protected void loadChunks() {
for (Chunk chunk : getChunksToLoad()) {
chunk.addPluginChunkTicket(Stargate.getInstance());
//Allow the chunk to unload after 3 seconds
Stargate.addChunkUnloadRequest(new ChunkUnloadRequest(chunk, 3000L));
@NotNull
private Location getExit(@NotNull Entity entity) {
Block exitLocation = null;
BlockVector relativeExit = portal.getGate().getLayout().getExit();
if (relativeExit != null) {
Block exit = portal.getBlockAt(relativeExit);
//Move one block out to prevent exiting inside the portal
BlockFace facing = portal.getLocation().getFacing();
if (portal.getOptions().isBackwards()) {
facing = facing.getOppositeFace();
}
SimpleVectorOperation operation = new SimpleVectorOperation(facing);
exitLocation = operation.getRealLocationBlock(portal.getLocation().getTopLeft(),
new BlockVector(0, 0, 1));
double entitySize = EntityHelper.getEntityMaxSize(entity);
//Prevent exit suffocation for players riding horses or similar
if (entitySize > 1) {
exitLocation = preventExitSuffocation(relativeExit, exitLocation, entity);
}
} else {
Stargate.logWarning(String.format("Missing destination point in .gate file %s",
portal.getGate().getFilename()));
}
//Adjust height and rotation
Location adjusted = adjustExitLocationHeight(entity, exitLocation);
adjustExitLocationRotation(adjusted);
return adjusted;
}
/**
@@ -227,10 +307,11 @@ public abstract class Teleporter {
*
* @return <p>A list of chunks to load</p>
*/
@NotNull
private List<Chunk> getChunksToLoad() {
List<Chunk> chunksToLoad = new ArrayList<>();
for (RelativeBlockVector vector : portal.getGate().getLayout().getEntrances()) {
BlockLocation entranceLocation = portal.getBlockAt(vector);
for (BlockVector vector : portal.getGate().getLayout().getEntrances()) {
Block entranceLocation = portal.getBlockAt(vector);
Chunk chunk = entranceLocation.getChunk();
//Make sure not to load chunks twice
if (!chunksToLoad.contains(chunk)) {
@@ -239,8 +320,7 @@ public abstract class Teleporter {
//Get the chunk in front of the gate entrance
int blockOffset = portal.getOptions().isBackwards() ? -5 : 5;
Location fiveBlocksForward = DirectionHelper.moveLocation(entranceLocation, 0, 0, blockOffset,
portal.getYaw());
Block fiveBlocksForward = entranceLocation.getRelative(portal.getLocation().getFacing(), blockOffset);
//Load the chunk five blocks forward to make sure the teleported entity will never spawn in unloaded chunks
Chunk forwardChunk = fiveBlocksForward.getChunk();
if (!chunksToLoad.contains(forwardChunk)) {
@@ -250,76 +330,4 @@ public abstract class Teleporter {
return chunksToLoad;
}
/**
* Checks whether a player has leashed creatures that block the teleportation
*
* @param player <p>The player trying to teleport</p>
* @return <p>False if the player has leashed any creatures that cannot go through the portal</p>
*/
public static boolean noLeashedCreaturesPreventTeleportation(Player player) {
//Find any nearby leashed entities to teleport with the player
List<Creature> nearbyCreatures = getLeashedCreatures(player);
//Disallow creatures with passengers to prevent smuggling
for (Creature creature : nearbyCreatures) {
if (!creature.getPassengers().isEmpty()) {
return false;
}
}
//If it's enabled, there is no problem
if (Stargate.getGateConfig().handleLeashedCreatures()) {
return true;
} else {
return nearbyCreatures.isEmpty();
}
}
/**
* Teleports any creatures leashed by the player
*
* <p>Will return false if the teleportation should be aborted because the player has leashed creatures that
* aren't allowed to be teleported with the player.</p>
*
* @param player <p>The player which is teleported</p>
* @param origin <p>The portal the player is teleporting from</p>
*/
protected void teleportLeashedCreatures(Player player, Portal origin) {
//If this feature is disabled, just return
if (!Stargate.getGateConfig().handleLeashedCreatures()) {
return;
}
//Find any nearby leashed entities to teleport with the player
List<Creature> nearbyEntities = getLeashedCreatures(player);
//Teleport all creatures leashed by the player to the portal the player is to exit from
for (Creature creature : nearbyEntities) {
creature.setLeashHolder(null);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> {
new EntityTeleporter(portal, creature).teleport(origin);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> creature.setLeashHolder(player), 6);
}, 2);
}
}
/**
* Gets all creatures leashed by a player within the given range
*
* @param player <p>The player to check</p>
* @return <p>A list of all creatures the player is holding in a leash (lead)</p>
*/
protected static List<Creature> getLeashedCreatures(Player player) {
List<Creature> leashedCreatures = new ArrayList<>();
//Find any nearby leashed entities to teleport with the player
List<Entity> nearbyEntities = player.getNearbyEntities(15, 15, 15);
//Teleport all creatures leashed by the player to the portal the player is to exit from
for (Entity entity : nearbyEntities) {
if (entity instanceof Creature creature && creature.isLeashed() && creature.getLeashHolder() == player) {
leashedCreatures.add(creature);
}
}
return leashedCreatures;
}
}

View File

@@ -2,17 +2,22 @@ package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.StargateGateConfig;
import net.knarcraft.stargate.event.StargateEntityPortalEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.utility.DirectionHelper;
import net.knarcraft.stargate.utility.TeleportHelper;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
/**
* The portal teleporter takes care of the actual portal teleportation for any vehicles
@@ -24,11 +29,11 @@ public class VehicleTeleporter extends EntityTeleporter {
/**
* Instantiates a new vehicle teleporter
*
* @param portal <p>The portal which is the target of the teleportation</p>
* @param targetPortal <p>The targetPortal which is the target of the teleportation</p>
* @param teleportingVehicle <p>The teleporting vehicle</p>
*/
public VehicleTeleporter(Portal portal, Vehicle teleportingVehicle) {
super(portal, teleportingVehicle);
public VehicleTeleporter(@NotNull Portal targetPortal, @NotNull Vehicle teleportingVehicle) {
super(targetPortal, teleportingVehicle);
this.teleportingVehicle = teleportingVehicle;
}
@@ -42,28 +47,22 @@ public class VehicleTeleporter extends EntityTeleporter {
* @return <p>True if the vehicle was teleported. False otherwise</p>
*/
@Override
public boolean teleport(Portal origin) {
Location traveller = teleportingVehicle.getLocation();
Location exit = getExit(teleportingVehicle, traveller);
public boolean teleportEntity(@NotNull Portal origin) {
Stargate.debug("VehicleTeleporter::teleport", "Preparing to teleport: " + teleportingVehicle);
double velocity = teleportingVehicle.getVelocity().length();
//Stop and teleport
//Stop the vehicle before teleporting
teleportingVehicle.setVelocity(new Vector());
//Get new velocity
Vector newVelocityDirection = DirectionHelper.getDirectionVectorFromYaw(portal.getYaw());
Vector newVelocityDirection = DirectionHelper.getDirectionVectorFromYaw(portal.getLocation().getYaw());
Vector newVelocity = newVelocityDirection.multiply(velocity);
//Make sure the vehicle points out from the portal
adjustRotation(exit);
//Call the StargateEntityPortalEvent to allow plugins to change destination
if (!origin.equals(portal)) {
exit = triggerEntityPortalEvent(origin, exit);
if (exit == null) {
return false;
}
exit = triggerPortalEvent(origin, new StargateEntityPortalEvent(teleportingVehicle, origin, portal, exit));
if (exit == null) {
return false;
}
//Teleport the vehicle
@@ -78,7 +77,7 @@ public class VehicleTeleporter extends EntityTeleporter {
* @param origin <p>The portal the vehicle teleported from</p>
* @return <p>True if the vehicle was teleported. False otherwise</p>
*/
private boolean teleportVehicle(Location exit, Vector newVelocity, Portal origin) {
private boolean teleportVehicle(@NotNull Location exit, @NotNull Vector newVelocity, @NotNull Portal origin) {
//Load chunks to make sure not to teleport to the void
loadChunks();
@@ -89,12 +88,13 @@ public class VehicleTeleporter extends EntityTeleporter {
return false;
}
if (!(teleportingVehicle instanceof LivingEntity)) {
if (!(teleportingVehicle instanceof LivingEntity) &&
Stargate.getGateConfig().enableCraftBookRemoveOnEjectFix()) {
//Teleport a normal vehicle with passengers (minecart or boat)
putPassengersInNewVehicle(passengers, exit, newVelocity, origin);
} else {
//Teleport a living vehicle with passengers (pig, horse, donkey, strider)
teleportLivingVehicle(exit, passengers, origin);
teleportVehicle(passengers, exit, newVelocity, origin);
}
} else {
//Check if teleportation of empty vehicles is enabled
@@ -115,57 +115,39 @@ public class VehicleTeleporter extends EntityTeleporter {
* @param passengers <p>The passengers to teleport</p>
* @return <p>True if the passengers are allowed to teleport</p>
*/
private boolean vehiclePassengersAllowed(List<Entity> passengers) {
private boolean vehiclePassengersAllowed(@NotNull List<Entity> passengers) {
StargateGateConfig config = Stargate.getGateConfig();
//Don't teleport if the vehicle contains a creature and creature transportation is disabled
if (containsNonPlayer(passengers) && !config.handleCreatureTransportation()) {
if (TeleportHelper.containsNonPlayer(passengers) && !config.handleCreatureTransportation()) {
return false;
}
//Don't teleport if the player does not contain a player and non-player vehicles is disabled
return containsPlayer(passengers) || config.handleNonPlayerVehicles();
}
/**
* Checks whether a list of entities contains any non-players
*
* @param entities <p>The list of entities to check</p>
* @return <p>True if at least one entity is not a player</p>
*/
private boolean containsNonPlayer(List<Entity> entities) {
for (Entity entity : entities) {
if (!(entity instanceof Player)) {
return true;
}
}
return false;
}
/**
* Checks whether a list of entities contains at least one player
*
* @param entities <p>The list of entities to check</p>
* @return <p>True if at least one player is present among the passengers</p>
*/
private boolean containsPlayer(List<Entity> entities) {
for (Entity entity : entities) {
if (entity instanceof Player) {
return true;
}
}
return false;
return TeleportHelper.containsPlayer(passengers) || config.handleNonPlayerVehicles();
}
/**
* Teleport a vehicle which is not a minecart or a boat
*
* @param exit <p>The location the vehicle will exit</p>
* @param passengers <p>The passengers of the vehicle</p>
* @param origin <p>The portal the vehicle teleported from</p>
* @param passengers <p>The passengers of the vehicle</p>
* @param exit <p>The location the vehicle will exit</p>
* @param newVelocity <p>The new velocity of the teleported vehicle</p>
* @param origin <p>The portal the vehicle teleported from</p>
*/
private void teleportLivingVehicle(Location exit, List<Entity> passengers, Portal origin) {
teleportingVehicle.eject();
teleportingVehicle.teleport(exit);
handleVehiclePassengers(passengers, teleportingVehicle, 2, origin);
private void teleportVehicle(@NotNull List<Entity> passengers, @NotNull Location exit, @NotNull Vector newVelocity,
@NotNull Portal origin) {
if (teleportingVehicle.eject()) {
TeleportHelper.handleEntityPassengers(passengers, teleportingVehicle, origin, portal, exit.getDirection(),
newVelocity);
}
Stargate.debug("VehicleTeleporter::teleportVehicle", "Teleporting " + teleportingVehicle +
" to final location " + exit + " with direction " + exit.getDirection());
teleportingVehicle.teleport(exit, PlayerTeleportEvent.TeleportCause.PLUGIN);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(),
() -> {
Stargate.debug("VehicleTeleporter::teleportVehicle", "Setting velocity " + newVelocity +
" for vehicle " + teleportingVehicle);
teleportingVehicle.setVelocity(newVelocity);
}, 1);
}
/**
@@ -180,61 +162,26 @@ public class VehicleTeleporter extends EntityTeleporter {
* @param newVelocity <p>The new velocity of the new vehicle</p>
* @param origin <p>The portal the vehicle teleported from</p>
*/
private void putPassengersInNewVehicle(List<Entity> passengers, Location exit,
Vector newVelocity, Portal origin) {
private void putPassengersInNewVehicle(@NotNull List<Entity> passengers, @NotNull Location exit,
@NotNull Vector newVelocity, Portal origin) {
World vehicleWorld = exit.getWorld();
if (vehicleWorld == null) {
Stargate.logWarning("Unable to get the world to teleport the vehicle to");
return;
}
//Spawn a new vehicle
Vehicle newVehicle = vehicleWorld.spawn(exit, teleportingVehicle.getClass());
Vehicle newVehicle = (Vehicle) vehicleWorld.spawn(exit,
Objects.requireNonNull(teleportingVehicle.getType().getEntityClass()));
if (teleportingVehicle instanceof Boat boat) {
boat.setBoatType(boat.getBoatType());
}
//Remove the old vehicle
teleportingVehicle.eject();
if (teleportingVehicle.eject()) {
TeleportHelper.handleEntityPassengers(passengers, newVehicle, origin, portal, exit.getDirection(),
newVelocity);
}
teleportingVehicle.remove();
//Set rotation, add passengers and restore velocity
newVehicle.setRotation(exit.getYaw(), exit.getPitch());
handleVehiclePassengers(passengers, newVehicle, 1, origin);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> newVehicle.setVelocity(newVelocity), 1);
}
/**
* Ejects, teleports and adds all passengers to the target vehicle
*
* @param passengers <p>The passengers to handle</p>
* @param vehicle <p>The vehicle the passengers should be put into</p>
* @param delay <p>The amount of milliseconds to wait before adding the vehicle passengers</p>
* @param origin <p>The portal the vehicle teleported from</p>
*/
private void handleVehiclePassengers(List<Entity> passengers, Vehicle vehicle, long delay, Portal origin) {
for (Entity passenger : passengers) {
passenger.eject();
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> {
if (passenger instanceof Player player) {
//Teleport any creatures leashed by the player in a 15-block range
teleportLeashedCreatures(player, origin);
}
teleportAndAddPassenger(vehicle, passenger);
}, delay);
}
}
/**
* Teleports and adds a passenger to a vehicle
*
* <p>Teleportation of living vehicles is really buggy if you wait between the teleportation and passenger adding,
* but there needs to be a delay between teleporting the vehicle and teleporting and adding the passenger.</p>
*
* @param targetVehicle <p>The vehicle to add the passenger to</p>
* @param passenger <p>The passenger to teleport and add</p>
*/
private void teleportAndAddPassenger(Vehicle targetVehicle, Entity passenger) {
if (!passenger.teleport(targetVehicle.getLocation())) {
Stargate.debug("handleVehiclePassengers", "Failed to teleport passenger");
}
if (!targetVehicle.addPassenger(passenger)) {
Stargate.debug("handleVehiclePassengers", "Failed to add passenger");
}
}
}

View File

@@ -8,11 +8,12 @@ import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.EndGateway;
import org.bukkit.block.data.Orientable;
import org.jetbrains.annotations.NotNull;
/**
* This thread changes gate blocks to display a gate as open or closed
*
* <p>This thread fetches some entries from blockPopulateQueue each time it's called.</p>
* <p>This thread fetches some entries from blockChangeRequestQueue each time it's called.</p>
*/
public class BlockChangeThread implements Runnable {
@@ -21,32 +22,36 @@ public class BlockChangeThread implements Runnable {
long sTime = System.nanoTime();
//Repeat for at most 0.025 seconds
while (System.nanoTime() - sTime < 25000000) {
pollQueue();
if (pollQueue()) {
break;
}
}
}
/**
* Polls the block change request queue for any waiting requests
*
* @return <p>True if the queue is empty and it's safe to quit</p>
*/
public static void pollQueue() {
public static boolean pollQueue() {
//Abort if there's no work to be done
BlockChangeRequest blockChangeRequest = Stargate.getBlockChangeRequestQueue().poll();
BlockChangeRequest blockChangeRequest = Stargate.getControlBlockUpdateRequestQueue().poll();
if (blockChangeRequest == null) {
return;
return true;
}
//Change the material of the pulled block
Block block = blockChangeRequest.getBlockLocation().getBlock();
block.setType(blockChangeRequest.getMaterial(), false);
if (blockChangeRequest.getMaterial() == Material.END_GATEWAY &&
block.getWorld().getEnvironment() == World.Environment.THE_END) {
if (blockChangeRequest.getMaterial() == Material.END_GATEWAY) {
//Force a specific location to prevent exit gateway generation
fixEndGatewayGate(block);
} else if (blockChangeRequest.getAxis() != null) {
//If orientation is relevant, adjust the block's orientation
orientBlock(block, blockChangeRequest.getAxis());
}
return false;
}
/**
@@ -54,10 +59,13 @@ public class BlockChangeThread implements Runnable {
*
* @param block <p>The block to fix</p>
*/
private static void fixEndGatewayGate(Block block) {
private static void fixEndGatewayGate(@NotNull Block block) {
EndGateway gateway = (EndGateway) block.getState();
gateway.setExitLocation(block.getLocation());
gateway.setExactTeleport(true);
gateway.setAge(Long.MIN_VALUE);
if (block.getWorld().getEnvironment() == World.Environment.THE_END) {
gateway.setExitLocation(block.getLocation());
gateway.setExactTeleport(true);
}
gateway.update(false, false);
}
@@ -67,7 +75,7 @@ public class BlockChangeThread implements Runnable {
* @param block <p>The block to orient</p>
* @param axis <p>The axis to use for orienting the block</p>
*/
private static void orientBlock(Block block, Axis axis) {
private static void orientBlock(@NotNull Block block, @NotNull Axis axis) {
Orientable orientable = (Orientable) block.getBlockData();
orientable.setAxis(axis);
block.setBlockData(orientable);

View File

@@ -0,0 +1,49 @@
package net.knarcraft.stargate.thread;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.container.ControlBlockUpdateRequest;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.utility.MaterialHelper;
import net.knarcraft.stargate.utility.PortalFileHelper;
import org.bukkit.Material;
import org.bukkit.block.Block;
/**
* This thread updates the signs and buttons of Stargates, if deemed necessary
*/
public class ControlBlocksUpdateThread implements Runnable {
@Override
public void run() {
//Abort if there's no work to be done
ControlBlockUpdateRequest controlBlockUpdateRequest = Stargate.getButtonUpdateRequestQueue().poll();
if (controlBlockUpdateRequest == null) {
return;
}
Portal portal = controlBlockUpdateRequest.portal();
portal.drawSign();
Block buttonLocation = portal.getLocation().getButtonBlock();
if (buttonLocation == null) {
return;
}
Stargate.debug("ControlBlocksUpdateThread", "Updating control blocks for portal " + portal);
if (portal.getOptions().isAlwaysOn()) {
//Clear button if it exists
if (MaterialHelper.isButtonCompatible(buttonLocation.getType())) {
Material newMaterial = PortalFileHelper.decideRemovalMaterial(buttonLocation, portal);
Stargate.addControlBlockUpdateRequest(new BlockChangeRequest(buttonLocation, newMaterial, null));
}
} else {
//Replace button if the material is not a button
if (!MaterialHelper.isButtonCompatible(buttonLocation.getType())) {
PortalFileHelper.generatePortalButton(portal, portal.getLocation().getFacing());
}
}
}
}

View File

@@ -0,0 +1,208 @@
package net.knarcraft.stargate.transformation;
import org.bukkit.Axis;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* A class for performing rotational operations on vectors
*
* @author Kristian Knarvik
*/
@SuppressWarnings("unused")
public class SimpleVectorOperation {
private static final Map<BlockFace, Double> rotationAngles = new HashMap<>();
private static final Map<BlockFace, Vector> rotationAxes = new HashMap<>();
private static final Map<BlockFace, Axis> normalAxes = new HashMap<>();
private static final BlockFace defaultDirection = BlockFace.SOUTH;
private static final Axis defaultVerticalAxis = Axis.Y;
private final Axis normalAxis;
private boolean flipXAxis = false;
private final BlockFace facing;
/**
* Instantiates a vector operation to rotate vectors in the direction of a sign face
*
* @param signFace <p>The sign face of a gate's sign</p>
*/
public SimpleVectorOperation(@NotNull BlockFace signFace) {
if (normalAxes.isEmpty()) {
initializeIrisNormalAxes();
initializeOperations();
}
this.facing = signFace;
this.normalAxis = normalAxes.get(signFace);
}
/**
* Gets the block face of a sign given upon instantiation
*
* @return <p>The block face of a sign given upon instantiation</p>
*/
@NotNull
public BlockFace getFacing() {
return facing;
}
/**
* Gets the normal axis orthogonal to the opening plane
*
* <p>Said another way, get the axis going directly towards or away from a stargate's entrance.</p>
*
* @return <p>The normal axis orthogonal to the opening plane</p>
*/
@NotNull
public Axis getNormalAxis() {
return normalAxis;
}
/**
* Sets whether to flip the X- axis
*
* @param flipXAxis <p>Whether to flip the X-axis</p>
*/
public void setFlipXAxis(boolean flipXAxis) {
this.flipXAxis = flipXAxis;
}
/**
* Performs an operation from the real space to the vector space
*
* @param vector <p>The vector to perform the operation on</p>
* @return vector <p>A new vector with the operation applied</p>
*/
@NotNull
public Vector performToAbstractSpaceOperation(@NotNull Vector vector) {
Vector clone = vector.clone();
clone.rotateAroundAxis(rotationAxes.get(facing), rotationAngles.get(facing));
if (flipXAxis) {
clone.setX(-clone.getX());
}
return clone;
}
/**
* Performs an operation from the vector space to the real space
*
* @param vector <p>The vector to perform the inverse operation on</p>
* @return vector <p>A new vector with the operation applied</p>
*/
@NotNull
public Vector performToRealSpaceOperation(@NotNull Vector vector) {
Vector clone = vector.clone();
if (flipXAxis) {
clone.setX(-clone.getX());
}
return clone.rotateAroundAxis(rotationAxes.get(facing), -rotationAngles.get(facing));
}
/**
* Performs an operation from the vector space to the real space
*
* @param vector <p>The vector to perform the inverse operation on</p>
* @return vector <p>A new vector with the operation applied</p>
*/
@NotNull
public BlockVector performToRealSpaceOperation(@NotNull BlockVector vector) {
return performToRealSpaceOperation((Vector) vector).toBlockVector();
}
/**
* Gets the block vector pointing to the real location of the relative block vector
*
* @param topLeft <p>The vector of the top-left portal block</p>
* @param relative <p>The relative vector of a portal block</p>
* @return <p>The vector for the true location of the block in the real space</p>
*/
public BlockVector getRealLocationVector(@NotNull BlockVector topLeft, @NotNull BlockVector relative) {
return topLeft.clone().add(this.performToRealSpaceOperation(relative)).toBlockVector();
}
/**
* Gets the block pointing to the real location of the relative block vector
*
* @param topLeft <p>The top-left portal block</p>
* @param relative <p>The relative vector of a portal block</p>
* @return <p>The corresponding block</p>
*/
public Block getRealLocationBlock(@NotNull Block topLeft, @NotNull BlockVector relative) {
return topLeft.getLocation().clone().add(this.performToRealSpaceOperation(relative)).getBlock();
}
/**
* Initializes the operations used for rotating to each block-face
*/
private static void initializeOperations() {
Map<Axis, Vector> axisVectors = new HashMap<>();
axisVectors.put(Axis.Y, new Vector(0, 1, 0));
axisVectors.put(Axis.X, new Vector(1, 0, 0));
axisVectors.put(Axis.Z, new Vector(0, 0, 1));
//Use the cross product to find the correct axis
for (BlockFace face : normalAxes.keySet()) {
Vector crossProduct = face.getDirection().crossProduct(defaultDirection.getDirection());
if (face == defaultDirection || face == defaultDirection.getOppositeFace()) {
rotationAxes.put(face, axisVectors.get(defaultVerticalAxis));
} else if (Math.abs(crossProduct.getZ()) > 0) {
rotationAxes.put(face, axisVectors.get(Axis.Z));
} else if (Math.abs(crossProduct.getY()) > 0) {
rotationAxes.put(face, axisVectors.get(Axis.Y));
} else {
rotationAxes.put(face, axisVectors.get(Axis.X));
}
}
calculateRotations();
}
/**
* Calculates the required rotations based on the default rotation
*/
private static void calculateRotations() {
double halfRotation = Math.PI;
double quarterRotation = halfRotation / 2;
Vector defaultDirectionVector = defaultDirection.getDirection();
boolean defaultDirectionPositive = defaultDirectionVector.getX() + defaultDirectionVector.getY() +
defaultDirectionVector.getZ() > 0;
for (BlockFace blockFace : normalAxes.keySet()) {
if (defaultDirection == blockFace) {
//The default direction requires no rotation
rotationAngles.put(blockFace, 0d);
} else if (defaultDirection.getOppositeFace() == blockFace) {
//The opposite direction requires a half rotation
rotationAngles.put(blockFace, halfRotation);
} else {
//All the other used directions require a quarter rotation
Vector faceDirectionVector = blockFace.getDirection();
boolean faceDirectionPositive = faceDirectionVector.getX() + faceDirectionVector.getY() +
faceDirectionVector.getZ() > 0;
double rotation = defaultDirectionPositive && faceDirectionPositive ? quarterRotation : -quarterRotation;
rotationAngles.put(blockFace, rotation);
}
}
}
/**
* Initializes the iris normal axes corresponding to each block face
*/
private static void initializeIrisNormalAxes() {
normalAxes.put(BlockFace.EAST, Axis.Z);
normalAxes.put(BlockFace.WEST, Axis.Z);
normalAxes.put(BlockFace.NORTH, Axis.X);
normalAxes.put(BlockFace.SOUTH, Axis.X);
normalAxes.put(BlockFace.UP, Axis.Y);
normalAxes.put(BlockFace.DOWN, Axis.Y);
}
}

View File

@@ -0,0 +1,54 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.ConfigOption;
import net.knarcraft.stargate.portal.property.gate.GateHandler;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bstats.charts.SingleLineChart;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Map;
/**
* A helper for dealing with BStats
*/
public final class BStatsHelper {
private static boolean hasBeenInitialized = false;
private BStatsHelper() {
}
/**
* Initializes BStats
*
* @param plugin <p>The plugin to initialize BStats for</p>
*/
public static void initialize(@NotNull JavaPlugin plugin) {
if (hasBeenInitialized) {
throw new IllegalArgumentException("BStats initialized twice");
} else {
hasBeenInitialized = true;
}
int pluginId = 10451;
Metrics metrics = new Metrics(plugin, pluginId);
Map<ConfigOption, Object> configValues = Stargate.getStargateConfig().getConfigOptions();
Map<String, List<String>> portalNetworks = PortalUtil.getAllPortalNetworks();
int totalPortals = 0;
for (List<String> portals : portalNetworks.values()) {
totalPortals += portals.size();
}
metrics.addCustomChart(new SimplePie("language", () -> (String) configValues.get(ConfigOption.LANGUAGE)));
metrics.addCustomChart(new SimplePie("gateformats", () -> String.valueOf(GateHandler.getGateCount())));
int finalTotalPortals = totalPortals;
metrics.addCustomChart(new SingleLineChart("gatesv3", () -> finalTotalPortals));
}
}

View File

@@ -1,12 +1,14 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -36,6 +38,7 @@ public final class BungeeHelper {
*
* @return <p>The bungee plugin channel</p>
*/
@NotNull
public static String getBungeeChannel() {
return bungeeChannel;
}
@@ -48,9 +51,10 @@ public final class BungeeHelper {
* queue and teleported to the destination.</p>
*
* @param playerUUID <p>The UUID of the player to remove</p>
* @return <p>The name of the destination portal the player should be teleported to</p>
* @return <p>The name of the destination portal the player should be teleported to, or null if not queued</p>
*/
public static String removeFromQueue(UUID playerUUID) {
@Nullable
public static String removeFromQueue(@NotNull UUID playerUUID) {
return bungeeQueue.remove(playerUUID);
}
@@ -61,7 +65,7 @@ public final class BungeeHelper {
* @param entrancePortal <p>The portal the player is teleporting from</p>
* @return <p>True if the message was successfully sent</p>
*/
public static boolean sendTeleportationMessage(Player player, Portal entrancePortal) {
public static boolean sendTeleportationMessage(@NotNull Player player, @NotNull Portal entrancePortal) {
try {
//Build the teleportation message, format is <player identifier>delimiter<destination>
String message = player.getUniqueId() + teleportMessageDelimiter + entrancePortal.getDestinationName();
@@ -72,7 +76,7 @@ public final class BungeeHelper {
//Build the message data and send it over the SGBungee BungeeCord channel
dataOutputStream.writeUTF("Forward");
//Send the message to the server defined in the entrance portal's network line
dataOutputStream.writeUTF(stripColor(entrancePortal.getNetwork()));
dataOutputStream.writeUTF(Portal.cleanString(entrancePortal.getNetwork()));
//Specify the sub-channel/tag to make it recognizable on arrival
dataOutputStream.writeUTF(bungeeSubChannel);
//Write the length of the message
@@ -81,9 +85,8 @@ public final class BungeeHelper {
dataOutputStream.writeBytes(message);
//Send the plugin message
player.sendPluginMessage(Stargate.getInstance(), bungeeChannel, byteArrayOutputStream.toByteArray());
} catch (IOException ex) {
Stargate.logSevere("Error sending BungeeCord teleport packet");
ex.printStackTrace();
} catch (IOException exception) {
Stargate.logSevere("Error sending BungeeCord teleport packet! Message: " + exception.getMessage());
return false;
}
return true;
@@ -96,20 +99,19 @@ public final class BungeeHelper {
* @param entrancePortal <p>The bungee portal the player is teleporting from</p>
* @return <p>True if the plugin message was sent successfully</p>
*/
public static boolean changeServer(Player player, Portal entrancePortal) {
public static boolean changeServer(@NotNull Player player, @NotNull Portal entrancePortal) {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
//Send a connect-message to connect the player to the server defined in the entrance portal's network line
dataOutputStream.writeUTF("Connect");
dataOutputStream.writeUTF(stripColor(entrancePortal.getNetwork()));
dataOutputStream.writeUTF(Portal.cleanString(entrancePortal.getNetwork()));
//Send the plugin message
player.sendPluginMessage(Stargate.getInstance(), bungeeChannel, byteArrayOutputStream.toByteArray());
} catch (IOException ex) {
Stargate.logSevere("Error sending BungeeCord connect packet");
ex.printStackTrace();
} catch (IOException exception) {
Stargate.logSevere("Error sending BungeeCord connect packet! Message: " + exception.getMessage());
return false;
}
return true;
@@ -121,6 +123,7 @@ public final class BungeeHelper {
* @param message <p>The byte array to read</p>
* @return <p>The message contained in the byte array, or null on failure</p>
*/
@Nullable
public static String readPluginMessage(byte[] message) {
byte[] data;
try {
@@ -137,9 +140,8 @@ public final class BungeeHelper {
data = new byte[dataLength];
//Read the message to the prepared array
dataInputStream.readFully(data);
} catch (IOException ex) {
Stargate.logSevere("Error receiving BungeeCord message");
ex.printStackTrace();
} catch (IOException exception) {
Stargate.logSevere("Error receiving BungeeCord message. Message: " + exception.getMessage());
return null;
}
return new String(data);
@@ -150,7 +152,7 @@ public final class BungeeHelper {
*
* @param receivedMessage <p>The received teleport message</p>
*/
public static void handleTeleportMessage(String receivedMessage) {
public static void handleTeleportMessage(@NotNull String receivedMessage) {
//Get the player id and destination from the message
String[] messageParts = receivedMessage.split(teleportMessageDelimiter);
UUID playerUUID = UUID.fromString(messageParts[0]);
@@ -161,7 +163,7 @@ public final class BungeeHelper {
if (player == null) {
bungeeQueue.put(playerUUID, destination);
} else {
Portal destinationPortal = PortalHandler.getBungeePortal(destination);
Portal destinationPortal = PortalUtil.getBungeePortal(destination);
//If teleporting to an invalid portal, let the server decide where the player arrives
if (destinationPortal == null) {
Stargate.logInfo(String.format("Bungee portal %s does not exist", destination));
@@ -179,18 +181,19 @@ public final class BungeeHelper {
* @param event <p>The event causing the teleportation</p>
* @return <p>True if the teleportation was successful</p>
*/
public static boolean bungeeTeleport(Player player, Portal entrancePortal, PlayerMoveEvent event) {
public static boolean bungeeTeleport(@NotNull Player player, @NotNull Portal entrancePortal,
@NotNull PlayerMoveEvent event) {
//Check if bungee is actually enabled
if (!Stargate.getGateConfig().enableBungee()) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeDisabled"));
if (!entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.BUNGEE_DISABLED).error(player);
}
entrancePortal.getPortalOpener().closePortal(false);
return false;
}
//Teleport the player back to this gate, for sanity's sake
new PlayerTeleporter(entrancePortal, player).teleport(entrancePortal, event);
new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
//Send the SGBungee packet first, it will be queued by BC if required
if (!BungeeHelper.sendTeleportationMessage(player, entrancePortal)) {
@@ -208,14 +211,4 @@ public final class BungeeHelper {
return true;
}
/**
* Strips all color tags from a string
*
* @param string <p>The string to strip color from</p>
* @return <p>The string without color codes</p>
*/
private static String stripColor(String string) {
return ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', string));
}
}

View File

@@ -1,8 +1,14 @@
package net.knarcraft.stargate.utility;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.type.Sign;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This class helps with direction-related calculations
@@ -13,6 +19,22 @@ public final class DirectionHelper {
}
/**
* Gets the direction the given block is facing
*
* @param block <p>The block to get the direction of</p>
* @return <p>The direction the block is facing, or null if the block is not directional</p>
*/
@Nullable
public static BlockFace getFacing(@NotNull Block block) {
BlockData blockData = block.getBlockData();
if (blockData instanceof Directional directional) {
return directional.getFacing();
} else {
return null;
}
}
/**
* Gets a yaw by comparing two locations
*
@@ -24,7 +46,7 @@ public final class DirectionHelper {
* @param location2 <p>The second location, which the yaw will point towards</p>
* @return <p>The yaw pointing from the first location to the second location</p>
*/
public static float getYawFromLocationDifference(Location location1, Location location2) {
public static float getYawFromLocationDifference(@NotNull Location location1, @NotNull Location location2) {
Location difference = location1.clone().subtract(location2.clone());
if (difference.getX() > 0) {
return 90;
@@ -38,6 +60,32 @@ public final class DirectionHelper {
throw new IllegalArgumentException("Locations given are equal or at the same x and y axis");
}
/**
* Gets a block face by comparing two locations
*
* <p>The block face here is the direction an observer at the from location has to look to face the to location.
* The block face is only meant to be calculated for locations where both have either the same x value or the same
* z value. Equal locations, or locations with equal x and equal z will throw an exception.</p>
*
* @param fromLocation <p>The origin location</p>
* @param toLocation <p>The target location, which the block face will point towards</p>
* @return <p>The yaw pointing from the first location to the second location</p>
* @throws IllegalArgumentException <p>If the locations are the same, or equal except y</p>
*/
public static BlockFace getBlockFaceFromLocationDifference(@NotNull Location fromLocation, @NotNull Location toLocation) throws IllegalArgumentException {
Location difference = fromLocation.clone().subtract(toLocation.clone());
if (difference.getX() > 0) {
return BlockFace.WEST;
} else if (difference.getX() < 0) {
return BlockFace.EAST;
} else if (difference.getZ() > 0) {
return BlockFace.NORTH;
} else if (difference.getZ() < 0) {
return BlockFace.SOUTH;
}
throw new IllegalArgumentException("Locations given are equal or at the same x and y axis");
}
/**
* Gets a block face given a yaw value
*
@@ -45,8 +93,10 @@ public final class DirectionHelper {
*
* @param yaw <p>The yaw value to convert</p>
* @return <p>The block face the yaw corresponds to</p>
* @throws IllegalArgumentException <p>If a yaw not divisible by 90 us given</p>
*/
public static BlockFace getBlockFaceFromYaw(double yaw) {
@NotNull
public static BlockFace getBlockFaceFromYaw(double yaw) throws IllegalArgumentException {
//Make sure the yaw is between 0 and 360
yaw = normalizeYaw(yaw);
@@ -63,13 +113,32 @@ public final class DirectionHelper {
}
}
/**
* Gets a yaw from the specified block face
*
* @param blockFace <p>The block face to convert into a yaw</p>
* @return <p>The yaw of the block face</p>
* @throws IllegalArgumentException <p>If the block face is not pointing to one of the four primary directions</p>
*/
public static double getYawFromBlockFace(@NotNull BlockFace blockFace) throws IllegalArgumentException {
return switch (blockFace) {
case SOUTH -> 0;
case WEST -> 90;
case NORTH -> 180;
case EAST -> 270;
default -> throw new IllegalArgumentException("Invalid block face given. It must be one of N,S,W,E");
};
}
/**
* Gets a direction vector given a yaw
*
* @param yaw <p>The yaw to convert to a direction vector</p>
* @return <p>The direction vector pointing in the same direction as the yaw</p>
* @throws IllegalArgumentException <p>If a yaw not divisible by 90 is given</p>
*/
public static Vector getDirectionVectorFromYaw(double yaw) {
@NotNull
public static Vector getDirectionVectorFromYaw(double yaw) throws IllegalArgumentException {
//Make sure the yaw is between 0 and 360
yaw = normalizeYaw(yaw);
@@ -99,7 +168,8 @@ public final class DirectionHelper {
* @param yaw <p>The yaw when looking directly outwards from a portal</p>
* @return <p>A location relative to the given location</p>
*/
public static Location moveLocation(Location location, double right, double down, double out, double yaw) {
@NotNull
public static Location moveLocation(@NotNull Location location, double right, double down, double out, double yaw) {
return location.add(getCoordinateVectorFromRelativeVector(right, down, out, yaw));
}
@@ -111,18 +181,59 @@ public final class DirectionHelper {
* @param out <p>The distance outward from the top-left origin</p>
* @param yaw <p>The yaw when looking directly outwards from a portal</p>
* @return <p>A normal vector</p>
* @throws IllegalArgumentException <p>If a yaw not divisible by 90 is given</p>
*/
public static Vector getCoordinateVectorFromRelativeVector(double right, double down, double out, double yaw) {
Vector distanceVector = DirectionHelper.getDirectionVectorFromYaw(yaw);
distanceVector.multiply(out);
@NotNull
public static Vector getCoordinateVectorFromRelativeVector(double right, double down, double out,
double yaw) throws IllegalArgumentException {
//Make sure the yaw is between 0 and 360
yaw = normalizeYaw(yaw);
Vector rightVector = DirectionHelper.getDirectionVectorFromYaw(yaw - 90);
rightVector.multiply(right);
if (yaw == 0) {
//South
return new Vector(right, -down, out);
} else if (yaw == 90) {
//West
return new Vector(-out, -down, right);
} else if (yaw == 180) {
//North
return new Vector(-right, -down, -out);
} else if (yaw == 270) {
//East
return new Vector(out, -down, -right);
} else {
throw new IllegalArgumentException(String.format("Invalid yaw %f given", yaw));
}
}
Vector depthVector = new Vector(0, -1, 0);
depthVector.multiply(down);
/**
* Gets this block location's parent block
*
* <p>The parent block is the block the item at this block location is attached to. Usually this is the block a
* sign or wall sign is attached to.</p>
*
* @return <p>This block location's parent block</p>
*/
@Nullable
public static Block getParent(@NotNull Block block) {
int offsetX = 0;
int offsetY = 0;
int offsetZ = 0;
return distanceVector.add(rightVector).add(depthVector);
BlockData blockData = block.getBlockData();
if (blockData instanceof Directional) {
//Get the offset of the block "behind" this block
BlockFace facing = ((Directional) blockData).getFacing().getOppositeFace();
offsetX = facing.getModX();
offsetZ = facing.getModZ();
} else if (blockData instanceof Sign) {
//Get offset the block beneath the sign
offsetY = -1;
} else {
return null;
}
return block.getRelative(offsetX, offsetY, offsetZ);
}
/**

View File

@@ -1,12 +1,19 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.EconomyConfig;
import net.knarcraft.stargate.config.Permission;
import net.knarcraft.stargate.config.addons.EconomyConfig;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.property.PortalOwner;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
@@ -27,7 +34,7 @@ public final class EconomyHelper {
* @param cost <p>The cost of teleportation</p>
* @return <p>False if payment was successful. True if the payment was unsuccessful</p>
*/
public static boolean cannotPayTeleportFee(Portal entrancePortal, Player player, int cost) {
public static boolean cannotPayTeleportFee(@NotNull Portal entrancePortal, @NotNull Player player, int cost) {
boolean success;
//Try to charge the player. Paying the portal owner is only possible if a UUID is available
@@ -76,10 +83,8 @@ public final class EconomyHelper {
* @param portalOwner <p>The owner of the portal</p>
* @param earnings <p>The amount the owner earned</p>
*/
public static void sendObtainMessage(String portalName, Player portalOwner, int earnings) {
String obtainedMsg = Stargate.getString("ecoObtain");
obtainedMsg = replaceVars(obtainedMsg, portalName, earnings);
Stargate.getMessageSender().sendSuccessMessage(portalOwner, obtainedMsg);
public static void sendObtainMessage(@NotNull String portalName, @NotNull Player portalOwner, int earnings) {
replacePlaceholders(new SGFormatBuilder(Message.ECONOMY_OBTAINED), portalName, earnings).success(portalOwner);
}
/**
@@ -89,10 +94,8 @@ public final class EconomyHelper {
* @param player <p>The interacting player</p>
* @param cost <p>The cost of the interaction</p>
*/
public static void sendDeductMessage(String portalName, Player player, int cost) {
String deductMsg = Stargate.getString("ecoDeduct");
deductMsg = replaceVars(deductMsg, portalName, cost);
Stargate.getMessageSender().sendSuccessMessage(player, deductMsg);
public static void sendDeductMessage(@NotNull String portalName, @NotNull Player player, int cost) {
replacePlaceholders(new SGFormatBuilder(Message.ECONOMY_DEDUCTED), portalName, cost).success(player);
}
/**
@@ -102,10 +105,8 @@ public final class EconomyHelper {
* @param player <p>The interacting player</p>
* @param cost <p>The cost of the interaction</p>
*/
public static void sendInsufficientFundsMessage(String portalName, Player player, int cost) {
String inFundMsg = Stargate.getString("ecoInFunds");
inFundMsg = replaceVars(inFundMsg, portalName, cost);
Stargate.getMessageSender().sendErrorMessage(player, inFundMsg);
public static void sendInsufficientFundsMessage(@NotNull String portalName, @NotNull Player player, int cost) {
replacePlaceholders(new SGFormatBuilder(Message.ECONOMY_INSUFFICIENT), portalName, cost).error(player);
}
/**
@@ -115,10 +116,8 @@ public final class EconomyHelper {
* @param player <p>The player breaking the portal</p>
* @param cost <p>The amount the user has to pay for destroying the portal. (expects a negative value)</p>
*/
public static void sendRefundMessage(String portalName, Player player, int cost) {
String refundMsg = Stargate.getString("ecoRefund");
refundMsg = replaceVars(refundMsg, portalName, -cost);
Stargate.getMessageSender().sendSuccessMessage(player, refundMsg);
public static void sendRefundMessage(@NotNull String portalName, @NotNull Player player, int cost) {
replacePlaceholders(new SGFormatBuilder(Message.ECONOMY_REFUNDED), portalName, -cost).success(player);
}
/**
@@ -129,7 +128,7 @@ public final class EconomyHelper {
* @param destination <p>The destination portal</p>
* @return <p>The cost of using the portal</p>
*/
public static int getUseCost(Player player, Portal source, Portal destination) {
public static int getUseCost(@NotNull Player player, @NotNull Portal source, @Nullable Portal destination) {
EconomyConfig config = Stargate.getEconomyConfig();
//No payment required
if (!config.useEconomy() || source.getOptions().isFree()) {
@@ -144,7 +143,7 @@ public final class EconomyHelper {
return 0;
}
//Player gets free gate use
if (PermissionHelper.hasPermission(player, "stargate.free.use")) {
if (PermissionHelper.hasPermission(player, Permission.FREE_USAGE)) {
return 0;
}
@@ -159,7 +158,7 @@ public final class EconomyHelper {
* @param cost <p>The cost of the transaction</p>
* @return <p>True if the player was charged successfully</p>
*/
public static boolean chargePlayerIfNecessary(Player player, UUID target, int cost) {
public static boolean chargePlayerIfNecessary(@NotNull Player player, @NotNull UUID target, int cost) {
if (skipPayment(cost)) {
return true;
}
@@ -174,17 +173,43 @@ public final class EconomyHelper {
* @param amount <p>The amount to charge</p>
* @return <p>True if the payment succeeded, or if no payment was necessary</p>
*/
private static boolean chargePlayer(Player player, double amount) {
private static boolean chargePlayer(@NotNull Player player, double amount) {
Economy economy = Stargate.getEconomyConfig().getEconomy();
if (Stargate.getEconomyConfig().isEconomyEnabled() && economy != null) {
if (!economy.has(player, amount)) {
return false;
}
economy.withdrawPlayer(player, amount);
if (amount > 0) {
economy.withdrawPlayer(player, amount);
} else {
economy.depositPlayer(player, -amount);
}
}
return true;
}
/**
* Transfers the given fees to the tax account
*
* @param economy <p>The economy to use</p>
* @param cost <p>The cost to transfer</p>
*/
@SuppressWarnings("deprecation")
private static void transferFees(@NotNull Economy economy, int cost) {
String accountName = Stargate.getEconomyConfig().getTaxAccount();
if (accountName == null || accountName.isEmpty()) {
return;
}
try {
UUID accountId = UUID.fromString(accountName);
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(accountId);
economy.depositPlayer(offlinePlayer, cost);
} catch (IllegalArgumentException exception) {
economy.depositPlayer(accountName, cost);
}
}
/**
* Charges the player for an action, if required
*
@@ -192,12 +217,20 @@ public final class EconomyHelper {
* @param cost <p>The cost of the transaction</p>
* @return <p>True if the player was charged successfully</p>
*/
public static boolean chargePlayerIfNecessary(Player player, int cost) {
public static boolean chargePlayerIfNecessary(@NotNull Player player, int cost) {
if (skipPayment(cost)) {
return true;
}
//Charge player
return chargePlayer(player, cost);
boolean charged = chargePlayer(player, cost);
// Transfer the charged amount to the tax account
Economy economy = Stargate.getEconomyConfig().getEconomy();
if (charged && economy != null) {
transferFees(economy, cost);
}
return charged;
}
/**
@@ -218,9 +251,10 @@ public final class EconomyHelper {
* @param amount <p>The amount to charge</p>
* @return <p>True if the payment succeeded, or if no payment was necessary</p>
*/
private static boolean chargePlayer(Player player, UUID target, double amount) {
private static boolean chargePlayer(@NotNull Player player, @NotNull UUID target, double amount) {
Economy economy = Stargate.getEconomyConfig().getEconomy();
if (Stargate.getEconomyConfig().isEconomyEnabled() && player.getUniqueId().compareTo(target) != 0 && economy != null) {
if (Stargate.getEconomyConfig().isEconomyEnabled() && player.getUniqueId().compareTo(target) != 0 &&
economy != null) {
if (!economy.has(player, amount)) {
return false;
}
@@ -232,16 +266,16 @@ public final class EconomyHelper {
}
/**
* Replaces the cost and portal variables in a string
* Replaces the cost and portal variables in a format builder
*
* @param message <p>The message to replace variables in</p>
* @param builder <p>The format builder to replace variables for</p>
* @param portalName <p>The name of the relevant portal</p>
* @param cost <p>The cost for a given interaction</p>
* @return <p>The same string with cost and portal variables replaced</p>
* @return <p>The same format builder</p>
*/
private static String replaceVars(String message, String portalName, int cost) {
return Stargate.replaceVars(message, new String[]{"%cost%", "%portal%"},
new String[]{Stargate.getEconomyConfig().format(cost), portalName});
private static FormatBuilder replacePlaceholders(@NotNull FormatBuilder builder, @NotNull String portalName, int cost) {
builder.replace("%cost%", Stargate.getEconomyConfig().format(cost)).replace("%portal%", portalName);
return builder;
}
}

View File

@@ -1,6 +1,7 @@
package net.knarcraft.stargate.utility;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;
/**
* This helper class helps with entity properties not immediately available
@@ -21,7 +22,7 @@ public final class EntityHelper {
* @param entity <p>The entity to get max size for</p>
* @return <p>The max size of the entity</p>
*/
public static int getEntityMaxSizeInt(Entity entity) {
public static int getEntityMaxSizeInt(@NotNull Entity entity) {
return (int) Math.ceil((float) getEntityMaxSize(entity));
}
@@ -31,7 +32,7 @@ public final class EntityHelper {
* @param entity <p>The entity to get max size for</p>
* @return <p>The max size of the entity</p>
*/
public static double getEntityMaxSize(Entity entity) {
public static double getEntityMaxSize(@NotNull Entity entity) {
return Math.max(entity.getBoundingBox().getWidthX(), entity.getBoundingBox().getWidthZ());
}

View File

@@ -1,127 +0,0 @@
package net.knarcraft.stargate.utility;
import org.bukkit.ChatColor;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* Helper class for reading files
*/
public final class FileHelper {
private FileHelper() {
}
/**
* Gets an input stream from a string pointing to an internal file
*
* <p>This is used for getting an input stream for reading a file contained within the compiled .jar file. The file
* should be in the resources directory, and the file path should start with a forward slash ("/") character.</p>
*
* @param file <p>The file to read</p>
* @return <p>An input stream for the file</p>
*/
public static InputStream getInputStreamForInternalFile(String file) {
return FileHelper.class.getResourceAsStream(file);
}
/**
* Gets a buffered reader from a string pointing to a file
*
* @param file <p>The file to read</p>
* @return <p>A buffered reader reading the file</p>
* @throws FileNotFoundException <p>If the given file does not exist</p>
*/
public static BufferedReader getBufferedReaderFromString(String file) throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream(file);
return getBufferedReaderFromInputStream(fileInputStream);
}
/**
* Gets a buffered reader given an input stream
*
* @param inputStream <p>The input stream to read</p>
* @return <p>A buffered reader reading the input stream</p>
*/
public static BufferedReader getBufferedReaderFromInputStream(InputStream inputStream) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return new BufferedReader(inputStreamReader);
}
/**
* Gets a buffered writer from a string pointing to a file
*
* @param file <p>The file to write to</p>
* @return <p>A buffered writer writing to the file</p>
* @throws FileNotFoundException <p>If the file does not exist</p>
*/
public static BufferedWriter getBufferedWriterFromString(String file) throws FileNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream(file);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8);
return new BufferedWriter(outputStreamWriter);
}
/**
* Reads key/value pairs from an input stream
*
* @param bufferedReader <p>The buffered reader to read</p>
* @return <p>A map containing the read pairs</p>
* @throws IOException <p>If unable to read from the stream</p>
*/
public static Map<String, String> readKeyValuePairs(BufferedReader bufferedReader) throws IOException {
Map<String, String> readPairs = new HashMap<>();
String line = bufferedReader.readLine();
boolean firstLine = true;
while (line != null) {
//Strip UTF BOM from the first line
if (firstLine) {
line = removeUTF8BOM(line);
firstLine = false;
}
//Split at first "="
int equalSignIndex = line.indexOf('=');
if (equalSignIndex == -1) {
line = bufferedReader.readLine();
continue;
}
//Read the line
String key = line.substring(0, equalSignIndex);
String value = ChatColor.translateAlternateColorCodes('&', line.substring(equalSignIndex + 1));
readPairs.put(key, value);
line = bufferedReader.readLine();
}
bufferedReader.close();
return readPairs;
}
/**
* Removes the UTF-8 Byte Order Mark if present
*
* @param string <p>The string to remove the BOM from</p>
* @return <p>A string guaranteed without a BOM</p>
*/
private static String removeUTF8BOM(String string) {
String UTF8_BOM = "\uFEFF";
if (string.startsWith(UTF8_BOM)) {
string = string.substring(1);
}
return string;
}
}

View File

@@ -1,13 +1,16 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.material.BukkitMaterialSpecifier;
import net.knarcraft.stargate.config.material.MaterialSpecifier;
import org.bukkit.Material;
import org.bukkit.configuration.InvalidConfigurationException;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
/**
* Helper class for reading gate files
@@ -25,15 +28,16 @@ public final class GateReader {
* @param characterMaterialMap <p>The map of characters to store valid symbols in</p>
* @param fileName <p>The filename of the loaded gate config file</p>
* @param design <p>The list to store the loaded design/layout to</p>
* @param frameTypes <p>The set to store frame/border materials to</p>
* @param config <p>The map of config values to store to</p>
* @return <p>The column count/width of the loaded gate</p>
*/
public static int readGateFile(Scanner scanner, Map<Character, Material> characterMaterialMap, String fileName,
List<List<Character>> design, Set<Material> frameTypes, Map<String, String> config) {
public static int readGateFile(@NotNull Scanner scanner,
@NotNull Map<Character, List<MaterialSpecifier>> characterMaterialMap,
@NotNull String fileName, @NotNull List<List<Character>> design,
@NotNull Map<String, String> config) {
boolean designing = false;
int columns = 0;
try {
try (scanner) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
@@ -46,7 +50,7 @@ public final class GateReader {
} else {
if (!line.isEmpty() && !line.startsWith("#")) {
//Read a normal config value
readGateConfigValue(line, characterMaterialMap, frameTypes, config);
readGateConfigValue(line, characterMaterialMap, config);
} else if ((line.isEmpty()) || (!line.contains("=") && !line.startsWith("#"))) {
//An empty line marks the start of the gate's layout/design
designing = true;
@@ -56,10 +60,6 @@ public final class GateReader {
} catch (Exception exception) {
Stargate.logSevere(String.format("Could not load Gate %s - %s", fileName, exception.getMessage()));
return -1;
} finally {
if (scanner != null) {
scanner.close();
}
}
return columns;
}
@@ -77,8 +77,9 @@ public final class GateReader {
* @param design <p>The two-dimensional list to store the loaded design to</p>
* @return <p>The new max columns value of the design</p>
*/
private static int readGateDesignLine(String line, int maxColumns, Map<Character, Material> characterMaterialMap,
String fileName, List<List<Character>> design) {
private static int readGateDesignLine(@NotNull String line, int maxColumns,
@NotNull Map<Character, List<MaterialSpecifier>> characterMaterialMap,
@NotNull String fileName, @NotNull List<List<Character>> design) {
List<Character> row = new ArrayList<>();
//Update the max columns number if this line has more columns
@@ -88,7 +89,7 @@ public final class GateReader {
for (Character symbol : line.toCharArray()) {
//Refuse read gate designs with unknown characters
if (symbol.equals('?') || (!characterMaterialMap.containsKey(symbol))) {
if (symbol.equals('?') || !characterMaterialMap.containsKey(symbol)) {
Stargate.logSevere(String.format("Could not load Gate %s - Unknown symbol '%s' in diagram", fileName,
symbol));
return -1;
@@ -107,12 +108,12 @@ public final class GateReader {
*
* @param line <p>The line to read</p>
* @param characterMaterialMap <p>The character to material map to store to</p>
* @param frameTypes <p>The set to store gate frame/border types to</p>
* @param config <p>The config value map to store to</p>
* @throws Exception <p>If an invalid material is encountered</p>
* @throws InvalidConfigurationException <p>If an invalid material is encountered</p>
*/
private static void readGateConfigValue(String line, Map<Character, Material> characterMaterialMap,
Set<Material> frameTypes, Map<String, String> config) throws Exception {
private static void readGateConfigValue(@NotNull String line,
@NotNull Map<Character, List<MaterialSpecifier>> characterMaterialMap,
@NotNull Map<String, String> config) throws InvalidConfigurationException {
String[] split = line.split("=");
String key = split[0].trim();
String value = split[1].trim();
@@ -120,14 +121,13 @@ public final class GateReader {
if (key.length() == 1) {
//Read a gate frame material
Character symbol = key.charAt(0);
Material material = Material.getMaterial(value);
if (material == null) {
throw new Exception("Invalid material in line: " + line);
List<MaterialSpecifier> materials = MaterialHelper.parseTagsAndMaterials(value);
if (!materials.isEmpty()) {
characterMaterialMap.put(symbol, materials);
} else {
throw new InvalidConfigurationException("Invalid material in line: " + line);
}
//Register the map between the read symbol and the corresponding material
characterMaterialMap.put(symbol, material);
//Save the material as one of the frame materials used for this kind of gate
frameTypes.add(material);
} else {
//Read a normal config value
config.put(key, value);
@@ -142,12 +142,13 @@ public final class GateReader {
* @param key <p>The config key to read</p>
* @return <p>The read value, or -1 if it could not be read</p>
*/
public static int readGateConfig(Map<String, String> config, String fileName, String key) {
public static int readGateConfig(@NotNull Map<String, String> config, @NotNull String fileName,
@NotNull String key) {
if (config.containsKey(key)) {
try {
return Integer.parseInt(config.get(key));
} catch (NumberFormatException ex) {
Stargate.logWarning(String.format("%s reading %s: %s is not numeric", ex.getClass().getName(),
} catch (NumberFormatException exception) {
Stargate.logWarning(String.format("%s reading %s: %s is not numeric", exception.getClass().getName(),
fileName, key));
}
}
@@ -164,17 +165,18 @@ public final class GateReader {
* @param defaultMaterial <p>The default material to use, in case the config is invalid</p>
* @return <p>The material specified in the config, or the default material if it could not be read</p>
*/
public static Material readGateConfig(Map<String, String> config, String fileName, String key,
Material defaultMaterial) {
@NotNull
public static List<MaterialSpecifier> readGateConfig(@NotNull Map<String, String> config, @NotNull String fileName,
@NotNull String key, @NotNull Material defaultMaterial) {
if (config.containsKey(key)) {
Material material = Material.getMaterial(config.get(key));
if (material != null) {
return material;
List<MaterialSpecifier> materialSpecifiers = MaterialHelper.parseTagsAndMaterials(config.get(key));
if (!materialSpecifiers.isEmpty()) {
return materialSpecifiers;
} else {
Stargate.logWarning(String.format("Error reading %s: %s is not a material", fileName, key));
}
}
return defaultMaterial;
return List.of(new BukkitMaterialSpecifier(defaultMaterial));
}
/**
@@ -187,7 +189,8 @@ public final class GateReader {
* @param columns <p>The largest amount of columns in the design</p>
* @return <p>A matrix containing the gate's layout</p>
*/
public static Character[][] generateLayoutMatrix(List<List<Character>> design, int columns) {
@NotNull
public static Character[][] generateLayoutMatrix(@NotNull List<List<Character>> design, int columns) {
Character[][] layout = new Character[design.size()][columns];
for (int lineIndex = 0; lineIndex < design.size(); lineIndex++) {
List<Character> row = design.get(lineIndex);

View File

@@ -0,0 +1,28 @@
package net.knarcraft.stargate.utility;
import java.util.List;
import java.util.Random;
/**
* A helper class for dealing with lists
*/
public final class ListHelper {
private static final Random random = new Random();
private ListHelper() {
}
/**
* Gets a random item from a list
*
* @param list <p>The list to get an item from</p>
* @param <T> <p>The type of item the list contains</p>
* @return <p>A random item</p>
*/
public static <T> T getRandom(List<T> list) {
return list.get(random.nextInt(list.size()));
}
}

View File

@@ -1,7 +1,19 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.config.material.BukkitMaterialSpecifier;
import net.knarcraft.stargate.config.material.BukkitTagSpecifier;
import net.knarcraft.stargate.config.material.MaterialSpecifier;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This class helps decide properties of materials not already present in the Spigot API
@@ -18,7 +30,7 @@ public final class MaterialHelper {
* @param material <p>The material to check</p>
* @return <p>True if the material is a wall coral</p>
*/
public static boolean isWallCoral(Material material) {
public static boolean isWallCoral(@NotNull Material material) {
//Unfortunately, there is no tag for dead wall corals, so they need to be checked manually
return Tag.WALL_CORALS.isTagged(material) ||
material.equals(Material.DEAD_BRAIN_CORAL_WALL_FAN) ||
@@ -34,7 +46,7 @@ public final class MaterialHelper {
* @param material <p>The material to check</p>
* @return <p>True if the material is a container</p>
*/
public static boolean isContainer(Material material) {
public static boolean isContainer(@NotNull Material material) {
return Tag.SHULKER_BOXES.isTagged(material) || material == Material.CHEST ||
material == Material.TRAPPED_CHEST || material == Material.ENDER_CHEST;
}
@@ -45,8 +57,84 @@ public final class MaterialHelper {
* @param material <p>The material to check</p>
* @return <p>True if the material can be used as a button</p>
*/
public static boolean isButtonCompatible(Material material) {
public static boolean isButtonCompatible(@NotNull Material material) {
return Tag.BUTTONS.isTagged(material) || isWallCoral(material) || isContainer(material);
}
@NotNull
public static String specifiersToString(@NotNull List<MaterialSpecifier> specifiers) {
List<String> names = new ArrayList<>();
for (MaterialSpecifier specifier : specifiers) {
names.add(specifier.asString());
}
return String.join(",", names);
}
/**
* Converts a list of material specifiers to a set of materials
*
* @param specifiers <p>The material specifiers to convert</p>
* @return <p>The materials the specifiers represent</p>
*/
@NotNull
public static Set<Material> specifiersToMaterials(@NotNull List<MaterialSpecifier> specifiers) {
Set<Material> output = new HashSet<>();
for (MaterialSpecifier specifier : specifiers) {
output.addAll(specifier.asMaterials());
}
return output;
}
/**
* Parses all materials and material tags found in the input string
*
* @param input <p>The input string to parse</p>
* @return <p>All material specifiers found</p>
*/
@NotNull
public static List<MaterialSpecifier> parseTagsAndMaterials(@NotNull String input) {
List<MaterialSpecifier> specifiers = new ArrayList<>();
// Nothing to parse
if (input.isBlank()) {
return specifiers;
}
String[] parts;
if (input.contains(",")) {
parts = input.split(",");
} else {
parts = new String[]{input};
}
for (String part : parts) {
MaterialSpecifier materialSpecifier = parseTagOrMaterial(part.trim());
if (materialSpecifier != null) {
specifiers.add(materialSpecifier);
}
}
return specifiers;
}
@Nullable
private static MaterialSpecifier parseTagOrMaterial(@NotNull String input) {
if (input.startsWith("#")) {
String tagString = input.replaceFirst("#", "").toLowerCase();
Tag<Material> tag = Bukkit.getTag(Tag.REGISTRY_BLOCKS, NamespacedKey.minecraft(tagString), Material.class);
if (tag != null) {
return new BukkitTagSpecifier(tag);
}
} else {
Material material = Material.matchMaterial(input);
if (material != null) {
return new BukkitMaterialSpecifier(material);
}
}
return null;
}
}

View File

@@ -1,12 +1,17 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.Permission;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.event.StargateAccessEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.property.PortalOption;
import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.knarcraft.stargate.Stargate.getMaxNameNetworkLength;
@@ -25,7 +30,7 @@ public final class PermissionHelper {
* @param player <p>The player opening the portal</p>
* @param portal <p>The portal to open</p>
*/
public static void openPortal(Player player, Portal portal) {
public static void openPortal(@NotNull Player player, @NotNull Portal portal) {
Portal destination = portal.getPortalActivator().getDestination();
//For an always open portal, no action is necessary
@@ -35,8 +40,8 @@ public final class PermissionHelper {
//Destination is invalid or the same portal. Send an error message
if (destination == null || destination == portal) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("invalidMsg"));
if (!portal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.INVALID_DESTINATION).error(player);
}
return;
}
@@ -50,28 +55,8 @@ public final class PermissionHelper {
return;
}
//Deny access if another player has activated the portal, and it's still in use
if (!portal.getOptions().isFixed() && portal.getPortalActivator().isActive() &&
portal.getActivePlayer() != player) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
return;
}
//Check if the player can use the private gate
if (portal.getOptions().isPrivate() && !PermissionHelper.canUsePrivatePortal(player, portal)) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
return;
}
//Destination is currently in use by another player, blocking teleportation
if (destination.isOpen() && !destination.getOptions().isAlwaysOn()) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("blockMsg"));
}
// Check if the player is able to open the portal
if (canNotOpen(player, portal, destination)) {
return;
}
@@ -79,6 +64,43 @@ public final class PermissionHelper {
portal.getPortalOpener().openPortal(player, false);
}
/**
* Checks whether something prevents the player from opening the given portal to the given destination
*
* @param player <p>The player trying to open the portal</p>
* @param portal <p>The portal to open</p>
* @param destination <p>The destination the player is attempting to open</p>
* @return <p>True if the player cannot open the portal</p>
*/
private static boolean canNotOpen(Player player, Portal portal, Portal destination) {
//Deny access if another player has activated the portal, and it's still in use
if (!portal.getOptions().isFixed() && portal.getPortalActivator().isActive() &&
portal.getActivePlayer() != player) {
if (!portal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ACCESS_DENIED).error(player);
}
return true;
}
//Check if the player can use the private gate
if (portal.getOptions().isPrivate() && !PermissionHelper.canUsePrivatePortal(player, portal)) {
if (!portal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ACCESS_DENIED).error(player);
}
return true;
}
//Destination is currently in use by another player, blocking teleportation
if (destination.isOpen() && !destination.getOptions().isAlwaysOn()) {
if (!portal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.DESTINATION_BLOCKED).error(player);
}
return true;
}
return false;
}
/**
* Creates a StargateAccessEvent and gets the updated deny value
*
@@ -89,7 +111,7 @@ public final class PermissionHelper {
* @param deny <p>Whether the player's access has already been denied by a previous check</p>
* @return <p>False if the player should be allowed through the portal</p>
*/
public static boolean portalAccessDenied(Player player, Portal portal, boolean deny) {
public static boolean portalAccessDenied(@NotNull Player player, @NotNull Portal portal, boolean deny) {
StargateAccessEvent event = new StargateAccessEvent(player, portal, deny);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
return event.getDeny();
@@ -103,23 +125,31 @@ public final class PermissionHelper {
* @param destination <p>The portal the user wants to exit from</p>
* @return <p>False if the user is allowed to access the portal</p>
*/
public static boolean cannotAccessPortal(Player player, Portal entrancePortal, Portal destination) {
public static boolean cannotAccessPortal(@NotNull Player player, @NotNull Portal entrancePortal,
@Nullable Portal destination) {
boolean deny = false;
String route = "PermissionHelper::cannotAccessPortal";
if (entrancePortal.getOptions().isBungee()) {
if (!PermissionHelper.canAccessServer(player, entrancePortal.getCleanNetwork())) {
//If the portal is a bungee portal, and the player cannot access the server, deny
Stargate.debug("cannotAccessPortal", "Cannot access server");
Stargate.debug(route, "Cannot access server");
deny = true;
}
} else if (PermissionHelper.cannotAccessNetwork(player, entrancePortal.getCleanNetwork())) {
//If the player does not have access to the network, deny
Stargate.debug("cannotAccessPortal", "Cannot access network");
deny = true;
} else if (PermissionHelper.cannotAccessWorld(player, destination.getWorld().getName())) {
//If the player does not have access to the portal's world, deny
Stargate.debug("cannotAccessPortal", "Cannot access world");
Stargate.debug(route, "Cannot access network");
deny = true;
} else {
if (destination == null) {
//If there is no destination, deny
Stargate.debug(route, "Portal has no destination");
deny = true;
} else if (PermissionHelper.cannotAccessWorld(player, destination.getLocation().getWorld().getName())) {
//If the player does not have access to the portal's world, deny
Stargate.debug(route, "Cannot access world");
deny = true;
}
}
//Allow other plugins to override whether the player can access the portal
return portalAccessDenied(player, entrancePortal, deny);
@@ -134,7 +164,7 @@ public final class PermissionHelper {
* @param permission <p>The permission to check</p>
* @return <p>True if the player has the permission</p>
*/
public static boolean hasPermission(Player player, String permission) {
public static boolean hasPermission(@NotNull Player player, @NotNull String permission) {
if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
Stargate.debug("hasPerm::Permission(" + player.getName() + ")", permission + " => " +
player.hasPermission(permission));
@@ -142,6 +172,19 @@ public final class PermissionHelper {
return player.hasPermission(permission);
}
/**
* Checks whether a player has the given permission
*
* <p>This is the same as player.hasPermission(), but this function allows for printing permission debugging info.</p>
*
* @param player <p>The player to check</p>
* @param permission <p>The permission to check</p>
* @return <p>True if the player has the permission</p>
*/
public static boolean hasPermission(@NotNull Player player, @NotNull Permission permission) {
return hasPermission(player, permission.getNode());
}
/**
* Check if a player has been given a permission implicitly
*
@@ -152,14 +195,15 @@ public final class PermissionHelper {
* @param permission <p>The permission to check</p>
* @return <p>True if the player has the permission implicitly or explicitly</p>
*/
public static boolean hasPermissionImplicit(Player player, String permission) {
public static boolean hasPermissionImplicit(@NotNull Player player, @NotNull String permission) {
boolean debug = Stargate.getStargateConfig().isPermissionDebuggingEnabled();
if (!player.isPermissionSet(permission)) {
if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
if (debug) {
Stargate.debug("hasPermissionImplicit::Permission", permission + " => implicitly true");
}
return true;
}
if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
if (debug) {
Stargate.debug("hasPermissionImplicit::Permission", permission + " => " +
player.hasPermission(permission));
}
@@ -173,14 +217,8 @@ public final class PermissionHelper {
* @param world <p>The world the player is trying to access</p>
* @return <p>False if the player should be allowed to access the world</p>
*/
public static boolean cannotAccessWorld(Player player, String world) {
//The player can access all worlds
if (hasPermission(player, "stargate.world")) {
//Check if the world permission has been explicitly denied
return !hasPermissionImplicit(player, "stargate.world." + world);
}
//The player can access the destination world
return !hasPermission(player, "stargate.world." + world);
public static boolean cannotAccessWorld(@NotNull Player player, @NotNull String world) {
return hasPermission(player, Permission.ACCESS_WORLD, world);
}
/**
@@ -190,22 +228,17 @@ public final class PermissionHelper {
* @param network <p>The network to check</p>
* @return <p>True if the player is denied from accessing the network</p>
*/
public static boolean cannotAccessNetwork(Player player, String network) {
//The player can access all networks
if (hasPermission(player, "stargate.network")) {
//Check if the world permission has been explicitly denied
return !hasPermissionImplicit(player, "stargate.network." + network);
}
//Check if the player can access this network
if (hasPermission(player, "stargate.network." + network)) {
public static boolean cannotAccessNetwork(@NotNull Player player, @NotNull String network) {
if (hasPermission(player, Permission.ACCESS_NETWORK, network)) {
return false;
}
//Is able to create personal gates (Assumption is made they can also access them)
String playerName = player.getName();
if (playerName.length() > getMaxNameNetworkLength()) {
playerName = playerName.substring(0, getMaxNameNetworkLength());
}
return !network.equals(playerName) || !hasPermission(player, "stargate.create.personal");
return !network.equals(playerName) || !hasPermission(player, Permission.CREATE_PERSONAL);
}
/**
@@ -215,14 +248,8 @@ public final class PermissionHelper {
* @param server <p>The server the player is trying to connect to</p>
* @return <p>True if the player is allowed to access the given server</p>
*/
public static boolean canAccessServer(Player player, String server) {
//The player can access all servers
if (hasPermission(player, "stargate.server")) {
//Check if the server permission has been explicitly denied
return hasPermissionImplicit(player, "stargate.server." + server);
}
//The player can access the destination server
return hasPermission(player, "stargate.server." + server);
public static boolean canAccessServer(@NotNull Player player, @NotNull String server) {
return hasPermission(player, Permission.ACCESS_SERVER, server);
}
/**
@@ -233,13 +260,13 @@ public final class PermissionHelper {
* @param dest <p>The portal the player wants to teleport to</p>
* @return <p>True if the player can travel for free</p>
*/
public static boolean isFree(Player player, Portal src, Portal dest) {
public static boolean isFree(@NotNull Player player, @NotNull Portal src, @Nullable Portal dest) {
//This portal is free
if (src.getOptions().isFree()) {
return true;
}
//Player can use this portal for free
if (hasPermission(player, "stargate.free.use")) {
if (hasPermission(player, Permission.FREE_USAGE)) {
return true;
}
//Don't charge for free destinations unless specified in the config
@@ -255,13 +282,13 @@ public final class PermissionHelper {
* @param portal <p>The portal to check</p>
* @return <p>True if the given player can see the given portal</p>
*/
public static boolean canSeePortal(Player player, Portal portal) {
public static boolean canSeePortal(@NotNull Player player, @NotNull Portal portal) {
//The portal is not hidden
if (!portal.getOptions().isHidden()) {
return true;
}
//The player can see all hidden portals
if (hasPermission(player, "stargate.admin.hidden")) {
if (hasPermission(player, Permission.SEE_HIDDEN)) {
return true;
}
//The player is the owner of the portal
@@ -275,13 +302,13 @@ public final class PermissionHelper {
* @param portal <p>The private portal used</p>
* @return <p>True if the player is allowed to use the portal</p>
*/
public static boolean canUsePrivatePortal(Player player, Portal portal) {
public static boolean canUsePrivatePortal(@NotNull Player player, @NotNull Portal portal) {
//Check if the player is the owner of the gate
if (portal.isOwner(player)) {
return true;
}
//The player is an admin with the ability to use private gates
return hasPermission(player, "stargate.admin.private");
return hasPermission(player, Permission.USE_PRIVATE);
}
/**
@@ -291,8 +318,8 @@ public final class PermissionHelper {
* @param option <p>The option the player is trying to use</p>
* @return <p>True if the player is allowed to create a portal with the given option</p>
*/
public static boolean canUseOption(Player player, PortalOption option) {
return hasPermission(player, option.getPermissionString());
public static boolean canUseOption(@NotNull Player player, @NotNull PortalOption option) {
return hasPermission(player, option.getPermission());
}
/**
@@ -302,14 +329,8 @@ public final class PermissionHelper {
* @param network <p>The network the player is trying to create a gate on</p>
* @return <p>True if the player is allowed to create the new gate</p>
*/
public static boolean canCreateNetworkGate(Player player, String network) {
//Check if the player is allowed to create a portal on any network
if (hasPermission(player, "stargate.create.network")) {
//Check if the network has been explicitly denied
return hasPermissionImplicit(player, "stargate.create.network." + network);
}
//Check if the player is allowed to create on this specific network
return hasPermission(player, "stargate.create.network." + network);
public static boolean canCreateNetworkGate(@NotNull Player player, @NotNull String network) {
return hasPermission(player, Permission.CREATE_NETWORK, network);
}
/**
@@ -318,8 +339,8 @@ public final class PermissionHelper {
* @param player <p>The player trying to create the new gate</p>
* @return <p>True if the player is allowed</p>
*/
public static boolean canCreatePersonalPortal(Player player) {
return hasPermission(player, "stargate.create.personal");
public static boolean canCreatePersonalPortal(@NotNull Player player) {
return hasPermission(player, Permission.CREATE_PERSONAL);
}
/**
@@ -329,14 +350,8 @@ public final class PermissionHelper {
* @param gate <p>The gate type of the new portal</p>
* @return <p>True if the player is allowed to create a portal with the given gate layout</p>
*/
public static boolean canCreatePortal(Player player, String gate) {
//Check if the player is allowed to create all gates
if (hasPermission(player, "stargate.create.gate")) {
//Check if the gate type has been explicitly denied
return hasPermissionImplicit(player, "stargate.create.gate." + gate);
}
//Check if the player can create the specific gate type
return hasPermission(player, "stargate.create.gate." + gate);
public static boolean canCreatePortal(@NotNull Player player, @NotNull String gate) {
return hasPermission(player, Permission.CREATE_GATE, gate);
}
/**
@@ -346,25 +361,19 @@ public final class PermissionHelper {
* @param portal <p>The portal to destroy</p>
* @return <p>True if the player is allowed to destroy the portal</p>
*/
public static boolean canDestroyPortal(Player player, Portal portal) {
public static boolean canDestroyPortal(@NotNull Player player, @NotNull Portal portal) {
String network = portal.getCleanNetwork();
//Use a special check for bungee portals
if (portal.getOptions().isBungee()) {
return hasPermission(player, "stargate.admin.bungee");
return hasPermission(player, Permission.CREATE_BUNGEE);
}
//Check if the player is allowed to destroy on all networks
if (hasPermission(player, "stargate.destroy.network")) {
//Check if the network has been explicitly denied
return hasPermissionImplicit(player, "stargate.destroy.network." + network);
}
//Check if the player is allowed to destroy on the network
if (hasPermission(player, "stargate.destroy.network." + network)) {
if (hasPermission(player, Permission.DESTROY_NETWORK, network)) {
return true;
}
//Check if personal portal and if the player is allowed to destroy it
return portal.isOwner(player) && hasPermission(player, "stargate.destroy.personal");
return portal.isOwner(player) && hasPermission(player, Permission.DESTROY_PERSONAL);
}
/**
@@ -376,7 +385,8 @@ public final class PermissionHelper {
* @param event <p>The move event causing the teleportation</p>
* @return <p>True if the player cannot teleport. False otherwise</p>
*/
public static boolean playerCannotTeleport(Portal entrancePortal, Portal destination, Player player, PlayerMoveEvent event) {
public static boolean playerCannotTeleport(@Nullable Portal entrancePortal, @Nullable Portal destination,
@NotNull Player player, @Nullable PlayerMoveEvent event) {
//No portal or not open
if (entrancePortal == null || !entrancePortal.isOpen()) {
return true;
@@ -384,24 +394,27 @@ public final class PermissionHelper {
//Not open for this player
if (!entrancePortal.getPortalOpener().isOpenFor(player)) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
if (!entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ACCESS_DENIED).error(player);
}
new PlayerTeleporter(entrancePortal, player).teleport(entrancePortal, event);
new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
return true;
}
//No destination
if (!entrancePortal.getOptions().isBungee() && destination == null) {
boolean isBungee = entrancePortal.getOptions().isBungee();
if (!isBungee && destination == null) {
return true;
}
//Player cannot access portal
if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destination)) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
if (!entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ACCESS_DENIED).error(player);
}
new PlayerTeleporter(entrancePortal, player).teleport(entrancePortal, event);
new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
Stargate.debug("PermissionHelper::playerCannotTeleport", "Closed portal because player is " +
"missing necessary permissions");
entrancePortal.getPortalOpener().closePortal(false);
return true;
}
@@ -414,4 +427,23 @@ public final class PermissionHelper {
return false;
}
/**
* Checks if the given player has a permission for the given value, or has the catch-all permission
*
* @param player <p>The player trying to create a portal</p>
* @param permission <p>The parent permission to check</p>
* @param value <p>The child node to check</p>
* @return <p>True if the player has the explicit child node, or gets the permission implicitly from the parent</p>
*/
private static boolean hasPermission(@NotNull Player player, @NotNull Permission permission, @NotNull String value) {
String fullNode = permission.getNode() + "." + value;
//Check if the player is allowed to create all gates
if (hasPermission(player, permission)) {
//Check if the gate type has been explicitly denied
return hasPermissionImplicit(player, fullNode);
}
//Check if the player can create the specific gate type
return hasPermission(player, fullNode);
}
}

View File

@@ -2,29 +2,34 @@ package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.container.ControlBlockUpdateRequest;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.portal.property.PortalLocation;
import net.knarcraft.stargate.portal.property.PortalOptions;
import net.knarcraft.stargate.portal.property.PortalOwner;
import net.knarcraft.stargate.portal.property.PortalStrings;
import net.knarcraft.stargate.portal.property.gate.Gate;
import net.knarcraft.stargate.portal.property.gate.GateHandler;
import net.knarcraft.stargate.transformation.SimpleVectorOperation;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
import static net.knarcraft.stargate.portal.PortalSignDrawer.markPortalWithInvalidGate;
@@ -43,7 +48,7 @@ public final class PortalFileHelper {
*
* @param world <p>The world to save portals for</p>
*/
public static void saveAllPortals(World world) {
public static void saveAllPortals(@NotNull World world) {
Stargate.getStargateConfig().addManagedWorld(world.getName());
String saveFileLocation = Stargate.getPortalFolder() + "/" + world.getName() + ".db";
@@ -52,7 +57,7 @@ public final class PortalFileHelper {
for (Portal portal : PortalRegistry.getAllPortals()) {
//Skip portals in other worlds
String worldName = portal.getWorld().getName();
String worldName = portal.getLocation().getWorld().getName();
if (!worldName.equalsIgnoreCase(world.getName())) {
continue;
}
@@ -61,8 +66,8 @@ public final class PortalFileHelper {
}
bufferedWriter.close();
} catch (Exception e) {
Stargate.logSevere(String.format("Exception while writing stargates to %s: %s", saveFileLocation, e));
} catch (Exception exception) {
Stargate.logSevere(String.format("Exception while writing stargates to %s: %s", saveFileLocation, exception));
}
}
@@ -73,21 +78,21 @@ public final class PortalFileHelper {
* @param portal <p>The portal to save</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private static void savePortal(BufferedWriter bufferedWriter, Portal portal) throws IOException {
private static void savePortal(@NotNull BufferedWriter bufferedWriter, @NotNull Portal portal) throws IOException {
StringBuilder builder = new StringBuilder();
BlockLocation button = portal.getStructure().getButton();
Block button = portal.getStructure().getButton();
//WARNING: Because of the primitive save format, any change in order will break everything!
builder.append(portal.getName()).append(':');
builder.append(portal.getSignLocation().toString()).append(':');
builder.append(portal.getLocation().getSignBlock()).append(':');
builder.append((button != null) ? button.toString() : "").append(':');
//Add removes config values to keep indices consistent
builder.append(0).append(':');
builder.append(0).append(':');
builder.append(portal.getYaw()).append(':');
builder.append(portal.getTopLeft().toString()).append(':');
builder.append(DirectionHelper.getYawFromBlockFace(portal.getLocation().getFacing())).append(':');
builder.append(portal.getLocation().getTopLeft()).append(':');
builder.append(portal.getGate().getFilename()).append(':');
//Only save the destination name if the gate is fixed as it doesn't matter otherwise
@@ -111,20 +116,20 @@ public final class PortalFileHelper {
* @param portal <p>The portal to save</p>
* @param builder <p>The string builder to append to</p>
*/
private static void savePortalOptions(Portal portal, StringBuilder builder) {
private static void savePortalOptions(@NotNull Portal portal, @NotNull StringBuilder builder) {
PortalOptions options = portal.getOptions();
builder.append(':');
builder.append(options.isHidden()).append(':');
builder.append(options.isAlwaysOn()).append(':');
builder.append(options.isPrivate()).append(':');
builder.append(portal.getWorld().getName()).append(':');
builder.append(portal.getLocation().getWorld().getName()).append(':');
builder.append(options.isFree()).append(':');
builder.append(options.isBackwards()).append(':');
builder.append(options.isShown()).append(':');
builder.append(options.isNoNetwork()).append(':');
builder.append(options.isRandom()).append(':');
builder.append(options.isBungee()).append(':');
builder.append(options.isSilent()).append(':');
builder.append(options.isQuiet()).append(':');
builder.append(options.hasNoSign());
}
@@ -134,7 +139,7 @@ public final class PortalFileHelper {
* @param world <p>The world to load portals for</p>
* @return <p>True if portals could be loaded</p>
*/
public static boolean loadAllPortals(World world) {
public static boolean loadAllPortals(@NotNull World world) {
String location = Stargate.getPortalFolder();
File database = new File(location, world.getName() + ".db");
@@ -154,7 +159,7 @@ public final class PortalFileHelper {
* @param database <p>The database file containing the portals</p>
* @return <p>True if the portals were loaded successfully</p>
*/
private static boolean loadPortals(World world, File database) {
private static boolean loadPortals(@NotNull World world, @NotNull File database) {
int lineIndex = 0;
try {
Scanner scanner = new Scanner(database);
@@ -170,10 +175,9 @@ public final class PortalFileHelper {
"Starting post loading tasks", world));
doPostLoadTasks(world, needsToSaveDatabase);
return true;
} catch (Exception e) {
Stargate.logSevere(String.format("Exception while reading stargates from %s: %d", database.getName(),
lineIndex));
e.printStackTrace();
} catch (Exception exception) {
Stargate.logSevere(String.format("Exception while reading stargates from %s: %d! Message: %s",
database.getName(), lineIndex, exception.getMessage()));
}
return false;
}
@@ -186,7 +190,7 @@ public final class PortalFileHelper {
* @param world <p>The world for which portals are currently being read</p>
* @return <p>True if the read portal has changed and the world's database needs to be saved</p>
*/
private static boolean readPortalLine(Scanner scanner, int lineIndex, World world) {
private static boolean readPortalLine(@NotNull Scanner scanner, int lineIndex, @NotNull World world) {
String line = scanner.nextLine().trim();
//Ignore empty and comment lines
@@ -214,21 +218,26 @@ public final class PortalFileHelper {
* @param world <p>The world portals have been loaded for</p>
* @param needsToSaveDatabase <p>Whether the portal database's file needs to be updated</p>
*/
private static void doPostLoadTasks(World world, boolean needsToSaveDatabase) {
private static void doPostLoadTasks(@NotNull World world, boolean needsToSaveDatabase) {
//Open any always-on portals. Do this here as it should be more efficient than in the loop.
PortalHandler.verifyAllPortals();
PortalUtil.verifyAllPortals();
int portalCount = PortalRegistry.getAllPortals().size();
int openCount = PortalHandler.openAlwaysOpenPortals();
int openCount = PortalUtil.openAlwaysOpenPortals();
//Print info about loaded stargates so that admins can see if all stargates loaded
Stargate.logInfo(String.format("{%s} Loaded %d stargates with %d set as always-on", world.getName(),
portalCount, openCount));
//Re-draw the signs in case a bug in the config prevented the portal from loading and has been fixed since
Stargate.debug("PortalFileHelper::doPostLoadTasks::update",
String.format("Queueing portal sign/button updates for %s", world));
for (Portal portal : PortalRegistry.getAllPortals()) {
if (portal.isRegistered()) {
portal.drawSign();
updatePortalButton(portal);
if (portal.isRegistered() && portal.getLocation().getWorld().equals(world) &&
world.getWorldBorder().isInside(portal.getLocation().getSignBlock().getLocation())) {
Stargate.addControlBlockUpdateRequest(new ControlBlockUpdateRequest(portal));
Stargate.debug("UpdateSignsButtons", String.format("Queued sign and button updates for portal %s",
portal.getName()));
}
}
//Save the portals to disk to update with any changes
@@ -246,16 +255,16 @@ public final class PortalFileHelper {
* @param lineIndex <p>The line index to report in case the user needs to fix an error</p>
* @return <p>True if the portal's data has changed and its database needs to be updated</p>
*/
private static boolean loadPortal(String[] portalData, World world, int lineIndex) {
private static boolean loadPortal(@NotNull String[] portalData, @NotNull World world, int lineIndex) {
//Load min. required portal data
String name = portalData[0];
BlockLocation button = (portalData[2].length() > 0) ? new BlockLocation(world, portalData[2]) : null;
Block button = (!portalData[2].isEmpty()) ? getBlock(world, portalData[2]) : null;
//Load the portal's location
PortalLocation portalLocation = new PortalLocation();
portalLocation.setSignLocation(new BlockLocation(world, portalData[1]));
portalLocation.setYaw(Float.parseFloat(portalData[5]));
portalLocation.setTopLeft(new BlockLocation(world, portalData[6]));
PortalLocation portalLocation = new PortalLocation(getBlock(world, portalData[6]),
DirectionHelper.getBlockFaceFromYaw(Float.parseFloat(portalData[5])), getBlock(world, portalData[1]), button);
//Check if the portal's gate type exists and is loaded
Gate gate = GateHandler.getGateByName(portalData[7]);
@@ -275,48 +284,30 @@ public final class PortalFileHelper {
PortalOwner owner = new PortalOwner(ownerString);
//Create the new portal
Portal portal = new Portal(portalLocation, button, destination, name, network, gate, owner,
PortalHandler.getPortalOptions(portalData));
PortalStrings portalStrings = new PortalStrings(name, network, destination);
Portal portal = new Portal(portalLocation, button, portalStrings, gate, owner,
PortalUtil.getPortalOptions(portalData));
//Register the portal, and close it in case it wasn't properly closed when the server stopped
boolean buttonLocationChanged = updateButtonVector(portal);
PortalHandler.registerPortal(portal);
PortalUtil.registerPortal(portal);
portal.getPortalOpener().closePortal(true);
return buttonLocationChanged;
}
/**
* Updates a portal's button if it does not match the correct material
*
* @param portal <p>The portal update the button of</p>
*/
private static void updatePortalButton(Portal portal) {
BlockLocation buttonLocation = getButtonLocation(portal);
if (portal.getOptions().isAlwaysOn()) {
//Clear button if not already air or water
if (MaterialHelper.isButtonCompatible(buttonLocation.getType())) {
Material newMaterial = decideRemovalMaterial(buttonLocation, portal);
Stargate.addBlockChangeRequest(new BlockChangeRequest(buttonLocation, newMaterial, null));
}
} else {
//Replace button if the material does not match
if (buttonLocation.getType() != portal.getGate().getPortalButton()) {
generatePortalButton(portal, DirectionHelper.getBlockFaceFromYaw(portal.getYaw()));
}
}
}
/**
* Decides the material to use for removing a portal's button/sign
*
* @param location <p>The location of the button/sign to replace</p>
* @param portal <p>The portal the button/sign belongs to</p>
* @param block <p>The location of the button/sign to replace</p>
* @param portal <p>The portal the button/sign belongs to</p>
* @return <p>The material to use for removing the button/sign</p>
*/
public static Material decideRemovalMaterial(BlockLocation location, Portal portal) {
@NotNull
public static Material decideRemovalMaterial(@NotNull Block block, @NotNull Portal portal) {
//Get the blocks to each side of the location
Location leftLocation = location.getRelativeLocation(-1, 0, 0, portal.getYaw());
Location rightLocation = location.getRelativeLocation(1, 0, 0, portal.getYaw());
SimpleVectorOperation vectorOperation = portal.getLocation().getVectorOperation();
Location leftLocation = block.getLocation().clone().add(vectorOperation.performToRealSpaceOperation(new Vector(-1, 0, 0)));
Location rightLocation = block.getLocation().clone().add(vectorOperation.performToRealSpaceOperation(new Vector(1, 0, 0)));
//If the block is water or is waterlogged, assume the portal is underwater
if (isUnderwater(leftLocation) || isUnderwater(rightLocation)) {
@@ -334,7 +325,7 @@ public final class PortalFileHelper {
* @param location <p>The location to check</p>
* @return <p>True if the location is underwater</p>
*/
private static boolean isUnderwater(Location location) {
private static boolean isUnderwater(@NotNull Location location) {
BlockData blockData = location.getBlock().getBlockData();
return blockData.getMaterial() == Material.WATER ||
(blockData instanceof Waterlogged waterlogged && waterlogged.isWaterlogged());
@@ -349,18 +340,15 @@ public final class PortalFileHelper {
* @param portal <p>The portal to update the button vector for</p>
* @return <p>True if the calculated button location is not the same as the one in the portal file</p>
*/
private static boolean updateButtonVector(Portal portal) {
for (RelativeBlockVector control : portal.getGate().getLayout().getControls()) {
BlockLocation controlLocation = portal.getLocation().getTopLeft().getRelativeLocation(control,
portal.getYaw());
BlockLocation buttonLocation = controlLocation.getRelativeLocation(
new RelativeBlockVector(0, 0, 1), portal.getYaw());
if (!buttonLocation.equals(portal.getLocation().getSignLocation())) {
portal.getLocation().setButtonVector(control);
private static boolean updateButtonVector(@NotNull Portal portal) {
for (BlockVector control : portal.getGate().getLayout().getControls()) {
Block buttonLocation = portal.getLocation().getRelative(control.clone().add(new Vector(0, 0, 1)).toBlockVector());
if (!buttonLocation.equals(portal.getLocation().getSignBlock())) {
portal.getLocation().setButtonBlock(buttonLocation);
BlockLocation oldButtonLocation = portal.getStructure().getButton();
Block oldButtonLocation = portal.getStructure().getButton();
if (oldButtonLocation != null && !oldButtonLocation.equals(buttonLocation)) {
Stargate.addBlockChangeRequest(new BlockChangeRequest(oldButtonLocation, Material.AIR, null));
Stargate.addControlBlockUpdateRequest(new BlockChangeRequest(oldButtonLocation, Material.AIR, null));
portal.getStructure().setButton(buttonLocation);
return true;
}
@@ -375,27 +363,40 @@ public final class PortalFileHelper {
* @param portal <p>The portal to generate button for</p>
* @param buttonFacing <p>The direction the button should be facing</p>
*/
public static void generatePortalButton(Portal portal, BlockFace buttonFacing) {
public static void generatePortalButton(@NotNull Portal portal, @NotNull BlockFace buttonFacing) {
//Go one block outwards to find the button's location rather than the control block's location
BlockLocation button = getButtonLocation(portal);
Block button = portal.getLocation().getButtonBlock();
Directional buttonData = (Directional) Bukkit.createBlockData(portal.getGate().getPortalButton());
buttonData.setFacing(buttonFacing);
button.getBlock().setBlockData(buttonData);
// If the button location is null here, it is assumed that the button generation wasn't necessary
if (button == null) {
return;
}
if (!MaterialHelper.isButtonCompatible(button.getType())) {
@NotNull List<Material> possibleMaterials = MaterialHelper.specifiersToMaterials(
portal.getGate().getPortalButtonMaterials()).stream().toList();
Material buttonType = ListHelper.getRandom(possibleMaterials);
Directional buttonData = (Directional) Bukkit.createBlockData(buttonType);
buttonData.setFacing(buttonFacing);
button.setBlockData(buttonData);
}
portal.getStructure().setButton(button);
}
/**
* Gets the location of a portal's button
* Gets the block specified in the input
*
* @param portal <p>The portal to find the button for</p>
* @return <p>The location of the portal's button</p>
* @param world <p>The world the block belongs to</p>
* @param string <p>Comma-separated coordinate string</p>
* @return <p>The specified block</p>
* @throws NumberFormatException <p>If non-numeric values are encountered</p>
*/
private static BlockLocation getButtonLocation(Portal portal) {
BlockLocation topLeft = portal.getTopLeft();
RelativeBlockVector buttonVector = portal.getLocation().getButtonVector();
return topLeft.getRelativeLocation(buttonVector.addToVector(RelativeBlockVector.Property.OUT, 1),
portal.getYaw());
@NotNull
private static Block getBlock(@NotNull World world, @NotNull String string) throws NumberFormatException {
String[] parts = string.split(",");
return new Location(world, Integer.parseInt(parts[0]), Integer.parseInt(parts[1]),
Integer.parseInt(parts[2])).getBlock();
}
}

View File

@@ -1,18 +1,25 @@
package net.knarcraft.stargate.portal;
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.config.Permission;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.config.material.BukkitTagSpecifier;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.portal.property.PortalLocation;
import net.knarcraft.stargate.portal.property.PortalOption;
import net.knarcraft.stargate.portal.property.PortalStructure;
import net.knarcraft.stargate.portal.property.gate.Gate;
import net.knarcraft.stargate.portal.property.gate.GateHandler;
import net.knarcraft.stargate.utility.PermissionHelper;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
@@ -22,9 +29,9 @@ import java.util.Map;
/**
* Keeps track of all loaded portals, and handles portal creation
*/
public class PortalHandler {
public final class PortalUtil {
private PortalHandler() {
private PortalUtil() {
}
@@ -33,26 +40,19 @@ public class PortalHandler {
*
* @return <p>A copy of all portal networks</p>
*/
@NotNull
public static Map<String, List<String>> getAllPortalNetworks() {
return PortalRegistry.getAllPortalNetworks();
}
/**
* Gets a copy of all bungee portals
*
* @return <p>A copy of all bungee portals</p>
*/
public static Map<String, Portal> getBungeePortals() {
return PortalRegistry.getBungeePortals();
}
/**
* Gets names of all portals within a network
*
* @param network <p>The network to get portals from</p>
* @return <p>A list of portal names</p>
*/
public static List<String> getNetwork(String network) {
@Nullable
public static List<String> getNetwork(@NotNull String network) {
return PortalRegistry.getNetwork(network);
}
@@ -64,53 +64,72 @@ public class PortalHandler {
* @param network <p>The network to get destinations from</p>
* @return <p>All destinations the player can go to</p>
*/
public static List<String> getDestinations(Portal entrancePortal, Player player, String network) {
@NotNull
public static List<String> getDestinations(@NotNull Portal entrancePortal, @Nullable Player player,
@NotNull String network) {
List<String> destinations = new ArrayList<>();
for (String destination : PortalRegistry.getAllPortalNetworks().get(network)) {
List<String> portalsInNetwork = PortalRegistry.getNetwork(network);
if (portalsInNetwork == null) {
return List.of();
}
for (String destination : portalsInNetwork) {
Portal portal = getByName(destination, network);
if (portal == null) {
continue;
}
//Check if destination is a random portal
if (portal.getOptions().isRandom()) {
continue;
}
//Check if destination is always open (Don't show if so)
if (portal.getOptions().isAlwaysOn() && !portal.getOptions().isShown()) {
continue;
}
//Check if destination is this portal
if (destination.equals(entrancePortal.getCleanName())) {
continue;
}
//Check if destination is a fixed portal not pointing to this portal
if (portal.getOptions().isFixed() &&
!Portal.cleanString(portal.getDestinationName()).equals(entrancePortal.getCleanName())) {
continue;
}
//Allow random use by non-players (Minecarts)
if (player == null) {
destinations.add(portal.getName());
continue;
}
//Check if this player can access the destination world
if (PermissionHelper.cannotAccessWorld(player, portal.getWorld().getName())) {
continue;
}
//The portal is visible to the player
if (PermissionHelper.canSeePortal(player, portal)) {
if (isDestinationAvailable(entrancePortal, portal, player)) {
destinations.add(portal.getName());
}
}
return destinations;
}
/**
* Checks whether the given destination is available to the given player
*
* @param entrancePortal <p>The portal the player has activated</p>
* @param destinationPortal <p>The destination portal to check if is valid</p>
* @param player <p>The player trying to attempt the destination</p>
* @return <p>True if the destination is available</p>
*/
private static boolean isDestinationAvailable(@NotNull Portal entrancePortal, @NotNull Portal destinationPortal,
@Nullable Player player) {
//Check if destination is a random portal
if (destinationPortal.getOptions().isRandom()) {
return false;
}
//Check if destination is always open (Don't show if so)
if (destinationPortal.getOptions().isAlwaysOn() && !destinationPortal.getOptions().isShown()) {
return false;
}
//Check if destination is this portal
if (destinationPortal.equals(entrancePortal)) {
return false;
}
//Check if destination is a fixed portal not pointing to this portal
if (destinationPortal.getOptions().isFixed() &&
!Portal.cleanString(destinationPortal.getDestinationName()).equals(entrancePortal.getCleanName())) {
return false;
}
//Allow random use by non-players (Minecarts)
if (player == null) {
return true;
}
//Check if this player can access the destination world
if (PermissionHelper.cannotAccessWorld(player, destinationPortal.getLocation().getWorld().getName())) {
return false;
}
//The portal is visible to the player
return PermissionHelper.canSeePortal(player, destinationPortal);
}
/**
* Registers a portal
*
* @param portal <p>The portal to register</p>
*/
public static void registerPortal(Portal portal) {
public static void registerPortal(@NotNull Portal portal) {
PortalRegistry.registerPortal(portal);
}
@@ -123,19 +142,20 @@ public class PortalHandler {
* @param network <p>The name of the portal's network</p>
* @return <p>False if the portal is an invalid bungee portal. True otherwise</p>
*/
static boolean isValidBungeePortal(Map<PortalOption, Boolean> portalOptions, Player player,
String destinationName, String network) {
if (portalOptions.get(PortalOption.BUNGEE)) {
if (!PermissionHelper.hasPermission(player, "stargate.admin.bungee")) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeDeny"));
return false;
} else if (!Stargate.getGateConfig().enableBungee()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeDisabled"));
return false;
} else if (destinationName.isEmpty() || network.isEmpty()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeEmpty"));
return false;
}
public static boolean isValidBungeePortal(@NotNull Map<PortalOption, Boolean> portalOptions, @NotNull Player player,
@NotNull String destinationName, String network) {
if (!portalOptions.get(PortalOption.BUNGEE)) {
return true;
}
if (!PermissionHelper.hasPermission(player, Permission.CREATE_BUNGEE)) {
new SGFormatBuilder(Message.BUNGEE_CREATION_DENIED).error(player);
return false;
} else if (!Stargate.getGateConfig().enableBungee()) {
new SGFormatBuilder(Message.BUNGEE_DISABLED).error(player);
return false;
} else if (destinationName.isEmpty() || network.isEmpty()) {
new SGFormatBuilder(Message.BUNGEE_MISSING_INFO).error(player);
return false;
}
return true;
}
@@ -144,34 +164,40 @@ public class PortalHandler {
* Tries to find a gate matching the portal the user is trying to create
*
* @param portalLocation <p>The location data for the new portal</p>
* @param world <p>The world the player is located in</p>
* @return <p>The matching gate type, or null if no such gate could be found</p>
*/
static Gate findMatchingGate(PortalLocation portalLocation, World world) {
Block signParent = portalLocation.getSignLocation().getParent();
BlockLocation parent = new BlockLocation(world, signParent.getX(), signParent.getY(),
signParent.getZ());
@Nullable
public static Gate findMatchingGate(@NotNull PortalLocation portalLocation) {
Block signParent = DirectionHelper.getParent(portalLocation.getSignBlock());
if (signParent == null) {
return null;
}
//Get all gates with the used type of control blocks
Gate[] possibleGates = GateHandler.getGatesByControlBlock(signParent);
double yaw = portalLocation.getYaw();
List<Gate> possibleGates = GateHandler.getGatesByControlBlock(signParent);
Gate gate = null;
for (Gate possibleGate : possibleGates) {
//Get gate controls
RelativeBlockVector[] vectors = possibleGate.getLayout().getControls();
BlockVector[] vectors = possibleGate.getLayout().getControls();
portalLocation.setButtonVector(null);
for (RelativeBlockVector controlVector : vectors) {
portalLocation.setButtonBlock(null);
for (BlockVector controlVector : vectors) {
//Assuming the top-left location is pointing to the gate's top-left location, check if it's a match
BlockLocation possibleTopLocation = parent.getRelativeLocation(controlVector.invert(), yaw);
if (possibleGate.matches(possibleTopLocation, portalLocation.getYaw(), true)) {
Block possibleTopLocation = portalLocation.getRelative(controlVector);
if (possibleGate.matches(possibleTopLocation, portalLocation.getFacing(), true)) {
gate = possibleGate;
portalLocation.setTopLeft(possibleTopLocation);
} else {
portalLocation.setButtonVector(controlVector);
portalLocation.setButtonBlock(possibleTopLocation);
}
}
//If our gate has been found, look no further
if (gate != null) {
break;
}
}
return gate;
@@ -182,12 +208,16 @@ public class PortalHandler {
*
* @param portal <p>The newly created portal</p>
*/
static void updatePortalsPointingAtNewPortal(Portal portal) {
for (String originName : PortalRegistry.getAllPortalNetworks().get(portal.getCleanNetwork())) {
public static void updatePortalsPointingAtNewPortal(@NotNull Portal portal) {
List<String> portalsInNetwork = PortalRegistry.getNetwork(portal.getCleanNetwork());
if (portalsInNetwork == null) {
return;
}
for (String originName : portalsInNetwork) {
Portal origin = getByName(originName, portal.getCleanNetwork());
if (origin == null ||
!Portal.cleanString(origin.getDestinationName()).equals(portal.getCleanName()) ||
!origin.getStructure().isVerified()) {
!new BukkitTagSpecifier(Tag.WALL_SIGNS).asMaterials().contains(origin.getLocation().getSignBlock().getType())) {
continue;
}
//Update sign of fixed gates pointing at this gate
@@ -209,7 +239,9 @@ public class PortalHandler {
* @param options <p>The string on the option line of the sign</p>
* @return <p>A map containing all portal options and their values</p>
*/
static Map<PortalOption, Boolean> getPortalOptions(Player player, String destinationName, String options) {
@NotNull
public static Map<PortalOption, Boolean> getPortalOptions(@NotNull Player player, @NotNull String destinationName,
@NotNull String options) {
Map<PortalOption, Boolean> portalOptions = new HashMap<>();
for (PortalOption option : PortalOption.values()) {
portalOptions.put(option, options.indexOf(option.getCharacterRepresentation()) != -1 &&
@@ -217,7 +249,7 @@ public class PortalHandler {
}
//Can not create a non-fixed always-on portal
if (portalOptions.get(PortalOption.ALWAYS_ON) && destinationName.length() == 0) {
if (portalOptions.get(PortalOption.ALWAYS_ON) && destinationName.isEmpty()) {
portalOptions.put(PortalOption.ALWAYS_ON, false);
}
@@ -247,13 +279,9 @@ public class PortalHandler {
* @param network <p>The network the portal is connected to</p>
* @return <p>The portal with the given name or null</p>
*/
public static Portal getByName(String name, String network) {
Map<String, Map<String, Portal>> lookupMap = PortalRegistry.getPortalLookupByNetwork();
if (!lookupMap.containsKey(network.toLowerCase())) {
return null;
}
return lookupMap.get(network.toLowerCase()).get(name.toLowerCase());
@Nullable
public static Portal getByName(@NotNull String name, @NotNull String network) {
return PortalRegistry.getPortalInNetwork(Portal.cleanString(network), Portal.cleanString(name));
}
/**
@@ -262,9 +290,12 @@ public class PortalHandler {
* @param location <p>The location of the portal's entrance</p>
* @return <p>The portal at the given location</p>
*/
public static Portal getByEntrance(Location location) {
return PortalRegistry.getLookupEntrances().get(new BlockLocation(location.getWorld(), location.getBlockX(),
location.getBlockY(), location.getBlockZ()));
@Nullable
public static Portal getByEntrance(@NotNull Location location) {
if (location.getWorld() == null) {
return null;
}
return PortalRegistry.getPortalFromEntrance(location.getBlock());
}
/**
@@ -273,8 +304,9 @@ public class PortalHandler {
* @param block <p>The block at the portal's entrance</p>
* @return <p>The portal at the given block's location</p>
*/
public static Portal getByEntrance(Block block) {
return PortalRegistry.getLookupEntrances().get(new BlockLocation(block));
@Nullable
public static Portal getByEntrance(@NotNull Block block) {
return PortalRegistry.getPortalFromEntrance(block);
}
/**
@@ -283,7 +315,8 @@ public class PortalHandler {
* @param location <p>A location adjacent to the portal's entrance</p>
* @return <p>The portal adjacent to the given location</p>
*/
public static Portal getByAdjacentEntrance(Location location) {
@Nullable
public static Portal getByAdjacentEntrance(@NotNull Location location) {
return getByAdjacentEntrance(location, 1);
}
@@ -294,26 +327,27 @@ public class PortalHandler {
* @param range <p>The range to scan for portals</p>
* @return <p>The portal adjacent to the given location</p>
*/
public static Portal getByAdjacentEntrance(Location location, int range) {
List<BlockLocation> adjacentPositions = new ArrayList<>();
BlockLocation centerLocation = new BlockLocation(location.getBlock());
@Nullable
public static Portal getByAdjacentEntrance(@NotNull Location location, int range) {
List<Block> adjacentPositions = new ArrayList<>();
Block centerLocation = location.getBlock();
adjacentPositions.add(centerLocation);
for (int index = 1; index <= range; index++) {
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(index, 0, 0));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(-index, 0, 0));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(0, 0, index));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(0, 0, -index));
adjacentPositions.add(location.clone().add(new Vector(index, 0, 0)).getBlock());
adjacentPositions.add(location.clone().add(new Vector(-index, 0, 0)).getBlock());
adjacentPositions.add(location.clone().add(new Vector(0, 0, index)).getBlock());
adjacentPositions.add(location.clone().add(new Vector(0, 0, -index)).getBlock());
if (index < range) {
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(index, 0, index));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(-index, 0, -index));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(index, 0, -index));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(-index, 0, index));
adjacentPositions.add(location.clone().add(new Vector(index, 0, index)).getBlock());
adjacentPositions.add(location.clone().add(new Vector(-index, 0, -index)).getBlock());
adjacentPositions.add(location.clone().add(new Vector(index, 0, -index)).getBlock());
adjacentPositions.add(location.clone().add(new Vector(-index, 0, index)).getBlock());
}
}
for (BlockLocation adjacentPosition : adjacentPositions) {
Portal portal = PortalRegistry.getLookupEntrances().get(adjacentPosition);
for (Block adjacentPosition : adjacentPositions) {
Portal portal = PortalRegistry.getPortalFromEntrance(adjacentPosition);
if (portal != null) {
return portal;
}
@@ -327,8 +361,9 @@ public class PortalHandler {
* @param block <p>The portal's control block</p>
* @return <p>The portal with the given control block</p>
*/
public static Portal getByControl(Block block) {
return PortalRegistry.getLookupControls().get(new BlockLocation(block));
@Nullable
public static Portal getByControl(@NotNull Block block) {
return PortalRegistry.getPortalFromControl(block);
}
/**
@@ -337,8 +372,9 @@ public class PortalHandler {
* @param block <p>One of the loaded lookup blocks</p>
* @return <p>The portal corresponding to the block</p>
*/
public static Portal getByBlock(Block block) {
return PortalRegistry.getLookupBlocks().get(new BlockLocation(block));
@Nullable
public static Portal getByBlock(@NotNull Block block) {
return PortalRegistry.getPortalFromFrame(block);
}
/**
@@ -347,8 +383,9 @@ public class PortalHandler {
* @param name <p>The name of the bungee portal to get</p>
* @return <p>A bungee portal</p>
*/
public static Portal getBungeePortal(String name) {
return PortalRegistry.getBungeePortals().get(name.toLowerCase());
@Nullable
public static Portal getBungeePortal(@NotNull String name) {
return PortalRegistry.getBungeePortal(name);
}
/**
@@ -357,7 +394,8 @@ public class PortalHandler {
* @param portalData <p>The string list containing all information about a portal</p>
* @return <p>A map between portal options and booleans</p>
*/
public static Map<PortalOption, Boolean> getPortalOptions(String[] portalData) {
@NotNull
public static Map<PortalOption, Boolean> getPortalOptions(@NotNull String[] portalData) {
Map<PortalOption, Boolean> portalOptions = new HashMap<>();
for (PortalOption option : PortalOption.values()) {
int saveIndex = option.getSaveIndex();
@@ -394,7 +432,19 @@ public class PortalHandler {
for (Portal portal : PortalRegistry.getAllPortals()) {
//Try and verify the portal. Invalidate it if it cannot be validated
PortalStructure structure = portal.getStructure();
if (!structure.wasVerified() && (!structure.isVerified() || !structure.checkIntegrity())) {
Stargate.debug("PortalHandler::verifyAllPortals", "Checking portal: " + portal.getName() + " | " + portal.getNetwork());
if (!portal.getOptions().hasNoSign() && !(new BukkitTagSpecifier(Tag.WALL_SIGNS).asMaterials().contains(
portal.getLocation().getSignBlock().getType()))) {
Stargate.debug("PortalHandler::verifyAllPortals", "Stargate is missing its sign");
invalidPortals.add(portal);
} else if (!portal.getOptions().isAlwaysOn() && portal.getLocation().getButtonBlock() != null &&
!MaterialHelper.isButtonCompatible(portal.getLocation().getButtonBlock().getLocation().add(
new Vector(0, 0, 1)).getBlock().getType())) {
Stargate.debug("PortalHandler::verifyAllPortals", "Stargate is missing a valid button");
invalidPortals.add(portal);
} else if (!structure.checkIntegrity()) {
Stargate.debug("PortalHandler::verifyAllPortals", "Stargate's border or entrance has invalid blocks");
invalidPortals.add(portal);
}
}
@@ -410,18 +460,19 @@ public class PortalHandler {
*
* @param portal <p>The portal of the star portal</p>
*/
private static void unregisterInvalidPortal(Portal portal) {
private static void unregisterInvalidPortal(@NotNull Portal portal) {
//Show debug information
for (RelativeBlockVector control : portal.getGate().getLayout().getControls()) {
Block block = portal.getBlockAt(control).getBlock();
for (BlockVector control : portal.getGate().getLayout().getControls()) {
Block block = portal.getLocation().getRelative(control);
//Log control blocks not matching the gate layout
if (!block.getType().equals(portal.getGate().getControlBlock())) {
Stargate.debug("PortalHandler::destroyInvalidPortal", "Control Block Type == " +
if (!MaterialHelper.specifiersToMaterials(portal.getGate().getControlBlockMaterials()).contains(
block.getType())) {
Stargate.debug("PortalHandler::unregisterInvalidPortal", "Control Block Type == " +
block.getType().name());
}
}
PortalRegistry.unregisterPortal(portal, false);
Stargate.logInfo(String.format("Destroying stargate at %s", portal));
Stargate.logInfo(String.format("Disabled stargate at %s", portal));
}
/**
@@ -442,10 +493,12 @@ public class PortalHandler {
* @param input <p>The name to filter</p>
* @return <p>The filtered name</p>
*/
public static String filterName(String input) {
@NotNull
public static String filterName(@Nullable String input) {
if (input == null) {
return "";
}
return input.replaceAll("[|:#]", "").trim();
}
}

View File

@@ -0,0 +1,107 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.portal.property.PortalOption;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* A helper class for saving and loading Stargates from or to YML files
*/
public final class PortalYMLHelper {
private PortalYMLHelper() {
}
/**
* Saves all portals
*
* @throws IOException <p>If unable to read or write a portal file</p>
*/
public void savePortals() throws IOException {
Map<World, File> worldFiles = new HashMap<>();
Map<World, FileConfiguration> configurations = new HashMap<>();
// Get and alter each configuration without saving
for (Portal portal : PortalRegistry.getAllPortals()) {
World world = portal.getLocation().getWorld();
File file = worldFiles.get(world);
FileConfiguration configuration = configurations.get(world);
if (file == null) {
file = new File(Stargate.getStargateConfig().getPortalFolder(), world.getUID() + ".yml");
worldFiles.put(world, file);
}
if (configuration == null) {
configuration = new YamlConfiguration();
configurations.put(world, configuration);
}
savePortal(portal, configuration);
}
// Save each configuration
for (Map.Entry<World, FileConfiguration> configuration : configurations.entrySet()) {
configuration.getValue().save(worldFiles.get(configuration.getKey()));
}
}
/**
* Saves the given portal
*
* @param portal <p>The portal to save</p>
*/
public void savePortal(@NotNull Portal portal, @NotNull FileConfiguration configuration) {
String root = portal.getNetwork() + "." + portal.getName();
ConfigurationSection portalSection = configuration.createSection(root);
portalSection.set("name", portal.getName());
portalSection.set("network", portal.getNetwork());
portalSection.set("destination", portal.getOptions().isFixed() ? portal.getDestinationName() : null);
portalSection.set("owner", portal.getOwner().getIdentifier());
portalSection.set("gate", portal.getGate().getFilename());
portalSection.set("topLeft", portal.getLocation().getTopLeft());
portalSection.set("signLocation", portal.getLocation().getSignBlock());
portalSection.set("buttonLocation", portal.getLocation().getButtonBlock());
portalSection.set("facing", portal.getLocation().getFacing());
for (PortalOption option : PortalOption.values()) {
portalSection.set("options." + option.getCharacterRepresentation(), portal.getOptions().checkOption(option));
}
}
@Nullable
public Portal loadPortal(@NotNull ConfigurationSection portalSection) {
/* PortalLocation location = new PortalLocation();
BlockLocation signLocation = (BlockLocation) portalSection.get("signLocation");
BlockLocation topLeft = (BlockLocation) portalSection.get("topLeft");
if (signLocation == null || topLeft == null) {
return null;
}
BlockFace facing = (BlockFace) Objects.requireNonNull(portalSection.get("facing"));
location.setSignLocation(signLocation).setButtonFacing(facing).setYaw(BlockFace.);
BlockLocation button;
PortalStrings portalStrings = new PortalStrings();
Gate gate = GateHandler.getGateByName();
PortalOwner owner;
Map<PortalOption, Boolean> options;
return new Portal(location, button, portalStrings, gate, owner, options);*/
return null;
}
}

View File

@@ -0,0 +1,51 @@
package net.knarcraft.stargate.utility;
import org.bukkit.DyeColor;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A helper class for dealing with signs
*/
public final class SignHelper {
private SignHelper() {
}
/**
* Gets the lines of the given sign
*
* @param sign <p>The sign to get lines from</p>
* @return <p>The lines of the sign</p>
*/
@NotNull
public static String[] getLines(@NotNull Sign sign) {
return sign.getSide(Side.FRONT).getLines();
}
/**
* Gets the dye color of the given sign
*
* @param sign <p>The sign to check</p>
* @return <p>The dye currently applied to the sign</p>
*/
@Nullable
public static DyeColor getDye(@NotNull Sign sign) {
return sign.getSide(Side.FRONT).getColor();
}
/**
* Sets the text of a line on a sign
*
* @param sign <p>The sign to set text for</p>
* @param line <p>The line to set</p>
* @param text <p>The text to set</p>
*/
public static void setSignLine(@NotNull Sign sign, int line, @NotNull String text) {
sign.getSide(Side.FRONT).setLine(line, text);
}
}

View File

@@ -0,0 +1,253 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.formatting.Message;
import net.knarcraft.stargate.config.formatting.SGFormatBuilder;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.teleporter.EntityTeleporter;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Creature;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* A helper class with methods for various teleportation tasks
*
* <p>The teleport helper mainly helps with passengers and leashed creatures</p>
*/
public final class TeleportHelper {
private TeleportHelper() {
}
/**
* Checks whether a player has leashed creatures that block the teleportation
*
* @param player <p>The player trying to teleport</p>
* @return <p>False if the player has leashed any creatures that cannot go through the portal</p>
*/
public static boolean noLeashedCreaturesPreventTeleportation(@NotNull Player player) {
//Find any nearby leashed entities to teleport with the player
List<Creature> nearbyCreatures = getLeashedCreatures(player);
//Disallow creatures with passengers to prevent smuggling
for (Creature creature : nearbyCreatures) {
if (!creature.getPassengers().isEmpty()) {
return false;
}
}
//If it's enabled, there is no problem
if (Stargate.getGateConfig().handleLeashedCreatures()) {
return true;
} else {
return nearbyCreatures.isEmpty();
}
}
/**
* Gets all creatures leashed by a player within the given range
*
* @param player <p>The player to check</p>
* @return <p>A list of all creatures the player is holding in a leash (lead)</p>
*/
@NotNull
public static List<Creature> getLeashedCreatures(@NotNull Player player) {
List<Creature> leashedCreatures = new ArrayList<>();
//Find any nearby leashed entities to teleport with the player
List<Entity> nearbyEntities = player.getNearbyEntities(15, 15, 15);
//Teleport all creatures leashed by the player to the portal the player is to exit from
for (Entity entity : nearbyEntities) {
if (entity instanceof Creature creature && creature.isLeashed() && creature.getLeashHolder() == player) {
leashedCreatures.add(creature);
}
}
return leashedCreatures;
}
/**
* Teleports and adds a passenger to an entity
*
* <p>Teleportation of living vehicles is really buggy if you wait between the teleportation and passenger adding,
* but there needs to be a delay between teleporting the vehicle and teleporting and adding the passenger.</p>
*
* @param targetVehicle <p>The entity to add the passenger to</p>
* @param passenger <p>The passenger to teleport and add</p>
* @param exitDirection <p>The direction of any passengers exiting the stargate</p>
* @param newVelocity <p>The new velocity of the teleported passenger</p>
*/
public static void teleportAndAddPassenger(@NotNull Entity targetVehicle, @NotNull Entity passenger,
@NotNull Vector exitDirection, @NotNull Vector newVelocity) {
Location passengerExit = targetVehicle.getLocation().clone().setDirection(exitDirection);
if (!passenger.teleport(passengerExit)) {
Stargate.debug("TeleportHelper::handleVehiclePassengers", "Failed to teleport passenger" +
passenger);
} else {
Stargate.debug("TeleportHelper::handleVehiclePassengers", "Teleported " + passenger +
" to " + passengerExit);
}
if (!targetVehicle.addPassenger(passenger)) {
Stargate.debug("TeleportHelper::handleVehiclePassengers", "Failed to add passenger" +
passenger);
} else {
Stargate.debug("TeleportHelper::handleVehiclePassengers", "Added passenger " + passenger +
" to " + targetVehicle);
}
Stargate.debug("VehicleTeleporter::teleportVehicle", "Setting velocity " + newVelocity +
" for passenger " + passenger);
passenger.setVelocity(newVelocity);
}
/**
* Ejects, teleports and adds all passengers to the target entity
*
* @param passengers <p>The passengers to handle</p>
* @param entity <p>The entity the passengers should be put into</p
* @param origin <p>The portal the entity teleported from</p>
* @param target <p>The portal the entity is teleporting to</p>
* @param exitRotation <p>The rotation of any passengers exiting the stargate</p>
* @param newVelocity <p>The new velocity of the teleported passengers</p>
*/
public static void handleEntityPassengers(@NotNull List<Entity> passengers, @NotNull Entity entity,
@NotNull Portal origin, @NotNull Portal target,
@NotNull Vector exitRotation, @NotNull Vector newVelocity) {
for (Entity passenger : passengers) {
List<Entity> passengerPassengers = passenger.getPassengers();
if (!passengerPassengers.isEmpty()) {
Stargate.debug("Teleporter::handleEntityPassengers", "Found the entities: " +
passengerPassengers + " as passengers of " + entity);
}
if (passenger.eject()) {
//Teleport any passengers of the passenger
handleEntityPassengers(passengerPassengers, passenger, origin, target, exitRotation, newVelocity);
}
Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), () -> {
if (passenger instanceof Player player) {
//Teleport any creatures leashed by the player in a 15-block range
teleportLeashedCreatures(player, origin, target);
}
teleportAndAddPassenger(entity, passenger, exitRotation, newVelocity);
}, passenger instanceof Player ? Stargate.getGateConfig().waitForPlayerAfterTeleportDelay() : 0);
}
}
/**
* Teleports any creatures leashed by the player
*
* <p>Will return false if the teleportation should be aborted because the player has leashed creatures that
* aren't allowed to be teleported with the player.</p>
*
* @param player <p>The player which is teleported</p>
* @param origin <p>The portal the player is teleporting from</p>
* @param target <p>The portal the player is teleporting to</p>
*/
public static void teleportLeashedCreatures(@NotNull Player player, @NotNull Portal origin, @NotNull Portal target) {
//If this feature is disabled, just return
if (!Stargate.getGateConfig().handleLeashedCreatures()) {
return;
}
BukkitScheduler scheduler = Bukkit.getScheduler();
//Find any nearby leashed entities to teleport with the player
List<Creature> nearbyEntities = TeleportHelper.getLeashedCreatures(player);
//Teleport all creatures leashed by the player to the portal the player is to exit from
for (Creature creature : nearbyEntities) {
creature.setLeashHolder(null);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> {
new EntityTeleporter(target, creature).teleportEntity(origin);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> creature.setLeashHolder(player),
Stargate.getGateConfig().waitForPlayerAfterTeleportDelay());
}, 2);
}
}
/**
* Checks whether a list of entities or any of their passengers contains any non-players
*
* @param entities <p>The list of entities to check</p>
* @return <p>True if at least one entity is not a player</p>
*/
public static boolean containsNonPlayer(@NotNull List<Entity> entities) {
for (Entity entity : entities) {
if (!(entity instanceof Player) || containsNonPlayer(entity.getPassengers())) {
return true;
}
}
return false;
}
/**
* Checks whether a list of entities of their passengers contains at least one player
*
* @param entities <p>The list of entities to check</p>
* @return <p>True if at least one player is present among the passengers</p>
*/
public static boolean containsPlayer(@NotNull List<Entity> entities) {
for (Entity entity : entities) {
if (entity instanceof Player || containsPlayer(entity.getPassengers())) {
return true;
}
}
return false;
}
/**
* Gets all players recursively from a list of entities
*
* @param entities <p>The entities to check for players</p>
* @return <p>The found players</p>
*/
@NotNull
public static List<Player> getPlayers(@NotNull List<Entity> entities) {
List<Player> players = new ArrayList<>(5);
for (Entity entity : entities) {
if (entity instanceof Player) {
players.add((Player) entity);
}
players.addAll(getPlayers(entity.getPassengers()));
}
return players;
}
/**
* Checks whether the given player is allowed to and can afford to teleport
*
* @param player <p>The player trying to teleport</p>
* @param entrancePortal <p>The portal the player is entering</p>
* @param destinationPortal <p>The portal the player is to exit from</p>
* @return <p>True if the player is allowed to teleport and is able to pay necessary fees</p>
*/
public static boolean playerCanTeleport(@NotNull Player player, @NotNull Portal entrancePortal,
@NotNull Portal destinationPortal) {
//Make sure the user can access the portal
if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destinationPortal)) {
if (!entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ACCESS_DENIED).error(player);
}
entrancePortal.getPortalOpener().closePortal(false);
return false;
}
//Check if the player is able to afford the teleport fee
int cost = EconomyHelper.getUseCost(player, entrancePortal, destinationPortal);
boolean canAffordFee = cost <= 0 || Stargate.getEconomyConfig().canAffordFee(player, cost);
if (!canAffordFee) {
if (!entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ECONOMY_INSUFFICIENT).error(player);
}
return false;
}
return TeleportHelper.noLeashedCreaturesPreventTeleportation(player);
}
}

View File

@@ -2,11 +2,11 @@ package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.portal.property.PortalOwner;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
@@ -35,7 +35,7 @@ public final class UUIDMigrationHelper {
*
* @param player <p>The player to migrate</p>
*/
public static void migrateUUID(OfflinePlayer player) {
public static void migrateUUID(@NotNull OfflinePlayer player) {
Map<String, List<Portal>> playersToMigrate = getPlayersToMigrate();
String playerName = player.getName();
@@ -53,7 +53,7 @@ public final class UUIDMigrationHelper {
migratePortalsToUUID(portalsOwned, player.getUniqueId());
//Remove the player to prevent the migration to happen every time the player joins
//Remove the player to prevent the migration from happening every time the player joins
playersToMigrate.remove(playerName);
}
@@ -63,15 +63,15 @@ public final class UUIDMigrationHelper {
* @param portals <p>The portals to migrate</p>
* @param uniqueId <p>The unique ID of the portals' owner</p>
*/
private static void migratePortalsToUUID(List<Portal> portals, UUID uniqueId) {
private static void migratePortalsToUUID(@NotNull List<Portal> portals, @NotNull UUID uniqueId) {
Set<World> worldsToSave = new HashSet<>();
//Get the real portal from the copy and set UUID
for (Portal portalCopy : portals) {
Portal portal = PortalHandler.getByName(portalCopy.getCleanName(), portalCopy.getCleanNetwork());
Portal portal = PortalUtil.getByName(portalCopy.getCleanName(), portalCopy.getCleanNetwork());
if (portal != null) {
portal.getOwner().setUUID(uniqueId);
worldsToSave.add(portal.getWorld());
worldsToSave.add(portal.getLocation().getWorld());
}
}
@@ -86,6 +86,7 @@ public final class UUIDMigrationHelper {
*
* @return <p>The player names to migrate</p>
*/
@NotNull
private static Map<String, List<Portal>> getPlayersToMigrate() {
//Make sure to only go through portals once
if (playerNamesToMigrate != null) {

View File

@@ -1,4 +1,9 @@
lang=language
defaultNetwork=gates.defaultGateNetwork
use-mysql=
ignoreEntrance=
enableEconomy=economy.useEconomy
portal-save-location=folders.portalFolder
portal-folder=folders.portalFolder
gate-folder=folders.gateFolder
default-gate-network=gates.defaultGateNetwork
@@ -12,17 +17,35 @@ sortLists=gates.cosmetic.sortNetworkDestinations
protectEntrance=gates.integrity.protectEntrance
enableBungee=gates.functionality.enableBungee
verifyPortals=gates.integrity.verifyPortals
signColor=gates.cosmetic.signColor
signColor=gates.cosmetic.mainSignColor
gates.cosmetic.signColor=gates.cosmetic.mainSignColor
debug=debugging.debug
permdebug=debugging.permissionDebug
useiconomy=economy.useEconomy
useeconomy=economy.useEconomy
cost-to-use=economy.useCost
cost-to-create=economy.createCost
createcost=economy.createCost
destroycost=economy.destroyCost
usecost=economy.useCost
toowner=economy.toOwner
cost-destination=economy.chargeFreeDestination
chargefreedestination=economy.chargeFreeDestination
freegatesgreen=economy.freeGatesGreen
CheckUpdates=
economy.freeGatesGreen=economy.freeGatesColored
economy.freeGatesGreen=economy.freeGatesColored
teleportMessage=
registerMessage=
destroyzMessage=
noownersMessage=
unselectMessage=
collisinMessage=
cantAffordToUse=
cantAffordToNew=
portal-open=
portal-closed=
cost-type=
cost-to-activate=
taxAccount=economy.taxAccount
taxaccount=economy.taxAccount
usevault=

Some files were not shown because too many files have changed in this diff Show More