531 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
445638a561 Releases a small hotfix for sign color as version 0.9.2.3 2021-11-25 04:27:36 +01:00
7f9dc6756b Updates version to 0.9.2.2 2021-11-25 03:58:42 +01:00
b2bb995d7e Prevents teleportation of a leashed creature with a passenger 2021-11-25 03:54:09 +01:00
f92fd2de5d Prevents loading of gate files using non-blocks as part of the border 2021-11-25 03:39:15 +01:00
2ed0fe7817 Updates README with recent changes 2021-11-25 03:26:12 +01:00
27b964837f Fixes a potential exception when a portal with an invalid gate type is missing a sign 2021-11-25 03:25:58 +01:00
4b34ea3cf5 Fixes a potential exception when a gate's open-block or closed-block is not a block 2021-11-25 03:25:22 +01:00
0740cd0a66 Removes the error message displayed when teleportation is cancelled because handleLeashedCreatures is disabled
The message should be removed as it's inconsistent with how disabled vehicle teleportation is silent
2021-11-25 02:02:20 +01:00
bab51d76fd Prevents teleportation of players holding one or more creatures on a leash if handleLeashedCreatures is disabled 2021-11-25 01:53:47 +01:00
6ddc15eeef Merge branch 'config-reloading' into dev 2021-11-24 22:39:15 +01:00
14511f558d Updates readme and version to 0.9.2.1
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-24 22:38:44 +01:00
32975ca35d Rewrites a lot of the config command to only do the minimum necessary steps to load the changes
Adds a ConfigTag class for helping to decide the action necessary for updating a given config option
Splits the color setting in PortalSignDrawer as only one color is set at a time when the /config command is used
Updates the configOptions map when a config option is changed
2021-11-24 22:33:45 +01:00
6e7ac5dbb9 Makes the protectEntrance option protect the entrance from all block placement 2021-11-24 22:20:47 +01:00
2b4d15dab4 Adds some boldness to the description and fixes an inaccuracy
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-24 03:06:35 +01:00
22bb75d4df Moves some methods from EconomyConfig to EconomyHelper and tries to simplify EconomyConfig
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-23 07:20:48 +01:00
ab9a118d49 Prevents an empty deny reason from being displayed
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-22 22:01:43 +01:00
a80a8520ce Improves the description of the /sg command 2021-11-22 17:58:56 +01:00
48bb68d665 Adds a missing 'a' in a sentence
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-21 13:34:52 +01:00
1247c54899 Fixes some wrong migration information 2021-11-21 13:10:13 +01:00
37ac3d4877 Fixes a debug route
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-15 17:25:03 +01:00
70495220eb Simplifies some function calls
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-15 17:18:00 +01:00
10c3914a60 Makes the free gate color configurable 2021-11-15 00:35:28 +01:00
4699f717ec Ignores color codes and case when sorting destination names
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-14 14:35:45 +01:00
29c1a00fcd Makes the max portal name and network character limit more easily changeable 2021-11-13 19:28:17 +01:00
51f5420f9e Removes the replacement of spaces to underscores for the cleanString method
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-13 17:04:13 +01:00
e1a3d2d560 Makes a fixed sign always use the destination portal's actual name if possible 2021-11-13 17:02:13 +01:00
a84210d550 Fixes a couple of bugs which prevented portals using spaces in the name from being properly connected 2021-11-13 03:44:27 +01:00
351d1762e7 Updates changelog, and updates version to 0.9.2.0
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-12 15:50:32 +01:00
2c53b7d2a6 Makes portal names and networks case and color-insensitive and increases length limit to 13. #17
Ignores &[0-9a-f] color codes when counting towards the name and network string limits
Makes portal lists store cleaned portal and network names to ignore case and color
Names and networks will now match regardless of case and color
Increases portal name/network limit to 13 characters
2021-11-12 15:33:15 +01:00
42e02eb141 Makes the UUIDMigrationHelper final 2021-11-12 13:46:01 +01:00
ad310ddb9c Allows a sneaking player to see information about a silent stargate (0.9.1.2)
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-10 22:54:20 +01:00
3db630f60e Updates version and changelog
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-09 22:51:11 +01:00
7a03f49fb1 Translates the & character to make sure portal signs are colored on all servers 2021-11-09 22:47:38 +01:00
1c2cad2ec1 Updates version to 0.9.1.0, updates README and adds a permission for changing config values
Adds the stargate.admin.config permission, which is required to use /sg config
2021-11-09 21:16:53 +01:00
b4d908eaa0 Improves tab completion for Stargate commands by taking into account typed text
Sends tab completion for the config command to the config tab completer
2021-11-09 20:58:55 +01:00
8546fd4f78 Fully implements the config command 2021-11-09 20:57:06 +01:00
05328fc456 Adds a tab completer for the config sub-command 2021-11-09 20:56:43 +01:00
aba70b669e Adds a method to be able to read config options 2021-11-09 18:21:25 +01:00
d5f4b87e9b Changes how config values are loaded by using the ConfigOption class 2021-11-09 15:40:10 +01:00
85edaa4657 Adds a new class to represent a data type usable by configs 2021-11-09 15:38:42 +01:00
7c501cefe8 Gives all config options data types to make loading config values work properly 2021-11-09 15:38:10 +01:00
6466c7b0ff Adds the config command to the stargate auto completer
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
Additionally makes the reload command only auto-complete if the command sender can use it
2021-11-09 02:04:59 +01:00
37cf75ada1 Adds the config command as a child to the Stargate command 2021-11-09 02:01:58 +01:00
1ca2d36f5f Adds an unfinished implementation of the config command, which is only able to display config options for now 2021-11-09 02:01:11 +01:00
01b2907b01 Adds an enum containing information about all config options 2021-11-09 01:59:54 +01:00
94b9848b70 Updates version and README
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-08 15:17:29 +01:00
2a17714e8d Makes sure to only remove buttons from always-on portals if the block is a type of button 2021-11-08 15:16:51 +01:00
901f9c555c Prevents the sign location of a portal with no sing from being added to lookup blocks and controls 2021-11-08 15:15:58 +01:00
1efd89cdb0 Makes sure to not display portal information when placing a block 2021-11-08 15:14:21 +01:00
88bb02dfbd Adds translation for portal information shown when right-clicking
Adds a distinction between network and server when displaying portal information
Adds translated portal info strings for english and both flavors of Norwegian
2021-11-08 14:16:44 +01:00
4db6274dc3 Fixes underwater signs and buttons being replaced with AIR instead of water 2021-11-08 01:34:18 +01:00
9c963c9e8c Renames Portal to Stargate when displaying information about a clicked portal
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-07 14:02:33 +01:00
fc744b04dc Adds an option for a stargate without a sign #10 2021-11-07 13:41:19 +01:00
1565707809 De-activates an unregistered portal to prevent its sign to be re-drawn if destroyed while activated 2021-11-07 13:40:12 +01:00
527562bc60 Fixes code formatting 2021-11-07 04:29:25 +01:00
42e208402e Documents stargates using several border materials 2021-11-07 04:27:39 +01:00
7f91808baf Adds a new default stargate to display the possibility of using several border materials 2021-11-07 04:26:23 +01:00
0540498818 Moves the drawing of unregistered signs to the portal sign drawer 2021-11-07 04:04:14 +01:00
57ec7071cf Updates README changelog 2021-11-06 18:12:47 +01:00
5f2d7988e2 Fixes button updating
Fixes a bug causing signs to be removed
Makes the old button properly disappear
Saves the portal database(s) to store the new button location if necessary
2021-11-06 18:08:01 +01:00
ac25f2747f Adds String.format to some debug strings 2021-11-06 18:06:00 +01:00
aa3bb58b33 Fixes some bugs regarding sign drawing and button updating
Fixes a bug displaying a portal as usable even if it's been unregistered
Fixes a bug which causes the portal button to be re-generated, even if the portal has been unregistered
2021-11-06 15:33:06 +01:00
ee0e66e9be Fixes the order in which the portal's button is updated to fix one of the bugs in #15
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-06 14:49:56 +01:00
f90a09143f Makes sure to also clear bungee portals when clearing portals 2021-11-06 04:10:15 +01:00
8c4cf16375 Adds an option for silent stargates which don't print teleportation messages or errors to the player's chat #7 2021-11-05 23:39:18 +01:00
4566c15350 Updates version to 0.9.0.6 and updates README 2021-11-05 21:43:38 +01:00
0297d62d6d Sets the always on option to true for bungee portals and removes some unnecessary checks 2021-11-05 21:40:06 +01:00
80ff241d4b Makes stargates' buttons update when portals are loaded #14 2021-11-05 21:38:33 +01:00
f3292cff99 Makes containers used as buttons no longer open when right-clicked 2021-11-05 19:21:27 +01:00
8c37b11484 Increases the delay before setting the leash holder for teleported creatures as they sometimes got stuck
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-05 14:04:24 +01:00
e0ac9b41e7 Adds some sub-packages to the portal package to improve logical structure
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-04 00:07:03 +01:00
e5c1ad1f3a 0.9.0.5 - Adds three new options to disable features of vehicle teleportation with more granularity #9
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-03 15:55:56 +01:00
cab99e11b0 Adds leashed teleportation as a feature to the README
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-01 19:25:52 +01:00
3c4d10ae3f Updates changelog and version to 0.9.0.4 2021-11-01 18:49:05 +01:00
aff0082906 Adds a config option to disable leashed creatures from teleporting with a player 2021-11-01 18:44:10 +01:00
20c3c93c06 Adds a few fixes which seem to make leash teleportation work as expected
Un-leashes and re-leashes teleported leashed entities
Delays leashed creature teleportation and the re-leashing to prevent odd behavior such as infinite leash or no-ai creature
2021-11-01 17:54:38 +01:00
dab9378a67 Merge branch 'master' into leashed-teleportation 2021-11-01 17:05:34 +01:00
62661f65f4 Updates info and version to 0.9.0.3
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-11-01 16:32:56 +01:00
b8d98c26d4 Adds triggers for teleporting leashed entities 2021-11-01 16:28:13 +01:00
8bb9c464d6 Adds some code for teleporting creatures leashed by a player 2021-11-01 16:27:29 +01:00
2b5d791581 Adds the EntityTeleporter for generic teleportation of entities 2021-11-01 16:26:46 +01:00
2a61480684 Adds UUID fetching on player join. See #12
Whenever a player joins, their names will be checked against a map containing all names which need to be migrated to UUID. All portals the player has created which still use the player name will be updated.
2021-11-01 15:13:22 +01:00
91a0316e6e Adds a missing insufficient funds message when checking if vehicle passengers can pay for the teleportation 2021-11-01 13:54:31 +01:00
b98aec4a20 0.9.0.2 Fixes a bug in the code to prevent nether portals from generating in the nether 2021-11-01 00:38:36 +01:00
89956cf359 Updates the plugin version in plugin.yml 2021-10-31 19:51:33 +01:00
0655d85356 Updates README with the changes in 0.9.0.1 2021-10-31 18:10:19 +01:00
06cb99de44 Bumps version to 0.9.0.1 2021-10-31 18:09:56 +01:00
99f8a92857 Adds a highlightSignColor option and fixes some color inconsistencies
Makes all characters used for highlighting/marking use the highlighting color
Adds a new config option for highlighting color
Renames signColor to mainSignColor and adds the change to the config-migrations.txt file
2021-10-31 18:08:58 +01:00
01df8db0bf Moves config loading to finisSetup() to prevent a NullPointerException 2021-10-31 18:05:04 +01:00
d754247455 Makes some smaller changes
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
Replaces the mystery code in sendMessage with translateAlternateColorCodes and removes the TODO
Removes the TODO in deactivate() as it is actually necessary because fixed portals are always active
Moves conflictsWithExistingGate to PortalCreator and removes the TODO for now
Removes the extra spaces in >Random<
Adjusts some comments
Adds missing information about Economy and the requirement of UUIDs
2021-10-31 14:05:06 +01:00
1f1fef3054 Changes Jenkins to JDK 16 as Maven doesn't like Java 17. Ugh.
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-10-30 15:33:58 +02:00
40f0a99e5a Forces Jenkins to use a compatible JDK
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-10-30 15:17:15 +02:00
8fa0cca5ce Adds Jenkinsfile for automated building
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
2021-10-30 14:48:11 +02:00
a4075363ec Fixes some slightly incorrect information about economy gate configuration 2021-10-30 06:14:24 +02:00
260211b677 Adds missing chest buttons to the readme 2021-10-30 05:59:12 +02:00
5d41b2114b Adds migration information and some missing information to the description 2021-10-29 22:08:54 +02:00
50c446f9d9 Adds package JavaDoc to help anyone who wants to use the Stargate API 2021-10-29 21:19:24 +02:00
56bf51f370 Adds missing JavaDoc 2021-10-29 18:43:22 +02:00
56ed0495b0 Removes the last of the unprotected variables from Stargate
Makes all classes use getInstance() to get a Stargate instance
Removes the server variable as it's not needed
2021-10-29 18:35:20 +02:00
0237f45046 Renames activatedTime to triggeredTime and makes some public fields private
Makes the active portals queue and open portals queue and languageLoader fields private
Cleans the StargateThread a bit
Renames activatedTime to triggeredTime as the dual use (open and activate) made the name confusing
2021-10-29 17:22:58 +02:00
5d84e1d78a Changes some sign coloring and adds some missing info to the readme
Makes the Disconnected message red
Makes the highlighting characters (><) white when cycling stargate destinations
Moves markPortalWithInvalidGate to PortalSignDrawer
Adds missing translation strings for reloaded and signInvalidGate to the readme
Moves some extra spacing around the >< characters
2021-10-29 16:05:23 +02:00
f52ba79ae9 Splits the preventExitSuffocation method and improves some more comments 2021-10-29 15:08:44 +02:00
945798916b Removes an unnecessary portal close statement after teleporting a player through BungeeCord 2021-10-29 15:07:45 +02:00
f0e5cd45a4 Adds some minor style improvements 2021-10-29 01:46:15 +02:00
487cb3ad9e Adds missing info about Bungee-related permissions to the readme and adjusts permissions a bit
Adds stargate.server and stargate.admin.bungee permissions to the readme
Renames stargate.reload to stargate.admin.reload for better consistency
Makes the stargate.admin permission only give the reload, hidden, private and bungee permissions while the wildcard permission gives all permissions
2021-10-29 01:45:50 +02:00
544384f69b Cleans up permissions a lot and adds missing permissions to plugin.yml
Removes the checking of both parent and child by adding the child permissions to plugin.yml
Cleans comments in the PermissionHelper class
Renames hasPermDeep to hasPermissionImplicit as I finally understand how it's supposed to work
Adds missing unmock() to prevent test errors
Adds a ton of permissions which were mentioned in the code, but did not exist in the plugin.yml
2021-10-28 18:29:33 +02:00
3fc0e6963a Adds tests for the material helper and adds all chest variations as button compatible 2021-10-27 20:56:56 +02:00
5c730eb613 Fixes some portal closing bugs, a NullPointerException and a typo
Fixes a typo in nn-no
Fixes a bug causing always-on portals not to be closed properly
Fixes a possible NullPointerException in onEntityPortalEnter
Waits for block change request on reload which makes it possible to change the open-material with just a reload
2021-10-26 16:22:20 +02:00
1c906528f2 Cleans up message logging quite a bit
Adds methods to Stargate for easier logging and less redundancy
Loads the language loader in two parts to make it available while loading
Adds a translated string to the reload-message
Uses String.format to make long messages more readable
Makes it possible to get strings directly from the backup language to make debugging easier
2021-10-26 15:05:05 +02:00
eaf7596014 Adds information about the new default gates to the Building a gate section 2021-10-26 12:51:05 +02:00
669767ef89 Renames RelativeBlockVector's right, depth and distance to right, down and out respectively 2021-10-24 22:48:13 +02:00
822f8fb2b5 Changes BungeeCord messages to use UUID instead of player name 2021-10-24 21:15:43 +02:00
3367d4bb76 Disables the generation of buttons on bungee portals 2021-10-23 22:17:02 +02:00
a100ad3fea Fixes a check which broke all bungee portals 2021-10-23 22:16:36 +02:00
2541391d3e Moves all config related code from Stargate to StargateConfig 2021-10-23 18:34:31 +02:00
b7998023f5 Moves activeTime and openTime into the StargateGateConfig 2021-10-23 14:35:07 +02:00
deba2e5c2c Makes the stargate logger private 2021-10-23 14:25:46 +02:00
7cc8685e26 Moves all methods for sending messages to players to the MessageSender class 2021-10-23 14:10:33 +02:00
50084c40f9 Moves the Bungee Queue to BungeeHelper 2021-10-23 12:58:31 +02:00
2196d5b99e Moves some config values from StarGate and into the StarGateConfig class
Renames EconomyHandler to EconomyConfig and puts it in the new config package
Moves the LanguageLoader to the config package
Fixes the explanation of chargeFreeDestination in the README
2021-10-23 03:56:59 +02:00
070e7250df Re-draws every portal sign each time a new world is loaded to prevent weird states 2021-10-22 21:56:35 +02:00
9a0f16e558 Moves the portal owner name and owner UUID to the PortalOwner class which makes the TwoTuple unnecessary 2021-10-22 19:51:46 +02:00
485ca284b0 Extracts portal saving and loading to the PortalFileHelper class 2021-10-22 16:18:35 +02:00
4e09b44c7c Extracts portal registration and all portal lookup to the PortalRegistry class 2021-10-21 23:59:16 +02:00
593d528bcd Extracts the portal creation and validation part of the PortalHandler into the PortalCreator 2021-10-21 20:15:29 +02:00
79a43b82e5 Fixes the readme's max sign character limits as the real limit is 11, not 12 2021-10-21 20:13:34 +02:00
cb2f7443f5 Improves comments for PortalOptions 2021-10-21 16:39:35 +02:00
8eff9ae5e6 Fixes comments for the PortalOpener class 2021-10-21 00:27:42 +02:00
4b42d4914a Renames openTime to activatedTime, though it's kind of used for both opening and activation 2021-10-20 20:57:00 +02:00
2650e31d97 Fixes comments for the portal activator 2021-10-20 20:55:51 +02:00
d2e8c81a5a Splits the portal class into Portal, PortalActivator, PortalOpener and PortalStructure
PortalStructure now contains information about the gate's physical structure, such as the location of border blocks, the location of entrances, the gate type and the button location.
PortalActivator is responsible for activating/de-activating portals, destination toggling and getting information about available destinations.
PortalOpener is responsible for opening/closing a portal. It's also the place to go for checking if the portal is open for a given player.
Comments of the Portal class have been improved, but the comments of the three new classes need fixing.
2021-10-20 16:09:35 +02:00
635d08b1b3 Makes the SignHelper helper class into the proper PortalSignDrawer which each Portal now has one instance of 2021-10-20 01:33:36 +02:00
1d4b988ca4 Moves the rest of the sign drawing code from Portal to SignHelper 2021-10-18 19:12:30 +02:00
82ed28bba0 Removes the DirectionHelper's getBlockAt method as it only increased complexity 2021-10-18 18:52:24 +02:00
0506cf1b61 Splits the PortalTeleporter into a PlayerTeleporter and a VehicleTeleporter for better structuring
Moves player-related teleportation code to PlayerTeleporter, moves vehicle-related teleportation code to VehicleTeleporter and makes them both extend the Teleporter which now contains all common teleportation code.
2021-10-18 18:38:36 +02:00
f4ec5e05d6 Improves sign drawing during portal loading
Adds "Invalid gate" to the last line of portals with an invalid or unloaded gate type
Re-draws all portals' signs on load in case some invalid portals becomes valid. This also updates any formatting
2021-10-18 18:34:35 +02:00
8c16ddbed5 Adds some tests to the relative block vector 2021-10-18 15:22:55 +02:00
27b1f0641e Adds some block location tests 2021-10-18 14:57:12 +02:00
ac045fa7db Fixes a severe bug caused by trying to simplify GateLayout's saveLayout method 2021-10-18 04:00:18 +02:00
982d8abf65 Extracts teleportation code into the PortalTeleporter class to improve readability 2021-10-18 03:41:16 +02:00
f96e8ed2da Adds a method for checking if a player can afford a given fee 2021-10-18 03:36:56 +02:00
d9ae5456cc Improves permission checking for vehicles with multiple passengers
Changes some log messages into debug messages
Makes sure that all player passengers of a vehicle have their permissions verified and fee paid. This ensures passengers won't get a free ride, or be allowed to access restricted areas by playing stowaway.
2021-10-18 03:35:59 +02:00
fabe0dda80 Fixes inconsistencies in coloring of the portal name when drawing signs. The - is now white. 2021-10-17 22:47:27 +02:00
3de785d5ab Improves and fixes comments for the gate layout class 2021-10-16 16:44:11 +02:00
59069d1423 Improves comments in the GateHandler class and extracts some code into GateReader
Moves code for reading .gate files into the GateReader helper class
Improves all comments in GateHandler where possible
Adds more helper comments
2021-10-15 22:16:02 +02:00
5299efaa86 Renames types to characterMaterialMap inside the GateHandler class 2021-10-15 19:46:25 +02:00
e2c91c1feb Changes EconomyHandler method names to be more consistent 2021-10-15 19:25:31 +02:00
d45af537cd Removes the unused getCorners method 2021-10-15 19:24:15 +02:00
6e658003e0 Improves Gate comments where possible
Renames types to characterMaterialMap
Simplifies writeConfig to a single method
2021-10-15 19:23:17 +02:00
382156a719 Adds missing information about gate economy config values 2021-10-15 18:52:02 +02:00
44325eeb6a Improves and fixes comments for listeners
Removes the enableBungee check in the BungeeCordListener as it should only be listening if the option is enabled anyway
Improves the checking for players teleporting from the end
2021-10-13 16:46:30 +02:00
bf7a10636e Improves comments for Stargate events, and adds a new event for teleporting entities
Adds information about what events can be used for
Tries to clarify event comments where possible
Renames The StargatePortalEvent to StargatePlayerPortalEvent
Adds StargateEntityPortalEvent
Makes the StargateEntityPortalEvent trigger whenever a vehicle is teleported
Removes the unused event name for all events
2021-10-13 15:45:15 +02:00
0ab6cb52c0 Makes some small comment adjustments 2021-10-13 14:08:38 +02:00
f16a7089f4 Updates the comments for BlockLocation 2021-10-13 13:35:56 +02:00
4bdc5b6bd9 Populates default gates after migrating config to update default gates for old installations 2021-10-12 20:41:45 +02:00
5b6e3f81a6 Updates readme to mark end portals as functional 2021-10-12 04:18:58 +02:00
0709c18e30 Adjusts height to get above slabs to an entire block as empty minecarts clipped through single slab layers 2021-10-12 03:48:13 +02:00
e14007380f Adds proper checking and odd case catching before teleporting players through artificial end portals
Adds a proper permission check to make sure players are allowed to teleport through the artificial portal
Adds a teleportation back to the entrance as the teleportation event cannot be properly cancelled
Adds a proper class for storing info about the teleportation
2021-10-12 02:47:09 +02:00
53cd55938b Fixes teleportation of players using end portals to and from the end 2021-10-12 01:11:52 +02:00
51afa1527f Updates the API version used as 1.16 is no longer the target version 2021-10-11 20:16:36 +02:00
72c1b5a239 Updates About information 2021-10-11 20:13:50 +02:00
b0c350a140 Updates README to reflect vehicle teleportation capabilities 2021-10-11 01:35:12 +02:00
06757ef9ee Removes debug output for chunk unloading 2021-10-11 01:20:50 +02:00
9efc960696 Makes sure to check entrance blocks when, and only when, protectEntrance is enabled 2021-10-11 00:11:04 +02:00
1bf9914c39 Generifies another vehicle check 2021-10-11 00:03:49 +02:00
05123d54bd Generifies the check for non-living vehicles just in case 2021-10-10 23:38:20 +02:00
2e4d545955 Changes the vehicle check to prevent players in boats from leaving their boat to teleport 2021-10-10 23:17:29 +02:00
f8fae1fbf1 Makes sure to ignore mounted players if handleVehicles is disabled 2021-10-10 23:11:52 +02:00
964eb0f898 Adds a new The End- inspired gate for more default diversity 2021-10-10 23:03:39 +02:00
38ea543b80 Improves chunk unloading
Adds all chunk unloading to a queue
Adds a thread which unloads chunks
Updates chunk unload requests such that a chunk won't be unloaded twice,
and an old unloading request cannot unload a chunk too soon
2021-10-10 22:33:30 +02:00
69a62c921c Fixes the color inconsistency for the portal selection "arrows" for gates with colored names 2021-10-10 17:21:19 +02:00
b847002617 Adds some small changes which seem to completely fix all horse teleportation bugs. Fixes #1 2021-10-10 17:15:00 +02:00
6ad7fa4cb9 Removes the IllegalStateException Removing entity while ticking! TODO as it seems to have been fixed by preventing portal creation 2021-10-10 16:14:51 +02:00
2abe10bcde Improves the way chunks are loaded, and decreases the wait time before players are put into minecarts 2021-10-10 15:10:36 +02:00
7a9dbb8046 Implements some Java 14 code migrations 2021-10-09 23:41:19 +02:00
3a8943baef Configures the maven-compiler-plugin to also compile the source code as Java 16 2021-10-09 23:38:55 +02:00
be8de83bcc Forces a teleported horse to become tamed 2021-10-09 18:48:59 +02:00
ba3304a716 Fixes a minecart rotation bug caused by changing the rotation of the deleted vehicle instead of the new one 2021-10-09 17:03:19 +02:00
051a6b8f98 Removes the temporary ignoreEntrances option and replaces it with proper snowman blocking. Fixes #3
Allows new gates to contain water as underwater gates are a thing now
Adds a check to prevent snowmen from placing snow inside a portal's entrance
Removes the ignoreEntrances option everywhere
2021-10-09 15:09:14 +02:00
f87ffc906c Minor comment and formatting cleanup 2021-10-09 03:57:24 +02:00
336c3c4bfb Updates information about plugin settings 2021-10-09 03:02:00 +02:00
2fec641d9d Fixes typos 2021-10-08 23:23:06 +02:00
0c29788a31 Fixes the behavior of backwards portals
Fixes a bug where backwards portals only rotated the player
Fixes the rotation being wrong when teleporting from a backwards portal
2021-10-08 18:59:14 +02:00
fff4d8d78b Gets rid of the rest of the modX and modY usages, and removes some unused code 2021-10-08 18:23:42 +02:00
a68dc4b464 Updates the README with new info and fixes a ton of gramatical errors 2021-10-08 18:21:30 +02:00
6d5c4802bc Creates some new methods to get a location from a relative location which I can actually understand 2021-10-08 15:28:12 +02:00
e7fc1daafe Moves functionality to the PortalOptions and PortalLocation classes 2021-10-08 01:26:12 +02:00
60c543e52a Adds a new class for keeping track of portal options 2021-10-08 01:25:25 +02:00
76b2aab057 Tries to improve readability of the portal creation code 2021-10-06 19:46:34 +02:00
201f7eaf15 Adds a class for storing a portal's location data 2021-10-06 19:45:49 +02:00
d86aae87f3 Extracts portal creation validation into its own method 2021-09-25 13:52:00 +02:00
e4f71f1b71 Extracts some portal creation code into separate methods 2021-09-25 13:22:50 +02:00
7dcf050d96 Removes a function for checking if a control block is powered as it's never used 2021-09-25 12:46:59 +02:00
8ada84ddb3 Improves an error message 2021-09-23 18:21:15 +02:00
98cee192aa Fixes some behavior when a language is valid, but the language file does not exist 2021-09-23 18:12:57 +02:00
650a26402a Moves all config migration mappings to its own file 2021-09-23 17:50:43 +02:00
461202503e Adds migration for useiconomy and CheckUpdates which were found in old config files 2021-09-22 13:52:22 +02:00
dd7176fa12 Adds config migration to reduce annoyance and improve backwards compatibility 2021-09-22 13:42:21 +02:00
b7c7252fad Improves the differentiation between portals (stargates) and gates 2021-09-21 18:28:18 +02:00
24af26324a Renames some methods to prevent confusion 2021-09-20 19:23:57 +02:00
f2579c4b12 Adds back default constructor as removing it caused it to no longer load 2021-09-20 18:52:16 +02:00
1e29db58b9 Improves some variable names and adds some comments 2021-09-20 18:22:20 +02:00
d24f35375a Changes the default folders to prevent problems on Unix systems 2021-09-20 18:21:26 +02:00
f681db629f Refactors a lot of code, and extracts permission-related functions to the PermissionHelper class 2021-09-20 13:56:30 +02:00
b57f988b62 Improves formatting for some files 2021-09-20 13:48:03 +02:00
f12306426b Renames strings in onCommand to args for consistency 2021-09-20 13:46:20 +02:00
8ff30ed03f Improves config readability, but breaks backwards compatibility 2021-09-19 17:46:20 +02:00
d5e6f1145c Removes unused getBalance method 2021-09-19 15:06:41 +02:00
8835e69e3c Moves some code from Stargate to EconomyHandler 2021-09-19 15:05:19 +02:00
b191ac1de5 Moves some classes to the new container package, and improves some code 2021-09-18 21:51:29 +02:00
4851a0b5e2 Only gets vehicle exit world when it's actually used 2021-09-16 21:31:32 +02:00
e253e95cec Minor function cleaning 2021-09-12 15:23:22 +02:00
c35378cfe0 Improves pre-teleport chunk loading 2021-09-12 06:18:20 +02:00
319849fd96 Prevents suffocation when teleporting on a horse 2021-09-12 06:02:10 +02:00
abd48b646d Fixes code for slab checking to prevent the player from teleporting underneath the block 2021-09-12 02:21:13 +02:00
f2332badb6 Adds some missing information about creating bungee gates 2021-09-12 01:31:21 +02:00
19018e46b8 Fixes some bugs regarding bungee teleportation
Fixes the server being teleported normally after it's teleported to another server
Fixes a nullpointerexception
2021-09-12 01:23:16 +02:00
ec4ed1e086 Fixes some more warnings 2021-09-11 17:02:43 +02:00
5c601710e7 Removes the unused frameBlocks variable and isGateBlock 2021-09-11 16:44:55 +02:00
a6fb7dcb62 Fixes some warnings 2021-09-11 16:43:31 +02:00
6005c2c6d1 Adds some information about usable portal open/closed materials to the readme 2021-09-11 16:37:55 +02:00
1c3dbbe81d Renames the blox populator and block populator thread as I finally understand what they actually do 2021-09-11 15:33:45 +02:00
87735e4935 Adds some helper functions to make getting direction-related values easier
Adds a function for getting the yaw given two locations
Adds a function for getting a block face given a yaw
2021-09-11 15:04:55 +02:00
93f8f715e5 Fixes some old bugs and renames rotX to yaw
Fixes the direction of minecarts sent through a portal. This prevent the minecarts to go back through the portal
and causing a lot of confusion
2021-09-10 23:38:56 +02:00
b4059dd169 Adds an event listener to prevent the nether portal stargates from creating actual nether portals 2021-09-10 23:35:27 +02:00
a86a5de8c3 Fixes the bug with teleporting horses, but introduces a bug with teleporting minecarts 2021-09-10 21:32:58 +02:00
7b83b2440c Sets junit version 2021-09-09 15:43:50 +02:00
8ae4ac3fc7 Makes sure to only try and create the portal folder if it does not exist 2021-09-09 15:42:30 +02:00
3ac6270897 Merge branch 'master' into vehicles 2021-09-09 15:26:25 +02:00
daa3c6f868 Cleans up a bit and changes to compile for java 16 and spigot 1.17 2021-09-09 15:25:08 +02:00
75fbd44af7 Removes an s in class name of PlayerEventsListener 2021-09-02 00:31:03 +02:00
0fe2a5b380 Adds some more fixes and improvements for vehicle teleportation 2021-06-11 20:46:14 +02:00
b1aa53c1a9 Adds missing comments to BlockPopulatorThread and make end gateways teleport entities back to itself to prevent strange behavior
Because of the teleport change, end gateways work to teleport player,
and end gateways work to the end for vehicles, but vehicles cannot teleport back from the end
2021-03-02 17:55:14 +01:00
44dfa2a10d Greatly refactors gate loading 2021-02-28 21:53:27 +01:00
504ef1b52f Adds remaining missing comments to Portal 2021-02-27 22:50:44 +01:00
da32cf11d1 Fixes some things regarding vehicle teleportation
Adds extra space between the portal and the vehicle if the destination portal is always on
Fixes a bug causing vehicles not being detected soon enough
Fixes boats facing into the portal rather than out from the portal
Fixes boats spawning inside water rather than on top of it if water is in front of a portal
2021-02-27 22:34:10 +01:00
ba64572254 Adds more comments, simplifies some code and improves positioning of teleported large entities, like horses 2021-02-27 21:17:36 +01:00
79703e49af Adds a class which helps with modZ and modX calculations 2021-02-27 21:15:39 +01:00
496b5d9779 Moves sign drawing to a helper class to reduce the complexity of the portal class 2021-02-24 18:12:26 +01:00
378a59586d Heavily simplifies sign drawing and cleans up vehicle teleportation code 2021-02-24 17:48:01 +01:00
2b52759e00 Makes sure teleportation not from a plugin does not trigger vehicle teleportation 2021-02-24 17:45:53 +01:00
4acea17ba3 Fixes boats sometimes not detecting the portal before the player detects the portal 2021-02-23 19:43:49 +01:00
5f685b2460 Fixes some oddities regarding vehicle teleportation
Accounts for size when blocking an entity near a portal from teleporting to the nether
Ignores boats and minecarts when teleporting a vehicle after the player
Makes it easy to get a portal by adjacent entrance for any given range
2021-02-23 19:17:05 +01:00
e42da6d6bd Updates the README with some of the recent changes 2021-02-23 00:41:40 +01:00
681014a431 Improves some code formatting in the portal open method 2021-02-23 00:35:48 +01:00
af693bddd2 Fixes the timing of the block populator thread which caused a delay between opening a gate and it displaying as open 2021-02-23 00:35:18 +01:00
151c242e69 Changes names of some variables and one method to increase readability 2021-02-22 20:36:37 +01:00
e5fef0b16a Moves the EconomyHandler to utility and adds some encapsulation for three of its variables 2021-02-22 20:26:10 +01:00
e665a49f03 Adds missing comments to Gate and changes the matches function from n^2 to n execution time 2021-02-22 20:25:07 +01:00
279ea9d8f0 Fixes some nullpointerexceptions in PlayerEventsListener's onPlayerMove 2021-02-22 20:23:12 +01:00
d26196b8aa Adds some extra explanations to gate layout's description 2021-02-22 18:34:23 +01:00
fb70b8bc75 Splits Gate into Gate, GateLayout and GateHandler, and creates a new portal package with portal related classes 2021-02-22 17:01:47 +01:00
c422cb9ea9 Overrides toString and equals methods of the relative block vector to make it testable 2021-02-22 15:49:44 +01:00
a475e8d8b1 Adds missing comments to the world event listener and adds faster gate unloading
Replaces the clear all + load all with a method which just removes all
relevant portals directly. This should be faster, especially with many gates and worlds
2021-02-20 16:21:18 +01:00
1d642bfcf2 Adds missing comments to the vehicle event listener 2021-02-20 14:59:59 +01:00
1da0f4eddc Adds comments to the plugin event listener 2021-02-20 14:55:23 +01:00
889a9d2cbc Finishes commenting and refactoring the player events listener 2021-02-20 14:42:41 +01:00
1721750aa1 Adds comments and simplifies some of the code
Adds a PortalOption enum to simplify portal options
Adds a BungeeHelper class to collect the bungee-related code
2021-02-20 13:57:04 +01:00
2ae4fc9645 Adds class comment to the stargate tab completer 2021-02-19 12:07:34 +01:00
c912624df1 Adds comments to all custom events
Adds full comments to every class implementing StarGateEvent
Adds another abstract event StargatePlayerEvent which reduces code duplication
2021-02-19 12:06:23 +01:00
5b7f5649b1 Makes a whole lot of changes
Adds some new tests
Improves plugin command handling by using one class for each command
Makes some changes to vehicle teleportation to support horses and pigs, but vehicle teleportation is still buggy and messy
Adds some more missing comments
Adds a wildcard permission and uses built-in permissions some places to avoid checking for three different permissions
2021-02-16 21:58:31 +01:00
df074b9ff5 Adds an entity portal event listener which fixes the infuriating bug which caused empty minecarts to disappear into the nether 2021-02-12 01:35:55 +01:00
42fa6ed8d7 Huge refactoring
Splits Portal into Portal and PortalHandler
Adds EconomyHelper to make messaging of economy string easier
Adds a lot of missing comments
Adds vehicle teleportation again, but it needs a lot of changes to work properly
2021-02-12 00:26:47 +01:00
bd4586e386 Adds annotations to prevent warnings 2021-02-12 00:24:27 +01:00
1719e92494 Moves a bunch of inner classes to their own files 2021-02-11 15:53:54 +01:00
56410a58f8 Improves formatting of custom gate information 2021-02-10 15:12:48 +01:00
ff8f762ea8 Fixes information about water gates and adds a list of all valid buttons 2021-02-10 15:05:41 +01:00
c41429b6e0 Makes default gates load from files rather than being defined in code 2021-02-10 14:32:01 +01:00
d472eab21b Adds two gate types to resources 2021-02-10 14:30:30 +01:00
095e59c65e Makes it easier to use any compatible block as a button 2021-02-10 03:29:28 +01:00
7b9f5a6de5 Adds underwater portal support using any wall coral as a button replacement 2021-02-10 02:20:50 +01:00
e49b94cf9a Adds a class for helping to decide if a material is a wall coral (dead or alive) 2021-02-10 02:19:48 +01:00
a8c0574f3b Adds other authors to plugin.yml 2021-02-10 02:17:49 +01:00
f0a7ff8c47 Adds more BlockLocation tests 2021-02-09 23:25:52 +01:00
32410a82ba Adds some tests for the equals method of BlockLocationTest 2021-02-09 21:12:43 +01:00
b6d18a4217 Adds an extra constructor to Stargate required for testing 2021-02-09 21:12:04 +01:00
68f3bca04f Adds MockBukkit and JUnit dependencies for testing 2021-02-09 21:11:22 +01:00
2bd5bb36cf Updates README to account for recent changes 2021-02-09 20:40:48 +01:00
5a9d70f827 Adds a warning against treating BlockLocation as a Location 2021-02-09 20:38:50 +01:00
a5cf1a7cd3 Improves translation rate of info and error strings 2021-02-09 20:10:17 +01:00
af6a2537b8 Adds more information regarding why economy could not be enabled 2021-02-09 20:09:49 +01:00
cdae2d8f35 Adds language strings related to loading Vault 2021-02-09 20:08:40 +01:00
9ace568047 Fixes garbled text caused by writing, but not reading language files as UTF-8 2021-02-09 20:07:56 +01:00
5a8e8a219e Replaces several static strings with strings from the language files 2021-02-09 18:47:54 +01:00
3521257cb2 Adds some vault related strings to the english language file 2021-02-09 18:46:55 +01:00
b9cbe9ee4c Makes Vault a soft dependency 2021-02-09 18:46:30 +01:00
e702a0d734 Tidies up and comments the economy handler and removes the depreciated method of getting an offline player by username 2021-02-08 14:30:14 +01:00
f97cb32466 Improves messages for the language loader 2021-02-08 14:27:53 +01:00
4f5cb84d02 Improves error handling when unable to load a language and always uses english as backup language to make sure the plugin won't crash when an invalid language is chosen 2021-02-08 05:10:10 +01:00
341a445d16 Fixes language file formats, fixes name of Norwegian Bokmål and adds Norewgian Bokmål 2021-02-08 05:07:35 +01:00
df111c2750 Adds Norwegian translation 2021-02-08 01:54:18 +01:00
6825266a92 Fixes a bug caused by BlockLocation not being able to be instantiated from a location object 2021-02-08 00:35:34 +01:00
6d6a7e52b2 Renames usages of BungeeCoordListener to BungeeCordListener 2021-02-08 00:33:55 +01:00
6e1a69881c Cleans, comments and renames BungeeCordListener 2021-02-08 00:32:58 +01:00
9233776b2c Adds comments to BloxPopulator 2021-02-08 00:32:20 +01:00
27aa0ed29d Removes a function from BlockLocation present in Location which caused an infinite loop 2021-02-08 00:31:53 +01:00
4e3867eae9 Simplifies BlockLocation by making it extend Location 2021-02-07 16:58:33 +01:00
ad2be87404 Cleans and commments the BlockLocation class by storing most of its information as a Location 2021-02-07 16:37:42 +01:00
a268370f52 Updates plugin version to 0.9 2021-02-07 16:34:13 +01:00
c8d82a8575 Removes CommonFunctions as it wasn't used 2021-02-07 16:33:45 +01:00
6ff998ac3b Restructures the plugin and starts work on cleaning and commenting the code 2021-02-07 03:37:25 +01:00
147 changed files with 17511 additions and 6101 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,6 @@
target/ target/
.idea/ .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/>.

33
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,33 @@
pipeline {
agent any
tools {
jdk 'JDK17'
}
stages {
stage('Build') {
steps {
echo 'Building...'
sh 'mvn clean & mvn validate & mvn compile'
}
}
stage('Test') {
steps {
echo 'Testing...'
sh 'mvn test'
}
}
stage('Verify') {
steps {
echo 'Verifying...'
sh 'mvn verify -Dmaven.test.skip=true'
}
}
stage('Deploy') {
steps {
echo 'Deploying...'
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>.

1851
README.md

File diff suppressed because it is too large Load Diff

269
pom.xml
View File

@@ -1,60 +1,211 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<modelVersion>4.0.0</modelVersion> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>org.TheDgtl</groupId> <modelVersion>4.0.0</modelVersion>
<artifactId>Stargate</artifactId>
<version>0.8.0.3</version> <groupId>net.knarcraft</groupId>
<properties> <artifactId>Stargate</artifactId>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <version>0.11.5.12-SNAPSHOT</version>
</properties>
<repositories> <licenses>
<repository> <license>
<id>spigot-repo</id> <name>GNU Lesser General Public License</name>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url> <url>https://www.gnu.org/licenses/lgpl-3.0.en.html</url>
</repository> </license>
<repository> </licenses>
<id>vault-repo</id>
<url>http://nexus.hc.to/content/repositories/pub_releases</url> <properties>
</repository> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</repositories> <java.version>16</java.version>
<dependencies> </properties>
<dependency>
<groupId>org.spigotmc</groupId> <repositories>
<artifactId>spigot-api</artifactId> <repository>
<version>1.16.2-R0.1-SNAPSHOT</version> <id>knarcraft-repo</id>
</dependency> <url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
<dependency> </repository>
<groupId>net.milkbowl.vault</groupId> <repository>
<artifactId>VaultAPI</artifactId> <id>spigot-repo</id>
<version>1.7</version> <url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</dependency> </repository>
</dependencies> <repository>
<build> <id>jitpack.io</id>
<sourceDirectory>src</sourceDirectory> <url>https://jitpack.io</url>
<resources> </repository>
<resource> <repository>
<targetPath>net/TheDgtl/Stargate/resources</targetPath> <id>dynmap</id>
<directory>src/net/TheDgtl/Stargate/resources</directory> <url>https://repo.mikeprimm.com/</url>
<includes> </repository>
<include>*.txt</include> <repository>
</includes> <id>papermc</id>
</resource> <url>https://repo.papermc.io/repository/maven-public/</url>
<resource> </repository>
<directory>src</directory> <repository>
<includes> <id>minebench-repo</id>
<include>*.yml</include> <url>https://repo.minebench.de/</url>
</includes> </repository>
</resource> <repository>
</resources> <id>opencollab-snapshot</id>
<plugins> <url>https://repo.opencollab.dev/main/</url>
<plugin> </repository>
<groupId>org.apache.maven.plugins</groupId> </repositories>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version> <distributionManagement>
<configuration> <repository>
<source>1.8</source> <id>knarcraft-repo</id>
<target>1.8</target> <url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</configuration> </repository>
</plugin> <snapshotRepository>
</plugins> <id>knarcraft-repo</id>
</build> <url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.21.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<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>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>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<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,54 +0,0 @@
# Stargate Configuration File
# Main Stargate config
#
# portal-folder - The folder for storing portals
# gate-folder - The folder for storing gate layouts
# default-gate-network - The default gate network
# destroyexplosion - Whether or not to destroy gates with explosions (Creeper, TNT, etc)
# maxgates - The maximum number of gates allowed on a network - 0 for unlimited
# lang - The language file to load for messages
# destMemory - Whether to remember the cursor location between uses
# ignoreEntrance - Ignore the entrance blocks of a gate when checking. Used to work around snowmen
# handleVehicles - Whether to allow vehicles through gates
# sortLists - Whether to sort network lists alphabetically
# protectEntrance - Whether to protect gate entrance material (More resource intensive. Only enable if using destroyable open/closed material)
# signColor - The color used for drawing signs (Default: BLACK).
# verifyPortals - Whether or not all the non-sign blocks are checked to match the gate layout when a stargate is loaded.
############################
# Stargate economy options #
############################
# useeconomy - Whether to use an economy plugin
# createcost - The cost to create a gate
# destroycost - The cost to destroy a gate
# usecost - The cost to use a gate
# toowner - Whether the charge for using a gate goes to the gates owner
# chargefreedestination - Whether a gate whose destination is a free gate is still charged
# freegatesgreen - Whether a free gate in the destination list is drawn green
#################
# Debug options #
#################
# debug - Debug -- Only enable if you have issues, massive console output
# permdebug - This will output any and all Permissions checks to console, used for permissions debugging (Requires debug: true)
portal-folder: plugins/Stargate/portals/
gate-folder: plugins/Stargate/gates/
default-gate-network: central
destroyexplosion: false
maxgates: 0
lang: en
destMemory: false
ignoreEntrance: false
handleVehicles: true
sortLists: false
protectEntrance: false
signColor: BLACK
useeconomy: false
createcost: 0
destroycost: 0
usecost: 0
toowner: false
chargefreedestination: true
freegatesgreen: false
debug: false
permdebug: false
enableBungee: false
verifyPortals: false

View File

@@ -0,0 +1,442 @@
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.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.container.ControlBlockUpdateRequest;
import net.knarcraft.stargate.listener.EnvironmentChangeListener;
import net.knarcraft.stargate.listener.PlayerEventListener;
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 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.JavaPluginLoader;
import org.bukkit.scheduler.BukkitScheduler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.LinkedList;
import java.util.PriorityQueue;
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 ConfigCommentPlugin {
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
*/
public Stargate() {
super();
}
/**
* Special constructor used for MockBukkit
*
* @param loader <p>The plugin loader to be used.</p>
* @param descriptionFile <p>The description file to be used.</p>
* @param dataFolder <p>The data folder to be used.</p>
* @param file <p>The file to be used</p>
*/
protected Stargate(JavaPluginLoader loader, PluginDescriptionFile descriptionFile, File dataFolder, File file) {
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;
}
/**
* Adds a block change request to the request queue
*
* @param request <p>The request to add</p>
*/
public static void addControlBlockUpdateRequest(@Nullable BlockChangeRequest request) {
if (request != null) {
controlBlockUpdateRequestQueue.add(request);
}
}
/**
* Gets the queue containing control block update requests
*
* @return <p>A control block update request queue</p>
*/
@NotNull
public static Queue<BlockChangeRequest> getControlBlockUpdateRequestQueue() {
return controlBlockUpdateRequestQueue;
}
/**
* Adds a control block update request to the request queue
*
* @param request <p>The request to add</p>
*/
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;
}
/**
* Gets the object containing gate configuration values
*
* @return <p>The object containing gate configuration values</p>
*/
@NotNull
public static StargateGateConfig getGateConfig() {
return stargateConfig.getStargateGateConfig();
}
/**
* Gets the version of this plugin
*
* @return <p>This plugin's version</p>
*/
@NotNull
public static String getPluginVersion() {
return pluginVersion;
}
/**
* Gets the logger used for logging to the console
*
* @return <p>The logger</p>
*/
@NotNull
public static Logger getConsoleLogger() {
return logger;
}
/**
* Gets the max length of portal names and networks
*
* @return <p>The max portal name/network length</p>
*/
@SuppressWarnings("SameReturnValue")
public static int getMaxNameNetworkLength() {
return 13;
}
/**
* Sends a debug message
*
* @param route <p>The class name/route where something happened</p>
* @param message <p>A message describing what happened</p>
*/
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);
}
}
/**
* Logs an info message to the console
*
* @param message <p>The message to log</p>
*/
public static void logInfo(@NotNull String message) {
log(Level.INFO, message);
}
/**
* Logs a severe error message to the console
*
* @param message <p>The message to log</p>
*/
public static void logSevere(@NotNull String message) {
log(Level.SEVERE, message);
}
/**
* Logs a warning message to the console
*
* @param message <p>The message to log</p>
*/
public static void logWarning(@NotNull String message) {
log(Level.WARNING, message);
}
/**
* Logs a message to the console
*
* @param severity <p>The severity of the event triggering the message</p>
* @param message <p>The message to log</p>
*/
private static void log(@NotNull Level severity, @NotNull String message) {
if (logger == null) {
logger = Bukkit.getLogger();
}
logger.log(severity, message);
}
/**
* Gets the folder for saving created portals
*
* <p>The returned String path is the full path to the folder</p>
*
* @return <p>The folder for storing the portal database</p>
*/
@NotNull
public static String getPortalFolder() {
return stargateConfig.getPortalFolder();
}
/**
* Gets the folder storing gate files
*
* <p>The returned String path is the full path to the folder</p>
*
* @return <p>The folder storing gate files</p>
*/
@NotNull
public static String getGateFolder() {
return stargateConfig.getGateFolder();
}
/**
* Gets the default network for gates where a network is not specified
*
* @return <p>The default network</p>
*/
@NotNull
public static String getDefaultNetwork() {
return stargateConfig.getStargateGateConfig().getDefaultPortalNetwork();
}
/**
* 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 @NotNull String getBackupString(@NotNull Message name) {
return stargateConfig.getLanguageLoader().getBackupString(name);
}
/**
* Gets this plugin's plugin manager
*
* @return <p>A plugin manager</p>
*/
@NotNull
public static PluginManager getPluginManager() {
return pluginManager;
}
/**
* Gets the object containing economy config values
*
* @return <p>The object containing economy config values</p>
*/
@NotNull
public static EconomyConfig getEconomyConfig() {
return stargateConfig.getEconomyConfig();
}
@Override
public void onDisable() {
PortalUtil.closeAllPortals();
PortalRegistry.clearPortals();
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();
// 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();
logger.info(pluginDescriptionFile.getName() + " v." + pluginDescriptionFile.getVersion() + " is enabled.");
//Register events before loading gates to stop weird things from happening.
registerEventListeners();
//Run necessary threads
runThreads();
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);
}
/**
* Starts threads using the bukkit scheduler
*/
private void runThreads() {
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);
}
/**
* Registers all event listeners
*/
private void registerEventListeners() {
pluginManager.registerEvents(new PlayerEventListener(), this);
pluginManager.registerEvents(new StargateCreateDestroyListener(), this);
pluginManager.registerEvents(new StargateTeleportListener(), this);
pluginManager.registerEvents(new EnvironmentChangeListener(this), this);
pluginManager.registerEvents(new StargateBreakListener(), this);
}
/**
* Gets the chunk unload queue containing chunks to unload
*
* @return <p>The chunk unload queue</p>
*/
@NotNull
public static Queue<ChunkUnloadRequest> getChunkUnloadQueue() {
return chunkUnloadQueue;
}
/**
* Adds a new chunk unload request to the chunk unload queue
*
* @param request <p>The new chunk unload request to add</p>
*/
public static void addChunkUnloadRequest(@NotNull ChunkUnloadRequest request) {
chunkUnloadQueue.removeIf((item) -> item.getChunkToUnload().equals(request.getChunkToUnload()));
chunkUnloadQueue.add(request);
}
/**
* Gets the stargate configuration
*
* @return <p>The stargate configuration</p>
*/
@NotNull
public static StargateConfig getStargateConfig() {
return stargateConfig;
}
}

View File

@@ -0,0 +1,48 @@
package net.knarcraft.stargate.command;
import de.themoep.minedown.MineDown;
import net.knarcraft.knarlib.util.FileHelper;
import net.knarcraft.stargate.Stargate;
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
*/
public class CommandAbout implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] strings) {
ChatColor textColor = ChatColor.GOLD;
ChatColor highlightColor = ChatColor.GREEN;
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

@@ -0,0 +1,448 @@
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 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
*/
public class CommandConfig implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (commandSender instanceof Player player) {
if (!PermissionHelper.hasPermission(player, Permission.CONFIG)) {
new SGFormatBuilder("Permission Denied").error(commandSender);
return true;
}
}
if (args.length > 0) {
ConfigOption selectedOption = ConfigOption.getByName(args[0]);
if (selectedOption == null) {
return false;
}
if (args.length > 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);
}
return true;
} else {
//Display all config options
displayConfigValues(commandSender);
}
return true;
}
/**
* 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 value <p>The new value of the config option</p>
*/
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.of(value.toUpperCase());
} catch (IllegalArgumentException | NullPointerException ignored) {
commandSender.sendMessage(ChatColor.RED + "Invalid color given");
return;
}
}
//Store the config values, accounting for the data type
switch (selectedOption.getDataType()) {
case BOOLEAN -> updateBooleanConfigValue(selectedOption, value, configuration);
case INTEGER -> {
Integer intValue = getInteger(commandSender, selectedOption, value);
if (intValue == null) {
return;
} else {
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, intValue);
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 -> {
updateStringConfigValue(selectedOption, commandSender, value);
configuration.set(selectedOption.getConfigNode(), value);
}
default -> {
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, value);
configuration.set(selectedOption.getConfigNode(), value);
}
}
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();
new SGFormatBuilder("Config updated").success(commandSender);
//Reload whatever is necessary
reloadIfNecessary(commandSender, selectedOption);
}
/**
* Registers the chat color if
*
* @param selectedOption <p>The option to change</p>
* @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(@NotNull ConfigOption selectedOption, @NotNull String value,
@NotNull CommandSender commandSender) {
ChatColor parsedColor = parseColor(value);
if (parsedColor == null) {
commandSender.sendMessage(ChatColor.RED + "Invalid color given");
return false;
}
if (selectedOption == ConfigOption.FREE_GATES_COLOR) {
PortalSignDrawer.setFreeColor(parsedColor);
} else if (selectedOption == ConfigOption.MAIN_SIGN_COLOR) {
PortalSignDrawer.setMainColor(parsedColor);
} else if (selectedOption == ConfigOption.HIGHLIGHT_SIGN_COLOR) {
PortalSignDrawer.setHighlightColor(parsedColor);
}
return true;
}
/**
* Parses a chat color
*
* @param value <p>The value to parse</p>
* @return <p>The parsed color or null</p>
*/
@Nullable
private ChatColor parseColor(@NotNull String value) {
try {
return ChatColor.of(value.toUpperCase());
} catch (IllegalArgumentException | NullPointerException ignored) {
return null;
}
}
/**
* Gets an integer 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>An integer, or null if it was invalid</p>
*/
@Nullable
private Integer getInteger(@NotNull CommandSender commandSender, @NotNull ConfigOption selectedOption,
@NotNull String value) {
try {
int intValue = Integer.parseInt(value);
if ((selectedOption == ConfigOption.USE_COST || selectedOption == ConfigOption.CREATE_COST) && intValue < 0) {
commandSender.sendMessage(ChatColor.RED + "This config option cannot be negative.");
return null;
}
return intValue;
} catch (NumberFormatException exception) {
commandSender.sendMessage(ChatColor.RED + "Invalid number given");
return null;
}
}
/**
* 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(@NotNull CommandSender commandSender, @NotNull ConfigOption configOption) {
if (ConfigTag.requiresFullReload(configOption)) {
//Reload everything
Stargate.getStargateConfig().reload(commandSender);
} 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();
}
}
}
/**
* Prints information about a config option and its current value
*
* @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(@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);
}
/**
* Displays the name and a small description of every config value
*
* @param sender <p>The command sender to display the config list to</p>
*/
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));
}
}
/**
* Gets the description of a single config option
*
* @param option <p>The option to describe</p>
* @return <p>A string describing the config option</p>
*/
@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 + stringValue + ChatColor.DARK_GRAY + ")";
}
}

View File

@@ -0,0 +1,31 @@
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;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* This command represents the plugin's reload command
*/
public class CommandReload implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (commandSender instanceof Player player) {
if (!PermissionHelper.hasPermission(player, Permission.RELOAD)) {
new SGFormatBuilder("Permission Denied").error(commandSender);
return true;
}
}
Stargate.getStargateConfig().reload(commandSender);
return true;
}
}

View File

@@ -0,0 +1,46 @@
package net.knarcraft.stargate.command;
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
*
* <p>This prefix command should only be used for commands which are certain to collide with others and which relate to
* 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,
@NotNull String[] args) {
if (args.length > 0) {
if (args[0].equalsIgnoreCase("about")) {
return new CommandAbout().onCommand(commandSender, command, s, args);
} else if (args[0].equalsIgnoreCase("reload")) {
return new CommandReload().onCommand(commandSender, command, s, args);
} else if (args[0].equalsIgnoreCase("config")) {
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
return new CommandConfig().onCommand(commandSender, command, s, subArgs);
}
return false;
} else {
commandSender.sendMessage(ChatColor.GREEN + "Stargate version " + ChatColor.GOLD + stargate.getDescription().getVersion()
+ ChatColor.GREEN + " running on " + ChatColor.GOLD + Bukkit.getServer().getVersion());
return true;
}
}
}

View File

@@ -0,0 +1,233 @@
package net.knarcraft.stargate.command;
import net.knarcraft.stargate.config.ConfigOption;
import net.knarcraft.stargate.config.OptionDataType;
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;
import org.jetbrains.annotations.NotNull;
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]);
}
} else {
List<String> configOptionNames = new ArrayList<>();
for (ConfigOption option : ConfigOption.values()) {
configOptionNames.add(option.getName());
}
return filterMatchingStartsWith(configOptionNames, args[0]);
}
}
/**
* Get possible values for the selected option
*
* @param selectedOption <p>The selected option</p>
* @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>
*/
@Nullable
private List<String> getPossibleOptionValues(@NotNull ConfigOption selectedOption,
@NotNull String typedText) {
switch (selectedOption) {
case LANGUAGE -> {
//Return available languages
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 List.of((String) selectedOption.getDefaultValue());
} else {
return new ArrayList<>();
}
}
case MAIN_SIGN_COLOR, HIGHLIGHT_SIGN_COLOR, FREE_GATES_COLOR -> {
//Return all colors
return filterMatchingStartsWith(chatColors, typedText);
}
}
//If the config value is a boolean, show the two boolean values
if (selectedOption.getDataType() == OptionDataType.BOOLEAN) {
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 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;
}
/**
* Get possible values for the selected string list option
*
* @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>
*/
@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("ja");
languages.add("nb");
languages.add("nl");
languages.add("nn");
languages.add("pt");
languages.add("ru");
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

@@ -0,0 +1,61 @@
package net.knarcraft.stargate.command;
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;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This is the tab completer for the /stargate (/sg) command
*/
public class StarGateTabCompleter implements TabCompleter {
@Override
@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<>();
for (String availableCommand : commands) {
if (availableCommand.startsWith(args[0])) {
matchingCommands.add(availableCommand);
}
}
return matchingCommands;
} else if (args.length > 1 && args[0].equalsIgnoreCase("config")) {
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
return new ConfigTabCompleter().onTabComplete(commandSender, command, s, subArgs);
} else {
return new ArrayList<>();
}
}
/**
* Gets the available commands
*
* @param commandSender <p>The command sender to get available commands for</p>
* @return <p>The commands available to the command sender</p>
*/
@NotNull
private List<String> getAvailableCommands(@NotNull CommandSender commandSender) {
List<String> commands = new ArrayList<>();
commands.add("about");
if (!(commandSender instanceof Player player) || PermissionHelper.hasPermission(player, Permission.RELOAD)) {
commands.add("reload");
}
if (!(commandSender instanceof Player player) || PermissionHelper.hasPermission(player, Permission.CONFIG)) {
commands.add("config");
}
return commands;
}
}

View File

@@ -0,0 +1,306 @@
package net.knarcraft.stargate.config;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A ConfigOption represents one of the available config options
*/
public enum ConfigOption {
/**
* The language used for player-interface text
*/
LANGUAGE("language", "The language used for all signs and all messages to players", "en"),
/**
* The folder for portal files
*/
PORTAL_FOLDER("folders.portalFolder", "The folder containing the portal databases", "plugins/Stargate/portals/"),
/**
* The folder for gate files
*/
GATE_FOLDER("folders.gateFolder", "The folder containing all gate files", "plugins/Stargate/gates/"),
/**
* The max number of portals on a single network
*/
MAX_GATES_EACH_NETWORK("gates.maxGatesEachNetwork", "The max number of stargates in a single network", 0),
/**
* The network used if not specified
*/
DEFAULT_GATE_NETWORK("gates.defaultGateNetwork", "The network used when no network is specified", "central"),
/**
* Whether to remember the lastly used destination
*/
REMEMBER_DESTINATION("gates.cosmetic.rememberDestination", "Whether to remember the last destination used", false),
/**
* Whether to sort the network destinations
*/
SORT_NETWORK_DESTINATIONS("gates.cosmetic.sortNetworkDestinations", "Whether to sort destinations by name", false),
/**
* The main color to use for all signs
*/
MAIN_SIGN_COLOR("gates.cosmetic.mainSignColor", "The main text color of all stargate signs", "BLACK"),
/**
* The color to use for highlighting sign text
*/
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
*/
DESTROYED_BY_EXPLOSION("gates.integrity.destroyedByExplosion", "Whether stargates should be destroyed by explosions", false),
/**
* Whether to verify each portal's gate layout after each load
*/
VERIFY_PORTALS("gates.integrity.verifyPortals", "Whether to verify that portals match their gate layout on load", false),
/**
* Whether to protect the entrance of portals
*/
PROTECT_ENTRANCE("gates.integrity.protectEntrance", "Whether to protect stargates' entrances", false),
/**
* Whether to enable BungeeCord support
*/
ENABLE_BUNGEE("gates.functionality.enableBungee", "Whether to enable BungeeCord support", false),
/**
* Whether to enable vehicle teleportation
*/
HANDLE_VEHICLES("gates.functionality.handleVehicles", "Whether to enable vehicle teleportation", true),
/**
* Whether to enable teleportation of empty vehicles
*/
HANDLE_EMPTY_VEHICLES("gates.functionality.handleEmptyVehicles", "Whether to enable teleportation of empty vehicles", true),
/**
* Whether to enable teleportation of creatures using vehicles
*/
HANDLE_CREATURE_TRANSPORTATION("gates.functionality.handleCreatureTransportation",
"Whether to enable teleportation of vehicles containing non-player creatures", true),
/**
* Whether to allow creatures to teleport alone, bypassing any access restrictions
*/
HANDLE_NON_PLAYER_VEHICLES("gates.functionality.handleNonPlayerVehicles",
"Whether to enable teleportation of non-empty vehicles without a player", true),
/**
* Whether to enable teleportations of creatures on a leash
*/
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
*/
USE_ECONOMY("economy.useEconomy", "Whether to use economy to incur fees when stargates are used, created or destroyed", false),
/**
* The cost of creating a new stargate
*/
CREATE_COST("economy.createCost", "The cost of creating a new stargate", 0),
/**
* The cost of destroying a stargate
*/
DESTROY_COST("economy.destroyCost", "The cost of destroying a stargate. Negative to refund", 0),
/**
* The cost of using (teleporting through) a stargate
*/
USE_COST("economy.useCost", "The cost of using (teleporting through) a stargate", 0),
/**
* Whether any payments should go to the stargate's owner
*/
TO_OWNER("economy.toOwner", "Whether any teleportation fees should go to the owner of the used stargate", false),
/**
* Whether to charge for using a stargate, even if its destination is free
*/
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
*/
FREE_GATES_COLORED("economy.freeGatesColored", "Whether to use coloring to mark all free stargates", false),
/**
* The color to use for marking free stargates
*/
FREE_GATES_COLOR("economy.freeGatesColor", "The color to use for marking free stargates", "DARK_GREEN"),
/**
* Whether to enable debug output
*/
DEBUG("debugging.debug", "Whether to enable debugging output", false),
/**
* Whether to enable debug output for debugging permissions
*/
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;
private final Object defaultValue;
private final OptionDataType dataType;
/**
* Instantiates a new config option
*
* @param configNode <p>The full path of this config option's config node</p>
* @param description <p>The description of what this config option does</p>
* @param defaultValue <p>The default value of this config option</p>
*/
ConfigOption(@NotNull String configNode, @NotNull String description, @NotNull Object defaultValue) {
this.configNode = configNode;
this.description = description;
this.defaultValue = defaultValue;
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: " + defaultValue);
}
}
/**
* Gets a config option given its name
*
* @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 @Nullable ConfigOption getByName(@NotNull String name) {
for (ConfigOption option : ConfigOption.values()) {
if (option.getName().equalsIgnoreCase(name)) {
return option;
}
}
return null;
}
/**
* Gets the name of this config option
*
* @return <p>The name of this config option</p>
*/
public @NotNull String getName() {
if (!this.configNode.contains(".")) {
return this.configNode;
}
String[] pathParts = this.configNode.split("\\.");
return pathParts[pathParts.length - 1];
}
/**
* Gets the data type used for storing this config option
*
* @return <p>The data type used</p>
*/
public @NotNull OptionDataType getDataType() {
return this.dataType;
}
/**
* Gets the config node of this config option
*
* @return <p>This config option's config node</p>
*/
public @NotNull String getConfigNode() {
return this.configNode;
}
/**
* Gets the description of what this config option does
*
* @return <p>The description of this config option</p>
*/
public @NotNull String getDescription() {
return this.description;
}
/**
* Gets this config option's default value
*
* @return <p>This config option's default value</p>
*/
public @NotNull Object getDefaultValue() {
return this.defaultValue;
}
}

View File

@@ -0,0 +1,109 @@
package net.knarcraft.stargate.config;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* A config tag groups config values by a property
*/
public enum ConfigTag {
/**
* Color-related configuration options
*/
COLOR(Set.of(ConfigOption.FREE_GATES_COLOR, ConfigOption.MAIN_SIGN_COLOR, ConfigOption.HIGHLIGHT_SIGN_COLOR,
ConfigOption.PER_SIGN_COLORS)),
/**
* 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(@NotNull Set<ConfigOption> taggedOptions) {
this.taggedOptions = taggedOptions;
}
/**
* Checks whether a config tag includes the given config option
*
* @param option <p>The config option to check</p>
* @return <p>True of the config option is tagged</p>
*/
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);
}
/**
* Checks whether a given config option requires a full reload to take effect
*
* @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(@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(@NotNull ConfigOption option) {
return COLOR.isTagged(option) || FOLDER.isTagged(option) || option == ConfigOption.VERIFY_PORTALS;
}
/**
* Checks whether a given config option requires the language loader to be reloaded
*
* @param option <p>The config option to check</p>
* @return <p>True if the language loader requires a reload</p>
*/
public static boolean requiresLanguageReload(@NotNull ConfigOption option) {
return option == ConfigOption.LANGUAGE;
}
/**
* Checks whether a given config option requires economy to be reloaded
*
* @param option <p>The config option to check</p>
* @return <p>True if economy requires a reload</p>
*/
public static boolean requiresEconomyReload(@NotNull ConfigOption option) {
return option == ConfigOption.USE_ECONOMY;
}
}

View File

@@ -0,0 +1,33 @@
package net.knarcraft.stargate.config;
/**
* An enum defining the different data types an option can have
*/
public enum OptionDataType {
/**
* The data type if the option is a String
*/
STRING,
/**
* The data type if the option is a Boolean
*/
BOOLEAN,
/**
* The data type if the option is a string list
*/
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

@@ -0,0 +1,641 @@
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.portal.Portal;
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.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.util.HashMap;
import java.util.HashSet;
import java.util.Map;
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
*/
public final class StargateConfig {
private final Queue<Portal> activePortalsQueue = new ConcurrentLinkedQueue<>();
private final Queue<Portal> openPortalsQueue = new ConcurrentLinkedQueue<>();
private final HashSet<String> managedWorlds = new HashSet<>();
private StargateGateConfig stargateGateConfig;
private final LanguageLoader languageLoader;
private EconomyConfig economyConfig;
private final Logger logger;
private final String dataFolderPath;
private String gateFolder;
private String portalFolder;
private String languageName = "en";
private boolean isLoaded = false;
private final Map<ConfigOption, Object> configOptions;
/**
* Instantiates a new stargate config
*
* @param logger <p>The logger to use for logging errors</p>
*/
public StargateConfig(@NotNull Logger logger) {
this.logger = logger;
configOptions = new HashMap<>();
dataFolderPath = Stargate.getInstance().getDataFolder().getPath().replaceAll("\\\\", "/");
portalFolder = dataFolderPath + "/portals/";
gateFolder = dataFolderPath + "/gates/";
languageLoader = new LanguageLoader(dataFolderPath + "/lang/");
}
/**
* Gets a direct reference to the config option map
*
* <p>This reference can be used to alter the value of config options. Values should only be altered after it's
* been verified that the value is valid.</p>
*
* @return <p>A reference to the config options map</p>
*/
@NotNull
public Map<ConfigOption, Object> getConfigOptionsReference() {
return configOptions;
}
/**
* Finish the config setup by loading languages, gates and portals, and loading economy if vault is loaded
*/
public void finishSetup() {
this.loadConfig();
//Enable the required channels for Bungee support
if (stargateGateConfig.enableBungee()) {
startStopBungeeListener(true);
}
//Set the chosen language and reload the language loader
languageLoader.setChosenLanguage(languageName);
languageLoader.reload();
// Update prefix of the format builder
SGFormatBuilder.setStringFormatter(getStringFormatter());
if (isDebuggingEnabled()) {
languageLoader.debug();
}
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;
}
/**
* Gets a copy of all loaded config options with its values
*
* @return <p>The loaded config options</p>
*/
@NotNull
public Map<ConfigOption, Object> getConfigOptions() {
return new HashMap<>(configOptions);
}
/**
* Gets the queue of open portals
*
* <p>The open portals queue is used to close open portals after some time has passed</p>
*
* @return <p>The open portals queue</p>
*/
@NotNull
public Queue<Portal> getOpenPortalsQueue() {
return openPortalsQueue;
}
/**
* Gets the queue of active portals
*
* <p>The active portals queue is used to de-activate portals after some time has passed</p>
*
* @return <p>The active portals queue</p>
*/
@NotNull
public Queue<Portal> getActivePortalsQueue() {
return activePortalsQueue;
}
/**
* Gets whether debugging is enabled
*
* @return <p>Whether debugging is enabled</p>
*/
public boolean isDebuggingEnabled() {
return (boolean) configOptions.get(ConfigOption.DEBUG);
}
/**
* Gets whether permission debugging is enabled
*
* @return <p>Whether permission debugging is enabled</p>
*/
public boolean isPermissionDebuggingEnabled() {
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;
}
/**
* Reloads all portals and files
*
* @param sender <p>The sender of the reload request</p>
*/
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.getControlBlockUpdateRequestQueue().peek();
while (firstElement != null) {
BlockChangeThread.pollQueue();
firstElement = Stargate.getControlBlockUpdateRequestQueue().peek();
}
//Store the old enable bungee state in case it changes
boolean oldEnableBungee = stargateGateConfig.enableBungee();
//Load all data
load();
//Enable or disable the required channels for Bungee support
if (oldEnableBungee != stargateGateConfig.enableBungee()) {
startStopBungeeListener(stargateGateConfig.enableBungee());
}
//Reload portal markers
DynmapManager.addAllPortalMarkers();
// Update prefix of the format builder
SGFormatBuilder.setStringFormatter(getStringFormatter());
new SGFormatBuilder(Message.RELOADED).error(sender);
}
/**
* Un-loads all loaded data
*/
private void unload() {
//De-activate, close and unload all loaded portals
unloadAllPortals();
//Clear all loaded gates
GateHandler.clearGates();
}
/**
* Un-loads all loaded portals
*/
public void unloadAllPortals() {
//De-activate all currently active portals
for (Portal activePortal : activePortalsQueue) {
activePortal.getPortalActivator().deactivate();
}
//Force all portals to close
closeAllOpenPortals();
PortalUtil.closeAllPortals();
//Clear queues and lists
activePortalsQueue.clear();
openPortalsQueue.clear();
managedWorlds.clear();
//Clear all loaded portals
PortalRegistry.clearPortals();
}
/**
* Clears the set of managed worlds
*/
public void clearManagedWorlds() {
managedWorlds.clear();
}
/**
* Gets a copy of the set of managed worlds
*
* @return <p>The managed worlds</p>
*/
@NotNull
public Set<String> getManagedWorlds() {
return new HashSet<>(managedWorlds);
}
/**
* Adds a world to the managed worlds
*
* @param worldName <p>The name of the world to manage</p>
*/
public void addManagedWorld(@NotNull String worldName) {
managedWorlds.add(worldName);
}
/**
* Removes a world from the managed worlds
*
* @param worldName <p>The name of the world to stop managing</p>
*/
public void removeManagedWorld(@NotNull String worldName) {
managedWorlds.remove(worldName);
}
/**
* Loads all necessary data
*/
private void load() {
//Load the config from disk
loadConfig();
//Load all gates
loadGates();
//Load all portals
loadAllPortals();
//Update the language loader in case the loaded language changed
languageLoader.setChosenLanguage(languageName);
languageLoader.reload();
if (isDebuggingEnabled()) {
languageLoader.debug();
}
//Load Economy support if enabled/clear if disabled
reloadEconomy();
}
/**
* Starts the listener for listening to BungeeCord messages
*/
public void startStopBungeeListener(boolean start) {
Messenger messenger = Bukkit.getMessenger();
String bungeeChannel = "BungeeCord";
if (start) {
messenger.registerOutgoingPluginChannel(Stargate.getInstance(), bungeeChannel);
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);
}
}
/**
* Reloads economy by enabling or disabling it as necessary
*/
public void reloadEconomy() {
EconomyConfig economyConfig = getEconomyConfig();
if (economyConfig.isEconomyEnabled() && economyConfig.getEconomy() == null) {
setupVaultEconomy();
} else if (!economyConfig.isEconomyEnabled()) {
economyConfig.disableEconomy();
}
}
/**
* Forces all open portals to close
*/
public void closeAllOpenPortals() {
for (Portal openPortal : openPortalsQueue) {
openPortal.getPortalOpener().closePortal(false);
}
}
/**
* 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
*/
public void loadConfig() {
Stargate.getInstance().reloadConfig();
FileConfiguration newConfig = Stargate.getInstance().getConfig();
boolean isMigrating = false;
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
newConfig.options().copyDefaults(true);
//Load all options
for (ConfigOption option : ConfigOption.values()) {
Object optionValue;
String configNode = option.getConfigNode();
//Load the option using its correct data type
switch (option.getDataType()) {
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);
}
//Get the language name from the config
languageName = (String) configOptions.get(ConfigOption.LANGUAGE);
//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);
}
//Load all gate config values
stargateGateConfig = new StargateGateConfig(configOptions);
//Load all economy config values
economyConfig = new EconomyConfig(configOptions);
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;
}
/**
* Loads all available gates
*/
public void loadGates() {
GateHandler.loadGates(gateFolder);
Stargate.logInfo(String.format("Loaded %s gate layouts", GateHandler.getGateCount()));
}
/**
* Loads economy from Vault
*/
private void setupVaultEconomy() {
EconomyConfig economyConfig = getEconomyConfig();
if (economyConfig.setupEconomy(Stargate.getPluginManager()) && economyConfig.getEconomy() != null &&
economyConfig.getVault() != null) {
String vaultVersion = economyConfig.getVault().getDescription().getVersion();
Stargate.logInfo(new SGFormatBuilder(Message.VAULT_LOADED).replace("%version%", vaultVersion).toString());
}
}
/**
* Loads all portals in all un-managed worlds
*/
public void loadAllPortals() {
for (World world : Stargate.getInstance().getServer().getWorlds()) {
if (!managedWorlds.contains(world.getName())) {
PortalFileHelper.loadAllPortals(world);
managedWorlds.add(world.getName());
}
}
}
/**
* Creates missing folders
*/
private void createMissingFolders() {
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() && !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);
}
}
/**
* Gets the folder all portals are stored in
*
* @return <p>The portal folder</p>
*/
@NotNull
public String getPortalFolder() {
return portalFolder;
}
/**
* Gets the folder storing gate files
*
* <p>The returned String path is the full path to the folder</p>
*
* @return <p>The folder storing gate files</p>
*/
@NotNull
public String getGateFolder() {
return gateFolder;
}
/**
* 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

@@ -0,0 +1,342 @@
package net.knarcraft.stargate.config;
import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.PortalSignDrawer;
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;
/**
* Instantiates a new stargate config
*
* @param configOptions <p>The loaded config options to use</p>
*/
public StargateGateConfig(@NotNull Map<ConfigOption, Object> configOptions) {
this.configOptions = configOptions;
loadGateConfig();
}
/**
* Gets the amount of seconds a portal should be open before automatically closing
*
* @return <p>The open time of a gate</p>
*/
public int getOpenTime() {
return openTime;
}
/**
* Gets the amount of seconds a portal should be active before automatically deactivating
*
* @return <p>The active time of a gate</p>
*/
public int getActiveTime() {
return activeTime;
}
/**
* Gets the maximum number of gates allowed on each network
*
* @return <p>Maximum number of gates for each network</p>
*/
public int maxGatesEachNetwork() {
return (int) configOptions.get(ConfigOption.MAX_GATES_EACH_NETWORK);
}
/**
* Gets whether a portal's lastly used destination should be remembered
*
* @return <p>Whether a portal's lastly used destination should be remembered</p>
*/
public boolean rememberDestination() {
return (boolean) configOptions.get(ConfigOption.REMEMBER_DESTINATION);
}
/**
* Gets whether vehicle teleportation should be handled in addition to player teleportation
*
* @return <p>Whether vehicle teleportation should be handled</p>
*/
public boolean handleVehicles() {
return (boolean) configOptions.get(ConfigOption.HANDLE_VEHICLES);
}
/**
* Gets whether vehicles with no passengers should be handled
*
* <p>The handle vehicles option overrides this option if disabled. This option allows empty passenger
* minecarts/boats, but also chest/tnt/hopper/furnace minecarts to teleport through stargates.</p>
*
* @return <p>Whether vehicles without passengers should be handled</p>
*/
public boolean handleEmptyVehicles() {
return (boolean) configOptions.get(ConfigOption.HANDLE_EMPTY_VEHICLES);
}
/**
* Gets whether vehicles containing creatures should be handled
*
* <p>The handle vehicles option overrides this option if disabled. This option allows creatures (pigs, pandas,
* zombies, etc.) to teleport through stargates if in a vehicle.</p>
*
* @return <p>Whether vehicles with creatures should be handled</p>
*/
public boolean handleCreatureTransportation() {
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
*
* <p>The handle vehicles option, and the handle creature transportation option, override this option if disabled.
* This option allows creatures (pigs, pandas, zombies, etc.) to teleport through stargates if in a vehicle, even
* if no player is in the vehicle.
* As it is not possible to check if a creature is allowed through a stargate, they will be able to go through
* regardless of whether the initiating player is allowed to enter the stargate. Enabling this is necessary to
* teleport creatures using minecarts, but only handleCreatureTransportation is required to teleport creatures
* using a boat manned by a player.</p>
*
* @return <p>Whether non-empty vehicles without a player should be handled</p>
*/
public boolean handleNonPlayerVehicles() {
return (boolean) configOptions.get(ConfigOption.HANDLE_NON_PLAYER_VEHICLES);
}
/**
* Gets whether leashed creatures should be teleported with a teleporting player
*
* @return <p>Whether leashed creatures should be handled</p>
*/
public boolean handleLeashedCreatures() {
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
*
* @return <p>Whether network destinations should be sorted</p>
*/
public boolean sortNetworkDestinations() {
return (boolean) configOptions.get(ConfigOption.SORT_NETWORK_DESTINATIONS);
}
/**
* Gets whether portal entrances should be protected from block breaking
*
* @return <p>Whether portal entrances should be protected</p>
*/
public boolean protectEntrance() {
return (boolean) configOptions.get(ConfigOption.PROTECT_ENTRANCE);
}
/**
* Gets whether BungeeCord support is enabled
*
* @return <p>Whether bungee support is enabled</p>
*/
public boolean enableBungee() {
return (boolean) configOptions.get(ConfigOption.ENABLE_BUNGEE);
}
/**
* Gets whether all portals' integrity has to be verified on startup and reload
*
* @return <p>Whether portals need to be verified</p>
*/
public boolean verifyPortals() {
return (boolean) configOptions.get(ConfigOption.VERIFY_PORTALS);
}
/**
* Gets whether portals should be destroyed by nearby explosions
*
* @return <p>Whether portals should be destroyed by explosions</p>
*/
public boolean destroyedByExplosion() {
return (boolean) configOptions.get(ConfigOption.DESTROYED_BY_EXPLOSION);
}
/**
* Gets the default portal network to use if no other network is given
*
* @return <p>The default portal network</p>
*/
public String getDefaultPortalNetwork() {
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
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);
}
}
}
/**
* Loads the correct sign color given a sign color string
*
* @param mainSignColor <p>A string representing the main sign color</p>
*/
private void loadPerSignColor(@NotNull String mainSignColor, @NotNull String highlightSignColor) {
try {
PortalSignDrawer.setMainColor(ChatColor.of(mainSignColor.toUpperCase()));
} catch (IllegalArgumentException | NullPointerException exception) {
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

@@ -0,0 +1,258 @@
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.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;
/**
* The economy config keeps track of economy config values and performs economy actions such as payment for using a gate
*/
public final class EconomyConfig {
private Economy economy = null;
private Plugin vault = null;
private final Map<ConfigOption, Object> configOptions;
/**
* Instantiates a new economy config
*
* @param configOptions <p>The loaded config options to read</p>
*/
public EconomyConfig(@NotNull Map<ConfigOption, Object> configOptions) {
this.configOptions = configOptions;
try {
String freeColor = (String) configOptions.get(ConfigOption.FREE_GATES_COLOR);
PortalSignDrawer.setFreeColor(ChatColor.of(freeColor.toUpperCase()));
} catch (IllegalArgumentException | NullPointerException ignored) {
PortalSignDrawer.setFreeColor(ChatColor.DARK_GREEN);
}
}
/**
* Gets the cost of using a gate without a specified cost
*
* @return <p>The gate use cost</p>
*/
public int getDefaultUseCost() {
return (Integer) configOptions.get(ConfigOption.USE_COST);
}
/**
* Gets whether economy is enabled
*
* @return <p>Whether economy is enabled</p>
*/
public boolean isEconomyEnabled() {
return (boolean) configOptions.get(ConfigOption.USE_ECONOMY);
}
/**
* Gets the economy object to use for transactions
*
* @return <p>An economy object, or null if economy is disabled or not initialized</p>
*/
@Nullable
public Economy getEconomy() {
return economy;
}
/**
* Gets an instance of the Vault plugin
*
* @return <p>An instance of the Vault plugin, or null if Vault is not loaded</p>
*/
@Nullable
public Plugin getVault() {
return vault;
}
/**
* Disables economy support by clearing relevant values
*/
public void disableEconomy() {
this.economy = null;
this.vault = null;
}
/**
* Gets whether free portals should be marked with a different coloring
*
* @return <p>Whether free portals should be colored</p>
*/
public boolean drawFreePortalsColored() {
return (boolean) configOptions.get(ConfigOption.FREE_GATES_COLORED);
}
/**
* Whether a gate whose destination is a free gate is still charged
*
* <p>If teleporting from a free portal, it's free regardless of destination. If chargeFreeDestination is disabled,
* it's also free to teleport back to the free portal. If chargeFreeDestination is enabled, it's only free to
* teleport back if teleporting from another free portal.</p>
*
* @return <p>Whether to charge for free destinations</p>
*/
public boolean freeIfFreeDestination() {
return !((boolean) configOptions.get(ConfigOption.CHARGE_FREE_DESTINATION));
}
/**
* Gets whether payments should be sent to the owner of the used portal
*
* @return <p>Whether to send payments to the portal owner</p>
*/
public boolean sendPaymentToOwner() {
return (boolean) configOptions.get(ConfigOption.TO_OWNER);
}
/**
* Gets the cost of creating a gate without a specified cost
*
* @return <p>The gate creation cost</p>
*/
public int getDefaultCreateCost() {
return (Integer) configOptions.get(ConfigOption.CREATE_COST);
}
/**
* Gets the cost of destroying a gate without a specified cost
*
* @return <p>The gate destruction cost</p>
*/
public int getDefaultDestroyCost() {
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
*
* @param player <p>The player to check</p>
* @param cost <p>The fee to pay</p>
* @return <p>True if the player can afford to pay the fee</p>
*/
public boolean canAffordFee(Player player, int cost) {
return economy.getBalance(player) > cost;
}
/**
* Gets a formatted string for an amount, adding the name of the currency
*
* @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);
} else {
return "";
}
}
/**
* Sets up economy by initializing vault and the vault economy provider
*
* @param pluginManager <p>The plugin manager to get plugins from</p>
* @return <p>True if economy was enabled</p>
*/
public boolean setupEconomy(@NotNull PluginManager pluginManager) {
if (!isEconomyEnabled()) {
return false;
}
//Check if vault is loaded
Plugin vault = pluginManager.getPlugin("Vault");
if (vault != null && vault.isEnabled()) {
ServicesManager servicesManager = Stargate.getInstance().getServer().getServicesManager();
RegisteredServiceProvider<Economy> economyProvider = servicesManager.getRegistration(Economy.class);
if (economyProvider != null) {
economy = economyProvider.getProvider();
this.vault = vault;
return true;
} else {
Stargate.logInfo(new SGFormatBuilder(Message.ECONOMY_LOAD_ERROR).toString());
}
} else {
Stargate.logInfo(new SGFormatBuilder(Message.VAULT_LOAD_ERROR).toString());
}
configOptions.put(ConfigOption.USE_ECONOMY, false);
return false;
}
/**
* Gets whether to use economy
*
* @return <p>True if the user has turned on economy and economy is available</p>
*/
public boolean useEconomy() {
return isEconomyEnabled() && economy != null;
}
/**
* Gets the cost of creating the given gate
*
* @param player <p>The player creating the gate</p>
* @param gate <p>The gate type used</p>
* @return <p>The cost of creating the gate</p>
*/
public int getCreateCost(@NotNull Player player, @NotNull Gate gate) {
if (isFree(player, Permission.FREE_CREATION)) {
return 0;
} else {
return gate.getCreateCost();
}
}
/**
* Gets the cost of destroying the given gate
*
* @param player <p>The player creating the gate</p>
* @param gate <p>The gate type used</p>
* @return <p>The cost of destroying the gate</p>
*/
public int getDestroyCost(@NotNull Player player, @NotNull Gate gate) {
if (isFree(player, Permission.FREE_DESTRUCTION)) {
return 0;
} else {
return gate.getDestroyCost();
}
}
/**
* Determines if a player can do a gate action for free
*
* @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(@NotNull Player player, @NotNull Permission permission) {
return !useEconomy() || PermissionHelper.hasPermission(player, permission.getNode());
}
}

View File

@@ -0,0 +1,284 @@
package net.knarcraft.stargate.config.formatting;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.FileHelper;
import net.knarcraft.stargate.Stargate;
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.EnumMap;
import java.util.Map;
/**
* This class is responsible for loading all strings which are translated into several languages
*/
public final class LanguageLoader {
private final String languageFolder;
private final Map<Message, String> loadedBackupStrings;
private String chosenLanguage;
private Map<Message, String> loadedStringTranslations;
/**
* Instantiates a new language loader
*
* <p>This will only load the backup language. Set the chosen language and reload afterwards.</p>
*
* @param languageFolder <p>The folder containing the language files</p>
*/
public LanguageLoader(@NotNull String languageFolder) {
this.languageFolder = languageFolder;
File testFile = new File(languageFolder, "en.txt");
if (!testFile.exists()) {
if (testFile.getParentFile().mkdirs()) {
Stargate.debug("LanguageLoader", "Created language folder");
}
}
//Load english as backup language in case the chosen language is missing newly added text strings
InputStream inputStream = FileHelper.getInputStreamForInternalFile("/lang/en.txt");
if (inputStream != null) {
loadedBackupStrings = load("en", inputStream);
} else {
loadedBackupStrings = null;
Stargate.logSevere("Error loading backup language. " +
"There may be missing text in-game");
}
}
/**
* Reloads languages from the files on disk
*/
public void reload() {
//Extracts/Updates the language as needed
updateLanguage(chosenLanguage);
loadedStringTranslations = load(chosenLanguage);
}
/**
* Gets the string to display given its message key
*
* @param message <p>The message to display</p>
* @return <p>The message in the user's preferred language</p>
*/
@NotNull
public String getString(@NotNull Message message) {
String value = null;
if (loadedStringTranslations != null) {
value = loadedStringTranslations.get(message);
}
if (value == null) {
value = getBackupString(message);
}
return value;
}
/**
* Gets the string to display given its message key
*
* @param message <p>The message to display</p>
* @return <p>The string in the backup language (English)</p>
*/
@NotNull
public String getBackupString(@NotNull Message message) {
String value = null;
if (loadedBackupStrings != null) {
value = loadedBackupStrings.get(message);
}
if (value == null) {
return "";
}
return value;
}
/**
* Sets the chosen plugin language
*
* @param chosenLanguage <p>The new plugin language</p>
*/
public void setChosenLanguage(@NotNull String chosenLanguage) {
this.chosenLanguage = chosenLanguage;
}
/**
* Updates files in the plugin directory with contents from the compiled .jar
*
* @param language <p>The language to update</p>
*/
private void updateLanguage(@NotNull String language) {
Map<Message, String> currentLanguageValues = load(language);
InputStream inputStream = FileHelper.getInputStreamForInternalFile("/lang/" + language + ".txt");
if (inputStream == null) {
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 exception) {
Stargate.logSevere("Unable to read language strings! Message: " + exception.getMessage());
}
}
/**
* Reads language strings
*
* @param inputStream <p>The input stream to read from</p>
* @param language <p>The selected language</p>
* @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(@NotNull InputStream inputStream, @NotNull String language,
@Nullable Map<Message, String> currentLanguageValues) throws IOException {
//Get language values
BufferedReader bufferedReader = FileHelper.getBufferedReaderFromInputStream(inputStream);
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) {
updateLanguageFile(language, internalLanguageValues, null);
Stargate.logInfo(String.format("Language (%s) has been loaded", language));
return;
}
//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<Message, String> newLanguageValues = new EnumMap<>(Message.class);
boolean updateNecessary = false;
for (Map.Entry<Message, String> entry : internalLanguageValues.entrySet()) {
Message key = entry.getKey();
String value = entry.getValue();
if (currentLanguageValues.get(key) == null) {
newLanguageValues.put(key, value);
//Found at least one value in the internal file not in the external file. Need to update
updateNecessary = true;
} else {
newLanguageValues.put(key, currentLanguageValues.get(key));
currentLanguageValues.remove(key);
}
}
//Update the file itself
if (updateNecessary) {
updateLanguageFile(language, newLanguageValues, currentLanguageValues);
Stargate.logInfo(String.format("Your language file (%s.txt) has been updated", language));
}
}
}
/**
* Updates the language file for a given language
*
* @param language <p>The language to update</p>
* @param languageStrings <p>The updated language strings</p>
* @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(@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 (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 (Map.Entry<Message, String> entry : customLanguageStrings.entrySet()) {
bufferedWriter.write(entry.getKey() + "=" + entry.getValue());
bufferedWriter.newLine();
}
}
bufferedWriter.close();
}
/**
* Loads the given language
*
* @param lang <p>The language to load</p>
* @return <p>A mapping between loaded string indexes and the strings to display</p>
*/
@Nullable
private Map<Message, String> load(@NotNull String lang) {
return load(lang, null);
}
/**
* Loads the given language
*
* @param lang <p>The language to load</p>
* @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>
*/
@Nullable
private Map<Message, String> load(@NotNull String lang, @Nullable InputStream inputStream) {
BufferedReader bufferedReader;
try {
if (inputStream == null) {
bufferedReader = FileHelper.getBufferedReaderFromString(languageFolder + lang + ".txt");
} else {
bufferedReader = FileHelper.getBufferedReaderFromInputStream(inputStream);
}
return fromStringMap(FileHelper.readKeyValuePairs(bufferedReader, "=", ColorConversion.NORMAL));
} catch (Exception exception) {
if (Stargate.getStargateConfig().isDebuggingEnabled()) {
Stargate.logInfo("Unable to load language " + lang);
}
return null;
}
}
/**
* Prints debug output to the console for checking loaded language strings/translations
*/
public void debug() {
if (loadedStringTranslations != null) {
for (Map.Entry<Message, String> entry : loadedStringTranslations.entrySet()) {
Stargate.debug("LanguageLoader::Debug::loadedStringTranslations", entry.getKey() +
" => " + entry.getValue());
}
}
if (loadedBackupStrings == null) {
return;
}
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

@@ -0,0 +1,61 @@
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 Block blockLocation;
private final Material newMaterial;
private final Axis newAxis;
/**
* Instantiates a new block change request
*
* @param blockLocation <p>The location of the block to change</p>
* @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(@NotNull Block blockLocation, @NotNull Material material, @Nullable Axis axis) {
this.blockLocation = blockLocation;
newMaterial = material;
newAxis = axis;
}
/**
* Gets the location of the block to change
*
* @return <p>The location of the block</p>
*/
@NotNull
public Block getBlockLocation() {
return blockLocation;
}
/**
* Gets the material to change the block into
*
* @return <p>The material to change the block into</p>
*/
@NotNull
public Material getMaterial() {
return newMaterial;
}
/**
* Gets the axis to orient the block along
*
* @return <p>The axis to orient the block along</p>
*/
@Nullable
public Axis getAxis() {
return newAxis;
}
}

View File

@@ -0,0 +1,187 @@
package net.knarcraft.stargate.container;
import net.knarcraft.stargate.utility.DirectionHelper;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This class represents a block location
*
* <p>The BlockLocation class is basically a Location with some extra functionality.
* Warning: Because of differences in the equals methods between Location and BlockLocation, a BlockLocation which
* equals another BlockLocation does not necessarily equal the same BlockLocation if treated as a Location.</p>
*/
public class BlockLocation extends Location {
/**
* Creates a new block location
*
* @param world <p>The world the block exists in</p>
* @param x <p>The x coordinate of the block</p>
* @param y <p>The y coordinate of the block</p>
* @param z <p>The z coordinate of the block</p>
*/
public BlockLocation(@NotNull World world, int x, int y, int z) {
super(world, x, y, z);
}
/**
* Creates a block location from a block
*
* @param block <p>The block to get the location of</p>
*/
public BlockLocation(@NotNull Block block) {
super(block.getWorld(), block.getX(), block.getY(), block.getZ());
}
/**
* Gets a block location from a string
*
* @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(@NotNull World world, @NotNull String string) {
super(world, Integer.parseInt(string.split(",")[0]), Integer.parseInt(string.split(",")[1]),
Integer.parseInt(string.split(",")[2]));
}
/**
* Creates a new block location in a relative position to this block location
*
* @param x <p>The number of blocks to move in the x-direction</p>
* @param y <p>The number of blocks to move in the y-direction</p>
* @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);
}
/**
* Creates a location in a relative position to this block location
*
* @param x <p>The number of blocks to move in the x-direction</p>
* @param y <p>The number of blocks to move in the y-direction</p>
* @param z <p>The z position relative to this block's position</p>
* @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);
newLocation.setPitch(0);
return newLocation.add(x, y, z);
}
/**
* Gets a location relative to this block location
*
* @param relativeVector <p>The relative block vector describing the relative location</p>
* @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>
*/
@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());
}
/**
* Makes a location relative to the current location according to given parameters
*
* <p>Out goes in the direction of the yaw. Right goes in the direction of (yaw - 90) degrees.
* Depth goes downwards following the -y direction.</p>
*
* @param right <p>The amount of blocks to go right when looking towards a portal</p>
* @param down <p>The amount of blocks to go downwards when looking towards a portal</p>
* @param out <p>The amount of blocks to go outwards when looking towards a portal</p>
* @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(),
0.5 + realVector.getBlockZ(), portalYaw);
}
/**
* Gets the type of block at this block location
*
* @return <p>The block's material type</p>
*/
@NotNull
public Material getType() {
return this.getBlock().getType();
}
/**
* Sets the type of block at this location
*
* @param type <p>The block's new material type</p>
*/
public void setType(@NotNull Material type) {
this.getBlock().setType(type);
}
/**
* Gets the location representing this block location
*
* @return <p>The location representing this block location</p>
*/
@NotNull
public Location getLocation() {
return this.clone();
}
@Override
@NotNull
public String toString() {
return String.valueOf(this.getBlockX()) + ',' + this.getBlockY() + ',' + this.getBlockZ();
}
@Override
public int hashCode() {
int result = 18;
result = result * 27 + this.getBlockX();
result = result * 27 + this.getBlockY();
result = result * 27 + this.getBlockZ();
if (this.getWorld() != null) {
result = result * 27 + this.getWorld().getName().hashCode();
}
return result;
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
BlockLocation blockLocation = (BlockLocation) object;
World thisWorld = this.getWorld();
World otherWorld = blockLocation.getWorld();
//Check if the worlds of the two locations match
boolean worldsEqual = (thisWorld == null && otherWorld == null) || ((thisWorld != null && otherWorld != null)
&& thisWorld == otherWorld);
//As this is a block location, only the block coordinates are compared
return blockLocation.getBlockX() == this.getBlockX() && blockLocation.getBlockY() == this.getBlockY() &&
blockLocation.getBlockZ() == this.getBlockZ() && worldsEqual;
}
}

View File

@@ -0,0 +1,58 @@
package net.knarcraft.stargate.container;
import org.bukkit.Chunk;
import org.jetbrains.annotations.NotNull;
/**
* Represents a requests for the unloading of a chunk which has been previously loaded by the Stargate plugin
*/
public class ChunkUnloadRequest implements Comparable<ChunkUnloadRequest> {
private final Long unloadNanoTime;
private final Chunk chunkToUnload;
/**
* Instantiates a new chunk unloading request
*
* @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(@NotNull Chunk chunkToUnload, @NotNull Long timeUntilUnload) {
this.chunkToUnload = chunkToUnload;
long systemNanoTime = System.nanoTime();
this.unloadNanoTime = systemNanoTime + (timeUntilUnload * 1000000);
}
/**
* Gets the chunk to unload
*
* @return <p>The chunk to unload</p>
*/
@NotNull
public Chunk getChunkToUnload() {
return this.chunkToUnload;
}
/**
* Gets the system nano time denoting at which time the unload request should be executed
*
* @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 + "}";
}
@Override
public int compareTo(@NotNull ChunkUnloadRequest otherRequest) {
//Prioritize requests based on time until unload
return unloadNanoTime.compareTo(otherRequest.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

@@ -0,0 +1,16 @@
package net.knarcraft.stargate.container;
import net.knarcraft.stargate.portal.Portal;
import org.jetbrains.annotations.NotNull;
/**
* 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 record FromTheEndTeleportation(@NotNull Portal exitPortal) {
}

View File

@@ -0,0 +1,96 @@
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
*
* <p>A relative block vector stores a vector relative to some origin. The origin in this plugin is usually the
* 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 record RelativeBlockVector(int right, int down, int out) {
/**
* 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>
*/
@NotNull
public RelativeBlockVector addRight(int valueToAdd) {
return new RelativeBlockVector(this.right + valueToAdd, this.down, this.out);
}
/**
* Adds the given value to this relative block vector's "down" property
*
* @param valueToAdd <p>The value to add</p>
* @return <p>The new resulting vector</p>
*/
@NotNull
public RelativeBlockVector addDown(int valueToAdd) {
return new RelativeBlockVector(this.right, this.down + valueToAdd, this.out);
}
/**
* Adds the given value to this relative block vector's "out" property
*
* @param valueToAdd <p>The value to add</p>
* @return <p>The new resulting vector</p>
*/
@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);
}
/**
* Gets a relative block vector which is this inverted (pointing in the opposite direction)
*
* @return <p>This vector, but inverted</p>
*/
@NotNull
public RelativeBlockVector invert() {
return new RelativeBlockVector(-this.right, -this.down, -this.out);
}
@Override
@NotNull
public String toString() {
return String.format("(right = %d, down = %d, out = %d)", right, down, out);
}
@Override
public boolean equals(@Nullable Object other) {
if (other == this) {
return true;
}
if (other == null || this.getClass() != other.getClass()) {
return false;
}
RelativeBlockVector otherVector = (RelativeBlockVector) other;
return this.right == otherVector.right && this.down == otherVector.down &&
this.out == otherVector.out;
}
}

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

@@ -0,0 +1,67 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a player attempts to access a stargate
*
* <p>This event is triggered whenever a player enters or activates a stargate. This event can be used to override
* whether the player should be allowed to access the stargate.</p>
*/
@SuppressWarnings("unused")
public class StargateAccessEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private boolean deny;
/**
* Instantiates a new stargate access event
*
* @param player <p>The player involved in the event</p>
* @param portal <p>The portal involved in the event</p>
* @param deny <p>Whether the stargate access should be denied</p>
*/
public StargateAccessEvent(@NotNull Player player, @NotNull Portal portal, boolean deny) {
super(portal, player);
this.deny = deny;
}
/**
* Gets whether the player should be denied access
*
* @return <p>Whether the player should be denied access</p>
*/
public boolean getDeny() {
return this.deny;
}
/**
* Sets whether to deny access to the player
*
* @param deny <p>Whether to deny access to the player</p>
*/
public void setDeny(boolean deny) {
this.deny = deny;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,93 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* This event should be called whenever a player activates a stargate
*
* <p>Activation of a stargate happens when a player right-clicks the sign of a stargate.
* This event can be used to overwrite the selected destination, and all destinations the player can see.</p>
*/
@SuppressWarnings("unused")
public class StargateActivateEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private List<String> destinations;
private String destination;
/**
* Instantiates a new stargate activate event
*
* @param portal <p>The activated portal</p>
* @param player <p>The player activating the portal</p>
* @param destinations <p>The destinations available to the player using the portal</p>
* @param destination <p>The currently selected destination</p>
*/
public StargateActivateEvent(@NotNull Portal portal, @NotNull Player player, @NotNull List<String> destinations,
@NotNull String destination) {
super(portal, player);
this.destinations = destinations;
this.destination = destination;
}
/**
* Gets the destinations available for the portal
*
* @return <p>The destinations available for the portal</p>
*/
@NotNull
public List<String> getDestinations() {
return destinations;
}
/**
* Sets the destinations available to the player using the portal
*
* @param destinations <p>The new list of available destinations</p>
*/
public void setDestinations(@NotNull List<String> destinations) {
this.destinations = destinations;
}
/**
* Gets the selected destination
*
* @return <p>The selected destination</p>
*/
@NotNull
public String getDestination() {
return destination;
}
/**
* Sets (changes) the selected destination
*
* @param destination <p>The new selected destination</p>
*/
public void setDestination(@NotNull String destination) {
this.destination = destination;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,65 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a stargate is closed
*
* <p>This event can be used to overwrite whether the stargate should be forced to close, even if it's set as
* always-on.</p>
*/
@SuppressWarnings("unused")
public class StargateCloseEvent extends StargateEvent {
private static final HandlerList handlers = new HandlerList();
private boolean force;
/**
* Instantiates a new stargate closing event
*
* @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(@NotNull Portal portal, boolean force) {
super(portal);
this.force = force;
}
/**
* Gets whether to force the stargate to close
*
* @return <p>Whether to force the stargate to close</p>
*/
public boolean getForce() {
return force;
}
/**
* Sets whether the stargate should be forced to close
*
* @param force <p>Whether the stargate should be forced to close</p>
*/
public void setForce(boolean force) {
this.force = force;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,124 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a stargate is created
*
* <p>This event can be used to deny or change the cost of a stargate creation.</p>
*/
@SuppressWarnings("unused")
public class StargateCreateEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private final String[] lines;
private boolean deny;
private String denyReason;
private int cost;
/**
* Instantiates a new stargate creation event
*
* @param player <p>Thg player creating the stargate</p>
* @param portal <p>The created portal</p>
* @param lines <p>The lines of the sign creating the star gate</p>
* @param deny <p>Whether to deny the creation of the new gate</p>
* @param denyReason <p>The reason stargate creation was denied</p>
* @param cost <p>The cost of creating the new star gate</p>
*/
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;
this.denyReason = denyReason;
this.cost = cost;
}
/**
* Gets a given line from the sign creating the star gate
*
* @param index <p>The line number to get</p>
* @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];
}
/**
* Gets whether the stargate creation should be denied
*
* @return <p>Whether the stargate creation should be denied</p>
*/
public boolean getDeny() {
return deny;
}
/**
* Sets whether the stargate creation should be denied
*
* @param deny <p>Whether the stargate creation should be denied</p>
*/
public void setDeny(boolean deny) {
this.deny = deny;
}
/**
* Gets the reason the stargate creation was denied
*
* @return <p>The reason the stargate creation was denied</p>
*/
@NotNull
public String getDenyReason() {
return denyReason;
}
/**
* Sets the reason the stargate creation was denied
*
* @param denyReason <p>The new reason why the stargate creation was denied</p>
*/
public void setDenyReason(@NotNull String denyReason) {
this.denyReason = denyReason;
}
/**
* Gets the cost of creating the stargate
*
* @return <p>The cost of creating the stargate</p>
*/
public int getCost() {
return cost;
}
/**
* Sets the cost of creating the stargate
*
* @param cost <p>The new cost of creating the stargate</p>
*/
public void setCost(int cost) {
this.cost = cost;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,43 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a stargate is deactivated
*
* <p>A deactivation is usually caused by no activity for a set amount of time.
* This event can only be used to listen for de-activation events.</p>
*/
@SuppressWarnings("unused")
public class StargateDeactivateEvent extends StargateEvent {
private static final HandlerList handlers = new HandlerList();
/**
* Instantiates a new stargate deactivation event
*
* @param portal <p>The portal which was deactivated</p>
*/
public StargateDeactivateEvent(@NotNull Portal portal) {
super(portal);
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,109 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a stargate is destroyed
*
* <p>This event can be used to deny or change the cost of a stargate destruction.</p>
*/
@SuppressWarnings("unused")
public class StargateDestroyEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private boolean deny;
private String denyReason;
private int cost;
/**
* Instantiates a new Stargate Destroy Event
*
* @param portal <p>The destroyed portal</p>
* @param player <p>The player destroying the portal</p>
* @param deny <p>Whether the event should be denied (cancelled)</p>
* @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(@NotNull Portal portal, @NotNull Player player, boolean deny, @NotNull String denyMsg,
int cost) {
super(portal, player);
this.deny = deny;
this.denyReason = denyMsg;
this.cost = cost;
}
/**
* Gets whether this event should be denied
*
* @return <p>Whether this event should be denied</p>
*/
public boolean getDeny() {
return deny;
}
/**
* Sets whether this event should be denied
*
* @param deny <p>Whether this event should be denied</p>
*/
public void setDeny(boolean deny) {
this.deny = deny;
}
/**
* Gets the reason the event was denied
*
* @return <p>The reason the event was denied</p>
*/
@NotNull
public String getDenyReason() {
return denyReason;
}
/**
* Sets the reason the event was denied
*
* @param denyReason <p>The reason the event was denied</p>
*/
public void setDenyReason(@NotNull String denyReason) {
this.denyReason = denyReason;
}
/**
* Gets the cost of destroying the portal
*
* @return <p>The cost of destroying the portal</p>
*/
public int getCost() {
return cost;
}
/**
* Sets the cost of destroying the portal
*
* @param cost <p>The cost of destroying the portal</p>
*/
public void setCost(int cost) {
this.cost = cost;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,95 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a non-player teleports through a stargate
*
* <p>This event can be used to overwrite the location the entity is teleported to.</p>
*/
@SuppressWarnings("unused")
public class StargateEntityPortalEvent extends StargateEvent implements StargateTeleportEvent {
private static final HandlerList handlers = new HandlerList();
final Entity travellingEntity;
private final Portal destination;
private Location exit;
/**
* Instantiates a new stargate portal event
*
* @param travellingEntity <p>The entity travelling through this portal</p>
* @param portal <p>The portal the entity entered from</p>
* @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(@NotNull Entity travellingEntity, @NotNull Portal portal,
@NotNull Portal destination, @NotNull Location exit) {
super(portal);
this.travellingEntity = travellingEntity;
this.destination = destination;
this.exit = exit;
}
/**
* Return the non-player entity teleporting
*
* @return <p>The non-player teleporting</p>
*/
@NotNull
public Entity getEntity() {
return travellingEntity;
}
/**
* Return the destination portal
*
* @return <p>The destination portal</p>
*/
@NotNull
public Portal getDestination() {
return destination;
}
/**
* Return the location of the players exit point
*
* @return <p>Location of the exit point</p>
*/
@Override
@NotNull
public Location getExit() {
return exit;
}
/**
* Set the location of the entity's exit point
*
* @param location <p>The new location of the entity's exit point</p>
*/
public void setExit(@NotNull Location location) {
this.exit = location;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,47 @@
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
*/
@SuppressWarnings("unused")
public abstract class StargateEvent extends Event implements Cancellable {
private final Portal portal;
private boolean cancelled;
/**
* Instantiates a new stargate event
*
* @param portal <p>The portal involved in this stargate event</p>
*/
StargateEvent(@NotNull Portal portal) {
this.portal = portal;
this.cancelled = false;
}
/**
* Gets the portal involved in this stargate event
*
* @return <p>The portal involved in this stargate event</p>
*/
@NotNull
public Portal getPortal() {
return portal;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View File

@@ -0,0 +1,67 @@
package net.knarcraft.stargate.event;
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
*
* <p>This event can be used to overwrite whether the stargate should be forced to open, even if it's already open.</p>
*/
@SuppressWarnings({"unused"})
public class StargateOpenEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private boolean force;
/**
* Instantiates a new stargate open event
*
* @param player <p>The player opening the stargate</p>
* @param portal <p>The opened portal</p>
* @param force <p>Whether to force the portal open</p>
*/
public StargateOpenEvent(@Nullable Player player, @NotNull Portal portal, boolean force) {
super(portal, player);
this.force = force;
}
/**
* Gets whether the portal should be forced open
*
* @return <p>Whether the portal should be forced open</p>
*/
public boolean getForce() {
return force;
}
/**
* Sets whether the portal should be forced open
*
* @param force <p>Whether the portal should be forced open</p>
*/
public void setForce(boolean force) {
this.force = force;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,36 @@
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
*/
@SuppressWarnings("unused")
public abstract class StargatePlayerEvent extends StargateEvent {
private final Player player;
/**
* Instantiates a new stargate player event
*
* @param portal <p>The portal involved in this stargate event</p>
*/
StargatePlayerEvent(@NotNull Portal portal, @Nullable Player player) {
super(portal);
this.player = player;
}
/**
* Gets the player creating the star gate
*
* @return <p>The player creating the star gate</p>
*/
@Nullable
public Player getPlayer() {
return player;
}
}

View File

@@ -0,0 +1,83 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a player teleports through a stargate
*
* <p>This event can be used to overwrite the location the player is teleported to.</p>
*/
@SuppressWarnings("unused")
public class StargatePlayerPortalEvent extends StargatePlayerEvent implements StargateTeleportEvent {
private static final HandlerList handlers = new HandlerList();
private final Portal destination;
private Location exit;
/**
* Instantiates a new stargate player portal event
*
* @param player <p>The player teleporting</p>
* @param portal <p>The portal the player entered from</p>
* @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(@NotNull Player player, @NotNull Portal portal, @NotNull Portal destination,
@NotNull Location exit) {
super(portal, player);
this.destination = destination;
this.exit = exit;
}
/**
* Return the destination portal
*
* @return <p>The destination portal</p>
*/
@NotNull
public Portal getDestination() {
return destination;
}
/**
* Return the location of the players exit point
*
* @return <p>Location of the exit point</p>
*/
@Override
@NotNull
public Location getExit() {
return exit;
}
/**
* Set the location of the player's exit point
*
* @param location <p>The new location of the player's exit point</p>
*/
public void setExit(@NotNull Location location) {
this.exit = location;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
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

@@ -0,0 +1,25 @@
/**
* Events for any plugins wanting to interact with the Stargate plugin
*
* <p>This package contains several events used for interactions with Stargate. All events can be cancelled. For
* several of the events, it is possible to overrule permissions. A general overview of the events' usage:</p>
*
* <ul>
* <li>The StargateAccessEvent is called whenever a player clicks a stargate's sign, and when a player enters a
* Stargate. It can be used to override whether the access should be allowed or denied.</li>
* <li>The StargateActivateEvent is called whenever a player activates a stargate (uses the stargate's sign). It
* can be used to override which destinations are available to the player.</li>
* <li>The StargateCloseEvent is called whenever a stargate is closed. Forcing the stargate closed can be toggled.</li>
* <li>The StargateCreateEvent is called whenever a new stargate is created. Its deny value can be overridden, the
* cost can be changed</li>
* <li>The StargateDeactivateEvent is called whenever a stargate is deactivated.</li>
* <li>The StargateDestroyEvent is called whenever a stargate is destroyed. Its deny value can be overridden or the
* cost can be changed.</li>
* <li>The StargateEntityPortalEvent is called whenever an entity teleports through a stargate. The exit location
* can be changed.</li>
* <li>The StargateOpenEvent is called whenever a stargate is opened. Forcing the stargate open can be toggled.</li>
* <li>The StargatePlayerPortalEvent is called whenever a player teleports through a stargate. The exit location can
* be changed.</li>
* </ul>
*/
package net.knarcraft.stargate.event;

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

@@ -0,0 +1,304 @@
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.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.teleporter.PlayerTeleporter;
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 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.Player;
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.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
*/
public class PlayerEventListener implements Listener {
private static final Map<Player, Long> previousEventTimes = new HashMap<>();
/**
* This event handler handles detection of any player teleporting through a bungee gate
*
* @param event <p>The event to check for a teleporting player</p>
*/
@EventHandler
public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
Player player = event.getPlayer();
//Migrate player name to UUID if necessary
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;
}
//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 = PortalUtil.getBungeePortal(destination);
if (portal == null) {
Stargate.debug("PlayerJoin", "Error fetching destination portal: " + destination);
return;
}
//Teleport the player to the stargate
new PlayerTeleporter(portal, player).teleport(portal, null);
}
/**
* 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(@NotNull PlayerInteractEvent event) {
Player player = event.getPlayer();
Block block = event.getClickedBlock();
if (block == null) {
return;
}
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
handleSignClick(event, player, block, true);
}
}
/**
* This method handles left- or right-clicking of a sign
*
* @param event <p>The event causing the click</p>
* @param player <p>The player clicking the sign</p>
* @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(@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
if (player.getGameMode().equals(GameMode.CREATIVE)) {
event.setCancelled(true);
}
} else {
//Prevent usage of item in the player's hand (placing block and such)
event.setUseItemInHand(Event.Result.DENY);
}
//Check if the user can use the portal
if (cannotAccessPortal(player, portal)) {
return;
}
//Cycle portal destination
if ((!portal.isOpen()) && (!portal.getOptions().isFixed())) {
PortalActivator destinations = portal.getPortalActivator();
if (leftClick) {
destinations.cycleDestination(player, -1);
} else {
destinations.cycleDestination(player);
}
}
}
/**
* 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
*
* @param player <p>The player trying to access the portal</p>
* @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(@NotNull Player player, @NotNull Portal portal) {
boolean deny = PermissionHelper.cannotAccessNetwork(player, portal.getCleanNetwork());
if (PermissionHelper.portalAccessDenied(player, portal, deny)) {
if (!portal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ACCESS_DENIED).error(player);
}
return true;
}
return false;
}
/**
* This method handles right-clicking of a sign or button belonging to a stargate
*
* @param event <p>The event triggering the right-click</p>
* @param player <p>The player doing the right-click</p>
* @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(@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.getPlayer())) {
return;
}
if (MaterialHelper.isButtonCompatible(block.getType())) {
Portal portal = PortalUtil.getByBlock(block);
if (portal == null) {
return;
}
//Prevent the held item from being placed
event.setUseItemInHand(Event.Result.DENY);
event.setUseInteractedBlock(Event.Result.DENY);
//Check if the user can use the portal
if (cannotAccessPortal(player, portal)) {
return;
}
PermissionHelper.openPortal(player, portal);
if (portal.getPortalOpener().isOpenFor(player) && !MaterialHelper.isContainer(block.getType())) {
event.setUseInteractedBlock(Event.Result.ALLOW);
}
} else {
//Display information about the portal if it has no sign
ItemStack heldItem = player.getInventory().getItem(hand);
if (heldItem != null && (heldItem.getType().isAir() || !heldItem.getType().isBlock())) {
displayPortalInfo(block, player);
}
}
}
/**
* Displays information about a clicked portal
*
* <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(@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().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()) {
builder.append(Message.PORTAL_INFO_SERVER).replace("%server%", portal.getNetwork());
} else {
builder.append(Message.PORTAL_INFO_NETWORK).replace("%network%", portal.getNetwork());
}
builder.displayRaw(player);
}
}
/**
* This function decides if a right click of a block is caused by a Spigot bug
*
* <p>The Spigot bug currently makes every right click of some blocks trigger twice, causing the portal to close
* immediately, or causing portal information printing twice. This fix should detect the bug without breaking
* clicking once the bug is fixed.</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(@NotNull Player player) {
Long previousEventTime = previousEventTimes.get(player);
if (previousEventTime != null && previousEventTime + 50 > System.currentTimeMillis()) {
previousEventTimes.put(player, null);
return true;
}
previousEventTimes.put(player, System.currentTimeMillis());
return 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

@@ -0,0 +1,4 @@
/**
* The root package of the Stargate plugin. Contains the main Stargate.java file
*/
package net.knarcraft.stargate;

View File

@@ -0,0 +1,316 @@
package net.knarcraft.stargate.portal;
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 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;
/**
* This class represents a portal in space which points to one or several portals
*/
public class Portal {
private final String name;
private final String cleanName;
private final String network;
private final String cleanNetwork;
private final PortalOwner portalOwner;
private boolean isRegistered;
private final PortalOptions options;
private final PortalOpener portalOpener;
private final PortalLocation location;
private final PortalSignDrawer signDrawer;
private final PortalStructure structure;
private final PortalActivator portalActivator;
/**
* Instantiates a new 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 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(@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 = portalStrings.network();
this.name = portalStrings.name();
this.portalOwner = portalOwner;
this.options = new PortalOptions(options, !portalStrings.destination().isEmpty());
this.signDrawer = new PortalSignDrawer(this);
this.portalOpener = new PortalOpener(this, portalStrings.destination());
this.structure = new PortalStructure(this, gate, button);
this.portalActivator = portalOpener.getPortalActivator();
this.cleanName = cleanString(name);
this.cleanNetwork = cleanString(network);
}
/**
* Checks if this portal is registered
*
* @return <p>True if this portal is registered</p>
*/
public boolean isRegistered() {
return isRegistered;
}
/**
* Sets whether this portal is registered
*
* @param isRegistered <p>True if this portal is registered</p>
*/
public void setRegistered(boolean isRegistered) {
this.isRegistered = isRegistered;
}
/**
* Gets the location data for this portal
*
* @return <p>This portal's location data</p>
*/
@NotNull
public PortalLocation getLocation() {
return this.location;
}
/**
* Gets the structure of this portal
*
* <p>The structure contains information about the portal's gate, button and real locations of frames and
* entrances. The structure is also responsible for verifying built StarGates to make sure they match the gate.</p>
*
* @return <p>This portal's structure</p>
*/
@NotNull
public PortalStructure getStructure() {
return this.structure;
}
/**
* Gets this portal's activator
*
* <p>The activator is responsible for activating/de-activating the portal and contains information about
* available destinations and which player activated the portal.</p>
*
* @return <p>This portal's activator</p>
*/
@NotNull
public PortalActivator getPortalActivator() {
return this.portalActivator;
}
/**
* Re-draws the sign on this portal
*/
public void drawSign() {
this.signDrawer.drawSign();
}
/**
* Gets the portal options for this portal
*
* @return <p>This portal's portal options</p>
*/
@NotNull
public PortalOptions getOptions() {
return this.options;
}
/**
* Gets whether this portal is currently open
*
* @return <p>Whether this portal is open</p>
*/
public boolean isOpen() {
return portalOpener.isOpen();
}
/**
* Gets the player currently using this portal
*
* @return <p>The player currently using this portal</p>
*/
@Nullable
public Player getActivePlayer() {
return portalActivator.getActivePlayer();
}
/**
* Gets the network this portal belongs to
*
* @return <p>The network this portal belongs to</p>
*/
@NotNull
public String getNetwork() {
return network;
}
/**
* Gets the clean name of the network this portal belongs to
*
* @return <p>The clean network name</p>
*/
@NotNull
public String getCleanNetwork() {
return cleanNetwork;
}
/**
* Gets the time this portal was triggered (activated/opened)
*
* <p>The time is given in the equivalent of a Unix timestamp. It's used to decide when a portal times out and
* automatically closes/deactivates.</p>
*
* @return <p>The time this portal was triggered (activated/opened)</p>
*/
public long getTriggeredTime() {
return portalOpener.getTriggeredTime();
}
/**
* Gets the name of this portal
*
* @return <p>The name of this portal</p>
*/
@NotNull
public String getName() {
return name;
}
/**
* Gets the clean name of this portal
*
* @return <p>The clean name of this portal</p>
*/
@NotNull
public String getCleanName() {
return cleanName;
}
/**
* Gets the portal opener used by this portal
*
* <p>The portal opener is responsible for opening and closing this portal.</p>
*
* @return <p>This portal's portal opener</p>
*/
@NotNull
public PortalOpener getPortalOpener() {
return portalOpener;
}
/**
* Gets the name of this portal's destination portal
*
* @return <p>The name of this portal's destination portal</p>
*/
@NotNull
public String getDestinationName() {
return portalOpener.getPortalActivator().getDestinationName();
}
/**
* Gets the gate type used by this portal
*
* @return <p>The gate type used by this portal</p>
*/
@NotNull
public Gate getGate() {
return structure.getGate();
}
/**
* Gets this portal's owner
*
* <p>The owner is the player which created the portal.</p>
*
* @return <p>This portal's owner</p>
*/
@NotNull
public PortalOwner getOwner() {
return portalOwner;
}
/**
* Checks whether a given player is the owner of this 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(@NotNull Player player) {
if (this.portalOwner.getUUID() != null) {
return player.getUniqueId().compareTo(this.portalOwner.getUUID()) == 0;
} else {
return player.getName().equalsIgnoreCase(this.portalOwner.getName());
}
}
/**
* 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>
*/
@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]", this.location.getSignBlock(), network, name,
structure.getGate().getFilename());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((cleanName == null) ? 0 : cleanName.hashCode());
result = prime * result + ((cleanNetwork == null) ? 0 : cleanNetwork.hashCode());
return result;
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
Portal other = (Portal) object;
if (cleanName == null) {
if (other.cleanName != null) {
return false;
}
} else if (!cleanName.equalsIgnoreCase(other.cleanName)) {
return false;
}
//If none of the portals have a name, check if the network is the same
if (cleanNetwork == null) {
return other.cleanNetwork == null;
} else {
return cleanNetwork.equalsIgnoreCase(other.cleanNetwork);
}
}
}

View File

@@ -0,0 +1,300 @@
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;
/**
* The portal activator activates/de-activates portals and keeps track of a portal's destinations
*
* <p>The portal activator is responsible for activating/de-activating the portal and contains information about
* available destinations and which player activated the portal.</p>
*/
public class PortalActivator {
private final Portal portal;
private final PortalOpener opener;
private List<String> destinations = new ArrayList<>();
private String destination;
private String lastDestination = "";
private Player activePlayer;
/**
* Instantiates a new portal destinations object
*
* @param portal <p>The portal which this this object stores destinations for</p>
* @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(@NotNull Portal portal, @NotNull PortalOpener portalOpener, @NotNull String destination) {
this.portal = portal;
this.opener = portalOpener;
this.destination = destination;
}
/**
* Gets the player which this activator's portal is currently activated for
*
* @return <p>The player this activator's portal is currently activated for</p>
*/
@NotNull
public Player getActivePlayer() {
return activePlayer;
}
/**
* Gets the available portal destinations
*
* @return <p>The available portal destinations</p>
*/
@NotNull
public List<String> getDestinations() {
return new ArrayList<>(this.destinations);
}
/**
* Gets the portal destination given a player
*
* @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>
*/
@Nullable
public Portal getDestination(@Nullable Player player) {
String portalNetwork = portal.getCleanNetwork();
if (portal.getOptions().isRandom()) {
//Find possible destinations
List<String> destinations = PortalUtil.getDestinations(portal, player, portalNetwork);
if (destinations.isEmpty()) {
return null;
}
//Get one random destination
String randomDestination = ListHelper.getRandom(destinations);
return PortalUtil.getByName(randomDestination, portalNetwork);
} else {
//Just return the normal fixed destination
return PortalUtil.getByName(destination, portalNetwork);
}
}
/**
* Gets the portal's destination
*
* <p>For random portals, getDestination must be given a player to decide which destinations are valid. Without a
* player, or with a null player, behavior is only defined for a non-random gate.</p>
*
* @return <p>The portal destination</p>
*/
@Nullable
public Portal getDestination() {
return getDestination(null);
}
/**
* Sets the destination of this portal activator's portal
*
* @param destination <p>The new destination of this portal activator's portal</p>
*/
public void setDestination(@NotNull Portal destination) {
setDestination(destination.getName());
}
/**
* Sets the destination of this portal activator's portal
*
* @param destination <p>The new destination of this portal activator's portal</p>
*/
public void setDestination(@NotNull String destination) {
this.destination = destination;
}
/**
* Gets the name of the selected destination
*
* @return <p>The name of the selected destination</p>
*/
@NotNull
public String getDestinationName() {
return destination;
}
/**
* Activates this activator's portal for the given player
*
* @param player <p>The player to activate the portal for</p>
* @return <p>True if the portal was activated</p>
*/
public boolean activate(@NotNull Player player) {
//Clear previous destination data
this.destination = "";
this.destinations.clear();
//Adds the active gate to the active queue to allow it to be remotely deactivated
Stargate.getStargateConfig().getActivePortalsQueue().add(portal);
//Set the given player as the active player
activePlayer = player;
String network = portal.getCleanNetwork();
destinations = PortalUtil.getDestinations(portal, player, network);
//Sort destinations if enabled
if (Stargate.getGateConfig().sortNetworkDestinations()) {
destinations.sort(Comparator.comparing(Portal::cleanString));
}
//Select last used destination if remember destination is enabled
if (Stargate.getGateConfig().rememberDestination() && !lastDestination.isEmpty() &&
destinations.contains(lastDestination)) {
destination = lastDestination;
}
//Trigger an activation event to allow the cancellation to be cancelled
return triggerStargateActivationEvent(player);
}
/**
* Triggers a stargate activation event to allow other plugins to cancel the activation
*
* <p>The event may also end up changing destinations.</p>
*
* @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(@NotNull Player player) {
StargateActivateEvent event = new StargateActivateEvent(portal, player, destinations, destination);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
Stargate.getStargateConfig().getActivePortalsQueue().remove(portal);
return false;
}
//Update destinations in case they changed, and update the sign
destination = event.getDestination();
destinations = event.getDestinations();
portal.drawSign();
return true;
}
/**
* Deactivates this portal
*/
public void deactivate() {
//Trigger a stargate deactivate event to allow other plugins to cancel the event
StargateDeactivateEvent event = new StargateDeactivateEvent(portal);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
//Un-mark the portal as activated
Stargate.getStargateConfig().getActivePortalsQueue().remove(portal);
//Fixed portals are active by definition, but should never be de-activated
if (portal.getOptions().isFixed()) {
return;
}
//Clear destinations and the active player before re-drawing the sign to show that it's deactivated
destinations.clear();
destination = "";
activePlayer = null;
portal.drawSign();
}
/**
* Gets whether this portal activator's portal is active
*
* @return <p>Whether this portal activator's portal is active</p>
*/
public boolean isActive() {
return portal.getOptions().isFixed() || (!destinations.isEmpty());
}
/**
* Cycles destination for a non-fixed gate by one forwards step
*
* @param player <p>The player to cycle the gate for</p>
*/
public void cycleDestination(@NotNull Player player) {
cycleDestination(player, 1);
}
/**
* Cycles destination for a non-fixed gate
*
* @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(@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.");
}
boolean activate = false;
if (!isActive() || getActivePlayer() != player) {
//If not active or not active for the given player, and the activation is denied, just abort
if (!activate(player)) {
return;
}
activate = true;
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.isEmpty()) {
if (!portal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.NO_DESTINATION).error(player);
}
return;
}
//Cycle if destination remembering is disabled, if the portal was already active, or it has no last destination
if (!Stargate.getGateConfig().rememberDestination() || !activate || lastDestination.isEmpty()) {
cycleDestination(direction);
}
//Update the activated time to allow it to be deactivated after a timeout, and re-draw the sign to show the
// selected destination
opener.setTriggeredTime(System.currentTimeMillis() / 1000);
portal.drawSign();
}
/**
* Performs the actual destination cycling with no input checks
*
* @param direction <p>The direction of the cycle (+1 for next, -1 for previous)</p>
*/
private void cycleDestination(int direction) {
int index = destinations.indexOf(destination);
index += direction;
//Wrap around if the last destination has been reached
if (index >= destinations.size()) {
index = 0;
} else if (index < 0) {
index = destinations.size() - 1;
}
//Store selected destination
destination = destinations.get(index);
lastDestination = destination;
}
}

View File

@@ -0,0 +1,393 @@
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.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;
import static net.knarcraft.stargate.Stargate.getMaxNameNetworkLength;
/**
* The portal creator can create and validate a new portal
*/
public class PortalCreator {
private Portal portal;
private final SignChangeEvent event;
private final Player player;
/**
* Instantiates a new portal creator
*
* @param event <p>The sign change event which initialized the creation</p>
* @param player <p>The player creating the portal</p>
*/
public PortalCreator(@NotNull SignChangeEvent event, @NotNull Player player) {
this.event = event;
this.player = player;
}
/**
* Creates a new portal
*
* @return <p>The created portal</p>
*/
@Nullable
public Portal createPortal() {
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).isEmpty()) {
Stargate.debug(route, "Control block not registered");
return null;
}
//The control block is already part of another portal
if (PortalUtil.getByBlock(signControlBlock) != null) {
Stargate.debug(route, "idParent belongs to existing stargate");
return null;
}
//Get necessary information from the gate's sign
@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 = PortalUtil.getPortalOptions(player, destinationName, options);
//Get the yaw
BlockFace facing = DirectionHelper.getFacing(signLocation);
if (facing == null) {
facing = DirectionHelper.getBlockFaceFromLocationDifference(signControlBlock.getLocation(),
signLocation.getLocation());
}
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 = 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 (!PortalUtil.isValidBungeePortal(portalOptions, player, portalStrings.destination(),
portalStrings.network())) {
Stargate.debug(route, "Portal is an invalid bungee portal");
return null;
}
//Debug
StringBuilder builder = new StringBuilder();
for (PortalOption option : portalOptions.keySet()) {
builder.append(option.getCharacterRepresentation()).append(" = ").append(portalOptions.get(option)).append(" ");
}
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 (!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(route, "Creating personal portal");
new SGFormatBuilder(Message.CREATION_PERSONAL).error(player);
return network;
} else {
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 (!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 (!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("PortalCreator::canCreatePortal", "Player does not have access to destination world");
return new SGFormatBuilder(Message.CREATION_WORLD_DENIED).toString();
}
}
}
return null;
}
/**
* Validates the newly created portal assigned to this portal validator
*
* @param denyMessage <p>The deny message to displayed if the creation has already been denied</p>
* @param lines <p>The lines on the sign causing the portal to be created</p>
* @param deny <p>Whether the portal creation has already been denied</p>
* @return <p>The portal or null if its creation was denied</p>
*/
@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();
String portalName = portal.getName();
String destinationName = portal.getDestinationName();
int createCost = Stargate.getEconomyConfig().getCreateCost(player, gate);
//Call StargateCreateEvent to let other plugins cancel or overwrite denial
StargateCreateEvent stargateCreateEvent = new StargateCreateEvent(player, portal, lines, deny,
denyMessage, createCost);
Stargate.getInstance().getServer().getPluginManager().callEvent(stargateCreateEvent);
if (stargateCreateEvent.isCancelled()) {
return null;
}
//Tell the user why it was denied from creating the portal
if (stargateCreateEvent.getDeny()) {
if (!stargateCreateEvent.getDenyReason().trim().isEmpty()) {
new SGFormatBuilder(stargateCreateEvent.getDenyReason()).error(player);
}
return null;
}
createCost = stargateCreateEvent.getCost();
//Check if the new portal is valid
if (!checkIfNewPortalIsValid(createCost, portalName)) {
return null;
}
//Add button if the portal is not always on
if (!portalOptions.isAlwaysOn()) {
PortalFileHelper.generatePortalButton(portal, portalLocation.getFacing());
}
//Register the new portal
PortalUtil.registerPortal(portal);
updateNewPortalOpenState(destinationName);
//Update portals pointing at this one if it's not a bungee portal
if (!portal.getOptions().isBungee()) {
PortalUtil.updatePortalsPointingAtNewPortal(portal);
}
PortalFileHelper.saveAllPortals(portal.getLocation().getWorld());
return portal;
}
/**
* Checks whether the newly created, but unregistered portal is valid
*
* @param cost <p>The cost of creating the portal</p>
* @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, @NotNull String portalName) {
String route = "PortalCreator::checkIfNewPortalIsValid";
//Check if the portal name can fit on the sign with padding (>name<)
if (portal.getCleanName().isEmpty() || portal.getCleanName().length() > getMaxNameNetworkLength()) {
Stargate.debug(route, String.format("Name length error. %s is too long.",
portal.getCleanName()));
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 (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 (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 = PortalUtil.getNetwork(portal.getCleanNetwork());
int maxGates = Stargate.getGateConfig().maxGatesEachNetwork();
if (maxGates > 0 && networkList != null && networkList.size() >= maxGates) {
new SGFormatBuilder(Message.CREATION_NETWORK_FULL).error(player);
return false;
}
}
if (cost > 0) {
//Deduct the required fee from the player
if (!EconomyHelper.chargePlayerIfNecessary(player, cost)) {
EconomyHelper.sendInsufficientFundsMessage(portalName, player, cost);
Stargate.debug(route, "Insufficient Funds");
return false;
} else {
EconomyHelper.sendDeductMessage(portalName, player, cost);
}
}
return true;
}
/**
* Updates the open state of the newly created portal
*
* @param destinationName <p>The name of the destination portal. Only used if set as always on</p>
*/
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 = PortalUtil.getByName(destinationName, portal.getCleanNetwork());
if (destinationPortal != null) {
portal.getPortalOpener().openPortal(true);
destinationPortal.drawSign();
}
} 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
@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);
}
}
}
/**
* Checks whether the new portal conflicts with an existing portal
*
* @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(@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;
}
}
return false;
}
}

View File

@@ -0,0 +1,250 @@
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.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
*/
public class PortalOpener {
private boolean isOpen = false;
private final Portal portal;
private long triggeredTime;
private Player player;
private final PortalActivator portalActivator;
/**
* Instantiates a new portal opener
*
* @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(@NotNull Portal portal, @NotNull String destination) {
this.portal = portal;
this.portalActivator = new PortalActivator(portal, this, destination);
}
/**
* Gets whether this portal opener's portal is currently open
*
* @return <p>Whether this portal opener's portal is open</p>
*/
public boolean isOpen() {
return isOpen || portal.getOptions().isAlwaysOn();
}
/**
* Sets the time when this portal was triggered (activated/opened)
*
* @param triggeredTime <p>Unix timestamp when portal was triggered</p>
*/
public void setTriggeredTime(long triggeredTime) {
this.triggeredTime = triggeredTime;
}
/**
* Gets the portal activator belonging to this portal opener
*
* @return <p>The portal activator belonging to this portal opener</p>
*/
@NotNull
public PortalActivator getPortalActivator() {
return this.portalActivator;
}
/**
* Open this portal opener's portal
*
* @param force <p>Whether to force the portal open, even if it's already open for some player</p>
*/
public void openPortal(boolean force) {
openPortal(null, force);
}
/**
* Open this portal opener's portal
*
* @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(@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);
if (event.isCancelled() || (isOpen() && !event.getForce())) {
return;
}
//Get the material to change the opening to
@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().getVectorOperation().getNormalAxis() : null;
//Change the entrance blocks to the correct type
for (Block inside : portal.getStructure().getEntrances()) {
Stargate.addControlBlockUpdateRequest(new BlockChangeRequest(inside, openType, axis));
}
//Update the portal state to make is actually open
updatePortalOpenState(openFor);
}
/**
* Updates this portal opener's portal to be recognized as open and opens its destination portal
*
* @param openFor <p>The player to open this portal opener's portal for</p>
*/
private void updatePortalOpenState(@Nullable Player openFor) {
//Update the open state of this portal
isOpen = true;
triggeredTime = System.currentTimeMillis() / 1000;
//Change state from active to open
Stargate.getStargateConfig().getOpenPortalsQueue().add(portal);
Stargate.getStargateConfig().getActivePortalsQueue().remove(portal);
PortalOptions options = portal.getOptions();
//If this portal is always open, opening the destination is not necessary
if (options.isAlwaysOn()) {
return;
}
//Update the player the portal is open for
this.player = openFor;
Portal destination = portal.getPortalActivator().getDestination();
if (destination == null) {
return;
}
boolean thisIsDestination = Portal.cleanString(destination.getDestinationName()).equals(portal.getCleanName());
//Only open destination if it's not-fixed or points at this portal, and is not already open
if (!options.isRandom() && (!destination.getOptions().isFixed() || thisIsDestination) && !destination.isOpen()) {
//Open the destination portal
destination.getPortalOpener().openPortal(openFor, false);
//Set the destination portal to this opener's portal
destination.getPortalActivator().setDestination(portal);
//Update the destination's sign if it exists
if (new BukkitTagSpecifier(Tag.WALL_SIGNS).asMaterials().contains(destination.getLocation().getSignBlock().getType())) {
destination.drawSign();
}
}
}
/**
* Closes this portal opener's portal
*
* @param force <p>Whether to force the portal closed, even if it's set as always on</p>
*/
public void closePortal(boolean force) {
//No need to close a portal which is already closed
if (!isOpen()) {
return;
}
//Call the StargateCloseEvent to allow other plugins to cancel the closing, or change whether to force it closed
StargateCloseEvent event = new StargateCloseEvent(portal, force);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
//Only close an always-open portal if forced to
if (portal.getOptions().isAlwaysOn() && !event.getForce()) {
return;
}
//Close the portal by requesting the opening blocks to change
@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
updatePortalClosedState();
//Finally, deactivate the portal
portalActivator.deactivate();
}
/**
* Updates this portal to be recognized as closed and closes its destination portal
*/
private void updatePortalClosedState() {
//Unset the stored player and set the portal to closed
player = null;
isOpen = false;
//Un-mark the portal as active and open
Stargate.getStargateConfig().getOpenPortalsQueue().remove(portal);
Stargate.getStargateConfig().getActivePortalsQueue().remove(portal);
//Close the destination portal if not always open
if (!portal.getOptions().isAlwaysOn()) {
Portal destination = portal.getPortalActivator().getDestination();
if (destination != null && destination.isOpen()) {
//De-activate and close the destination portal
destination.getPortalActivator().deactivate();
destination.getPortalOpener().closePortal(false);
}
}
}
/**
* Gets whether this portal opener's portal is open for the given player
*
* @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(@Nullable Player player) {
//If closed, it's closed for everyone
if (!isOpen) {
return false;
}
//If always on, or player is null which only happens with an always on portal, allow the player to pass
if (portal.getOptions().isAlwaysOn() || this.player == null) {
return true;
}
//If the player is the player which the portal opened for, allow it to pass
return player != null && player.getName().equalsIgnoreCase(this.player.getName());
}
/**
* Gets the time this portal opener's portal was triggered (activated/opened)
*
* @return <p>The time this portal opener's portal was triggered</p>
*/
public long getTriggeredTime() {
return triggeredTime;
}
}

View File

@@ -0,0 +1,330 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
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;
import java.util.List;
import java.util.Map;
/**
* The portal registry keeps track of all registered portals and all their lookup blocks
*/
public class PortalRegistry {
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<>();
private static final Map<String, Portal> bungeePortals = new HashMap<>();
private static final List<Portal> allPortals = new ArrayList<>();
/**
* Clears all portals and all data held by the portal registry
*/
public static void clearPortals() {
lookupBlocks.clear();
portalLookupByNetwork.clear();
lookupEntrances.clear();
lookupControls.clear();
allPortals.clear();
allPortalNetworks.clear();
bungeePortals.clear();
}
/**
* Clears all portals loaded in a given world
*
* @param world <p>The world containing the portals to clear</p>
*/
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.getLocation().getWorld().equals(world)) {
portalsToRemove.add(portal);
}
});
clearPortals(portalsToRemove);
}
/**
* Clears a given list of portals from all relevant variables
*
* @param portalsToRemove <p>A list of portals to remove</p>
*/
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()));
//Clear all the lookup locations for the portals
lookupBlocks.keySet().removeIf((key) -> portalsToRemove.contains(lookupBlocks.get(key)));
lookupEntrances.keySet().removeIf((key) -> portalsToRemove.contains(lookupEntrances.get(key)));
lookupControls.keySet().removeIf((key) -> portalsToRemove.contains(lookupControls.get(key)));
//Remove the portals from all networks, and then remove any empty networks. This is done for both network maps
portalLookupByNetwork.keySet().forEach((network) -> portalLookupByNetwork.get(network).keySet().removeIf((key) ->
portalsToRemove.contains(portalLookupByNetwork.get(network).get(key))));
portalLookupByNetwork.keySet().removeIf((key) -> portalLookupByNetwork.get(key).isEmpty());
allPortalNetworks.keySet().forEach((network) -> allPortalNetworks.get(network).removeIf(portalNames::contains));
allPortalNetworks.keySet().removeIf((network) -> allPortalNetworks.get(network).isEmpty());
//Finally, remove the portals from the portal list
allPortals.removeIf(portalsToRemove::contains);
}
/**
* Gets a copy of the list of all portals
*
* @return <p>A copy of the list of all portals</p>
*/
@NotNull
public static List<Portal> getAllPortals() {
return new ArrayList<>(allPortals);
}
/**
* Gets a portal that the given frame block belongs to
*
* @param blockLocation <p>The location that might be a frame block</p>
* @return <p>The portal the frame block belongs to, or null</p>
*/
@Nullable
public static Portal getPortalFromFrame(@NotNull Block blockLocation) {
return lookupBlocks.get(blockLocation);
}
/**
* Gets the portal that the given control block belongs to
*
* @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>
*/
@Nullable
public static Portal getPortalFromControl(@NotNull Block blockLocation) {
return lookupControls.get(blockLocation);
}
/**
* Gets the portal identified by the given network name and portal name
*
* @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>
*/
@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 portal from the location of a possible entrance
*
* @param blockLocation <p>A location that might be a portal's entrance</p>
* @return <p>A portal, or null</p>
*/
@Nullable
public static Portal getPortalFromEntrance(@NotNull Block blockLocation) {
return lookupEntrances.get(blockLocation);
}
/**
* Gets a copy of all portal networks
*
* @return <p>A copy of all portal networks</p>
*/
@NotNull
public static Map<String, List<String>> getAllPortalNetworks() {
return new HashMap<>(allPortalNetworks);
}
/**
* Gets the BungeeCord portal with the given name
*
* @param portalName <p>The name of the portal to get</p>
* @return <p>The portal, or null</p>
*/
@Nullable
public static Portal getBungeePortal(@NotNull String portalName) {
return bungeePortals.get(Portal.cleanString(portalName));
}
/**
* 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>
*/
@Nullable
public static List<String> getNetwork(@NotNull String network) {
return allPortalNetworks.get(network.toLowerCase());
}
/**
* Un-registers the given portal
*
* @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(@NotNull Portal portal, boolean removeAll) {
Stargate.debug("Unregister", "Unregistering gate " + portal.getName());
portal.getPortalActivator().deactivate();
portal.getPortalOpener().closePortal(true);
String portalName = portal.getCleanName();
String networkName = portal.getCleanNetwork();
clearLookupMaps(portal, removeAll);
if (portal.getOptions().isBungee()) {
//Remove the bungee listing
bungeePortals.remove(portalName);
} else {
//Remove from network lists
portalLookupByNetwork.get(networkName).remove(portalName);
allPortalNetworks.get(networkName).remove(portalName);
//Update all portals in the same network with this portal as its destination
for (String originName : allPortalNetworks.get(networkName)) {
Portal origin = PortalUtil.getByName(originName, portal.getCleanNetwork());
if (origin == null || !origin.getDestinationName().equalsIgnoreCase(portalName) ||
!new BukkitTagSpecifier(Tag.WALL_SIGNS).asMaterials().contains(origin.getLocation().getSignBlock().getType())) {
continue;
}
//Update the portal's sign
if (origin.getOptions().isFixed()) {
origin.drawSign();
}
//Close portal without destination
if (origin.getOptions().isAlwaysOn()) {
origin.getPortalOpener().closePortal(true);
}
}
}
//Mark the portal's sign as unregistered
new PortalSignDrawer(portal).drawUnregisteredSign();
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);
}
}
/**
* Registers a portal
*
* @param portal <p>The portal to register</p>
*/
public static void registerPortal(@NotNull Portal portal) {
portal.getOptions().setFixed(!portal.getDestinationName().isEmpty() || portal.getOptions().isRandom() ||
portal.getOptions().isBungee());
String portalName = portal.getCleanName();
String networkName = portal.getCleanNetwork();
//Bungee portals are stored in their own list
if (portal.getOptions().isBungee()) {
bungeePortals.put(portalName, portal);
} else {
//Check if network exists in the lookup list. If not, register the new network
if (!portalLookupByNetwork.containsKey(networkName)) {
Stargate.debug("register", String.format("Network %s not in lookupNamesNet, adding",
portal.getNetwork()));
portalLookupByNetwork.put(networkName, new HashMap<>());
}
//Check if this network exists in the network list. If not, register the network
if (!allPortalNetworks.containsKey(networkName)) {
Stargate.debug("register", String.format("Network %s not in allPortalsNet, adding",
portal.getNetwork()));
allPortalNetworks.put(networkName, new ArrayList<>());
}
//Register the portal
portalLookupByNetwork.get(networkName).put(portalName, portal);
if (!allPortalNetworks.get(networkName).contains(portalName)) {
allPortalNetworks.get(networkName).add(portalName);
} else {
Stargate.logSevere(String.format("Portal %s on network %s was registered twice. Check your portal " +
"database for duplicates.", portal.getName(), portal.getNetwork()));
}
}
//Register all frame blocks to the lookup list
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.getLocation().getSignBlock(), portal);
lookupControls.put(portal.getLocation().getSignBlock(), portal);
}
Block button = portal.getStructure().getButton();
if (button != null) {
lookupBlocks.put(button, portal);
lookupControls.put(button, portal);
}
//Register entrances to the lookup list
for (Block entrance : portal.getStructure().getEntrances()) {
lookupEntrances.put(entrance, portal);
}
allPortals.add(portal);
portal.setRegistered(true);
DynmapManager.addPortalMarker(portal);
}
}

View File

@@ -0,0 +1,451 @@
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 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
*/
public class PortalSignDrawer {
private final Portal portal;
private final static ChatColor errorColor = ChatColor.DARK_RED;
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(@NotNull Portal portal) {
this.portal = portal;
}
/**
* Sets the highlighting sign color
*
* <p>The highlighting color is used for the markings around portal names and network names ('>','<','-',')','(').</p>
*
* @param newHighlightColor <p>The new highlight color</p>
*/
public static void setHighlightColor(@NotNull ChatColor newHighlightColor) {
highlightColor = newHighlightColor;
}
/**
* Sets the main sign color
*
* <p>The main sign color is used for most text on the sign.</p>
*
* @param newMainColor <p>The new main sign color</p>
*/
public static void setMainColor(@NotNull ChatColor newMainColor) {
mainColor = newMainColor;
}
/**
* Sets the color to use for marking free stargates
*
* @param freeColor <p>The new color to use for marking free stargates</p>
*/
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
*/
public void drawSign() {
Sign sign = getSign();
if (sign == null) {
return;
}
drawSign(new SignData(sign, getMainColor(sign.getType()), getHighlightColor(sign.getType())));
}
/**
* Gets the sign for this sign drawer's portal
*
* @return <p>The sign of this sign drawer's portal</p>
*/
@Nullable
private Sign getSign() {
Block signBlock = portal.getLocation().getSignBlock();
BlockState state = signBlock.getState();
if (!(state instanceof Sign sign)) {
if (!portal.getOptions().hasNoSign()) {
Stargate.logWarning("Sign block is not a Sign object");
Stargate.debug("Portal::drawSign", String.format("Block: %s @ %s", signBlock.getType(),
signBlock.getLocation()));
}
return null;
}
return sign;
}
/**
* Draws the sign of the portal this sign drawer is responsible for
*
* @param signData <p>All necessary sign information</p>
*/
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(signData, lines);
} else {
if (portal.getOptions().isBungee()) {
//Bungee sign
drawBungeeSign(signData, lines);
} else if (portal.getOptions().isFixed()) {
//Sign pointing at one other portal
drawFixedSign(signData, lines);
} else {
//Networking stuff
drawNetworkSign(signData, lines);
}
}
updateSign(sign, lines);
}
/**
* Updates a sign, if necessary
*
* @param sign <p>The sign to update</p>
* @param lines <p>The sign's new lines</p>
*/
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();
}
}
/**
* Marks this sign drawer's portal as unregistered
*/
public void drawUnregisteredSign() {
Sign sign = getSign();
if (sign == null) {
return;
}
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 signData <p>All necessary sign information</p>
* @param output <p>The output list to write to</p>
*/
private void drawNetworkSign(@NotNull SignData signData, @NotNull String[] output) {
PortalActivator destinations = portal.getPortalActivator();
int maxIndex = destinations.getDestinations().size() - 1;
int signLineIndex = 0;
int destinationIndex = destinations.getDestinations().indexOf(portal.getDestinationName());
boolean freeGatesColored = Stargate.getEconomyConfig().useEconomy() &&
Stargate.getEconomyConfig().drawFreePortalsColored();
//Last, and not only entry. Draw the entry two back
if ((destinationIndex == maxIndex) && (maxIndex > 1)) {
drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex - 2, output);
}
//Not first entry. Draw the previous entry
if (destinationIndex > 0) {
drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex - 1, output);
}
//Draw the chosen entry (line 2 or 3)
drawNetworkSignChosenLine(signData, freeGatesColored, ++signLineIndex, output);
//Has another entry and space on the sign
if ((maxIndex >= destinationIndex + 1)) {
drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex + 1, output);
}
//Has another entry and space on the sign
if ((maxIndex >= destinationIndex + 2) && (++signLineIndex <= 3)) {
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 signLineIndex <p>The line to draw on</p>
* @param output <p>The output list to write to</p>
*/
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 = PortalUtil.getByName(portal.getDestinationName(), portal.getNetwork());
boolean free = PermissionHelper.isFree(Objects.requireNonNull(portal.getActivePlayer()), portal, destination);
ChatColor nameColor = (free ? freeColor : highlightColor);
setLine(signData, signLineIndex, nameColor + ">" + (free ? freeColor : mainColor) +
translateAllColorCodes(portal.getDestinationName()) + nameColor + "<", output);
} else {
setLine(signData, signLineIndex, highlightColor + ">" + mainColor +
translateAllColorCodes(portal.getDestinationName()) + highlightColor + "<", output);
}
}
/**
* Sets a line on a sign, adding the chosen sign color
*
* @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(@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 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(@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 = 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(signData, signLineIndex, mainColor + translateAllColorCodes(destinationName), output);
}
}
/**
* Draws the sign of a BungeeCord portal
*
* @param signData <p>All necessary sign information</p>
* @param output <p>The output list to write to</p>
*/
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);
}
/**
* Draws the sign of an in-active portal
*
* <p>The sign for an in-active portal should display the right-click prompt and the network.</p>
*
* @param signData <p>All necessary sign information</p>
* @param output <p>The output list to write to</p>
*/
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(signData, 3, highlightColor + "(" + mainColor + translateAllColorCodes(portal.getNetwork()) +
highlightColor + ")", output);
} else {
setLine(signData, 3, "", output);
}
}
/**
* Draws a sign pointing to a fixed location
*
* @param signData <p>All necessary sign information</p>
* @param output <p>The output list to write to</p>
*/
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(signData, 1, highlightColor + ">" + mainColor + translateAllColorCodes(destinationName) +
highlightColor + "<", output);
if (portal.getOptions().isNoNetwork()) {
setLine(signData, 2, "", output);
} else {
setLine(signData, 2, highlightColor + "(" + mainColor +
translateAllColorCodes(portal.getNetwork()) + highlightColor + ")", output);
}
Portal destination = PortalUtil.getByName(portal.getDestinationName(), portal.getNetwork());
if (destination == null && !portal.getOptions().isRandom()) {
setLine(signData, 3, errorColor + new SGFormatBuilder(Message.SIGN_DISCONNECTED).toString(), output);
} else {
setLine(signData, 3, "", output);
}
}
/**
* Marks a portal with an invalid gate by changing its sign and writing to the console
*
* @param portalLocation <p>The location of the portal with an invalid gate</p>
* @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(@NotNull PortalLocation portalLocation, @NotNull String gateName,
int lineIndex) {
BlockState blockState = portalLocation.getSignBlock().getState();
if (!(blockState instanceof Sign sign)) {
return;
}
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));
}
/**
* Gets the main color to use for the given sign type
*
* @param signType <p>The sign type to get the main color for</p>
* @return <p>The main color for the given sign type</p>
*/
@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

@@ -0,0 +1,182 @@
package net.knarcraft.stargate.portal.property;
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
*/
@SuppressWarnings("UnusedReturnValue")
public class PortalLocation {
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>
*/
@Nullable
public Block getTopLeft() {
return topLeft;
}
/**
* Gets the block face the portal is facing towards
*
* @return <p>The portal's facing direction</p>
*/
public BlockFace getFacing() {
return facing;
}
/**
* Gets the location of the portal's sign
*
* @return <p>The location of the portal's sign</p>
*/
@NotNull
public Block getSignBlock() {
return signBlock;
}
/**
* The relative block vector pointing to the portal's button
*
* @return <p>The relative location of the portal's button</p>
*/
@Nullable
public Block getButtonBlock() {
return buttonBlock;
}
/**
* Gets the world this portal resides in
*
* @return <p>The world this portal resides in</p>
*/
@NotNull
public World 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;
}
/**
* Sets the portal's top-left location
*
* <p>Assuming the portal is a square, the top-left block is the top-left block when looking at the portal at the
* side with the portal's sign.</p>
*
* @param topLeft <p>The new top-left block of the portal's square structure</p>
* @return <p>The portal location Object</p>
*/
@NotNull
public PortalLocation setTopLeft(@NotNull Block topLeft) {
this.topLeft = topLeft;
return this;
}
/**
* Sets the portal's facing direction
*
* <p>The portal's facing direction is the direction a player would get when looking directly out from the portal</p>
*
* @param facing <p>The portal's new facing direction</p>
* @return <p>The portal location Object</p>
*/
@NotNull
public PortalLocation setFacing(@NotNull BlockFace facing) {
this.facing = facing;
return this;
}
/**
* Sets the location of the portal's sign
*
* @param signBlock <p>The new sign location</p>
* @return <p>The portal location Object</p>
*/
@NotNull
public PortalLocation setSignBlock(@NotNull Block signBlock) {
this.signBlock = signBlock;
return this;
}
/**
* Sets the relative location of the portal's button
*
* @param buttonBlock <p>The new relative button location</p>
* @return <p>The portal location Object</p>
*/
@NotNull
public PortalLocation setButtonBlock(@Nullable Block buttonBlock) {
this.buttonBlock = buttonBlock;
return this;
}
/**
* Gets a block relative to this portal's top-left block
*
* @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>
*/
@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

@@ -0,0 +1,110 @@
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
*/
public enum PortalOption {
/**
* This option allows a portal to be hidden from others
*/
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', Permission.OPTION_ALWAYS_ON, 12),
/**
* This option allows a portal that's private to the stargate's owner
*/
PRIVATE('p', Permission.OPTIONS_PRIVATE, 13),
/**
* This option allows a portal that's free even if stargates usually are not
*/
FREE('f', Permission.OPTIONS_FREE, 15),
/**
* This option allows a portal where players exit through the back of the portal
*/
BACKWARDS('b', Permission.OPTIONS_BACKWARDS, 16),
/**
* This option shows the gate in the network list even if it's always on
*/
SHOW('s', Permission.OPTIONS_SHOW, 17),
/**
* This option hides the network name on the sign
*/
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', Permission.OPTIONS_RANDOM, 19),
/**
* This option allows a portal to teleport to another server connected through BungeeCord
*/
BUNGEE('u', Permission.OPTIONS_BUNGEE, 20),
/**
* This option allows a portal which does not display a teleportation message, for better immersion
*/
QUIET('q', Permission.OPTIONS_QUIET, 21),
/**
* This option causes a fixed portal's sign to be removed after creation
*/
INVISIBLE('v', Permission.OPTIONS_INVISIBLE, 22);
private final char characterRepresentation;
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 permission <p>The permission necessary to use this option</p>
*/
PortalOption(final char characterRepresentation, @NotNull Permission permission, int saveIndex) {
this.characterRepresentation = characterRepresentation;
this.permission = permission;
this.saveIndex = saveIndex;
}
/**
* Gets the character representation used to enable this setting on the sign
*
* @return <p>The character representation of this option</p>
*/
public char getCharacterRepresentation() {
return this.characterRepresentation;
}
/**
* Gets the permission necessary to use this option
*
* @return <p>The permission necessary for this option</p>
*/
@NotNull
public Permission getPermission() {
return this.permission;
}
/**
* Gets the index of the save file this option is stored at
*
* @return <p>This option's save index</p>
*/
public int getSaveIndex() {
return this.saveIndex;
}
}

View File

@@ -0,0 +1,207 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.Stargate;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
/**
* Keeps track of all options for one portal
*/
public class PortalOptions {
private final Map<PortalOption, Boolean> options;
private boolean isFixed;
/**
* Instantiates a new portal options object
*
* @param options <p>All options to keep track of</p>
* @param hasDestination <p>Whether the portal has a fixed destination</p>
*/
public PortalOptions(@NotNull Map<PortalOption, Boolean> options, boolean hasDestination) {
this.options = options;
isFixed = hasDestination || this.isRandom() || this.isBungee();
if (this.isAlwaysOn() && !isFixed) {
this.options.put(PortalOption.ALWAYS_ON, false);
Stargate.debug("PortalOptions", "Can not create a non-fixed always-on gate. Setting AlwaysOn = false");
}
if ((this.isRandom() || this.isBungee()) && !this.isAlwaysOn()) {
this.options.put(PortalOption.ALWAYS_ON, true);
Stargate.debug("PortalOptions", "Gate marked as random or bungee, set to always-on");
}
if (this.hasNoSign() && !this.isFixed) {
this.options.put(PortalOption.INVISIBLE, false);
Stargate.debug("PortalOptions", "Gate marked with no sign, but not fixed. Setting NoSign = false");
}
}
/**
* Gets whether this portal is fixed
*
* <p>A fixed portal is a portal for which the player cannot choose destination. A portal with a set destination, a
* random portal and bungee portals are fixed. While the player has no choice regarding destinations, a fixed gate
* may still need to be activated if not set to always on.</p>
*
* @return <p>Whether this portal is fixed</p>
*/
public boolean isFixed() {
return this.isFixed;
}
/**
* Sets whether this portal is fixed
*
* @param fixed <p>Whether this gate should be fixed</p>
*/
public void setFixed(boolean fixed) {
this.isFixed = fixed;
}
/**
* Gets whether this portal is always on
*
* <p>An always on portal is always open for everyone, and always uses the open-block. It never needs to be
* activated or opened manually.</p>
*
* @return <p>Whether this portal is always on</p>
*/
public boolean isAlwaysOn() {
return this.options.get(PortalOption.ALWAYS_ON);
}
/**
* Gets whether this portal is hidden
*
* <p>A hidden portal will be hidden on a network for everyone but admins and the portal owner. In other words,
* when selecting a destination using a portal's sign, hidden gates will only be available in the list for the
* owner and players with the appropriate permission.</p>
*
* @return <p>Whether this portal is hidden</p>
*/
public boolean isHidden() {
return this.options.get(PortalOption.HIDDEN);
}
/**
* Gets whether this portal is private
*
* <p>A private portal can only be opened by the owner and players with the appropriate permission. A private gate
* is not hidden unless the hidden option is also enabled.</p>
*
* @return <p>Whether this portal is private</p>
*/
public boolean isPrivate() {
return this.options.get(PortalOption.PRIVATE);
}
/**
* Gets whether this portal is free
*
* <p>A free portal is exempt from any fees which would normally occur from using the portal. It does nothing if
* economy is disabled.</p>
*
* @return <p>Whether this portal is free</p>
*/
public boolean isFree() {
return this.options.get(PortalOption.FREE);
}
/**
* Gets whether this portal is backwards
*
* <p>A backwards portal is one where players exit through the back. It's important to note that the exit is
* mirrored, not rotated, when exiting backwards.</p>
*
* @return <p>Whether this portal is backwards</p>
*/
public boolean isBackwards() {
return this.options.get(PortalOption.BACKWARDS);
}
/**
* Gets whether this portal is shown on the network even if it's always on
*
* <p>Normally, always-on portals are not selectable on a network, but enabling this option allows the portal to be
* shown.</p>
*
* @return <p>Whether portal gate is shown</p>
*/
public boolean isShown() {
return this.options.get(PortalOption.SHOW);
}
/**
* Gets whether this portal shows no network
*
* <p>Enabling the no network option allows the portal's network to be hidden for whatever reason. If allowing
* normal players to create portals, this can be used to prevent random users from connecting gates to
* "protected networks".</p>
*
* @return <p>Whether this portal shows no network/p>
*/
public boolean isNoNetwork() {
return this.options.get(PortalOption.NO_NETWORK);
}
/**
* Gets whether this portal goes to a random location on the network
*
* <p>A random portal is always on and will teleport to a random destination within the same network.</p>
*
* @return <p>Whether this portal goes to a random location</p>
*/
public boolean isRandom() {
return this.options.get(PortalOption.RANDOM);
}
/**
* Gets whether this portal is a bungee portal
*
* <p>A bungee portal is able to teleport to a portal on another server. It works differently from other portals as
* it does not have a network, but instead the network line specifies the same of the server it connects to.</p>
*
* @return <p>Whether this portal is a bungee portal</p>
*/
public boolean isBungee() {
return this.options.get(PortalOption.BUNGEE);
}
/**
* Gets whether this portal is QUIET
*
* <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 quiet</p>
*/
public boolean isQuiet() {
return this.options.get(PortalOption.QUIET);
}
/**
* Gets whether this portal has no sign
*
* <p>An always-on portal is allowed to not have a sign as it will never be interacted with anyway.</p>
*
* @return <p>Whether this portal has no sign</p>
*/
public boolean hasNoSign() {
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

@@ -0,0 +1,136 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.Stargate;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* The portal owner represents the owner of a portal
*/
public class PortalOwner {
private UUID ownerUUID;
private String ownerName;
/**
* Instantiates a new portal owner
*
* @param ownerIdentifier <p>A UUID, or a username for legacy support</p>
*/
public PortalOwner(@NotNull String ownerIdentifier) {
parseIdentifier(ownerIdentifier);
}
/**
* Instantiates a new portal owner
*
* @param player <p>The player which is the owner of the portal</p>
*/
public PortalOwner(@NotNull OfflinePlayer player) {
this.ownerUUID = player.getUniqueId();
this.ownerName = player.getName();
}
/**
* Gets the UUID of this owner
*
* @return <p>The UUID of this owner, or null if a UUID is not available</p>
*/
@Nullable
public UUID getUUID() {
return ownerUUID;
}
/**
* Sets the unique id for a portal owner without one
*
* <p>This method is only meant to be used to set the unique id for an owner without one. If the owner already has
* an unique id, an exception will be thrown.</p>
*
* @param uniqueId <p>The new unique id for the portal owner</p>
*/
public void setUUID(@NotNull UUID uniqueId) {
if (ownerUUID == null) {
ownerUUID = uniqueId;
} else {
throw new IllegalArgumentException("An existing UUID cannot be overwritten.");
}
}
/**
* Gets the name of this owner
*
* @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;
}
/**
* Gets the one identifier used for saving the owner
*
* <p>If the UUID is available, a string representation of the UUID will be returned. If not, the owner's name will
* be returned.</p>
*
* @return <p>The owner's identifier</p>
*/
@NotNull
public String getIdentifier() {
if (ownerUUID != null) {
return ownerUUID.toString();
} else {
return ownerName;
}
}
/**
* Parses the identifier of a portal's owner
*
* <p>The identifier should be a valid UUID, but can be a username of max 16 characters for legacy support. Strings
* longer than 16 characters not parse-able as a UUID will silently fail by setting the owner name to the
* identifier.</p>
*
* @param ownerIdentifier <p>The identifier for a portal's owner</p>
*/
private void parseIdentifier(@NotNull String ownerIdentifier) {
UUID ownerUUID = null;
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);
} 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);
}
} else {
//Old username from the pre-UUID times. Just keep it as the owner name
ownerName = ownerIdentifier;
}
this.ownerName = ownerName;
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

@@ -0,0 +1,126 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.Stargate;
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
*
* <p>The portal structure knows which gate type is used, where the real locations of buttons, frames and entrances are
* and whether the portal is verified.</p>
*/
public class PortalStructure {
private final Portal portal;
private final Gate gate;
private Block button;
private Block[] frame;
private Block[] entrances;
/**
* Instantiates a new portal structure
*
* @param portal <p>The portal whose structure to store</p>
* @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(@NotNull Portal portal, @NotNull Gate gate, @Nullable Block button) {
this.portal = portal;
this.gate = gate;
this.button = button;
}
/**
* Gets the gate used by this portal structure
*
* @return <p>The gate used by this portal structure</p>
*/
@NotNull
public Gate getGate() {
return gate;
}
/**
* Gets the location of this portal's button
*
* @return <p>The location of this portal's button</p>
*/
@Nullable
public Block getButton() {
return button;
}
/**
* Sets the location of this portal's button
*
* @param button <p>The location of this portal's button</p>
*/
public void setButton(@NotNull Block button) {
this.button = button;
}
/**
* Checks if all blocks in a gate matches the gate template
*
* @return <p>True if all blocks match the gate template</p>
*/
public boolean checkIntegrity() {
if (Stargate.getGateConfig().verifyPortals()) {
return gate.matches(portal.getLocation().getTopLeft(), portal.getLocation().getFacing());
} else {
return true;
}
}
/**
* Gets a list of block locations from a list of relative block vectors
*
* <p>The block locations will be calculated by using this portal's top-left block as the origin for the relative
* vectors..</p>
*
* @param vectors <p>The relative block vectors to convert</p>
* @return <p>A list of block locations</p>
*/
@NotNull
private Block[] relativeBlockVectorsToBlockLocations(@NotNull BlockVector[] vectors) {
Block[] locations = new Block[vectors.length];
for (int i = 0; i < vectors.length; i++) {
locations[i] = portal.getLocation().getRelative(vectors[i]);
}
return locations;
}
/**
* Gets the locations of this portal's entrances
*
* @return <p>The locations of this portal's entrances</p>
*/
@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());
}
return entrances;
}
/**
* Gets the locations of this portal's frame
*
* @return <p>The locations of this portal's frame</p>
*/
@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());
}
return frame;
}
}

View File

@@ -0,0 +1,390 @@
package net.knarcraft.stargate.portal.property.gate;
import net.knarcraft.stargate.Stargate;
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;
/**
* A gate describes the physical structure of a stargate
*
* <p>While the portal class represents a portal in space, the Gate class represents the physical gate/portal entrance.</p>
*/
public class Gate {
private final String filename;
private final GateLayout layout;
private final Map<Character, List<MaterialSpecifier>> characterMaterialMap;
//Gate materials
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;
private final int destroyCost;
private final boolean toOwner;
/**
* 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 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(@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 = 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();
}
/**
* Gets this gate's layout
*
* @return <p>This gate's layout</p>
*/
@NotNull
public GateLayout getLayout() {
return layout;
}
/**
* Gets a copy of the character to material mapping for this gate
*
* @return <p>The character to material map</p>
*/
@NotNull
public Map<Character, List<MaterialSpecifier>> getCharacterMaterialMap() {
return new HashMap<>(characterMaterialMap);
}
/**
* Gets the material type used for this gate's control blocks
*
* @return <p>The material type used for control blocks</p>
*/
@NotNull
public List<MaterialSpecifier> getControlBlockMaterials() {
return characterMaterialMap.get(GateHandler.getControlBlockCharacter());
}
/**
* Gets the filename of this gate's file
*
* @return <p>The filename of this gate's file</p>
*/
@NotNull
public String getFilename() {
return filename;
}
/**
* Gets the block type to use for the opening when a portal using this gate is open
*
* @return <p>The block type to use for the opening when open</p>
*/
@NotNull
public List<MaterialSpecifier> getPortalOpenMaterials() {
return portalOpenMaterials;
}
/**
* Gets the block type to use for the opening when a portal using this gate is closed
*
* @return <p>The block type to use for the opening when closed</p>
*/
@NotNull
public List<MaterialSpecifier> getPortalClosedMaterials() {
return portalClosedMaterials;
}
/**
* Gets the material to use for a portal's button if using this gate type
*
* @return <p>The material to use for a portal's button if using this gate type</p>
*/
@NotNull
public List<MaterialSpecifier> getPortalButtonMaterials() {
return portalButtonMaterials;
}
/**
* Gets the cost of using a portal with this gate
*
* @return <p>The cost of using a portal with this gate</p>
*/
public int getUseCost() {
return useCost < 0 ? Stargate.getEconomyConfig().getDefaultUseCost() : useCost;
}
/**
* Gets the cost of creating a portal with this gate
*
* @return <p>The cost of creating a portal with this gate</p>
*/
@NotNull
public Integer getCreateCost() {
return createCost < 0 ? Stargate.getEconomyConfig().getDefaultCreateCost() : createCost;
}
/**
* Gets the cost of destroying a portal with this gate
*
* @return <p>The cost of destroying a portal with this gate</p>
*/
@NotNull
public Integer getDestroyCost() {
return destroyCost < 0 ? Stargate.getEconomyConfig().getDefaultDestroyCost() : destroyCost;
}
/**
* Gets whether portal payments go to this portal's owner
*
* @return <p>Whether portal payments go to the owner</p>
*/
@NotNull
public Boolean getToOwner() {
return toOwner;
}
/**
* Checks whether a portal's gate matches this gate type
*
* @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 this gate matches the portal</p>
*/
public boolean matches(@NotNull Block topLeft, @NotNull BlockFace facing) {
return matches(topLeft, facing, false);
}
/**
* Checks whether a portal's gate matches this gate type
*
* <p>If enabling onCreate, opening blocks with materials AIR and WATER will be allowed even if the gate closed
* material is a different one. If checking and onCreate is not enabled, any inconsistency with opening blocks
* 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 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(@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'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(@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];
List<MaterialSpecifier> materialInLayout = characterMaterialMap.get(key);
Material type = new SimpleVectorOperation(facing).getRealLocationBlock(topLeft, borderVector).getType();
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. */
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(), type));
}
}
return true;
}
/**
* Verifies that all entrances of a portal gate matches this gate type
*
* @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(@NotNull Block topLeft, @NotNull BlockFace facing, boolean onCreate) {
Stargate.debug("verifyGateEntrancesMatch", String.valueOf(topLeft));
for (BlockVector entranceVector : layout.getEntrances()) {
Stargate.debug("verifyGateEntrancesMatch", String.valueOf(entranceVector));
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.isAir() || type == Material.WATER)) {
continue;
}
if (!MaterialHelper.specifiersToMaterials(portalClosedMaterials).contains(type) &&
!MaterialHelper.specifiersToMaterials(portalOpenMaterials).contains(type)) {
Stargate.debug("Gate::Matches", "Entrance/Exit Material Mismatch: " + type);
return false;
}
}
return true;
}
/**
* Saves this gate to a file
*
* <p>This method will save the gate to its filename in the given folder.</p>
*
* @param gateFolder <p>The folder to save the gate file in</p>
*/
public void save(@NotNull String gateFolder) {
try {
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(new File(gateFolder, filename)));
//Save main material names
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
saveFrameBlockType(bufferedWriter);
bufferedWriter.newLine();
//Save the gate layout
layout.saveLayout(bufferedWriter);
bufferedWriter.close();
} catch (IOException exception) {
Stargate.logSevere(String.format("Could not save Gate %s - %s", filename, exception.getMessage()));
}
}
/**
* Saves current economy related values using a buffered writer
*
* @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(@NotNull BufferedWriter bufferedWriter) throws IOException {
//Write use cost if not disabled
if (useCost != -1) {
writeConfig(bufferedWriter, "usecost", useCost);
}
//Write create cost if not disabled
if (createCost != -1) {
writeConfig(bufferedWriter, "createcost", createCost);
}
//Write destroy cost if not disabled
if (destroyCost != -1) {
writeConfig(bufferedWriter, "destroycost", destroyCost);
}
writeConfig(bufferedWriter, "toowner", toOwner);
}
/**
* Saves the types of blocks used for the gate frame/border using a buffered writer
*
* @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 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 (key.equals(GateHandler.getAnythingCharacter()) ||
key.equals(GateHandler.getEntranceCharacter()) ||
key.equals(GateHandler.getExitCharacter())) {
continue;
}
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
*
* @param bufferedWriter <p>The buffered writer to write the formatted string to</p>
* @param key <p>The config key to save</p>
* @param value <p>The config value to save</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
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) {
format += "%b";
} else if (value instanceof Integer) {
format += "%d";
} else if (value instanceof String) {
format += "%s";
} else {
throw new IllegalArgumentException("Unrecognized config value type");
}
bufferedWriter.append(String.format(format, key, value));
bufferedWriter.newLine();
}
}

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

@@ -0,0 +1,403 @@
package net.knarcraft.stargate.portal.property.gate;
import net.knarcraft.stargate.Stargate;
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.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;
import static net.knarcraft.stargate.utility.GateReader.readGateFile;
/**
* The gate handler keeps track of all gates
*/
public class GateHandler {
private static final Character ANYTHING = ' ';
private static final Character ENTRANCE = '.';
private static final Character EXIT = '*';
private static final Character CONTROL_BLOCK = '-';
private static final Material defaultPortalBlockOpen = Material.NETHER_PORTAL;
private static final Material defaultPortalBlockClosed = Material.AIR;
private static final Material defaultButton = Material.STONE_BUTTON;
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() {
}
/**
* Gets the character used for blocks that are not part of the gate
*
* @return <p>The character used for blocks that are not part of the gate</p>
*/
@NotNull
public static Character getAnythingCharacter() {
return ANYTHING;
}
/**
* Gets the character used for defining the entrance
*
* @return <p>The character used for defining the entrance</p>
*/
@NotNull
public static Character getEntranceCharacter() {
return ENTRANCE;
}
/**
* Gets the character used for defining the exit
*
* @return <p>The character used for defining the exit</p>
*/
@NotNull
public static Character getExitCharacter() {
return EXIT;
}
/**
* Gets the character used for defining control blocks
*
* @return <p>The character used for defining control blocks</p>
*/
@NotNull
public static Character getControlBlockCharacter() {
return CONTROL_BLOCK;
}
/**
* Register a gate into the list of available gates
*
* @param gate <p>The gate to register</p>
*/
private static void registerGate(@NotNull Gate gate) {
gates.put(gate.getFilename(), gate);
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);
}
}
/**
* Loads a gate from a file
*
* @param file <p>The file containing the gate data</p>
* @return <p>The loaded gate, or null if unable to load the gate</p>
*/
@Nullable
private static Gate loadGate(@NotNull File file) {
try (Scanner scanner = new Scanner(file)) {
return loadGate(file.getName(), file.getParent(), scanner);
} catch (Exception exception) {
Stargate.logSevere(String.format("Could not load Gate %s - %s", file.getName(), exception.getMessage()));
return null;
}
}
/**
* Loads a gate from a file
*
* @param fileName <p>The name of the file containing the gate data</p>
* @param parentFolder <p>The parent folder of the gate data file</p>
* @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>
*/
@Nullable
private static Gate loadGate(@NotNull String fileName, @NotNull String parentFolder,
@NotNull Scanner scanner) {
List<List<Character>> design = new ArrayList<>();
Map<Character, List<MaterialSpecifier>> characterMaterialMap = new HashMap<>();
Map<String, String> config = new HashMap<>();
//Initialize character to material map
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, config);
if (columns < 0) {
return null;
}
Character[][] layout = generateLayoutMatrix(design, columns);
//Create and validate the new gate
Gate gate = createGate(config, fileName, layout, characterMaterialMap);
if (gate == null) {
return null;
}
//Update gate file in case the format has changed between versions
gate.save(parentFolder);
return gate;
}
/**
* Creates a new gate
*
* @param config <p>The config map to get configuration values from</p>
* @param fileName <p>The name of the saved gate config file</p>
* @param layout <p>The layout matrix of the new gate</p>
* @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>
*/
@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
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 = 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, gateCosts);
if (!validateGate(gate, fileName)) {
return null;
}
return gate;
}
/**
* Validates that a gate is valid
*
* @param gate <p>The gate to validate</p>
* @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(@NotNull Gate gate, @NotNull String fileName) {
String failString = String.format("Could not load Gate %s", fileName) + " - %s";
if (gate.getLayout().getControls().length != 2) {
Stargate.logSevere(String.format(failString, "Gates must have exactly 2 control points."));
return false;
}
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 (checkMaterialPredicateFail(gate.getPortalOpenMaterials(), Material::isBlock)) {
Stargate.logSevere(String.format(failString, "Gate open block must be a type of block."));
return false;
}
if (checkMaterialPredicateFail(gate.getPortalClosedMaterials(), Material::isBlock)) {
Stargate.logSevere(String.format(failString, "Gate closed block must be a type of block."));
return false;
}
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;
}
}
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(@NotNull String gateFolder) {
File directory = new File(gateFolder);
File[] files;
if (directory.exists()) {
//Get all files with a .gate extension
files = directory.listFiles((file) -> file.isFile() && file.getName().endsWith(".gate"));
} else {
//Set files to empty list to signal that default gates need to be copied
files = new File[0];
}
if (files == null || files.length == 0) {
//The gates-folder was not found. Assume this is the first run
if (directory.mkdir()) {
writeDefaultGatesToFolder(gateFolder);
}
} else {
//Load and register the corresponding gate for each file
for (File file : files) {
Gate gate = loadGate(file);
if (gate != null) {
registerGate(gate);
}
}
}
}
/**
* Writes the default gates to the given folder
*
* @param gateFolder <p>The folder containing gate config files</p>
*/
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);
}
/**
* Loads the given gate file from within the Jar's resources directory
*
* @param gateFile <p>The name of the gate file</p>
* @param gateFolder <p>The folder containing gates</p>
*/
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) {
Scanner scanner = new Scanner(gateFileStream);
//Load and register the gate
Gate gate = loadGate(gateFile, gateFolder, scanner);
if (gate != null) {
registerGate(gate);
}
}
}
/**
* Gets the gates with the given control block
*
* <p>The control block is the block type where the sign should be placed. It is used to decide whether a user
* is creating a new portal.</p>
*
* @param block <p>The control block to check</p>
* @return <p>A list of gates using the given control block</p>
*/
@NotNull
public static List<Gate> getGatesByControlBlock(@NotNull Block block) {
return getGatesByControlBlock(block.getType());
}
/**
* Gets the gates with the given control block
*
* <p>The control block is the block type where the sign should be placed. It is used to decide whether a user
* is creating a new portal.</p>
*
* @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>
*/
@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 (fromId != null) {
result.addAll(fromId);
}
if (fromTag != null) {
result.addAll(fromTag);
}
return result;
}
/**
* Gets a portal given its filename
*
* @param fileName <p>The filename of the gate to get</p>
* @return <p>The gate with the given filename</p>
*/
@Nullable
public static Gate getGateByName(@NotNull String fileName) {
return gates.get(fileName);
}
/**
* Gets the number of loaded gate configurations
*
* @return <p>The number of loaded gate configurations</p>
*/
public static int getGateCount() {
return gates.size();
}
/**
* Clears all loaded gates and control blocks
*/
public static void clearGates() {
gates.clear();
controlBlocks.clear();
controlBlockTags.clear();
}
}

View File

@@ -0,0 +1,219 @@
package net.knarcraft.stargate.portal.property.gate;
import org.bukkit.util.BlockVector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* The gate layout describes where every part of the gate should be
*
* <p>The gate layout parses a layout described by a Character matrix and stores the different parts of the gate as
* relative block vectors. All relative vectors has an origin in the top-left block when looking at the gate's front
* (the side with the sign). The origin of the relative vectors can also be seen as 0,0 in the character matrix.</p>
*/
public class GateLayout {
private final Character[][] layout;
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(@NotNull Character[][] layout) {
this.layout = layout;
readLayout();
}
/**
* Gets the character array describing this layout
*
* @return <p>The character array describing this layout</p>
*/
@NotNull
public Character[][] getLayout() {
return this.layout;
}
/**
* Gets the locations of all entrances for this gate
*
* <p>Entrances contain both the portal entrance blocks and the portal exit blocks.</p>
*
* @return <p>The locations of entrances for this gate</p>
*/
@NotNull
public BlockVector[] getEntrances() {
return entrances;
}
/**
* Gets the locations of border blocks for the gate described by this layout
*
* <p>A border block is basically any block of the frame. In terms of the nether gate, the border blocks are every
* block of the gate that's not air when the gate is closed. The sign and button are not border blocks.</p>
*
* @return <p>The locations of border blocks for this gate</p>
*/
@NotNull
public BlockVector[] getBorder() {
return border;
}
/**
* Gets the exit block defined in the layout
*
* @return <p>The exit block defined in the layout</p>
*/
@Nullable
public BlockVector getExit() {
return exitBlock;
}
/**
* Gets all possible exit locations defined in the layout
*
* <p>This returns all blocks usable as exits. This basically means it returns the lowest block in each opening of
* the gate layout.</p>
*
* @return <p>All possible exits</p>
*/
@NotNull
public List<BlockVector> getExits() {
return exits;
}
/**
* Gets the locations of the control blocks for this gate
*
* <p>The control blocks are the blocks where a sign can be placed to create a portal. The control block without a
* sign will be used for the button if necessary. There will always be exactly two control blocks.</p>
*
* @return <p>The locations of the control blocks for this gate</p>
*/
@NotNull
public BlockVector[] getControls() {
return controls;
}
/**
* Saves the gate layout using a buffered writer
*
* @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(@NotNull BufferedWriter bufferedWriter) throws IOException {
for (Character[] line : this.layout) {
for (Character character : line) {
bufferedWriter.append(character);
}
bufferedWriter.newLine();
}
}
/**
* Reads the layout and stores key information
*
* <p>This methods reads the layout and stores exits, entrances, border blocks and control blocks.</p>
*/
private void readLayout() {
List<BlockVector> entranceList = new ArrayList<>();
List<BlockVector> borderList = new ArrayList<>();
List<BlockVector> controlList = new ArrayList<>();
readLayout(controlList, entranceList, borderList);
this.entrances = entranceList.toArray(this.entrances);
this.border = borderList.toArray(this.border);
this.controls = controlList.toArray(this.controls);
}
/**
* Reads the given layout matrix, filling in the given lists of relative block vectors
*
* @param controlList <p>The list of control blocks to save to</p>
* @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(@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];
//A row is the same as one line in the gate file
int lineCount = layout.length;
for (int rowIndex = 0; rowIndex < lineCount; rowIndex++) {
Character[] row = layout[rowIndex];
int rowSize = row.length;
for (int columnIndex = 0; columnIndex < rowSize; columnIndex++) {
Character key = row[columnIndex];
parseLayoutCharacter(key, columnIndex, rowIndex, exitDepths, controlList, entranceList, borderList);
}
}
//Generate all possible exits
for (int x = 0; x < exitDepths.length; x++) {
//Ignore invalid exits
if (exitDepths[x] > 0) {
this.exits.add(new BlockVector(x, exitDepths[x], 0));
}
}
}
/**
* Parses one character of the layout
*
* @param key <p>The read character</p>
* @param columnIndex <p>The column containing the read character</p>
* @param rowIndex <p>The row containing the read character</p>
* @param exitDepths <p>The list of exit depths to save to</p>
* @param controlList <p>The list of control blocks to save to</p>
* @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(@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 BlockVector(columnIndex, rowIndex, 0));
}
if (isOpening(key)) {
//Register entrance
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 BlockVector(columnIndex, rowIndex, 0);
}
} else if (!key.equals(GateHandler.getAnythingCharacter())) {
//Register border block
borderList.add(new BlockVector(columnIndex, rowIndex, 0));
}
}
/**
* Checks whether the given character represents a gate opening
*
* @param character <p>The character to check</p>
* @return <p>True if the character represents an opening</p>
*/
private boolean isOpening(@NotNull Character character) {
return character.equals(GateHandler.getEntranceCharacter()) || character.equals(GateHandler.getExitCharacter());
}
}

View File

@@ -0,0 +1,35 @@
package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.event.StargateEntityPortalEvent;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;
/**
* The portal teleporter takes care of the actual portal teleportation for any entities
*/
public class EntityTeleporter extends Teleporter {
private final Entity teleportingEntity;
/**
* Instantiates a new portal teleporter
*
* @param targetPortal <p>The portal which is the target of the teleportation</p>
*/
public EntityTeleporter(@NotNull Portal targetPortal, @NotNull Entity teleportingEntity) {
super(targetPortal, teleportingEntity);
this.teleportingEntity = teleportingEntity;
}
/**
* Teleports an entity to this teleporter's portal
*
* @param origin <p>The portal the entity is teleporting from</p>
* @return <p>True if the entity was teleported. False otherwise</p>
*/
public boolean teleportEntity(@NotNull Portal origin) {
return teleport(origin, new StargateEntityPortalEvent(teleportingEntity, origin, portal, exit));
}
}

View File

@@ -0,0 +1,80 @@
package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.event.StargatePlayerPortalEvent;
import net.knarcraft.stargate.portal.Portal;
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
*/
public class PlayerTeleporter extends Teleporter {
private final Player player;
/**
* Instantiates a new player teleporter
*
* @param targetPortal <p>The portal which is the target of the teleportation</p>
* @param player <p>The teleporting player</p>
*/
public PlayerTeleporter(@NotNull Portal targetPortal, @NotNull Player player) {
super(targetPortal, player);
this.player = player;
}
/**
* Teleports a player to this teleporter's portal
*
* @param origin <p>The portal the player teleports from</p>
* @param event <p>The player move event triggering the event</p>
*/
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 = 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
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 {
//Set the exit location of the event
event.setTo(exit);
}
//Set the velocity of the teleported player after the teleportation is finished
Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), () -> player.setVelocity(newVelocity), 1);
}
}

View File

@@ -0,0 +1,333 @@
package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.Stargate;
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.Entity;
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;
/**
* The portal teleporter takes care of common teleportation logic
*/
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 teleportedEntity <p>The entity teleported by this teleporter</p>
*/
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 adjustExitLocationRotation(@NotNull Location exit) {
int adjust = 0;
if (portal.getOptions().isBackwards()) {
adjust = 180;
}
float newYaw = (portal.getLocation().getYaw() + adjust) % 360;
Stargate.debug("Portal::adjustRotation", "Setting exit yaw to " + newYaw);
exit.setDirection(DirectionHelper.getDirectionVectorFromYaw(newYaw));
}
/**
* Loads the chunks outside the portal's entrance
*/
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));
}
}
/**
* Adjusts the positioning of the portal exit to prevent the given entity from suffocating
*
* @param relativeExit <p>The relative exit defined as the portal's exit</p>
* @param exitLocation <p>The currently calculated portal exit</p>
* @param entity <p>The travelling entity</p>
* @return <p>A location which won't suffocate the entity inside the portal</p>
*/
@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);
//Go right to find the end of the opening
RelativeBlockVector openingRight = getPortalExitEdge(relativeExit, 1);
//Get the width to check if the entity fits
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.getLocation().getYaw());
//Move large entities further from the portal
return moveExitLocationOutwards(exitLocation, entity);
}
/**
* Moves the exit location out from the portal to prevent the entity from entering a teleportation loop
*
* @param exitLocation <p>The current exit location to adjust</p>
* @param entity <p>The entity to adjust the exit location for</p>
* @return <p>The adjusted exit location</p>
*/
@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);
} else {
entityOffset = (entitySize / 2D) - 1;
}
//If a horse has a player riding it, the player will spawn inside the roof of a standard portal unless it's
// moved one block out.
if (entity instanceof AbstractHorse) {
entityOffset += 1;
}
exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, entityOffset, portal.getLocation().getYaw());
}
return exitLocation;
}
/**
* Gets one of the edges of a portal's opening/exit
*
* @param relativeExit <p>The known exit to start from</p>
* @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>
*/
@NotNull
private RelativeBlockVector getPortalExitEdge(@NotNull RelativeBlockVector relativeExit, int direction) {
RelativeBlockVector openingEdge = relativeExit;
do {
RelativeBlockVector possibleOpening = new RelativeBlockVector(openingEdge.right() + direction,
openingEdge.down(), openingEdge.out());
if (portal.getGate().getLayout().getExits().contains(possibleOpening)) {
openingEdge = possibleOpening;
} else {
break;
}
} while (true);
return openingEdge;
}
/**
* Adjusts an exit location by setting pitch and adjusting height
*
* <p>If the exit location is a slab or water, the exit location will be changed to arrive one block above. The
* 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 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>
*/
@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) ||
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);
}
return exitLocation;
} else {
Stargate.logWarning("Unable to generate exit location");
return entity.getLocation();
}
}
/**
* 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>
*/
@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;
}
/**
* Gets all relevant chunks near this teleporter's portal's entrance which need to be loaded before teleportation
*
* @return <p>A list of chunks to load</p>
*/
@NotNull
private List<Chunk> getChunksToLoad() {
List<Chunk> chunksToLoad = new ArrayList<>();
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)) {
chunksToLoad.add(chunk);
}
//Get the chunk in front of the gate entrance
int blockOffset = portal.getOptions().isBackwards() ? -5 : 5;
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)) {
chunksToLoad.add(forwardChunk);
}
}
return chunksToLoad;
}
}

View File

@@ -0,0 +1,187 @@
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.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
*/
public class VehicleTeleporter extends EntityTeleporter {
private final Vehicle teleportingVehicle;
/**
* Instantiates a new vehicle teleporter
*
* @param targetPortal <p>The targetPortal which is the target of the teleportation</p>
* @param teleportingVehicle <p>The teleporting vehicle</p>
*/
public VehicleTeleporter(@NotNull Portal targetPortal, @NotNull Vehicle teleportingVehicle) {
super(targetPortal, teleportingVehicle);
this.teleportingVehicle = teleportingVehicle;
}
/**
* Teleports a vehicle to this teleporter's portal
*
* <p>It is assumed that if a vehicle contains any players, their permissions have already been validated before
* calling this method.</p>
*
* @param origin <p>The portal the vehicle is teleporting from</p>
* @return <p>True if the vehicle was teleported. False otherwise</p>
*/
@Override
public boolean teleportEntity(@NotNull Portal origin) {
Stargate.debug("VehicleTeleporter::teleport", "Preparing to teleport: " + teleportingVehicle);
double velocity = teleportingVehicle.getVelocity().length();
//Stop the vehicle before teleporting
teleportingVehicle.setVelocity(new Vector());
//Get new velocity
Vector newVelocityDirection = DirectionHelper.getDirectionVectorFromYaw(portal.getLocation().getYaw());
Vector newVelocity = newVelocityDirection.multiply(velocity);
//Call the StargateEntityPortalEvent to allow plugins to change destination
exit = triggerPortalEvent(origin, new StargateEntityPortalEvent(teleportingVehicle, origin, portal, exit));
if (exit == null) {
return false;
}
//Teleport the vehicle
return teleportVehicle(exit, newVelocity, origin);
}
/**
* Teleports a vehicle with any passengers to the given location
*
* @param exit <p>The location the vehicle should be teleported to</p>
* @param newVelocity <p>The velocity to give the vehicle right after teleportation</p>
* @param origin <p>The portal the vehicle teleported from</p>
* @return <p>True if the vehicle was teleported. False otherwise</p>
*/
private boolean teleportVehicle(@NotNull Location exit, @NotNull Vector newVelocity, @NotNull Portal origin) {
//Load chunks to make sure not to teleport to the void
loadChunks();
List<Entity> passengers = teleportingVehicle.getPassengers();
if (!passengers.isEmpty()) {
//Check if the passengers are allowed according to current config settings
if (!vehiclePassengersAllowed(passengers)) {
return false;
}
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)
teleportVehicle(passengers, exit, newVelocity, origin);
}
} else {
//Check if teleportation of empty vehicles is enabled
if (!Stargate.getGateConfig().handleEmptyVehicles()) {
return false;
}
//Teleport an empty vehicle
teleportingVehicle.teleport(exit);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(),
() -> teleportingVehicle.setVelocity(newVelocity), 1);
}
return true;
}
/**
* Checks whether current config values allow the teleportation of the given passengers
*
* @param passengers <p>The passengers to teleport</p>
* @return <p>True if the passengers are allowed to teleport</p>
*/
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 (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 TeleportHelper.containsPlayer(passengers) || config.handleNonPlayerVehicles();
}
/**
* Teleport a vehicle which is not a minecart or a boat
*
* @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 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);
}
/**
* Creates a new vehicle equal to the player's previous vehicle and puts any passengers inside
*
* <p>While it is possible to teleport boats and minecarts using the same methods as "teleportLivingVehicle", this
* method works better with CraftBook with minecart options enabled. Using normal teleportation, CraftBook destroys
* the minecart once the player is ejected, causing the minecart to disappear and the player to teleport without it.</p>
*
* @param passengers <p>A list of all passengers in the vehicle</p>
* @param exit <p>The exit location to spawn the new vehicle on</p>
* @param newVelocity <p>The new velocity of the new vehicle</p>
* @param origin <p>The portal the vehicle teleported from</p>
*/
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 = (Vehicle) vehicleWorld.spawn(exit,
Objects.requireNonNull(teleportingVehicle.getType().getEntityClass()));
if (teleportingVehicle instanceof Boat boat) {
boat.setBoatType(boat.getBoatType());
}
//Remove the old vehicle
if (teleportingVehicle.eject()) {
TeleportHelper.handleEntityPassengers(passengers, newVehicle, origin, portal, exit.getDirection(),
newVelocity);
}
teleportingVehicle.remove();
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> newVehicle.setVelocity(newVelocity), 1);
}
}

View File

@@ -0,0 +1,84 @@
package net.knarcraft.stargate.thread;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockChangeRequest;
import org.bukkit.Axis;
import org.bukkit.Material;
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 blockChangeRequestQueue each time it's called.</p>
*/
public class BlockChangeThread implements Runnable {
@Override
public void run() {
long sTime = System.nanoTime();
//Repeat for at most 0.025 seconds
while (System.nanoTime() - sTime < 25000000) {
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 boolean pollQueue() {
//Abort if there's no work to be done
BlockChangeRequest blockChangeRequest = Stargate.getControlBlockUpdateRequestQueue().poll();
if (blockChangeRequest == null) {
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) {
//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;
}
/**
* Prevents end gateway portal from behaving strangely
*
* @param block <p>The block to fix</p>
*/
private static void fixEndGatewayGate(@NotNull Block block) {
EndGateway gateway = (EndGateway) block.getState();
gateway.setAge(Long.MIN_VALUE);
if (block.getWorld().getEnvironment() == World.Environment.THE_END) {
gateway.setExitLocation(block.getLocation());
gateway.setExactTeleport(true);
}
gateway.update(false, false);
}
/**
* Sets the orientation axis of the placed block
*
* @param block <p>The block to orient</p>
* @param axis <p>The axis to use for orienting the block</p>
*/
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,31 @@
package net.knarcraft.stargate.thread;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.ChunkUnloadRequest;
import org.bukkit.Chunk;
import java.util.Queue;
/**
* Unloads chunks which should no longer be forced to stay loaded
*/
public class ChunkUnloadThread implements Runnable {
@Override
public void run() {
long systemNanoTime = System.nanoTime();
Queue<ChunkUnloadRequest> unloadQueue = Stargate.getChunkUnloadQueue();
//Peek at the first element to check if the chunk should be unloaded
ChunkUnloadRequest firstElement = unloadQueue.peek();
//Repeat until all un-loadable chunks have been processed
while (firstElement != null && firstElement.getUnloadNanoTime() < systemNanoTime) {
unloadQueue.remove();
Chunk chunkToUnload = firstElement.getChunkToUnload();
//Allow the chunk to be unloaded
chunkToUnload.removePluginChunkTicket(Stargate.getInstance());
firstElement = unloadQueue.peek();
}
}
}

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,66 @@
package net.knarcraft.stargate.thread;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
/**
* This class contains the function used to close servers which should no longer be open/active
*/
public class StarGateThread implements Runnable {
@Override
public void run() {
long time = System.currentTimeMillis() / 1000;
closeOpenPortals(time);
deactivateActivePortals(time);
}
/**
* Closes portals which are open and have timed out
*
* @param time <p>The current time</p>
*/
private void closeOpenPortals(long time) {
List<Portal> closedPortals = new ArrayList<>();
Queue<Portal> openPortalsQueue = Stargate.getStargateConfig().getOpenPortalsQueue();
for (Portal portal : openPortalsQueue) {
//Skip always open and non-open gates
if (portal.getOptions().isAlwaysOn() || !portal.isOpen()) {
continue;
}
if (time > portal.getTriggeredTime() + Stargate.getGateConfig().getOpenTime()) {
portal.getPortalOpener().closePortal(false);
closedPortals.add(portal);
}
}
openPortalsQueue.removeAll(closedPortals);
}
/**
* De-activates portals which are active and have timed out
*
* @param time <p>The current time</p>
*/
private void deactivateActivePortals(long time) {
List<Portal> deactivatedPortals = new ArrayList<>();
Queue<Portal> activePortalsQueue = Stargate.getStargateConfig().getActivePortalsQueue();
for (Portal portal : activePortalsQueue) {
//Skip portals which aren't active
if (!portal.getPortalActivator().isActive()) {
continue;
}
if (time > portal.getTriggeredTime() + Stargate.getGateConfig().getActiveTime()) {
portal.getPortalActivator().deactivate();
deactivatedPortals.add(portal);
}
}
activePortalsQueue.removeAll(deactivatedPortals);
}
}

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

@@ -0,0 +1,214 @@
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.PlayerTeleporter;
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;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* This class contains helpful functions to help with sending and receiving BungeeCord plugin messages
*/
public final class BungeeHelper {
private final static String bungeeSubChannel = "SGBungee";
private final static String bungeeChannel = "BungeeCord";
private final static String teleportMessageDelimiter = "#@#";
private final static Map<UUID, String> bungeeQueue = new HashMap<>();
private BungeeHelper() {
}
/**
* Get the plugin message channel use for BungeeCord messages
*
* @return <p>The bungee plugin channel</p>
*/
@NotNull
public static String getBungeeChannel() {
return bungeeChannel;
}
/**
* Removes a player from the queue of players teleporting through BungeeCord
*
* <p>Whenever a BungeeCord teleportation message is received and the player is not currently connected to this
* server, it'll be added to this queue. Once the player joins this server, the player should be removed from the
* 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, or null if not queued</p>
*/
@Nullable
public static String removeFromQueue(@NotNull UUID playerUUID) {
return bungeeQueue.remove(playerUUID);
}
/**
* Sends a plugin message to BungeeCord allowing the target server to catch it
*
* @param player <p>The teleporting player</p>
* @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(@NotNull Player player, @NotNull Portal entrancePortal) {
try {
//Build the teleportation message, format is <player identifier>delimiter<destination>
String message = player.getUniqueId() + teleportMessageDelimiter + entrancePortal.getDestinationName();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
//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(Portal.cleanString(entrancePortal.getNetwork()));
//Specify the sub-channel/tag to make it recognizable on arrival
dataOutputStream.writeUTF(bungeeSubChannel);
//Write the length of the message
dataOutputStream.writeShort(message.length());
//Write the actual message
dataOutputStream.writeBytes(message);
//Send the plugin message
player.sendPluginMessage(Stargate.getInstance(), bungeeChannel, byteArrayOutputStream.toByteArray());
} catch (IOException exception) {
Stargate.logSevere("Error sending BungeeCord teleport packet! Message: " + exception.getMessage());
return false;
}
return true;
}
/**
* Sends the bungee message necessary to make a player connect to another server
*
* @param player <p>The player to teleport</p>
* @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(@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(Portal.cleanString(entrancePortal.getNetwork()));
//Send the plugin message
player.sendPluginMessage(Stargate.getInstance(), bungeeChannel, byteArrayOutputStream.toByteArray());
} catch (IOException exception) {
Stargate.logSevere("Error sending BungeeCord connect packet! Message: " + exception.getMessage());
return false;
}
return true;
}
/**
* Reads a plugin message byte array to a string if it's sent from another stargate plugin
*
* @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 {
DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(message));
String subChannel = dataInputStream.readUTF();
//Only listen for the SGBungee channel
if (!subChannel.equals(bungeeSubChannel)) {
return null;
}
//Get the length of the contained message
short dataLength = dataInputStream.readShort();
//Prepare a byte array for the sent message
data = new byte[dataLength];
//Read the message to the prepared array
dataInputStream.readFully(data);
} catch (IOException exception) {
Stargate.logSevere("Error receiving BungeeCord message. Message: " + exception.getMessage());
return null;
}
return new String(data);
}
/**
* Handles the receival of a teleport message
*
* @param receivedMessage <p>The received teleport message</p>
*/
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]);
String destination = messageParts[1];
//Check if the player is online, if so, teleport, otherwise, queue
Player player = Stargate.getInstance().getServer().getPlayer(playerUUID);
if (player == null) {
bungeeQueue.put(playerUUID, destination);
} else {
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));
return;
}
new PlayerTeleporter(destinationPortal, player).teleport(destinationPortal, null);
}
}
/**
* Teleports a player to a bungee gate
*
* @param player <p>The player to teleport</p>
* @param entrancePortal <p>The gate the player is entering from</p>
* @param event <p>The event causing the teleportation</p>
* @return <p>True if the teleportation was successful</p>
*/
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().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).teleportPlayer(entrancePortal, event);
//Send the SGBungee packet first, it will be queued by BC if required
if (!BungeeHelper.sendTeleportationMessage(player, entrancePortal)) {
Stargate.debug("bungeeTeleport", "Unable to send teleportation message");
return false;
}
//Send the connect-message to make the player change server
if (!BungeeHelper.changeServer(player, entrancePortal)) {
Stargate.debug("bungeeTeleport", "Unable to change server");
return false;
}
Stargate.debug("bungeeTeleport", "Teleported player to another server");
return true;
}
}

View File

@@ -0,0 +1,253 @@
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
*/
public final class DirectionHelper {
private 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
*
* <p>The yaw here is the direction an observer a the first location has to look to face the second location.
* The yaw 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 location1 <p>The first location, which works as the origin</p>
* @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(@NotNull Location location1, @NotNull Location location2) {
Location difference = location1.clone().subtract(location2.clone());
if (difference.getX() > 0) {
return 90;
} else if (difference.getX() < 0) {
return 270;
} else if (difference.getZ() > 0) {
return 180;
} else if (difference.getZ() < 0) {
return 0;
}
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
*
* <p>The supplied yaw must be a value such that (yaw mod 90) = 0. If not, an exception is thrown.</p>
*
* @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>
*/
@NotNull
public static BlockFace getBlockFaceFromYaw(double yaw) throws IllegalArgumentException {
//Make sure the yaw is between 0 and 360
yaw = normalizeYaw(yaw);
if (yaw == 0) {
return BlockFace.SOUTH;
} else if (yaw == 90) {
return BlockFace.WEST;
} else if (yaw == 180) {
return BlockFace.NORTH;
} else if (yaw == 270) {
return BlockFace.EAST;
} else {
throw new IllegalArgumentException("Invalid yaw given. Yaw must be divisible by 90.");
}
}
/**
* 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>
*/
@NotNull
public static Vector getDirectionVectorFromYaw(double yaw) throws IllegalArgumentException {
//Make sure the yaw is between 0 and 360
yaw = normalizeYaw(yaw);
if (yaw == 0) {
return new Vector(0, 0, 1);
} else if (yaw == 90) {
return new Vector(-1, 0, 0);
} else if (yaw == 180) {
return new Vector(0, 0, -1);
} else if (yaw == 270) {
return new Vector(1, 0, 0);
} else {
throw new IllegalArgumentException(String.format("Invalid yaw %f given", yaw));
}
}
/**
* Moves a location by the given amounts
*
* <p>The right, down and out work the same as for the relative block vector. Looking a the front of a portal,
* right goes rightwards, down goes downwards and out goes towards the observer.</p>
*
* @param location <p>The location to start at</p>
* @param right <p>The amount to go right</p>
* @param down <p>The amount to go downward</p>
* @param out <p>The amount to go outward</p>
* @param yaw <p>The yaw when looking directly outwards from a portal</p>
* @return <p>A location relative to the given location</p>
*/
@NotNull
public static Location moveLocation(@NotNull Location location, double right, double down, double out, double yaw) {
return location.add(getCoordinateVectorFromRelativeVector(right, down, out, yaw));
}
/**
* Gets a vector in Minecraft's normal X,Y,Z-space from a relative block vector
*
* @param right <p>The amount of rightward steps from the top-left origin</p>
* @param down <p>The amount of downward steps from the top-left origin</p>
* @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>
*/
@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);
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));
}
}
/**
* 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;
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);
}
/**
* Normalizes a yaw to make it positive and no larger than 360 degrees
*
* @param yaw <p>The yaw to normalize</p>
* @return <p>The normalized yaw</p>
*/
private static double normalizeYaw(double yaw) {
while (yaw < 0) {
yaw += 360;
}
yaw = yaw % 360;
return yaw;
}
}

View File

@@ -0,0 +1,281 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.knarcraft.stargate.Stargate;
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;
/**
* The economy helper class has helper functions for player payment
*/
public final class EconomyHelper {
private EconomyHelper() {
}
/**
* Tries to make the given user pay the teleport fee
*
* @param entrancePortal <p>The portal the player is entering</p>
* @param player <p>The player wishing to teleport</p>
* @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(@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
UUID ownerUUID = entrancePortal.getOwner().getUUID();
if (ownerUUID == null) {
Stargate.logWarning(String.format("The owner of the portal %s does not have a UUID and payment to owner " +
"was therefore not possible. Make the owner re-create the portal to fix this.", entrancePortal));
}
if (entrancePortal.getGate().getToOwner() && ownerUUID != null) {
success = chargePlayerIfNecessary(player, ownerUUID, cost);
} else {
success = chargePlayerIfNecessary(player, cost);
}
//Send the insufficient funds message
if (!success) {
sendInsufficientFundsMessage(entrancePortal.getName(), player, cost);
entrancePortal.getPortalOpener().closePortal(false);
return true;
}
//Send the deduct-message to the player
sendDeductMessage(entrancePortal.getName(), player, cost);
if (entrancePortal.getGate().getToOwner()) {
PortalOwner owner = entrancePortal.getOwner();
Player portalOwner;
if (owner.getUUID() != null) {
portalOwner = Stargate.getInstance().getServer().getPlayer(owner.getUUID());
} else {
portalOwner = Stargate.getInstance().getServer().getPlayer(owner.getName());
}
//Notify the gate owner of received payment
if (portalOwner != null) {
sendObtainMessage(entrancePortal.getName(), portalOwner, cost);
}
}
return false;
}
/**
* Sends a message to the gate owner telling him/her how much he/she earned from a player using his/her gate
*
* @param portalName <p>The name of the used portal</p>
* @param portalOwner <p>The owner of the portal</p>
* @param earnings <p>The amount the owner earned</p>
*/
public static void sendObtainMessage(@NotNull String portalName, @NotNull Player portalOwner, int earnings) {
replacePlaceholders(new SGFormatBuilder(Message.ECONOMY_OBTAINED), portalName, earnings).success(portalOwner);
}
/**
* Sends a message telling the user how much they paid for interacting with a portal
*
* @param portalName <p>The name of the portal interacted with</p>
* @param player <p>The interacting player</p>
* @param cost <p>The cost of the interaction</p>
*/
public static void sendDeductMessage(@NotNull String portalName, @NotNull Player player, int cost) {
replacePlaceholders(new SGFormatBuilder(Message.ECONOMY_DEDUCTED), portalName, cost).success(player);
}
/**
* Sends a message telling the user they don't have enough funds to do a portal interaction
*
* @param portalName <p>The name of the portal interacted with</p>
* @param player <p>The interacting player</p>
* @param cost <p>The cost of the interaction</p>
*/
public static void sendInsufficientFundsMessage(@NotNull String portalName, @NotNull Player player, int cost) {
replacePlaceholders(new SGFormatBuilder(Message.ECONOMY_INSUFFICIENT), portalName, cost).error(player);
}
/**
* Sends a message telling the user how much they are refunded for breaking their portal
*
* @param portalName <p>The name of the broken portal</p>
* @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(@NotNull String portalName, @NotNull Player player, int cost) {
replacePlaceholders(new SGFormatBuilder(Message.ECONOMY_REFUNDED), portalName, -cost).success(player);
}
/**
* Determines the cost of using a gate
*
* @param player <p>The player trying to use the gate</p>
* @param source <p>The source/entry portal</p>
* @param destination <p>The destination portal</p>
* @return <p>The cost of using the portal</p>
*/
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()) {
return 0;
}
//Not charging for free destinations
if (destination != null && config.freeIfFreeDestination() && destination.getOptions().isFree()) {
return 0;
}
//Cost is 0 if the player owns this gate and funds go to the owner
if (source.getGate().getToOwner() && source.isOwner(player)) {
return 0;
}
//Player gets free gate use
if (PermissionHelper.hasPermission(player, Permission.FREE_USAGE)) {
return 0;
}
return source.getGate().getUseCost();
}
/**
* Charges the player for an action, if required
*
* @param player <p>The player to take money from</p>
* @param target <p>The target to pay</p>
* @param cost <p>The cost of the transaction</p>
* @return <p>True if the player was charged successfully</p>
*/
public static boolean chargePlayerIfNecessary(@NotNull Player player, @NotNull UUID target, int cost) {
if (skipPayment(cost)) {
return true;
}
//Charge player
return chargePlayer(player, target, cost);
}
/**
* Charges a player
*
* @param player <p>The player to charge</p>
* @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(@NotNull Player player, double amount) {
Economy economy = Stargate.getEconomyConfig().getEconomy();
if (Stargate.getEconomyConfig().isEconomyEnabled() && economy != null) {
if (!economy.has(player, amount)) {
return false;
}
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
*
* @param player <p>The player to take money from</p>
* @param cost <p>The cost of the transaction</p>
* @return <p>True if the player was charged successfully</p>
*/
public static boolean chargePlayerIfNecessary(@NotNull Player player, int cost) {
if (skipPayment(cost)) {
return true;
}
//Charge player
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;
}
/**
* Checks whether a payment transaction should be skipped
*
* @param cost <p>The cost of the transaction</p>
* @return <p>True if the transaction should be skipped</p>
*/
private static boolean skipPayment(int cost) {
return cost == 0 || !Stargate.getEconomyConfig().useEconomy();
}
/**
* Charges a player, giving the charge to a target
*
* @param player <p>The player to charge</p>
* @param target <p>The UUID of the player to pay</p>
* @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(@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 (!economy.has(player, amount)) {
return false;
}
//Take money from the user and give to the owner
economy.withdrawPlayer(player, amount);
economy.depositPlayer(Bukkit.getOfflinePlayer(target), amount);
}
return true;
}
/**
* Replaces the cost and portal variables in a format builder
*
* @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 format builder</p>
*/
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

@@ -0,0 +1,39 @@
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
*/
public final class EntityHelper {
private EntityHelper() {
}
/**
* Gets the max size of an entity along its x and z axis
*
* <p>This function gets the ceiling of the max size of an entity, thus calculating the smallest box, using whole
* blocks as unit, needed to contain the entity. Assuming n is returned, an (n x n) box is needed to contain the
* entity.</p>
*
* @param entity <p>The entity to get max size for</p>
* @return <p>The max size of the entity</p>
*/
public static int getEntityMaxSizeInt(@NotNull Entity entity) {
return (int) Math.ceil((float) getEntityMaxSize(entity));
}
/**
* Gets the max size of an entity along its x and z axis
*
* @param entity <p>The entity to get max size for</p>
* @return <p>The max size of the entity</p>
*/
public static double getEntityMaxSize(@NotNull Entity entity) {
return Math.max(entity.getBoundingBox().getWidthX(), entity.getBoundingBox().getWidthZ());
}
}

View File

@@ -0,0 +1,213 @@
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;
/**
* Helper class for reading gate files
*/
public final class GateReader {
private GateReader() {
}
/**
* Reads a gate file
*
* @param scanner <p>The scanner to read from</p>
* @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 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(@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 (scanner) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (designing) {
//If we have reached the gate's layout/design, read it
columns = readGateDesignLine(line, columns, characterMaterialMap, fileName, design);
if (columns < 0) {
return -1;
}
} else {
if (!line.isEmpty() && !line.startsWith("#")) {
//Read a normal config value
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;
}
}
}
} catch (Exception exception) {
Stargate.logSevere(String.format("Could not load Gate %s - %s", fileName, exception.getMessage()));
return -1;
}
return columns;
}
/**
* Reads one design line of the gate layout file
*
* <p>The max columns value is sent through this method in such a way that when the last gate design line is read,
* the max columns value contains the largest amount of columns (character) found in any of the design's lines.</p>
*
* @param line <p>The line to read</p>
* @param maxColumns <p>The current max columns value of the design</p>
* @param characterMaterialMap <p>The map between characters and the corresponding materials to use</p>
* @param fileName <p>The filename of the loaded gate config file</p>
* @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(@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
if (line.length() > maxColumns) {
maxColumns = line.length();
}
for (Character symbol : line.toCharArray()) {
//Refuse read gate designs with unknown characters
if (symbol.equals('?') || !characterMaterialMap.containsKey(symbol)) {
Stargate.logSevere(String.format("Could not load Gate %s - Unknown symbol '%s' in diagram", fileName,
symbol));
return -1;
}
//Add the read character to the row
row.add(symbol);
}
//Add this row of the gate's design to the two-dimensional design list
design.add(row);
return maxColumns;
}
/**
* Reads one config value from the gate layout file
*
* @param line <p>The line to read</p>
* @param characterMaterialMap <p>The character to material map to store to</p>
* @param config <p>The config value map to store to</p>
* @throws InvalidConfigurationException <p>If an invalid material is encountered</p>
*/
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();
if (key.length() == 1) {
//Read a gate frame material
Character symbol = key.charAt(0);
List<MaterialSpecifier> materials = MaterialHelper.parseTagsAndMaterials(value);
if (!materials.isEmpty()) {
characterMaterialMap.put(symbol, materials);
} else {
throw new InvalidConfigurationException("Invalid material in line: " + line);
}
} else {
//Read a normal config value
config.put(key, value);
}
}
/**
* Reads an integer configuration value
*
* @param config <p>The configuration to read</p>
* @param fileName <p>The filename of the config file</p>
* @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(@NotNull Map<String, String> config, @NotNull String fileName,
@NotNull String key) {
if (config.containsKey(key)) {
try {
return Integer.parseInt(config.get(key));
} catch (NumberFormatException exception) {
Stargate.logWarning(String.format("%s reading %s: %s is not numeric", exception.getClass().getName(),
fileName, key));
}
}
return -1;
}
/**
* Reads a material configuration value
*
* @param config <p>The configuration to read</p>
* @param fileName <p>The filename of the config file</p>
* @param key <p>The config key to read</p>
* @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>
*/
@NotNull
public static List<MaterialSpecifier> readGateConfig(@NotNull Map<String, String> config, @NotNull String fileName,
@NotNull String key, @NotNull Material defaultMaterial) {
if (config.containsKey(key)) {
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 List.of(new BukkitMaterialSpecifier(defaultMaterial));
}
/**
* Generates a matrix containing the gate layout
*
* <p>This basically changes the list of lists into a primitive matrix. Additionally, spaces are added to the end of
* each row which to too short relative to the longest row.</p>
*
* @param design <p>The design of the gate layout</p>
* @param columns <p>The largest amount of columns in the design</p>
* @return <p>A matrix containing the gate's layout</p>
*/
@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);
Character[] result = new Character[columns];
for (int rowIndex = 0; rowIndex < columns; rowIndex++) {
if (rowIndex < row.size()) {
result[rowIndex] = row.get(rowIndex);
} else {
//Add spaces to all lines which are too short
result[rowIndex] = ' ';
}
}
layout[lineIndex] = result;
}
return layout;
}
}

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

@@ -0,0 +1,140 @@
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
*/
public final class MaterialHelper {
private MaterialHelper() {
}
/**
* Checks whether the given material is a dead or alive wall coral
*
* @param material <p>The material to check</p>
* @return <p>True if the material is a wall coral</p>
*/
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) ||
material.equals(Material.DEAD_BUBBLE_CORAL_WALL_FAN) ||
material.equals(Material.DEAD_FIRE_CORAL_WALL_FAN) ||
material.equals(Material.DEAD_HORN_CORAL_WALL_FAN) ||
material.equals(Material.DEAD_TUBE_CORAL_WALL_FAN);
}
/**
* Checks whether the given material is a container
*
* @param material <p>The material to check</p>
* @return <p>True if the material is a container</p>
*/
public static boolean isContainer(@NotNull Material material) {
return Tag.SHULKER_BOXES.isTagged(material) || material == Material.CHEST ||
material == Material.TRAPPED_CHEST || material == Material.ENDER_CHEST;
}
/**
* Checks whether the given material can be used as a button
*
* @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(@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

@@ -0,0 +1,449 @@
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;
/**
* Helper class for deciding which actions a player is allowed to perform
*/
public final class PermissionHelper {
private PermissionHelper() {
}
/**
* Opens a portal if the given player is allowed to, and if the portal is not already open
*
* @param player <p>The player opening the portal</p>
* @param portal <p>The portal to open</p>
*/
public static void openPortal(@NotNull Player player, @NotNull Portal portal) {
Portal destination = portal.getPortalActivator().getDestination();
//For an always open portal, no action is necessary
if (portal.getOptions().isAlwaysOn()) {
return;
}
//Destination is invalid or the same portal. Send an error message
if (destination == null || destination == portal) {
if (!portal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.INVALID_DESTINATION).error(player);
}
return;
}
//Portal is already open
if (portal.isOpen()) {
//Close the portal if this player opened the portal
if (portal.getActivePlayer() == player) {
portal.getPortalOpener().closePortal(false);
}
return;
}
// Check if the player is able to open the portal
if (canNotOpen(player, portal, destination)) {
return;
}
//Open the portal
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
*
* <p>The event is used for other plugins to bypass the permission checks.</p>
*
* @param player <p>The player trying to use the portal</p>
* @param portal <p>The portal the player is trying to use</p>
* @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(@NotNull Player player, @NotNull Portal portal, boolean deny) {
StargateAccessEvent event = new StargateAccessEvent(player, portal, deny);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
return event.getDeny();
}
/**
* Checks whether a given user cannot travel between two portals
*
* @param player <p>The player to check</p>
* @param entrancePortal <p>The portal the user wants to enter</p>
* @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(@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(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(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);
}
/**
* 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 String permission) {
if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
Stargate.debug("hasPerm::Permission(" + player.getName() + ")", permission + " => " +
player.hasPermission(permission));
}
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
*
* <p>This should be run if a player has a parent permission to check for the child permission. It is assumed the
* player has the child permission unless it's explicitly set to false.</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 implicitly or explicitly</p>
*/
public static boolean hasPermissionImplicit(@NotNull Player player, @NotNull String permission) {
boolean debug = Stargate.getStargateConfig().isPermissionDebuggingEnabled();
if (!player.isPermissionSet(permission)) {
if (debug) {
Stargate.debug("hasPermissionImplicit::Permission", permission + " => implicitly true");
}
return true;
}
if (debug) {
Stargate.debug("hasPermissionImplicit::Permission", permission + " => " +
player.hasPermission(permission));
}
return player.hasPermission(permission);
}
/**
* Checks whether a player can access the given world
*
* @param player <p>The player trying to access the world</p>
* @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(@NotNull Player player, @NotNull String world) {
return hasPermission(player, Permission.ACCESS_WORLD, world);
}
/**
* Checks whether a player can access the given network
*
* @param player <p>The player to check</p>
* @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(@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, Permission.CREATE_PERSONAL);
}
/**
* Checks whether a player can access the given bungee server
*
* @param player <p>The player trying to teleport</p>
* @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(@NotNull Player player, @NotNull String server) {
return hasPermission(player, Permission.ACCESS_SERVER, server);
}
/**
* Checks whether the given player can teleport the given stretch for free
*
* @param player <p>The player trying to teleport</p>
* @param src <p>The portal the player is entering</p>
* @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(@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, Permission.FREE_USAGE)) {
return true;
}
//Don't charge for free destinations unless specified in the config
return dest != null && Stargate.getEconomyConfig().freeIfFreeDestination() && dest.getOptions().isFree();
}
/**
* Checks whether the player can see this gate (Hidden property check)
*
* <p>This decides if the player can see the gate on the network selection screen</p>
*
* @param player <p>The player to check</p>
* @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(@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, Permission.SEE_HIDDEN)) {
return true;
}
//The player is the owner of the portal
return portal.isOwner(player);
}
/**
* Checks if the given player is allowed to use the given private portal
*
* @param player <p>The player trying to use the portal</p>
* @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(@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, Permission.USE_PRIVATE);
}
/**
* Checks if the given player has access to the given portal option
*
* @param player <p>The player trying to use the option</p>
* @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(@NotNull Player player, @NotNull PortalOption option) {
return hasPermission(player, option.getPermission());
}
/**
* Checks if the given player is allowed to create gates on the given network
*
* @param player <p>The player trying to create a new gate</p>
* @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(@NotNull Player player, @NotNull String network) {
return hasPermission(player, Permission.CREATE_NETWORK, network);
}
/**
* Checks whether the given player is allowed to create a personal gate
*
* @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(@NotNull Player player) {
return hasPermission(player, Permission.CREATE_PERSONAL);
}
/**
* Checks if the given player can create a portal with the given gate layout
*
* @param player <p>The player trying to create a portal</p>
* @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(@NotNull Player player, @NotNull String gate) {
return hasPermission(player, Permission.CREATE_GATE, gate);
}
/**
* Checks if the given player can destroy the given portal
*
* @param player <p>The player trying to destroy the portal</p>
* @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(@NotNull Player player, @NotNull Portal portal) {
String network = portal.getCleanNetwork();
//Use a special check for bungee portals
if (portal.getOptions().isBungee()) {
return hasPermission(player, Permission.CREATE_BUNGEE);
}
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, Permission.DESTROY_PERSONAL);
}
/**
* Decide of the player can teleport through a portal
*
* @param entrancePortal <p>The portal the player is entering from</p>
* @param destination <p>The destination of the portal the player is inside</p>
* @param player <p>The player wanting to teleport</p>
* @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(@Nullable Portal entrancePortal, @Nullable Portal destination,
@NotNull Player player, @Nullable PlayerMoveEvent event) {
//No portal or not open
if (entrancePortal == null || !entrancePortal.isOpen()) {
return true;
}
//Not open for this player
if (!entrancePortal.getPortalOpener().isOpenFor(player)) {
if (!entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ACCESS_DENIED).error(player);
}
new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
return true;
}
//No destination
boolean isBungee = entrancePortal.getOptions().isBungee();
if (!isBungee && destination == null) {
return true;
}
//Player cannot access portal
if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destination)) {
if (!entrancePortal.getOptions().isQuiet()) {
new SGFormatBuilder(Message.ACCESS_DENIED).error(player);
}
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;
}
//Player cannot pay for teleportation
int cost = EconomyHelper.getUseCost(player, entrancePortal, destination);
if (cost > 0) {
return EconomyHelper.cannotPayTeleportFee(entrancePortal, player, cost);
}
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

@@ -0,0 +1,402 @@
package net.knarcraft.stargate.utility;
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.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;
/**
* Helper class for saving and loading portal save files
*/
public final class PortalFileHelper {
private PortalFileHelper() {
}
/**
* Saves all portals for the given world
*
* @param world <p>The world to save portals for</p>
*/
public static void saveAllPortals(@NotNull World world) {
Stargate.getStargateConfig().addManagedWorld(world.getName());
String saveFileLocation = Stargate.getPortalFolder() + "/" + world.getName() + ".db";
try {
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(saveFileLocation, false));
for (Portal portal : PortalRegistry.getAllPortals()) {
//Skip portals in other worlds
String worldName = portal.getLocation().getWorld().getName();
if (!worldName.equalsIgnoreCase(world.getName())) {
continue;
}
//Save the portal
savePortal(bufferedWriter, portal);
}
bufferedWriter.close();
} catch (Exception exception) {
Stargate.logSevere(String.format("Exception while writing stargates to %s: %s", saveFileLocation, exception));
}
}
/**
* Saves one portal
*
* @param bufferedWriter <p>The buffered writer to write to</p>
* @param portal <p>The portal to save</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private static void savePortal(@NotNull BufferedWriter bufferedWriter, @NotNull Portal portal) throws IOException {
StringBuilder builder = new StringBuilder();
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.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(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
builder.append(portal.getOptions().isFixed() ? portal.getDestinationName() : "").append(':');
builder.append(portal.getNetwork()).append(':');
//Name is saved as a fallback if the UUID is unavailable
builder.append(portal.getOwner().getIdentifier());
//Save all the portal options
savePortalOptions(portal, builder);
bufferedWriter.append(builder.toString());
bufferedWriter.newLine();
}
/**
* Saves all portal options for the given portal
*
* @param portal <p>The portal to save</p>
* @param builder <p>The string builder to append to</p>
*/
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.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.isQuiet()).append(':');
builder.append(options.hasNoSign());
}
/**
* Loads all portals for the given world
*
* @param world <p>The world to load portals for</p>
* @return <p>True if portals could be loaded</p>
*/
public static boolean loadAllPortals(@NotNull World world) {
String location = Stargate.getPortalFolder();
File database = new File(location, world.getName() + ".db");
if (database.exists()) {
return loadPortals(world, database);
} else {
Stargate.logInfo(String.format("{%s} No stargates for world ", world.getName()));
}
return false;
}
/**
* Loads all the given portals
*
* @param world <p>The world to load portals for</p>
* @param database <p>The database file containing the portals</p>
* @return <p>True if the portals were loaded successfully</p>
*/
private static boolean loadPortals(@NotNull World world, @NotNull File database) {
int lineIndex = 0;
try {
Scanner scanner = new Scanner(database);
boolean needsToSaveDatabase = false;
while (scanner.hasNextLine()) {
//Read the line and do whatever needs to be done
needsToSaveDatabase = readPortalLine(scanner, ++lineIndex, world) || needsToSaveDatabase;
}
scanner.close();
//Do necessary tasks after all portals have loaded
Stargate.debug("PortalFileHelper::loadPortals", String.format("Finished loading portals for %s. " +
"Starting post loading tasks", world));
doPostLoadTasks(world, needsToSaveDatabase);
return true;
} catch (Exception exception) {
Stargate.logSevere(String.format("Exception while reading stargates from %s: %d! Message: %s",
database.getName(), lineIndex, exception.getMessage()));
}
return false;
}
/**
* Reads one file line containing information about one portal
*
* @param scanner <p>The scanner to read</p>
* @param lineIndex <p>The index of the read line</p>
* @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(@NotNull Scanner scanner, int lineIndex, @NotNull World world) {
String line = scanner.nextLine().trim();
//Ignore empty and comment lines
if (line.startsWith("#") || line.isEmpty()) {
return false;
}
//Check if the min. required portal data is present
String[] portalData = line.split(":");
if (portalData.length < 8) {
Stargate.logInfo(String.format("Invalid line - %s", lineIndex));
return false;
}
//Load the portal defined in the current line
return loadPortal(portalData, world, lineIndex);
}
/**
* Performs tasks which must be run after portals have loaded
*
* <p>This will open always on portals, print info about loaded stargates and re-draw portal signs for loaded
* portals.</p>
*
* @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(@NotNull World world, boolean needsToSaveDatabase) {
//Open any always-on portals. Do this here as it should be more efficient than in the loop.
PortalUtil.verifyAllPortals();
int portalCount = PortalRegistry.getAllPortals().size();
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.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
Stargate.debug("PortalFileHelper::doPostLoadTasks", String.format("Saving database for world %s", world));
if (needsToSaveDatabase) {
saveAllPortals(world);
}
}
/**
* Loads one portal from a data array
*
* @param portalData <p>The array describing the portal</p>
* @param world <p>The world to create the portal in</p>
* @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(@NotNull String[] portalData, @NotNull World world, int lineIndex) {
//Load min. required portal data
String name = portalData[0];
Block button = (!portalData[2].isEmpty()) ? getBlock(world, portalData[2]) : null;
//Load the portal's location
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]);
if (gate == null) {
//Mark the sign as invalid to reduce some player confusion
markPortalWithInvalidGate(portalLocation, portalData[7], lineIndex);
return false;
}
//Load extra portal data
String destination = (portalData.length > 8) ? portalData[8] : "";
String network = (portalData.length > 9 && !portalData[9].isEmpty()) ? portalData[9] :
Stargate.getDefaultNetwork();
String ownerString = (portalData.length > 10) ? portalData[10] : "";
//Get the owner from the owner string
PortalOwner owner = new PortalOwner(ownerString);
//Create the new portal
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);
PortalUtil.registerPortal(portal);
portal.getPortalOpener().closePortal(true);
return buttonLocationChanged;
}
/**
* Decides the material to use for removing a portal's button/sign
*
* @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>
*/
@NotNull
public static Material decideRemovalMaterial(@NotNull Block block, @NotNull Portal portal) {
//Get the blocks to each side of the location
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)) {
return Material.WATER;
} else {
return Material.AIR;
}
}
/**
* Checks whether the given location is underwater
*
* <p>If the location has a water block, or a block which is waterlogged, it will be considered underwater.</p>
*
* @param location <p>The location to check</p>
* @return <p>True if the location is underwater</p>
*/
private static boolean isUnderwater(@NotNull Location location) {
BlockData blockData = location.getBlock().getBlockData();
return blockData.getMaterial() == Material.WATER ||
(blockData instanceof Waterlogged waterlogged && waterlogged.isWaterlogged());
}
/**
* Updates the button vector for the given portal
*
* <p>As the button vector isn't saved, it is null when the portal is loaded. This method allows it to be
* explicitly set when necessary.</p>
*
* @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(@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);
Block oldButtonLocation = portal.getStructure().getButton();
if (oldButtonLocation != null && !oldButtonLocation.equals(buttonLocation)) {
Stargate.addControlBlockUpdateRequest(new BlockChangeRequest(oldButtonLocation, Material.AIR, null));
portal.getStructure().setButton(buttonLocation);
return true;
}
}
}
return false;
}
/**
* Generates a button for a portal
*
* @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(@NotNull Portal portal, @NotNull BlockFace buttonFacing) {
//Go one block outwards to find the button's location rather than the control block's location
Block button = portal.getLocation().getButtonBlock();
// 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 block specified in the input
*
* @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>
*/
@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

@@ -0,0 +1,504 @@
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.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 org.bukkit.Location;
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;
import java.util.List;
import java.util.Map;
/**
* Keeps track of all loaded portals, and handles portal creation
*/
public final class PortalUtil {
private PortalUtil() {
}
/**
* Gets a copy of all portal networks
*
* @return <p>A copy of all portal networks</p>
*/
@NotNull
public static Map<String, List<String>> getAllPortalNetworks() {
return PortalRegistry.getAllPortalNetworks();
}
/**
* 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>
*/
@Nullable
public static List<String> getNetwork(@NotNull String network) {
return PortalRegistry.getNetwork(network);
}
/**
* Gets all destinations in the network viewable by the given player
*
* @param entrancePortal <p>The portal the user is entering from</p>
* @param player <p>The player who wants to see destinations</p>
* @param network <p>The network to get destinations from</p>
* @return <p>All destinations the player can go to</p>
*/
@NotNull
public static List<String> getDestinations(@NotNull Portal entrancePortal, @Nullable Player player,
@NotNull String network) {
List<String> destinations = new ArrayList<>();
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;
}
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(@NotNull Portal portal) {
PortalRegistry.registerPortal(portal);
}
/**
* Checks if the new portal is a valid bungee portal
*
* @param portalOptions <p>The enabled portal options</p>
* @param player <p>The player trying to create the new portal</p>
* @param destinationName <p>The name of the portal's destination</p>
* @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>
*/
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;
}
/**
* 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>
* @return <p>The matching gate type, or null if no such gate could be found</p>
*/
@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
List<Gate> possibleGates = GateHandler.getGatesByControlBlock(signParent);
Gate gate = null;
for (Gate possibleGate : possibleGates) {
//Get gate controls
BlockVector[] vectors = possibleGate.getLayout().getControls();
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
Block possibleTopLocation = portalLocation.getRelative(controlVector);
if (possibleGate.matches(possibleTopLocation, portalLocation.getFacing(), true)) {
gate = possibleGate;
portalLocation.setTopLeft(possibleTopLocation);
} else {
portalLocation.setButtonBlock(possibleTopLocation);
}
}
//If our gate has been found, look no further
if (gate != null) {
break;
}
}
return gate;
}
/**
* Updates the sign and open state of portals pointing at the newly created portal
*
* @param portal <p>The newly created portal</p>
*/
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()) ||
!new BukkitTagSpecifier(Tag.WALL_SIGNS).asMaterials().contains(origin.getLocation().getSignBlock().getType())) {
continue;
}
//Update sign of fixed gates pointing at this gate
if (origin.getOptions().isFixed()) {
origin.drawSign();
}
//Open any always on portal pointing at this portal
if (origin.getOptions().isAlwaysOn()) {
origin.getPortalOpener().openPortal(true);
}
}
}
/**
* Gets all portal options to be applied to a new portal
*
* @param player <p>The player creating the portal</p>
* @param destinationName <p>The destination of the portal</p>
* @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>
*/
@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 &&
PermissionHelper.canUseOption(player, option));
}
//Can not create a non-fixed always-on portal
if (portalOptions.get(PortalOption.ALWAYS_ON) && destinationName.isEmpty()) {
portalOptions.put(PortalOption.ALWAYS_ON, false);
}
//Show isn't useful if always on is false
if (portalOptions.get(PortalOption.SHOW) && !portalOptions.get(PortalOption.ALWAYS_ON)) {
portalOptions.put(PortalOption.SHOW, false);
}
//Random portals are always on and can't be shown
if (portalOptions.get(PortalOption.RANDOM)) {
portalOptions.put(PortalOption.ALWAYS_ON, true);
portalOptions.put(PortalOption.SHOW, false);
}
//Bungee portals are always on and don't support Random
if (portalOptions.get(PortalOption.BUNGEE)) {
portalOptions.put(PortalOption.ALWAYS_ON, true);
portalOptions.put(PortalOption.RANDOM, false);
}
return portalOptions;
}
/**
* Gets a portal given its name
*
* @param name <p>The name of the portal</p>
* @param network <p>The network the portal is connected to</p>
* @return <p>The portal with the given name or null</p>
*/
@Nullable
public static Portal getByName(@NotNull String name, @NotNull String network) {
return PortalRegistry.getPortalInNetwork(Portal.cleanString(network), Portal.cleanString(name));
}
/**
* Gets a portal given its entrance
*
* @param location <p>The location of the portal's entrance</p>
* @return <p>The portal at the given location</p>
*/
@Nullable
public static Portal getByEntrance(@NotNull Location location) {
if (location.getWorld() == null) {
return null;
}
return PortalRegistry.getPortalFromEntrance(location.getBlock());
}
/**
* Gets a portal given its entrance
*
* @param block <p>The block at the portal's entrance</p>
* @return <p>The portal at the given block's location</p>
*/
@Nullable
public static Portal getByEntrance(@NotNull Block block) {
return PortalRegistry.getPortalFromEntrance(block);
}
/**
* Gets a portal given a location adjacent to its entrance
*
* @param location <p>A location adjacent to the portal's entrance</p>
* @return <p>The portal adjacent to the given location</p>
*/
@Nullable
public static Portal getByAdjacentEntrance(@NotNull Location location) {
return getByAdjacentEntrance(location, 1);
}
/**
* Gets a portal given a location adjacent to its entrance
*
* @param location <p>A location adjacent to the portal's entrance</p>
* @param range <p>The range to scan for portals</p>
* @return <p>The portal adjacent to the given location</p>
*/
@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(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(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 (Block adjacentPosition : adjacentPositions) {
Portal portal = PortalRegistry.getPortalFromEntrance(adjacentPosition);
if (portal != null) {
return portal;
}
}
return null;
}
/**
* Gets a portal given its control block (the block type used for the sign and button)
*
* @param block <p>The portal's control block</p>
* @return <p>The portal with the given control block</p>
*/
@Nullable
public static Portal getByControl(@NotNull Block block) {
return PortalRegistry.getPortalFromControl(block);
}
/**
* Gets a portal given a block
*
* @param block <p>One of the loaded lookup blocks</p>
* @return <p>The portal corresponding to the block</p>
*/
@Nullable
public static Portal getByBlock(@NotNull Block block) {
return PortalRegistry.getPortalFromFrame(block);
}
/**
* Gets a bungee portal given its name
*
* @param name <p>The name of the bungee portal to get</p>
* @return <p>A bungee portal</p>
*/
@Nullable
public static Portal getBungeePortal(@NotNull String name) {
return PortalRegistry.getBungeePortal(name);
}
/**
* Gets all portal options stored in the portal data
*
* @param portalData <p>The string list containing all information about a portal</p>
* @return <p>A map between portal options and booleans</p>
*/
@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();
portalOptions.put(option, portalData.length > saveIndex && Boolean.parseBoolean(portalData[saveIndex]));
}
return portalOptions;
}
/**
* Opens all always-on portals
*
* @return <p>The number of always open portals enabled</p>
*/
public static int openAlwaysOpenPortals() {
int alwaysOpenCount = 0;
for (Portal portal : PortalRegistry.getAllPortals()) {
//Open the gate if it's set as always open or if it's a bungee gate
if (portal.getOptions().isFixed() && (Stargate.getGateConfig().enableBungee() &&
portal.getOptions().isBungee() || portal.getPortalActivator().getDestination() != null &&
portal.getOptions().isAlwaysOn())) {
portal.getPortalOpener().openPortal(true);
alwaysOpenCount++;
}
}
return alwaysOpenCount;
}
/**
* Tries to verify all portals and un-registers non-verifiable portals
*/
public static void verifyAllPortals() {
List<Portal> invalidPortals = new ArrayList<>();
for (Portal portal : PortalRegistry.getAllPortals()) {
//Try and verify the portal. Invalidate it if it cannot be validated
PortalStructure structure = portal.getStructure();
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);
}
}
//Un-register any invalid portals found
for (Portal portal : invalidPortals) {
unregisterInvalidPortal(portal);
}
}
/**
* Un-registers a portal which has failed its integrity tests
*
* @param portal <p>The portal of the star portal</p>
*/
private static void unregisterInvalidPortal(@NotNull Portal portal) {
//Show debug information
for (BlockVector control : portal.getGate().getLayout().getControls()) {
Block block = portal.getLocation().getRelative(control);
//Log control blocks not matching the gate layout
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("Disabled stargate at %s", portal));
}
/**
* Closes all portals
*/
public static void closeAllPortals() {
Stargate.logInfo("Closing all stargates.");
for (Portal portal : PortalRegistry.getAllPortals()) {
if (portal != null) {
portal.getPortalOpener().closePortal(true);
}
}
}
/**
* Removes the special characters |, : and # from a portal name
*
* @param input <p>The name to filter</p>
* @return <p>The filtered name</p>
*/
@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

@@ -0,0 +1,116 @@
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.PortalOwner;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Helps migrate player names to UUID where necessary
*/
public final class UUIDMigrationHelper {
private UUIDMigrationHelper() {
}
private static Map<String, List<Portal>> playerNamesToMigrate;
/**
* Migrates the player's name to a UUID
*
* <p>If any portals are missing a UUID for their owner, and the given player is the owner of those portals, the
* given player's UUID will be used as UUID for the portals' owner.</p>
*
* @param player <p>The player to migrate</p>
*/
public static void migrateUUID(@NotNull OfflinePlayer player) {
Map<String, List<Portal>> playersToMigrate = getPlayersToMigrate();
String playerName = player.getName();
//Nothing to do
if (!playersToMigrate.containsKey(playerName)) {
return;
}
Stargate.debug("UUIDMigrationHelper::migrateUUID", String.format("Migrating name to UUID for player %s",
playerName));
List<Portal> portalsOwned = playersToMigrate.get(playerName);
if (portalsOwned == null) {
return;
}
migratePortalsToUUID(portalsOwned, player.getUniqueId());
//Remove the player to prevent the migration from happening every time the player joins
playersToMigrate.remove(playerName);
}
/**
* Migrates a list of portals to use UUID instead of only player name
*
* @param portals <p>The portals to migrate</p>
* @param uniqueId <p>The unique ID of the portals' owner</p>
*/
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 = PortalUtil.getByName(portalCopy.getCleanName(), portalCopy.getCleanNetwork());
if (portal != null) {
portal.getOwner().setUUID(uniqueId);
worldsToSave.add(portal.getLocation().getWorld());
}
}
//Need to make sure the changes are saved
for (World world : worldsToSave) {
PortalFileHelper.saveAllPortals(world);
}
}
/**
* Gets all player names which need to be migrated to UUIDs
*
* @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) {
return playerNamesToMigrate;
}
playerNamesToMigrate = new HashMap<>();
for (Portal portal : PortalRegistry.getAllPortals()) {
PortalOwner owner = portal.getOwner();
String ownerName = owner.getName();
//If a UUID is missing, add the portal to the list owned by the player
if (owner.getUUID() == null) {
List<Portal> portalList = playerNamesToMigrate.get(ownerName);
if (portalList == null) {
List<Portal> newList = new ArrayList<>();
newList.add(portal);
playerNamesToMigrate.put(ownerName, newList);
} else {
portalList.add(portal);
}
}
}
return playerNamesToMigrate;
}
}

View File

@@ -0,0 +1,51 @@
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
destroyexplosion=gates.integrity.destroyedByExplosion
maxgates=gates.maxGatesEachNetwork
destMemory=gates.cosmetic.rememberDestination
ignoreEntrance=
gates.integrity.ignoreEntrance=
handleVehicles=gates.functionality.handleVehicles
sortLists=gates.cosmetic.sortNetworkDestinations
protectEntrance=gates.integrity.protectEntrance
enableBungee=gates.functionality.enableBungee
verifyPortals=gates.integrity.verifyPortals
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
teleportMessage=
registerMessage=
destroyzMessage=
noownersMessage=
unselectMessage=
collisinMessage=
cantAffordToUse=
cantAffordToNew=
portal-open=
portal-closed=
cost-type=
cost-to-activate=
taxAccount=economy.taxAccount
taxaccount=economy.taxAccount
usevault=

View File

@@ -0,0 +1,230 @@
# Version: ${project.version}
# +--------------▄▄▄-- ‚——. -▄--▄-▄--▄-▄-▄▄▄▄▄---------------------------------------------------+ #
# | █▄▄▀ (“‡‡”) █▄▄▀ █▄▄▀ █ █ Support available at: sgrewritten.org/discord | #
# | █▄▄█ \__/ █ █ █ █ █ █ -. | #
# | —————————————————————————————————————————————————— ((_)) —————————————————— | #
# | - | #
# | .|'''.| |''||''| | '||''|. ..|'''.| | |''||''| '||''''| | #
# | ||.. ' || ||| || || .|' ' ||| || || . | #
# | ''|||. || | || ||''|' || .... | || || ||''| | #
# | . '|| || .''''|. || |. '|. || ; |. || || | #
# | '....|' .||. .|. .||. .||. '|' ''|...'| .|. .||. .||. .||.....| | #
# +----------------------------------------------------------------------------------------------+ #
# | UNIFIED LEGACY BRANCH | #
# +----------------------------------------------+-----------------------------------------------+ #
# | Documentation: sgrewritten.org/legacywiki | Bug Reports: sgrewritten.org/report | #
# +----------------------------------------------+-----------------------------------------------+ #
# +----------------------------------------------------------------------------------------------+ #
# | General Preferences | #
# +----------------------------------------------------------------------------------------------+ #
# What language will stargate use when communicating with your users?
# Supported values: [cs, de, en, es, fr, hu, it, ja, nb, nl, nn, pt, ru, sv, tr, uk, zh]
#
# If SG isn't yet available in your language, please consider adding a translation:
# https://sgrewritten.org/translate
# For more information on language codes, see ISO 639-1: https://git.io/JcwaI
language: en
# Would you like to us to notify admin users when new StarGate updates are available?
adminUpdateAlert: true
# +----------------------------------------------------------------------------------------------+ #
# | Compatibility | #
# +----------------------------------------------------------------------------------------------+ #
dynmap:
# Should StarGate enable integration with Dynmap? This will show StarGates on your map.
enableDynmap: true
# Should StarGate icons be hidden by default? (must users manually enable their checkbox?)
dynmapIconsHiddenByDefault: true
# +----------------------------------------------------------------------------------------------+ #
# | Gate Behaviour | #
# +----------------------------------------------------------------------------------------------+ #
gates:
# What is the maximum number of gates a single network may contain? (0 for unlimited)
maxGatesEachNetwork: 0
# What network will be used when none has been specified? (Max length 12 characters)
#
# Note that this primarily applies to users with the stargate.create.network node.
# Generally, that node is given to staff (we recommend stargate.create.personal for users).
defaultGateNetwork: central
# At what speed should players be sent out of portals?
# When exiting a gate, players retain their original movement velocity, multiplied by this value.
exitVelocity: 0.1
functionality:
# Should an alternative (and less effective) teleportation script be used to teleport vehicles?
# This will fix an incompatibility with CraftBook, but will cause vehicles' data to be wiped on teleportation.
enableCraftBookRemoveOnEjectFix: false
# Are you connected to a BungeeCord-compatible proxy?
# Set this value to true if you intend on building gates with the bUngee flag.
enableBungee: false
# Will vehicles and their passengers be able to travel through StarGate portals?
# [minecarts, boats, & saddled mobs = vehicles | players & mobs = passengers]
handleVehicles: true
# Should vehicles without any passengers be allowed to go through a StarGate?
handleEmptyVehicles: true
# Should StarGate teleport creatures through portals?
# For example, if a player and a mob are in a boat, should the teleportation be allowed?
handleCreatureTransportation: true
# If handleCreatureTransportation is true, must the player be present?
# I.e., can a mob in a vehicle go through a gate on its own?
handleNonPlayerVehicles: true
# Should StarGate also handle creatures attached to a player via a lead?
handleLeashedCreatures: true
integrity:
# Can StarGates be broken via an explosion?
# [tnt, creepers, etc.]
destroyedByExplosion: false
# Will the server re-verify all existing portals on startup?
#
# This checks that the expected gates are both present and using a valid layout.
# Designed to catch .gate file modifications, world changes, and terrain regeneration.
#
# …««»»… If you set this to true, you should also set protectEntrance (below) to true!
# “NOTE” Otherwise, disallowed players, block updates, and snowmen can invalidate your portals!
verifyPortals: false
# Should the portal verification process account for iris (open/closed) materials?
# i.e. will a gate still validate if its portal-open material isn't present?
#
# This is more resource intensive, and should really only be used if verifyPortals is true.
# Or if using an easily destroyable open/closed material.
protectEntrance: false
# How many ticks should go between each Stargate control update? This process updates any signs that have incorrect
# information, and buttons that are missing. While a value of one works fine for small servers with few Stargates,
# it has been known to cause lag and high initial RAM usage for huge servers. A value of 20 is one second, which
# should work no matter how many Stargates the server has.
controlUpdateDelay: 3
# +----------------------------------------------------------------------------------------------+ #
# | Aesthetic Tweaks | #
# +----------------------------------------------------------------------------------------------+ #
cosmetic:
# Will the destination a networked portal last connected to be listed first in its scroll menu?
rememberDestination: false
# For networked gates, are destinations listed alphabetically instead of chronologically?
# (This applies to all non-fixed and non-random gates).
sortNetworkDestinations: false
# What color will StarGate use for the text on gate signs?
# Note that players can override this with DYE and/or GLOW_INK_SAC
mainSignColor: BLACK
# What color will StarGate use to accent the above text?
highlightSignColor: WHITE
# Text and highlight colors can be modified on a per-sign basis (below).
# Format: 'SIGN_TYPE:mainSignColor,highlightSignColor'
perSignColors:
- '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'
- 'PALE_OAK:default,default'
- 'MANGROVE:default,default'
economy:
# When scrolling through a networked portal's destination list, should SG color free gates?
freeGatesColored: false
# If freeGatesColored is true, which color should SG use?
#
# All color names should be a hex RGB color (#ffffff) or follow this format:
# https://hub.spigotmc.org/javadocs/spigot/org/bukkit/ChatColor.html
freeGatesColor: DARK_GREEN
# +----------------------------------------------------------------------------------------------+ #
# | Economy | #
# +----------------------------------------------------------------------------------------------+ #
# Will StarGate interact with your server's economy?
#
# …««»»… For these features, StarGate depends on the Vault plugin.
# “NOTE” https://www.spigotmc.org/resources/vault.34315/
useEconomy: false
# How much will be deducted from players' accounts when creating a new gate?
createCost: 0
# How much will be deducted from players' accounts when destroying an existing gate?
destroyCost: 0
# How much will be deducted from players' accounts when using a stargate to teleport?
# This does not apply to stargates with the Free flag.
useCost: 0
# Will fees collected for usecost be paid to whomever created (owns) the applicable stargate?
# If false, collected funds will be deleted.
toOwner: false
# Will createcost still be charged if the new gate's destination is a gate with the Free flag?
# Note that this only applies to fixed gates.
chargeFreeDestination: true
# Does your server have a tax account (closed economy)?
# If so, please provide the name of your tax account (collected money will be sent to it).
# If not, leave this section blank (collected money will be deleted).
#
# Note that useCost money is excluded from this system when toOwner is true.
taxAccount: ''
# +----------------------------------------------------------------------------------------------+ #
# | Technical | #
# +----------------------------------------------------------------------------------------------+ #
# | [ Storage ] | #
# |__ These settings are provided to customise how SG stores its data. __| #
folders:
# Currently, all valid gates, their details, and their owners, are stored in a flatfile database
# Where should that flatfile be stored?
#
# This option is provided as a patch to allow data to be imported from some older forks.
portalFolder: plugins/Stargate/portals/
# Currently, on startup, a folder is checked for gate layouts stored as .gate files.
# Where is that folder?
#
# This option is provided as a patch to allow data to be imported from some older forks.
gateFolder: plugins/Stargate/gates/
# | [ Debug ] | #
# |__ These settings are provided to help developers diagnose issues with this plugin. __| #
debugging:
# Should SG spam console with A LOT of technical information?
# This includes checks, events, etc.
debug: false
# Should SG spam console with A LOT of permission-based information?
# This visualises how stargate determines what players can do.
#
# Note that (regardless of this setting), permdebug is always hidden when debug is false.
permissionDebug: false
advanced:
# How long should SG wait before returning players to their vehicles after teleportation?
# This is done to prevent possible visual bugs; slower servers need larger values.
# In most cases, a value of 6 should be fine.
waitForPlayerAfterTeleportDelay: 6

View File

@@ -0,0 +1,12 @@
portal-open=END_GATEWAY
portal-closed=AIR
button=BIRCH_BUTTON
toowner=false
X=END_STONE_BRICKS
-=END_STONE_BRICKS
XX
X..X
-..-
X*.X
XX

View File

@@ -0,0 +1,24 @@
#This is the default gate type. You can copy this and make as many .gate files as you need.
#The portal-open block can be most blocks which do not fill the entire block or otherwise prevent the player from
#entering the portal, but NETHER_PORTAL, AIR, WATER, LAVA, KELP_PLANT, OAK_FENCE, IRON_BARS, CHAIN, BAMBOO, SUGAR_CANE,
#COBWEB and VINE gives an impression of which blocks will work.
portal-open=NETHER_PORTAL
#The portal-closed block can be any of the blocks used for portal-open, but also any solid, full-size block such as DIRT.
portal-closed=AIR
#The button can be the following: A chest (CHEST), any type of button (STONE_BUTTON, OAK_BUTTON), any type of shulker
#box (LIME_SHULKER_BOX), or any wall coral (DEAD_TUBE_CORAL_WALL_FAN, TUBE_CORAL_WALL_FAN, DEAD_BRAIN_CORAL_WALL_FAN,
#BRAIN_CORAL_WALL_FAN, etc.)
button=STONE_BUTTON
#Whether payment for entry should go to this gate's owner
toowner=false
#The material to use for the normal frame
X=OBSIDIAN
#The material to use for the sign and button blocks of the frame
-=OBSIDIAN
#The description of the required portal blocks. X = Frame block. - = Sign/button position. . = Empty blocks. * = Exit
XX
X..X
-..-
X*.X
XX

View File

@@ -0,0 +1,13 @@
portal-open=NETHER_PORTAL
portal-closed=AIR
button=OAK_BUTTON
toowner=false
X=OBSIDIAN
-=GLOWSTONE
A=GLOWSTONE
XAX
X...X
-...-
X.*.X
XAX

View File

@@ -0,0 +1,12 @@
portal-open=KELP_PLANT
portal-closed=WATER
button=BRAIN_CORAL_WALL_FAN
toowner=false
X=SEA_LANTERN
-=SEA_LANTERN
XX
X..X
-..-
X*.X
XX

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