diff --git a/HEADER b/HEADER
new file mode 100644
index 0000000..126ab6a
--- /dev/null
+++ b/HEADER
@@ -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
+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 .
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..2e16cba
--- /dev/null
+++ b/Jenkinsfile
@@ -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
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 19ed669..5d4f412 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,64 @@
# Description
-Create gates that allow for instant-teleportation between large distances. Gates can be always-open or triggered; they can share a network or be split into clusters; they can be hidden on a network or accessible to everybody.
-- Player permissions -- let players build their own networks.
-- Vault economy support -- can add costs for create, destroy and use.
-- Multiple custom gate configurations
-- Message customization
+Create gates that allow for instant-teleportation between large distances. Gates can be always-open or triggered; they
+can share a network or be split into clusters; they can be hidden on a network or accessible to everybody.
+
+- **Player permissions** -- let players build their own networks.
+- **Vault economy support** -- can add costs for create, destroy and use.
+- **Ability to create custom gate configurations**. Four different default gate configurations are available.
+- **Message customization**
+- **Multiple built-in languages** (de, en, es, fr, hu, it, ja, nb-no, nl, nn-no, pt-br, ru, zh_cn)
+- **Teleport across worlds or servers** (BungeeCord supported)
+- **Vehicle teleportation** -- teleport minecarts, boats, horses, pigs and striders
+- **Leashed teleportation** -- teleport any creature in a leash with the player
+- **Underwater portals** -- portals can be placed underwater as long as a waterproof button is used
+- **API available** -- using the API, a lot of behavior can be changed
+- **Button customization** -- a large amount of materials usable as buttons (buttons, wall corals, shulkers, chests)
+- **Config commands** -- All main config values can be changed from the commandline
+- **Color customization** -- Stargate signs can be colored in many ways. Colors can be set globally, or on a per sign
+ type basis
+- **RGB and dye support** -- Signs can use RGB colors (using hex codes) as their main and highlighting colors, and can
+ also be dyed on a per-sign basis
## Background
-This was originally TheDgtl's Bukkit port of the Stargate plugin for hMod by Dinnerbone.
-This fork updates it for modern versions of Spigot.
+
+This was originally TheDgtl's Bukkit port of the Stargate plugin for hMod by Dinnerbone. This is a fork
+of [PseudoKnight's fork](https://github.com/PseudoKnight/Stargate-Bukkit). This fork's main purpose is to create a clean
+version of Stargate compliant with Spigot 1.17, even if it means changing the entire project's previous structure.
+
+## License
+
+Stargate is licensed under the GNU Lesser General Public License Version 3.0. This includes every source and resource
+file. See the HEADER file for a more detailed license description.
+
+## Migration
+
+This plugin should be compatible with configurations from the Stargate plugin all the way back. The nethergate.gate
+file, the endgate.gate file and the watergate.gate file will be overwritten if they exist. Take a backup of the files
+and overwrite the files after the first startup if you want to keep your custom gates.
+
+If you have legacy gate files using the old numeric material ids, you need to change them to the new format manually.
+Use F3 + H to see material ids. Use them exactly as written after "minecraft:". The configuration will be updated to a
+more easily readable format, but the old configuration will be saved in case you want to change back right away.
+
+Permissions have had a few changes, so you should check the permissions section for any differences since you set up
+permissions.
+
+Payment to owner using Economy, through Vault, is only possible if the portal owner in the portal database is defined by
+a UUID, and not a username. A player name will be upgraded to a UUID when the player with the given name joins the
+server.
# Permissions
-```
-stargate.use -- Allow use of all gates linking to any world in any network (Override ALL network/world permissions. Set to false to use network/world specific permissions)
- stargate.world -- Allow use of gates linking to any world
- stargate.world.{world} -- Allow use of gates with a destination in {world}. Set to false to disallow use.
- stargate.network -- Allow use of gates on all networks
- stargate.network.{network} -- Allow use of all gates in {network}. Set to false to disallow use.
+```
+stargate.use -- Allow use of all Stargates linking to any world in any network (Override ALL network/world permissions. Set to false to use network/world specific permissions)
+ stargate.world -- Allow use of Stargates linking to any world
+ stargate.world.{world} -- Allow use of Stargates with a destination in {world}. Set to false to disallow use.
+ stargate.network -- Allow use of Stargates on all networks
+ stargate.network.{network} -- Allow use of all Stargates in {network}. Set to false to disallow use.
+ stargate.server -- Allow use of Stargates going to all servers
+ stargate.server.{server} -- Allow usee of all Stargates going to {server}. Set to false to disallow use.
+
stargate.option -- Allow use of all options
stargate.option.hidden -- Allow use of 'H'idden
stargate.option.alwayson -- Allow use of 'A'lways-On
@@ -26,31 +67,38 @@ stargate.option -- Allow use of all options
stargate.option.backwards -- Allow use of 'B'ackwards
stargate.option.show -- Allow use of 'S'how
stargate.option.nonetwork -- Allow use of 'N'oNetwork
- stargate.option.random -- Allow use of 'Random' gates
+ stargate.option.random -- Allow use of 'R'andom stargates
+ stargate.option.silent -- Allow use of S'i'lent stargates
+ stargate.option.nosign -- Allow use of 'E' (No sign)
-stargate.create -- Allow creating gates on any network (Override all create permissions)
- stargate.create.personal -- Allow creating gates on network {playername}
- stargate.create.network -- Allow creating gates on any network
- stargate.create.network.{networkname} -- Allow creating gates on network {networkname}. Set to false to disallow creation on {networkname}
- stargate.create.gate -- Allow creation of any gate layout
- stargate.create.gate.{gatefile} -- Allow creation of only {gatefile} gates
+stargate.create -- Allow creating Stargates on any network (Override all create permissions)
+ stargate.create.personal -- Allow creating Stargates on network {playername}
+ stargate.create.network -- Allow creating Stargates on any network
+ stargate.create.network.{networkname} -- Allow creating Stargates on network {networkname}. Set to false to disallow creation on {networkname}
+ stargate.create.gate -- Allow creation using any gate layout
+ stargate.create.gate.{gatefile} -- Allow creation using only {gatefile} gates
-stargate.destroy -- Allow destruction gates on any network (Orderride all destroy permissions)
- stargate.destroy.personal -- Allow destruction of gates owned by user only
- stargate.destroy.network -- Allow destruction of gates on any network
- stargate.destroy.network.{networkname} -- Allow destruction of gates on network {networkname}. Set to false to disallow destruction of {networkname}
+stargate.destroy -- Allow destruction of Stargates on any network (Orderride all destroy permissions)
+ stargate.destroy.personal -- Allow destruction of Stargates owned by the player only
+ stargate.destroy.network -- Allow destruction of Stargates on any network
+ stargate.destroy.network.{networkname} -- Allow destruction of Stargates on network {networkname}. Set to false to disallow destruction of {networkname}
-stargate.free -- Allow free use/creation/destruction of gates
+stargate.free -- Allow free use/creation/destruction of Stargates
stargate.free.use -- Allow free use of Stargates
stargate.free.create -- Allow free creation of Stargates
stargate.free.destroy -- Allow free destruction of Stargates
-stargate.admin -- Allow all admin features (Hidden/Private only so far)
+stargate.admin -- Allow all admin features (Hidden/Private bypass, BungeeCord, Reload, Config)
stargate.admin.private -- Allow use of Private gates not owned by user
stargate.admin.hidden -- Allow access to Hidden gates not ownerd by user
- stargate.admin.reload -- Allow use of /sg reload
+ stargate.admin.bungee -- Allow the creation of BungeeCord stargates (U option)
+ stargate.admin.reload -- Allow use of the reload command
+ stargate.admin.config -- Allows the player to change config values from the chat
+ stargate.admin.dye -- Allows this player to change the dye of any stargate's sign
```
+
## Default Permissions
+
```
stargate.use -- Everyone
stargate.create -- Op
@@ -61,72 +109,88 @@ stargate.admin -- Op
```
# Instructions
+
## Building a gate:
-This is the default gate configuration. See the Custom Gate Layout section on how to change this.
+
+There are currently three default gate configurations. They all use the same structure as a standard nether portal. One
+gate is using obsidian blocks, one is using end bricks and the last uses sea lanterns. Only the sea lantern one can be
+used underwater. You must put a sign on one of the blocks in the middle of the layout to activate the portal (see next
+section). See the Custom Gate Layout section to learn how to add custom gates.
+
```
OO
- O O - These are Obsidian blocks. You need 10.
- O O - Place a sign on either of these two blocks of Obsidian.
+ O O - These are Obsidian blocks, End bricks or Sea Lanterns. You need 10.
+ O O - Place a sign on either of these two middle blocks.
O O
OO
```
### Sign Layout:
-- Line 1: Gate Name (Max 12 characters)
-- Line 2: Destination Name [Optional] (Max 12 characters, used for fixed-gates only)
-- Line 3: Network name [Optional] (Max 12 characters)
-- Line 4: Options [Optional] :
- - 'A' for always-on fixed gate
- - 'H' for hidden networked gate
- - 'P' for a private gate
- - 'F' for a free gate
- - 'B' is for a backwards facing gate (You will exit the back)
- - 'S' is for showing an always-on gate in the network list
- - 'N' is for hiding the network name
- - 'R' is for random gates. These follow standard permissions of gates, but have a random exit location every time a player enters.
+- Line 1: Gate Name (Max 13 characters)
+- Line 2: Destination Name \[Optional] (Max 13 characters, used for fixed-gates only)
+- Line 3: Network name \[Optional] (Max 13 characters)
+- Line 4: Options \[Optional] :
+ - 'A' for always-on fixed gate
+ - 'H' for hidden networked gate
+ - 'P' for a private gate
+ - 'F' for a free gate
+ - 'B' is for a backwards facing gate (You will exit the back)
+ - 'S' is for showing an always-on gate in the network list
+ - 'N' is for hiding the network name
+ - 'R' is for random gates. These follow standard permissions of gates, but have a random exit location every time a
+ player enters. (Implicitly always on)
+ - 'U' is for a gate connecting to another through bungee (Implicitly always on)
+ - 'I' is for a silent gate, which does not output anything to the chat while teleporting. Increases immersion
+ - 'E' is for a gate without a sign. Only for fixed stargates
-The options are the single letter, not the word. So to make a private hidden gate, your 4th line would be 'PH'.
+The options are the single letter, not the word. So to make a private hidden gate, your 4th line would be 'PH'. The
+&\[0-9a-f] color codes are not counted in the character limit, thus allowing a 13-character name with an additional 2
+characters used for the color code.
#### Gate networks:
- - Gates are all part of a network, by default this is "central".
- - You can specify (and create) your own network on the third line of the sign when making a new gate.
- - Gates on one network will not see gates on the second network, and vice versa.
- - Gates on different worlds, but in the same network, will see eachother.
+
+- Gates are all part of a network, by default this is "central".
+- You can specify (and create) your own network on the third line of the sign when making a new gate.
+- Gates on one network will not see gates on the second network, and vice versa.
+- Gates on different worlds, but in the same network, will see each other.
+- If the gate is a bungee gate, the network name should be the name of the server as displayed when typing /servers
#### Fixed gates:
- - Fixed gates go to only one set destination.
- - Fixed gates can be linked to other fixed gates, or normal gates. A normal gate cannot open a portal to a fixed gate however.
- - To create a fixed gate, specify a destination on the second line of the stargate sign.
- - Set the 4th line of the stargate sign to "A" to enable an always-open fixed gate.
-
+
+- Fixed gates go to only one set destination.
+- Fixed gates can be linked to other fixed gates, or normal gates. A normal gate cannot open a portal to a fixed gate,
+ however.
+- To create a fixed gate, specify a destination on the second line of the stargate sign.
+- Set the 4th line of the stargate sign to "A" to enable an always-open fixed gate.
+- A bungee gate is always automatically a fixed gate
+
#### Hidden Gates:
- - Hidden gates are like normal gates, but only show on the destination list of other gates under certain conditions.
- - A hidden gate is only visible to the creator of the gate, or somebody with the stargate.hidden permission.
- - Set the 4th line of the stargate sign to 'H' to make it a hidden gate.
+
+- Hidden gates are like normal gates, but only show on the destination list of other gates under certain conditions.
+- A hidden gate is only visible to the creator of the gate, or somebody with the stargate.hidden permission.
+- Set the 4th line of the stargate sign to 'H' to make it a hidden gate.
## Using a gate:
- - Right click the sign to choose a destination.
- - Right click the button to open up a portal.
- - Step through.
-
-## Economy Support:
-The latest version of Stargate has support for Vault. Gate creation, destruction and use can all have different costs associated with them. You can also define per-gate layout costs. The default cost is assigned in the config.yml file, while the per-gate costs re defined in the .gate files. To define a certain cost to a gate just add these lines to your .gate file:
-```
-usecost=5
-destroycost=5
-createcost=5
-toowner=true
-```
+
+- Right-click the sign to choose a destination.
+- Right-click the button to open up a portal.
+- Step through.
# Custom Gate Layout
+
You can create as many gate formats as you want, the gate layouts are stored in `plugins/Stargate/gates/`.
-The .gate file must be laid out a specific way, the first lines will be config information,
-and after a blank line you will lay out the gate format. Here is the default nether.gate file:
+The .gate file must be laid out a specific way, the first lines will be config information, and after a blank line you
+will lay out the gate format. Here is the default nethergate.gate file:
+
```
portal-open=NETHER_PORTAL
portal-closed=AIR
button=STONE_BUTTON
+usecost=0
+createcost=0
+destroycost=0
+toowner=false
X=OBSIDIAN
-=OBSIDIAN
@@ -136,430 +200,1004 @@ X..X
X*.X
XX
```
-The keys `portal-open` and `portal-closed` are used to define the material in the gate when it is open or closed.
-The key `button` is used to define the type of button that is generated for this gate. It must be a button material.
+
+The keys `portal-open` and `portal-closed` are used to define the material in the gate when it is open or closed. The
+material for `portal-closed` can be most things, including solid blocks. Some materials may act weirdly though. The
+material for `portal-open` can be any block the player can partially enter, even things like `GLOW_LICHEN`.
+`NETHER_PORTAL`, `END_GATEWAY` and `END_PORTAL` all work.
+
+The `usecost`, `createcost` and `destroycost` keys can be used to set an economy price for gates of this type, different
+from the cost defined in the config. With economy enabled, all gates without these values set will use the values from
+the config. If you want to have different costs for different portals, you must create different gate types and set
+different costs for each one. The `toowner` key can be used to set whether funds withdrawn for using portals with this
+gate type should go to the portal's owner.
+
+The key `button` is used to define the type of button that is generated for this gate. It can be a button (of any type),
+a type of wall coral (dead or alive), a type of shulker box or a chest.
+
`X` and `-` are used to define block types for the layout (Any single-character can be used, such as `#`).
-In the gate format, you can see we use `X` to show where obsidian must be, `-` where the controls (Button/sign) are.
-You will also notice a `*` in the gate layout, this is the "exit point" of the gate, the block at which the player will teleport in front of.
+In the gate format, you can see we use `X` to show where obsidian must be, `-` where the controls (Button/sign) are.
+
+For more complex gate designs, it is possible to add more materials. If you add something like a=GLOWSTONE, `a` can then
+be used in the gate layout, just as `X` is used. See the `squarenetherglowstonegate.gate` file for an example.
+
+You will also notice a `*` in the gate layout, this is the "exit point" of the gate, the block at which the player will
+teleport in front of.
+
+## Buttons
+
+The actual buttons cannot be used underwater, but all the other items in the button list can be.
+
+ The entire list of button types is as follows: (Click to expand)
+
+```
+STONE_BUTTON
+OAK_BUTTON
+SPRUCE_BUTTON
+BIRCH_BUTTON
+JUNGLE_BUTTON
+ACACIA_BUTTON
+DARK_OAK_BUTTON
+CRIMSON_BUTTON
+WARPED_BUTTON
+POLISHED_BLACKSTONE_BUTTON
+
+CHEST
+TRAPPED_CHEST
+ENDER_CHEST
+SHULKER_BOX
+WHITE_SHULKER_BOX
+ORANGE_SHULKER_BOX
+MAGENTA_SHULKER_BOX
+LIGHT_BLUE_SHULKER_BOX
+YELLOW_SHULKER_BOX
+LIME_SHULKER_BOX
+PINK_SHULKER_BOX
+GRAY_SHULKER_BOX
+LIGHT_GRAY_SHULKER_BOX
+CYAN_SHULKER_BOX
+PURPLE_SHULKER_BOX
+BLUE_SHULKER_BOX
+BROWN_SHULKER_BOX
+GREEN_SHULKER_BOX
+RED_SHULKER_BOX
+BLACK_SHULKER_BOX
+TUBE_CORAL_WALL_FAN
+BRAIN_CORAL_WALL_FAN
+BUBBLE_CORAL_WALL_FAN
+FIRE_CORAL_WALL_FAN
+HORN_CORAL_WALL_FAN
+DEAD_TUBE_CORAL_WALL_FAN
+DEAD_BRAIN_CORAL_WALL_FAN
+DEAD_BUBBLE_CORAL_WALL_FAN
+DEAD_FIRE_CORAL_WALL_FAN
+DEAD_HORN_CORAL_WALL_FAN
+```
+
+
+
+## Underwater Portals
+
+There is a default gate type for underwater gates. There are no real restrictions on underwater gate materials, except
+normal buttons cannot be used since they'd fall off. Using wall coral fans work much better, though `CHEST` and
+`SHULKER_BOX` works too.
+
+Using `AIR` for a closed underwater gate looks weird, so `WATER` might be better. If using `AIR` for the closed gate,
+you need to make sure it actually contains air when creating it. For partially submerged portals, like ones used for
+boat teleportation, you need to keep water away from the portal entrance/opening until it's been created.
+
+## Economy Support:
+
+The latest version of Stargate has support for Vault. Gate creation, destruction and use can all have different costs
+associated with them. You can also define per-gate layout costs. The default cost is assigned in the config.yml file,
+while the per-gate costs re defined in the .gate files. To define a certain cost to a gate just add these lines to your
+.gate file:
+
+```
+ createCost: 5 -- Will cost 5 currency to create
+ destroyCost: 5 -- Will clost 5 currency to destroy (negative to get back the spent money)
+ useCost: 5 -- Will cost 5 currency to use the stargate
+ toOwner: true -- Will send any fees to the gate's owner
+```
# Configuration
-```
-default-gate-network - The default gate network
-portal-folder - The folder your portal databases are saved in
-gate-folder - The folder containing your .gate files
-destroyexplosion - Whether to destroy a stargate with explosions, or stop an explosion if it contains a gates controls.
-useeconomy - Whether or not to use Economy
-createcost - The cost to create a stargate
-destroycost - The cost to destroy a stargate (Can be negative for a "refund"
-usecost - The cost to use a stargate
-chargefreedestination - Enable to allow free travel from any gate to a free gate
-freegatesgreen - Enable to make gates that won't cost the player money show up as green
-toowner - Whether the money from gate-use goes to the owner or nobody
-maxgates - If non-zero, will define the maximum amount of gates allowed on any network.
-lang - The language to use (Included languages: en, de)
-destMemory - Whether to set the first destination as the last used destination for all gates
-ignoreEntrance - Set this option to true to not check the entrance of a gate on startup. This is a workaround for snowmen breaking gates.
-handleVehicles - Whether or not to handle vehicles going through gates. Set to false to disallow vehicles (Manned or not) going through gates.
-sortLists - If true, network lists will be sorted alphabetically.
-protectEntrance - If true, will protect from users breaking gate entrance blocks (This is more resource intensive than the usual check, and should only be enabled for servers that use solid open/close blocks)
-signColor: This allows you to specify the color of the gate signs. Valid colors:
-verifyPortals: Whether or not all the non-sign blocks are checked to match the gate layout when an old stargate is loaded at startup.
-debug: Whether to show massive debug output
-permdebug: Whether to show massive permission debug output
+```
+language - The language to use (Included languages: en, de, es, fr, hu, it, ja, nb-no, nl, nn-no, pt-br, ru, zh_cn)
+adminUpdateAlert - Whether to alert admins about an available update when joining the server
+folders:
+ portalFolder - The folder your portal databases are saved in
+ gateFolder - The folder containing your .gate files
+gates:
+ maxGatesEachNetwork - If non-zero, will define the maximum amount of gates allowed on any network.
+ defaultGateNetwork - The default gate network
+ exitVelocity - The velocity to give players exiting stargates, relative to the entry velocity (1 = same as entry velocity)
+ cosmetic:
+ rememberDestination - Whether to set the first destination as the last used destination for all gates
+ sortNetworkDestinations - If true, network lists will be sorted alphabetically.
+ mainSignColor - This allows you to specify the color of the gate signs. Use a color code such as WHITE,BLACK,YELLOW or a hex color code such as '#ed76d9'. You need quotes around hex color codes.
+ highlightSignColor - This allows you to specify the color of the sign markings. Use a color code such as WHITE,BLACK,YELLOW or a hex color code such as '#ed76d9'. You need quotes around hex color codes.
+ perSignColors: - A list of per-sign color specifications. Format: "SIGN_TYPE:mainColor,highlight_color". The SIGN_TYPE is OAK for an oak sign, DARK_OAK for a dark oak sign and so on. The colors can be "default" to use the color specified in "mainSignColor" or "highlightSignColor", "inverted" to use the inverse color of the default color, a normal color such as BLACK,WHITE,YELLOW or a hex color code such as #ed76d9.
+ integrity:
+ destroyedByExplosion - Whether to destroy a stargate with explosions, or stop an explosion if it contains a gates controls.
+ verifyPortals - Whether or not all the non-sign blocks are checked to match the gate layout when an old stargate is loaded at startup.
+ protectEntrance - If true, will protect from users breaking gate entrance blocks (This is more resource intensive than the usual check, and should only be enabled for servers that use solid open/close blocks)
+ functionality:
+ enableBungee - Enable this for BungeeCord support. This allows portals across Bungee servers.
+ handleVehicles - Whether or not to handle vehicles going through gates. Set to false to disallow vehicles (Manned or not) going through gates.
+ handleEmptyVehicles - Whether or not to handle empty vehicles going through gates (chest/hopper/tnt/furnace minecarts included).
+ handleCreatureTransportation - Whether or not to handle players that transport creatures by sending vehicles (minecarts, boats) through gates.
+ handleNonPlayerVehicles - Whether or not to handle vehicles with a passenger which is not a player going through gates (pigs, horses, villagers, creepers, etc.). handleCreatureTransportation must be enabled.
+ handleLeashedCreatures - Whether or not to handle creatures leashed by a player going through gates. Set to false to disallow leashed creatures going through gates.
+ 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
+economy:
+ useEconomy - Whether or not to enable Economy using Vault (must have the Vault plugin)
+ createCost - The cost to create a stargate
+ destroyCost - The cost to destroy a stargate (Can be negative for a "refund"
+ useCost - The cost to use a stargate
+ toOwner - Whether the money from gate-use goes to the owner or nobody
+ chargeFreeDestination - Enable to make players pay for teleportation even if the destination is free
+ freeGatesColored - Enable to make gates that won't cost the player money show up as green
+ freeGatesColor - This allows you to specify the color of the markings and name of free stargates
+debugging:
+ debug - Whether to show massive debug output
+ permissionDebug - Whether to show massive permission debug output
+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.
```
# Message Customization
-It is possible to customize all of the messages Stargate displays, including the [Stargate] prefix. You can find the strings in plugins/Stargate/lang/en.txt.
-If a string is removed, or left blank, it will not be shown when the user does the action associated with it.
-There are three special cases when it comes to messages, these are:
-```
-ecoDeduct=Spent %cost%
-ecoRefund=Refunded %cost%
-ecoObtain=Obtained %cost% from Stargate %portal%
-```
-As you can see, these three strings have %cost% and %portal% variables in them. These variables are fairly self-explanatory.
+It is possible to customize all the messages Stargate displays, including the \[Stargate] prefix. You can find the
+strings in plugins/Stargate/lang/chosenLanguage.txt.
+
+If a string is removed, or left blank, it will default to the default english string. There are some special cases
+regarding messages. When you see %variableName%, you need to keep this part in your string, as it will be replaced with
+relevant values.
The full list of strings is as follows:
+
```
prefix=[Stargate]
teleportMsg=Teleported
destroyMsg=Gate Destroyed
invalidMsg=Invalid Destination
blockMsg=Destination Blocked
-denyMsg=Access Denied
destEmpty=Destination List Empty
+denyMsg=Access Denied
+reloaded=Stargate Reloaded
ecoDeduct=Deducted %cost%
-ecoRefund=Redunded %cost%
+ecoRefund=Refunded %cost%
ecoObtain=Obtained %cost% from Stargate %portal%
ecoInFunds=Insufficient Funds
+ecoLoadError=Vault was loaded, but no economy plugin could be hooked into
+vaultLoadError=Economy is enabled but Vault could not be loaded. Economy disabled
+vaultLoaded=Vault v%version% found
createMsg=Gate Created
createNetDeny=You do not have access to that network
+createGateDeny=You do not have access to that gate layout
createPersonal=Creating gate on personal network
createNameLength=Name too short or too long.
createExists=A gate by that name already exists
createFull=This network is full
createWorldDeny=You do not have access to that world
createConflict=Gate conflicts with existing gate
+
+signRightClick=Right click
+signToUse=to use gate
+signRandom=Random
+signDisconnected=Disconnected
+signInvalidGate=Invalid gate
+
+bungeeDisabled=BungeeCord support is disabled.
+bungeeDeny=You do not have permission to create BungeeCord gates.
+bungeeEmpty=BungeeCord gates require both a destination and network.
+bungeeSign=Teleport to
+
+portalInfoTitle=[STARGATE INFO]
+portalInfoName=Name: %name%
+portalInfoDestination=Destination: %destination%
+portalInfoNetwork=Network: %network%
+portalInfoServer=Server: %server%
```
+
# Changes
-#### [Version 0.8.0.3] PseudoKnight fork
- - Fix economy
- - Add custom buttons
-#### [Version 0.8.0.2] PseudoKnight fork
- - Fix player relative yaw when exiting portal
- - Add color code support in lang files
-#### [Version 0.8.0.1] PseudoKnight fork
- - Fix slab check for portal exits
- - Improve material checks for gate configuration
-#### [Version 0.8.0.0] PseudoKnight fork
- - Update for 1.13/1.14 compatibility. This changes gate layouts to use new material names instead of numeric ids. You need to update your gate layout configs.
- - Adds "verifyPortals" config option, which sets whether an old stargate's blocks are verified when loaded.
- - Adds UUID support. (falls back to player names)
-#### [Version 0.7.9.11] PseudoKnight fork
- - Removed iConomy support. Updated Vault support. Changed setting from "useiconomy" to "useeconomy".
- - Updated to support Metrics for 1.7.10
-#### [Version 0.7.9.10]
- - Fix personal gate permission check for players with mixed-case names
-#### [Version 0.7.9.9]
- - Remove "Permissions" support, we now only support SuperPerms handlers.
-#### [Version 0.7.9.8]
- - Make sure buttons stay where they should
-#### [Version 0.7.9.7]
- - Do the Bungee check after the gate layout check.
-#### [Version 0.7.9.6]
- - Actually remove the player from the BungeeQueue when they connect. Oops :)
- - Implement stargate.server nodes
- - Improve the use of negation. You can now negate networks/worlds/servers while using stargate.use permissions.
-#### [Version 0.7.9.5]
- - Fixed an issue with portal material not showing up (Oh, that code WAS useful)
-#### [Version 0.7.9.4]
- - Fixed an issue where water gates broke, oops
-#### [Version 0.7.9.3]
- - Update BungeeCord integration for b152+
-#### [Version 0.7.9.2]
- - Remove my custom sign class. Stupid Bukkit team.
- - Will work with CB 1.4.5 builds, but now will break randomly due to Bukkit screwup
- - Update MetricsLite to R6
-#### [Version 0.7.9.1]
- - Optimize gate lookup in onPlayerMove
- - Resolve issue where Stargates would teleport players to the nether
-#### [Version 0.7.9.0]
- - Added BungeeCord multi-server support (Requires Stargate-Bungee for BungeeCord)
- - Updated Spanish language file
- - Added basic plugin metrics via http://mcstats.org/
- - Resolve issue where language updating overwrote custom strings
-#### [Version 0.7.8.1]
- - Resolve issue of language file being overwritten as ANSI instead of UTF8
-#### [Version 0.7.8.0]
- - Updated languages to include sign text (Please update any languages you are able!)
- - Resolved NPE due to Bukkit bug with signs
- - Resolved issue regarding new getTargetBlock code throwing an exception
- - Languages now auto-update based on the .JAR version (New entries only, doesn't overwrite customization)
- - New command "/sg about", will list the author of the current language file if available
- - Language now has a fallback to English for missing lines (It's the only language I can personally update on release)
- - Added Spanish (Thanks Manuestaire) and Hungarian (Thanks HPoltergeist)
- - Added portal.setOwner(String) API
-#### [Version 0.7.7.5]
- - Resolve issue of right clicking introduced in 1.3.1/2
-#### [Version 0.7.7.4]
- - Removed try/catch, it was still segfaulting.
- - Built against 1.3.1
-#### [Version 0.7.7.3]
- - Wrap sign changing in try/catch. Stupid Bukkit
-#### [Version 0.7.7.2]
- - Load chunk before trying to draw signs
- - Implement a workaround for BUKKIT-1033
-#### [Version 0.7.7.1]
- - Permission checking for 'R'andom gates.
- - Random now implies AlwaysOn
- - Added all languages to JAR
-#### [Version 0.7.7.0]
- - Added 'R'andom option - This still follows the permission rules defined for normal gate usage
- - Added a bit more debug output
-#### [Version 0.7.6.8]
- - Hopefully fix backwards gate exiting
-#### [Version 0.7.6.7]
- - Reload all gates on world unload, this stops gates with invalid destinations being in memory.
-#### [Version 0.7.6.6]
- - Check move/portal/interact/signchange events for cancellation
-#### [Version 0.7.6.5]
- - Resolve issue with buttons on glass gates falling off
- - /sg reload can now be used ingame (stargate.admin.reload permission)
-#### [Version 0.7.6.4]
- - Move blockBreak to HIGHEST priority, this resolves issues with region protection plugins
-#### [Version 0.7.6.3]
- - Fixed issue with displaying iConomy prices
- - iConomy is now hooked on "sg reload" if not already hooked and enabled
- - iConomy is now unhooked on "sg reload" if hooked and disabled
-#### [Version 0.7.6.2]
- - Button now activates if gate is opened, allowing redstone interaction
- - Fixed issue with sign line lengths. All sign text should now fit with color codes.
-#### [Version 0.7.6.1]
- - Update API for StargateCommand
- - Resolved issue with block data on explosion
- - Added signColor option
- - Added protectEntrance option
-#### [Version 0.7.6]
- - Moved gate opening/closing to a Queue/Runnable system to resolve server lag issues with very large gates
-#### [Version 0.7.5.11]
- - PEX now returns accurate results without requiring use of the bridge.
-#### [Version 0.7.5.10]
- - Added sortLists options
-#### [Version 0.7.5.9]
- - Quick event fix for latest dev builds
- - Fix for sign ClassCastException
-#### [Version 0.7.5.8]
- - Fixed an exploit with pistons to destroy gates
-#### [Version 0.7.5.7]
- - Removed SignPost class
- - Resolved issues with signs in 1.2
-#### [Version 0.7.5.6]
- - Quick update to the custom event code, works with R5+ now.
-#### [Version 0.7.5.5]
- - PEX is built of fail, if we have it, use bridge instead.
-#### [Version 0.7.5.4]
- - Fix issue with private gates for players with long names
-#### [Version 0.7.5.3]
- - Added another check for Perm bridges.
-#### [Version 0.7.5.2]
- - Make sure our timer is stopped on disable
- - Move Event reg before loading gates to stop portal material vanishing
-#### [Version 0.7.5.1]
- - Don't create button on failed creation
-#### [Version 0.7.5.0]
- - Refactored creation code a bit
- - Added StargateCreateEvent, see Stargate-API for usage.
- - Added StargateDestroyEvent, see Stargate-API for usage.
- - Updated Event API to the new standard, please see: http://wiki.bukkit.org/Introduction_to_the_New_Event_System
- - Added handleVehicles option.
- - Added 'N'o Network option (Hides the network from the sign)
-#### [Version 0.7.4.4]
- - Changed the implementation of StargateAccessEvent.
- - Disable Permissions if version is 2.7.2 (Common version used between bridges)
- - Fix long-standing bug with hasPermDeep check. Oops.
-#### [Version 0.7.4.3]
- - Implement StargateAccessEvent, used for bypassing permission checks/denying access to gates.
-#### [Version 0.7.4.2]
- - stargate.create.personal permission now also allows user to use personal gates
-#### [Version 0.7.4.1]
- - Quick API update to add player to the activate event
-#### [Version 0.7.4.0]
- - Fixed issue with non-air closed portal blocks
- - Added StargatePortalEvent/onStargatePortal event
-#### [Version 0.7.3.3]
- - Added "ignoreEntrance" option to not check entrance to gate on integrity check (Workaround for snowmen until event is pulled)
-#### [Version 0.7.3.2]
- - Actually fixed "><" issue with destMemory
-#### [Version 0.7.3.1]
- - Hopefully fixed "><" issue with destMemory
-#### [Version 0.7.3]
- - Lava and water gates no longer destroy on reload
- - "sg reload" now closes gates before reloading
- - Added Vault support
- - Added missing "useiConomy" option in config
-#### [Version 0.7.2.1]
- - Quick fix for an NPE
-#### [Version 0.7.2]
- - Make it so you can still destroy gates in Survival mode
-#### [Version 0.7.1]
- - Added destMemory option
- - Switched to sign.update() as Bukkit implemented my fix
- - Threw in a catch for a null from location for portal events
-#### [Version 0.7.0]
- - Minecraft 1.0.0 support
- - New FileConfiguration implemented
- - Stop gates being destroyed on right-click in Creative mode
- - Fixed signs not updating with a hackish workaround until Bukkit is fixed
-#### [Version 0.6.10]
- - Added Register support as opposed to iConomy
-#### [Version 0.6.9]
- - Added UTF8 support for lang files (With or without BOM)
-#### [Version 0.6.8]
- - Fixed unmanned carts losing velocity through gates
- - /sg reload now properly switches languages
-#### [Version 0.6.7]
- - Added lang option
- - Removed language debug output
- - Added German language (lang=de) -- Thanks EduardBaer
-#### [Version 0.6.6]
- - Added %cost% and %portal% to all eco* messages
- - Fixed an issue when creating a gate on a network you don't have access to
-#### [Version 0.6.5]
- - Moved printed message config to a seperate file
- - Added permdebug option
- - Hopefully fix path issues some people were having
- - Fixed iConomy creation cost
- - Added 'S'how option for Always-On gates
- - Added 'stargate.create.gate' permissions
-#### [Version 0.6.4]
- - Fixed iConomy handling
-#### [Version 0.6.3]
- - Fixed (Not Connected) showing on inter-world gate loading
- - Added the ability to negate Network/World permissions (Use, Create and Destroy)
- - Fixed Lockette compatibility
- - More stringent verification checks
-#### [Version 0.6.2]
- - Fixed an issue with private gates
- - Added default permissions
-#### [Version 0.6.1]
- - Stop destruction of open gates on startup
-#### [Version 0.6.0]
- - Completely re-wrote Permission handling (REREAD/REDO YOUR PERMISSIONS!!!!!!!!)
- - Added custom Stargate events (See Stargate-DHD code for use)
- - Fixed portal event cancellation
- - Umm... Lots of other small things.
-#### [Version 0.5.5]
- - Added 'B'ackwards option
- - Fixed opening of gates with a fixed gate as a destination
- - Added block metadata support to gates
-#### [Version 0.5.1]
- - Take into account world/network restrictions for Vehicles
- - Properly teleport empty vehicles between worlds
- - Properly teleport StoreageMinecarts between worlds
- - Take into account vehicle type when teleporting
-#### [Version 0.5.0]
- - Updated the teleport method
- - Remove always-open gates from lists
- - Hopefully stop Stargate and Nether interference
-#### [Version 0.4.9]
- - Left-click to scroll signs up
- - Show "(Not Connected)" on fixed-gates with a non-existant destination
- - Added "maxgates" option
- - Removed debug message
- - Started work on disabling damage for lava gates, too much work to finish with the current implementation of EntityDamageByBlock
-#### [Version 0.4.8]
- - Added chargefreedestination option
- - Added freegatesgreen option
-#### [Version 0.4.7]
- - Added debug option
- - Fixed gates will now show in the list of gates they link to.
- - iConomy no longer touched if not enabled in config
-#### [Version 0.4.6]
- - Fixed a bug in iConomy handling.
-#### [Version 0.4.5]
- - Owner of gate now isn't charged for use if target is owner
- - Updated for iConomy 5.x
- - Fixed random iConomy bugs
-#### [Version 0.4.4]
- - Added a check for stargate.network.*/stargate.world.* on gate creation
- - Check for stargate.world.*/stargate.network.* on gate entrance
- - Warp player outside of gate on access denied
-#### [Version 0.4.3]
- - Made some errors more user-friendly
- - Properly take into account portal-closed material
-#### [Version 0.4.2]
- - Gates can't be created on existing gate blocks
-#### [Version 0.4.1]
- - Sign option permissions
- - Per-gate iconomy target
- - /sg reload command
- - Other misc fixes
-#### [Version 0.4.0]
- - Carts with no player can now go through gates.
- - You can set gates to send their cost to their owner.
- - Per-gate layout option for "toOwner".
- - Cleaned up the iConomy code a bit, messages should only be shown on actual deduction now.
- - Created separate 'stargate.free.{use/create/destroy}' permissions.
-#### [Version 0.3.5]
- - Added 'stargate.world.*' permissions
- - Added 'stargate.network.*' permissions
- - Added 'networkfilter' config option
- - Added 'worldfilter' config option
-#### [Version 0.3.4]
- - Added 'stargate.free' permission
- - Added iConomy cost into .gate files
-#### [Version 0.3.3]
- - Moved sign update into a schedule event, should fix signs
-#### [Version 0.3.2]
- - Updated to latest RB
- - Implemented proper vehicle handling
- - Added iConomy to vehicle handling
- - Can now set cost to go to creator on use
-#### [Version 0.3.1]
- - Changed version numbering.
- - Changed how plugins are hooked into.
-#### [Version 0.30]
- - Fixed a bug in iConomy checking.
-#### [Version 0.29]
- - Added iConomy support. Currently only works with iConomy 4.4 until Niji fixes 4.5
- - Thanks @Jonbas for the base iConomy implementation
-#### [Version 0.28]
- - Fixed an issue with removing stargates during load
-#### [Version 0.27]
- - Fixed portal count on load
-#### [Version 0.26]
- - Added stargate.create.personal for personal stargate networks
- - Fixed a bug with destroying stargates by removing sign/button
-#### [Version 0.25]
- - Fixed a bug with worlds in subfolders
- - Fixed gates being destroyed with explosions
- - Added stargate.destroy.owner
-#### [Version 0.24]
- - Fixed a loading bug in which invalid gates caused file truncation
-#### [Version 0.23]
- - Added a check to make sure "nethergate.gate" exists, otherwise create it
-#### [Version 0.22]
- - Fixed multi-world stargates causing an NPE
-#### [Version 0.21]
- - Code cleanup
- - Added a few more errors when a gate can't be loaded
- - Hopefully fixed path issue on some Linux installs
-#### [Version 0.20]
- - Fixed the bug SIGN_CHANGE exception when using plugins such as Lockette
-#### [Version 0.19]
- - Set button facing on new gates, fixes weirdass button glitch
- - Beginning of very buggy multi-world support
-#### [Version 0.18]
- - Small permissions handling update.
-#### [Version 0.17]
- - Core GM support removed, depends on FakePermissions if you use GM.
-#### [Version 0.16]
- - Fixed Permissions, will work with GroupManager, Permissions 2.0, or Permissions 2.1
- - Left-clicking to activate a stargate works again
-#### [Version 0.15]
- - Built against b424jnks -- As such nothing lower is supported at the moment.
- - Moved gate destruction code to onBlockBreak since onBlockDamage no longer handles breaking blocks.
- - Removed long constructor.
-#### [Version 0.14]
- - Fixed infinite loop in fixed gates.
- - Fixed gate destination will not open when dialed into.
-#### [Version 0.13]
- - Fixed gates no longer show in destination list.
-#### [Version 0.12]
- - Implemented fixed destination block using * in .gate file. This is the recommended method of doing an exit point for custom gates, as the automatic method doesn't work in a lot of cases.
- - Split networks up in memory, can now use same name in different networks. As a result, fixed gates must now specify a network.
- - Added the ability to have a private gate, which only you can activate. Use the 'P' option to create.
- - Fixed but not AlwaysOn gates now open the destination gate.
- - Fixed gates now show their network. Existing fixed gates are added to the default network (Sorry! It had to be done)
-#### [Version 0.11]
- - Fuuuu- Some code got undid and broke everything. Fixed.
-#### [Version 0.10]
- - Hopefully fixed the "No position found" bug.
- - If dest > origin, any blocks past origin.size will drop you at dest[0]
- - Switched to scheduler instead of our own thread for closing gates and deactivating signs
- - No longer depend on Permissions, use it as an option. isOp() used as defaults.
-#### [Version 0.09]
- - Gates can now be any shape
-#### [Version 0.08]
- - Gates can now consist of any material.
- - You can left or right click the button to open a gate
- - Gates are now initialized on sign placement, not more right clicking!
-#### [Version 0.07]
- - Fixed where the default gate is saved to.
-#### [Version 0.06]
- - Forgot to make gates load from new location, oops
-#### [Version 0.05]
- - Moved Stargate files into the plugins/Stargate/ folder
- - Added migration code so old gates/portals are ported to new folder structure
- - Create default config.yml if it doesn't exist
- - Fixed removing a gate, it is now completely removed
-#### [Version 0.04]
- - Updated to multi-world Bukkit
-#### [Version 0.03]
- - Changed package to net.TheDgtl.*
- - Everything now uses Blox instead of Block objects
- - Started on vehicle code, but it's still buggy
+
+#### \[Version 0.9.4.2] EpicKnarvik97 fork
+
+- Avoids a NullPointerException if Dynmap is present, but isn't properly loaded.
+- Avoids some potential NullPointerExceptions related to Dynmap integration
+- Fixes end portals hijacking BungeeCord teleportation
+- Fixes a problem where a player might not be properly teleported from an end portal Stargate in the end to the
+ over-world.
+
+#### \[Version 0.9.4.1] EpicKnarvik97 fork
+
+- Reverts to Spigot API 1.18
+- Adds Dynmap integration
+
+#### \[Version 0.9.4.0] EpicKnarvik97 fork
+
+- Updates Stargate to 1.19
+
+#### \[Version 0.9.3.7] EpicKnarvik97 fork
+
+- Adds the Japanese language file provided by spigot user furplag
+
+#### \[Version 0.9.3.6] EpicKnarvik97 fork
+
+- Adds the simplified Chinese language file provided by spigot user YKDZ
+
+#### \[Version 0.9.3.5] EpicKnarvik97 fork
+
+- Fixes the wait for player delay being too low by default
+- Performs some minor code optimizations and restructuring
+
+#### \[Version 0.9.3.4] EpicKnarvik97 fork
+
+- Includes passengers of passengers when teleporting entities
+- Fixes a bug which caused Stargate to use more CPU for no reason
+- Teleports boats/minecarts like other vehicles unless *enableCraftBookRemoveOnEjectFix* is enabled
+- Adds the *waitForPlayerAfterTeleportDelay* config option which allows changing the delay between vehicle teleportation
+ and the player being teleported to the vehicle
+- Makes boats keep their wood type even when re-created
+
+#### \[Version 0.9.3.3] EpicKnarvik97 fork
+
+- Prevents Zombified Piglins from randomly spawning at Stargates
+
+#### \[Version 0.9.3.2] EpicKnarvik97 fork
+
+- Adds a config option to set the exit velocity of any players exiting a stargate
+- Adjusts vehicle teleportation a bit to prevent passengers' exit rotation from being wrong
+- Improves the checking for buggy double-clicks on non-button blocks
+
+#### \[Version 0.9.3.1] EpicKnarvik97 fork
+
+- Ignores the type of air when checking if a stargate is valid
+
+#### \[Version 0.9.3.0] EpicKnarvik97 fork
+
+- Adds support for RGB colors (use hex color codes)
+- Adds support for dyed and glowing signs
+- Adds support for specifying sign colors per sign type
+- Adds a tab-completable config sub-command for easily changing per-sign colors
+- Allows a per-sign color to be set as the inverse of the default color of the given type
+
+#### \[Version 0.9.2.5] EpicKnarvik97 fork
+
+- Updates Java version to JDK 17
+- Updates Spigot API version to 1.18
+
+#### \[Version 0.9.2.4] EpicKnarvik97 fork
+
+- Adds update checking, which will display a notice in the console when updates are available
+- Adds an alert about an available update when an admin joins the server
+- Adds the adminUpdateAlert config option to allow the admin notices to be turned off
+
+#### \[Version 0.9.2.3] EpicKnarvik97 fork
+
+- Fixes a typo which caused both colors to change into the highlightSignColor
+
+#### \[Version 0.9.2.2] EpicKnarvik97 fork
+
+- Prevents teleportation of a player holding creatures on a leash when handleLeashedCreatures is disabled, to prevent
+ players accidentally losing the creatures during teleportation
+- Fixes a potential exception when a gate's open-block or closed-block is set to a material which isn't a block
+- Fixes a potential exception when a portal without a sign has an invalid gate type
+- Prevents loading of gate files using non-blocks as part of the border
+- Prevents a player smuggling another player through a restricted stargate by sitting on a creature held in a lead by
+ the first player
+
+#### \[Version 0.9.2.1] EpicKnarvik97 fork
+
+- Makes sure to only reload whatever is necessary when config values are changed using commands, instead of reloading
+ the entire config file every time
+- Protects portals from block placement when protectEntrance is enabled
+
+#### \[Version 0.9.2.0] EpicKnarvik97 fork
+
+- Increases max length of names and networks to 13 characters
+- Excludes color codes from the counted character length to allow a colored, 13-character name
+- Makes portal names and networks case- and color-agnostic to prevent some confusion caused by typos or sloppy
+ configuration
+- Makes the free gate color configurable, and renames freeGatesGreen to freeGatesColored
+
+#### \[Version 0.9.1.2] EpicKnarvik97 fork
+
+- Allows a sneaking player to see information about a silent stargate with no sign
+
+#### \[Version 0.9.1.1] EpicKnarvik97 fork
+
+- Makes sure to translate the `&` character to fix a bug causing portal signs to not be colored on some servers
+
+#### \[Version 0.9.1.0] EpicKnarvik97 fork
+
+- Rewrites config loading as a part of the changes required to implement config commands
+- This update adds commands to change all config values from the chat or the console, complete with tab completion
+- Adds a new permission "stargate.admin.config" which is required to edit config values from the chat
+
+#### \[Version 0.9.0.7] EpicKnarvik97 fork
+
+- Stops registering the sign as a lookup block for stargates without a sign
+- Only removes a stargate's button if it's actually a button-compatible block
+- Only displays portal info if not placing a block
+
+#### \[Version 0.9.0.6] EpicKnarvik97 fork
+
+- Makes containers no longer open when used as buttons
+- Validates and updates stargate buttons when the plugin is loaded or reloaded
+- Adds an option to make a stargate silent (no text in chat when teleporting) for better immersion on RP servers
+- Makes buttons update and/or remove themselves when their location or material changes
+- Adds another default gate to show that it's possible to use any number of materials for a stargate's border
+- Adds an option for stargates without a sign. Right-clicking such a stargate will display gate information
+- Fixes a bug causing signs to be re-drawn after they're broken
+- Makes buttons and signs be replaced by water instead of air when underwater
+- Makes portal info shown when right-clicking a stargate fully customizable
+
+#### \[Version 0.9.0.5] EpicKnarvik97 fork
+
+- Adds an option to stargate functionality to disable all teleportation of creatures
+- Adds an option to stargate functionality to disable all teleportation of empty minecarts
+- Adds an option to stargate functionality to disable teleportation of creatures if no player is present in the vehicle
+- Prevents a player in a vehicle from teleporting without the vehicle if vehicle teleportation is disabled
+- Prevents an infinite number of teleportation messages if vehicle teleportation is detected but denied
+
+#### \[Version 0.9.0.4] EpicKnarvik97 fork
+
+- Adds teleportation of leashed creatures. By default, any creature connected to a player by a lead will be teleported
+ with the player through stargates, even if the player is in a vehicle. This behavior can be disabled in the config
+ file.
+
+#### \[Version 0.9.0.3] EpicKnarvik97 fork
+
+- Adds a missing error message when a player in a vehicle cannot pay the teleportation fee
+- Adds UUID migration to automatically update player names to UUID when possible
+
+#### \[Version 0.9.0.2] EpicKnarvik97 fork
+
+- Fixes a bug causing Stargates using NETHER_PORTAL blocks to generate nether portals in the nether.
+
+#### \[Version 0.9.0.1] EpicKnarvik97 fork
+
+- Adds the highlightSignColor option and renames the signColor option to mainSignColor
+- Fixes some inconsistencies in sign coloring by using the highlight color for all markings
+- Fixes the order in which configs are loaded to prevent an exception
+- Adds migrations for the config change
+
+#### \[Version 0.9.0.0] EpicKnarvik97 fork
+
+- Changes entire path structure to a more modern and maven-compliant one
+- Changes package structure to net.knarcraft.stargate.*
+- Moves language files into the resources folder
+- Fixes some bugs caused by language files not being read as UTF-8
+- Adds JavaDoc to a lot of the code
+- Adds Norwegian translation for both Norwegian languages
+- Adds missing dependency information to plugin.yml
+- Uses text from the language files in more places
+- Changes how backup language works, causing english strings to be shown if not available from the chosen language
+- Removes some pre-UUID code
+- Adds underwater portals
+- Makes it easier to add more default gates
+- Adds a new default gate which can be used underwater
+- Adds more items usable as buttons (corals, chest, shulker-box), which allows underwater portals
+- Splits a lot of the code into smaller objects
+- Moves duplicated code into helper classes
+- Re-implements vehicle teleportation
+- Makes boat teleportation work as expected, including being able to teleport with two passengers. This allows players
+ to use boats to transport creatures through portals and to other areas, or even worlds
+- Makes it possible to teleport a player riding a living entity (a pig, a horse, a donkey, a zombie horse, a skeleton
+ horse or a strider). It does not work for entities the player cannot control, such as llamas.
+- Makes both nether portals and end gateways work properly without causing mayhem
+- Replaces the modX and modZ stuff with yaw calculation to make it easier to understand
+- Comments all the code
+- Extracts portal options and portal-related locations to try and reduce size
+- Rewrites tons of code to make it more readable and manageable
+- Implements proper snowman snow blocking, and removes the "temporary" ignoreEntrances option
+- Adds a default gate using end stone bricks and end gateway for more default diversity
+- Makes portals using end portal blocks work as expected
+- Adds missing permissions to the readme
+- Adds missing permissions to plugin.yml and simplifies permission checks by specifying default values for child
+ permissions
+- Renames stargate.reload to stargate.admin.reload to maintain consistency
+- Marks stargates which cannot be loaded because of the gate layout not having been loaded
+- Uses white for the "-" characters on the side of each stargate name when drawing signs to increase readability
+- Uses white to mark the selected destination when cycling through stargate destinations
+- Uses dark red to mark portals which are inactive (missing destination or invalid gate type)
+- Re-draws signs on startup in case they change
+- Fixes some bugs preventing changing the portal-open block on the fly
+- Adds a translate-able string for when the plugin has been reloaded
+
+#### \[Version 0.8.0.3] PseudoKnight fork
+
+- Fix economy
+- Add custom buttons
+
+#### \[Version 0.8.0.2] PseudoKnight fork
+
+- Fix player relative yaw when exiting portal
+- Add color code support in lang files
+
+#### \[Version 0.8.0.1] PseudoKnight fork
+
+- Fix slab check for portal exits
+- Improve material checks for gate configuration
+
+#### \[Version 0.8.0.0] PseudoKnight fork
+
+- Update for 1.13/1.14 compatibility. This update changes gate layouts to use new material names instead of numeric ids.
+ You need to update your gate layout configs.
+- Adds "verifyPortals" config option, which sets whether an old stargate's blocks are verified when loaded.
+- Adds UUID support. (falls back to player names)
+
+#### \[Version 0.7.9.11] PseudoKnight fork
+
+- Removed iConomy support. Updated Vault support. Changed setting from "useiconomy" to "useeconomy".
+- Updated to support Metrics for 1.7.10
+
+#### \[Version 0.7.9.10]
+
+- Fix personal gate permission check for players with mixed-case names
+
+#### \[Version 0.7.9.9]
+
+- Remove "Permissions" support, we now only support SuperPerms handlers.
+
+#### \[Version 0.7.9.8]
+
+- Make sure buttons stay where they should
+
+#### \[Version 0.7.9.7]
+
+- Do the Bungee check after the gate layout check.
+
+#### \[Version 0.7.9.6]
+
+- Actually remove the player from the BungeeQueue when they connect. Oops :)
+- Implement stargate.server nodes
+- Improve the use of negation. You can now negate networks/worlds/servers while using stargate.use permissions.
+
+#### \[Version 0.7.9.5]
+
+- Fixed an issue with portal material not showing up (Oh, that code WAS useful)
+
+#### \[Version 0.7.9.4]
+
+- Fixed an issue where water gates broke, oops
+
+#### \[Version 0.7.9.3]
+
+- Update BungeeCord integration for b152+
+
+#### \[Version 0.7.9.2]
+
+- Remove my custom sign class. Stupid Bukkit team.
+- Will work with CB 1.4.5 builds, but now will break randomly due to Bukkit screw-up
+- Update MetricsLite to R6
+
+#### \[Version 0.7.9.1]
+
+- Optimize gate lookup in onPlayerMove
+- Resolve issue where Stargates would teleport players to the nether
+
+#### \[Version 0.7.9.0]
+
+- Added BungeeCord multi-server support (Requires Stargate-Bungee for BungeeCord)
+- Updated Spanish language file
+- Added basic plugin metrics via http://mcstats.org/
+- Resolve issue where language updating overwrote custom strings
+
+#### \[Version 0.7.8.1]
+
+- Resolve issue of language file being overwritten as ANSI instead of UTF8
+
+#### \[Version 0.7.8.0]
+
+- Updated languages to include sign text (Please update any languages you are able!)
+- Resolved NPE due to Bukkit bug with signs
+- Resolved issue regarding new getTargetBlock code throwing an exception
+- Languages now auto-update based on the .JAR version (New entries only, doesn't overwrite customization)
+- New command "/sg about", will list the author of the current language file if available
+- Language now has a fallback to English for missing lines (It's the only language I can personally update on release)
+- Added Spanish (Thanks Manuestaire) and Hungarian (Thanks HPoltergeist)
+- Added portal.setOwner(String) API
+
+#### \[Version 0.7.7.5]
+
+- Resolve issue of right-clicking introduced in 1.3.1/2
+
+#### \[Version 0.7.7.4]
+
+- Removed try/catch, it was still segfault-ing.
+- Built against 1.3.1
+
+#### \[Version 0.7.7.3]
+
+- Wrap sign changing in try/catch. Stupid Bukkit
+
+#### \[Version 0.7.7.2]
+
+- Load chunk before trying to draw signs
+- Implement a workaround for BUKKIT-1033
+
+#### \[Version 0.7.7.1]
+
+- Permission checking for 'R'andom gates.
+- Random now implies AlwaysOn
+- Added all languages to JAR
+
+#### \[Version 0.7.7.0]
+
+- Added 'R'andom option - This still follows the permission rules defined for normal gate usage
+- Added a bit more debug output
+
+#### \[Version 0.7.6.8]
+
+- Hopefully fix backwards gate exiting
+
+#### \[Version 0.7.6.7]
+
+- Reload all gates on world unload, this stops gates with invalid destinations being in memory.
+
+#### \[Version 0.7.6.6]
+
+- Check move/portal/interact/sign-change events for cancellation
+
+#### \[Version 0.7.6.5]
+
+- Resolve issue with buttons on glass gates falling off
+- /sg reload can now be used in-game (stargate.admin.reload permission)
+
+#### \[Version 0.7.6.4]
+
+- Move blockBreak to the HIGHEST priority, this resolves issues with region protection plugins
+
+#### \[Version 0.7.6.3]
+
+- Fixed issue with displaying iConomy prices
+- iConomy is now hooked on "sg reload" if not already hooked and enabled
+- iConomy is now unhooked on "sg reload" if hooked and disabled
+
+#### \[Version 0.7.6.2]
+
+- Button now activates if gate is opened, allowing redstone interaction
+- Fixed issue with sign line lengths. All sign text should now fit with color codes.
+
+#### \[Version 0.7.6.1]
+
+- Update API for StargateCommand
+- Resolved issue with block data on explosion
+- Added signColor option
+- Added protectEntrance option
+
+#### \[Version 0.7.6]
+
+- Moved gate opening/closing to a Queue/Runnable system to resolve server lag issues with very large gates
+
+#### \[Version 0.7.5.11]
+
+- PEX now returns accurate results without requiring use of the bridge.
+
+#### \[Version 0.7.5.10]
+
+- Added sortLists options
+
+#### \[Version 0.7.5.9]
+
+- Quick event fix for latest dev builds
+- Fix for sign ClassCastException
+
+#### \[Version 0.7.5.8]
+
+- Fixed an exploit with pistons to destroy gates
+
+#### \[Version 0.7.5.7]
+
+- Removed SignPost class
+- Resolved issues with signs in 1.2
+
+#### \[Version 0.7.5.6]
+
+- Quick update to the custom event code, works with R5+ now.
+
+#### \[Version 0.7.5.5]
+
+- PEX is built of fail, if we have it, use bridge instead.
+
+#### \[Version 0.7.5.4]
+
+- Fix issue with private gates for players with long names
+
+#### \[Version 0.7.5.3]
+
+- Added another check for Perm bridges.
+
+#### \[Version 0.7.5.2]
+
+- Make sure our timer is stopped on disable
+- Move Event reg before loading gates to stop portal material vanishing
+
+#### \[Version 0.7.5.1]
+
+- Don't create button on failed creation
+
+#### \[Version 0.7.5.0]
+
+- Refactored creation code a bit
+- Added StargateCreateEvent, see Stargate-API for usage.
+- Added StargateDestroyEvent, see Stargate-API for usage.
+- Updated Event API to the new standard, please see: http://wiki.bukkit.org/Introduction_to_the_New_Event_System
+- Added handleVehicles option.
+- Added 'N'o Network option (Hides the network from the sign)
+
+#### \[Version 0.7.4.4]
+
+- Changed the implementation of StargateAccessEvent.
+- Disable Permissions if version is 2.7.2 (Common version used between bridges)
+- Fix long-standing bug with hasPermDeep check. Oops.
+
+#### \[Version 0.7.4.3]
+
+- Implement StargateAccessEvent, used for bypassing permission checks/denying gate access.
+
+#### \[Version 0.7.4.2]
+
+- stargate.create.personal permission now also allows user to use personal gates
+
+#### \[Version 0.7.4.1]
+
+- Quick API update to add player to the activate event
+
+#### \[Version 0.7.4.0]
+
+- Fixed issue with non-air closed portal blocks
+- Added StargatePortalEvent/onStargatePortal event
+
+#### \[Version 0.7.3.3]
+
+- Added "ignoreEntrance" option to not check entrance to gate on integrity check (Workaround for snowmen until event is
+ pulled)
+
+#### \[Version 0.7.3.2]
+
+- Actually fixed "><" issue with destMemory
+
+#### \[Version 0.7.3.1]
+
+- Hopefully fixed "><" issue with destMemory
+
+#### \[Version 0.7.3]
+
+- Lava and water gates no longer destroy on reload
+- "sg reload" now closes gates before reloading
+- Added Vault support
+- Added missing "useiConomy" option in config
+
+#### \[Version 0.7.2.1]
+
+- Quick fix for an NPE
+
+#### \[Version 0.7.2]
+
+- Make it so that you can still destroy gates in Survival mode
+
+#### \[Version 0.7.1]
+
+- Added destMemory option
+- Switched to sign.update() as Bukkit implemented my fix
+- Threw in a catch for a null from location for portal events
+
+#### \[Version 0.7.0]
+
+- Minecraft 1.0.0 support
+- New FileConfiguration implemented
+- Stop gates being destroyed on right-click in Creative mode
+- Fixed signs not updating with a hackish workaround until Bukkit is fixed
+
+#### \[Version 0.6.10]
+
+- Added Register support as opposed to iConomy
+
+#### \[Version 0.6.9]
+
+- Added UTF8 support for lang files (With or without BOM)
+
+#### \[Version 0.6.8]
+
+- Fixed unmanned carts losing velocity through gates
+- /sg reload now properly switches languages
+
+#### \[Version 0.6.7]
+
+- Added lang option
+- Removed language debug output
+- Added German language (lang=de) -- Thanks EduardBaer
+
+#### \[Version 0.6.6]
+
+- Added %cost% and %portal% to all eco* messages
+- Fixed an issue when creating a gate on a network you don't have access to
+
+#### \[Version 0.6.5]
+
+- Moved printed message config to a separate file
+- Added permdebug option
+- Hopefully fix path issues some people were having
+- Fixed iConomy creation cost
+- Added 'S'how option for Always-On gates
+- Added 'stargate.create.gate' permissions
+
+#### \[Version 0.6.4]
+
+- Fixed iConomy handling
+
+#### \[Version 0.6.3]
+
+- Fixed (Not Connected) showing on inter-world gate loading
+- Added the ability to negate Network/World permissions (Use, Create and Destroy)
+- Fixed Lockette compatibility
+- More stringent verification checks
+
+#### \[Version 0.6.2]
+
+- Fixed an issue with private gates
+- Added default permissions
+
+#### \[Version 0.6.1]
+
+- Stop destruction of open gates on startup
+
+#### \[Version 0.6.0]
+
+- Completely re-wrote Permission handling (REREAD/REDO YOUR PERMISSIONS!!!!!!!!)
+- Added custom Stargate events (See Stargate-DHD code for use)
+- Fixed portal event cancellation
+- Umm... Lots of other small things.
+
+#### \[Version 0.5.5]
+
+- Added 'B'ackwards option
+- Fixed opening of gates with a fixed gate as a destination
+- Added block metadata support to gates
+
+#### \[Version 0.5.1]
+
+- Take into account world/network restrictions for Vehicles
+- Properly teleport empty vehicles between worlds
+- Properly teleport StoreageMinecarts between worlds
+- Take into account vehicle type when teleporting
+
+#### \[Version 0.5.0]
+
+- Updated the teleport method
+- Remove always-open gates from lists
+- Hopefully stop Stargate and Nether interferenceF
+
+#### \[Version 0.4.9]
+
+- Left-click to scroll signs up
+- Show "(Not Connected)" on fixed-gates with a non-existent destination
+- Added "maxgates" option
+- Removed debug message
+- Started work on disabling damage for lava gates, too much work to finish with the current implementation of
+ EntityDamageByBlock
+
+#### \[Version 0.4.8]
+
+- Added chargefreedestination option
+- Added freegatesgreen option
+
+#### \[Version 0.4.7]
+
+- Added debug option
+- Fixed gates will now show in the list of gates they link to.
+- iConomy no longer touched if not enabled in config
+
+#### \[Version 0.4.6]
+
+- Fixed a bug in iConomy handling.
+
+#### \[Version 0.4.5]
+
+- Owner of gate now isn't charged for use if target is owner
+- Updated for iConomy 5.x
+- Fixed random iConomy bugs
+
+#### \[Version 0.4.4]
+
+- Added a check for stargate.network.*/stargate.world.* on gate creation
+- Check for stargate.world.*/stargate.network.* on gate entrance
+- Warp player outside of gate on access denied
+
+#### \[Version 0.4.3]
+
+- Made some errors more user-friendly
+- Properly take into account portal-closed material
+
+#### \[Version 0.4.2]
+
+- Gates can't be created on existing gate blocks
+
+#### \[Version 0.4.1]
+
+- Sign option permissions
+- Per-gate iconomy target
+- /sg reload command
+- Other misc fixes
+
+#### \[Version 0.4.0]
+
+- Carts with no player can now go through gates.
+- You can set gates to send their cost to their owner.
+- Per-gate layout option for "toOwner".
+- Cleaned up the iConomy code a bit, messages should only be shown on actual deduction now.
+- Created separate 'stargate.free.{use/create/destroy}' permissions.
+
+#### \[Version 0.3.5]
+
+- Added 'stargate.world.*' permissions
+- Added 'stargate.network.*' permissions
+- Added 'networkfilter' config option
+- Added 'worldfilter' config option
+
+#### \[Version 0.3.4]
+
+- Added 'stargate.free' permission
+- Added iConomy cost into .gate files
+
+#### \[Version 0.3.3]
+
+- Moved sign update into a schedule event, should fix signs
+
+#### \[Version 0.3.2]
+
+- Updated to the latest RB
+- Implemented proper vehicle handling
+- Added iConomy to vehicle handling
+- Can now set cost to go to creator on use
+
+#### \[Version 0.3.1]
+
+- Changed version numbering.
+- Changed how plugins are hooked into.
+
+#### \[Version 0.30]
+
+- Fixed a bug in iConomy checking.
+
+#### \[Version 0.29]
+
+- Added iConomy support. It currently only works with iConomy 4.4 until Niji fixes 4.5
+- Thanks, @Jonbas, for the base iConomy implementation
+
+#### \[Version 0.28]
+
+- Fixed an issue with removing stargates during load
+
+#### \[Version 0.27]
+
+- Fixed portal count on load
+
+#### \[Version 0.26]
+
+- Added stargate.create.personal for personal stargate networks
+- Fixed a bug with destroying stargates by removing sign/button
+
+#### \[Version 0.25]
+
+- Fixed a bug with worlds in sub-folders
+- Fixed gates being destroyed with explosions
+- Added stargate.destroy.owner
+
+#### \[Version 0.24]
+
+- Fixed a loading bug in which invalid gates caused file truncation
+
+#### \[Version 0.23]
+
+- Added a check to make sure "nethergate.gate" exists, otherwise create it
+
+#### \[Version 0.22]
+
+- Fixed multi-world stargates causing an NPE
+
+#### \[Version 0.21]
+
+- Code cleanup
+- Added a few more errors when a gate can't be loaded
+- Hopefully fixed path issue on some Linux installs
+
+#### \[Version 0.20]
+
+- Fixed the bug SIGN_CHANGE exception when using plugins such as Lockette
+
+#### \[Version 0.19]
+
+- Set button facing on new gates, fixes weird-ass button glitch
+- Beginning of very buggy multi-world support
+
+#### \[Version 0.18]
+
+- Small permissions handling update.
+
+#### \[Version 0.17]
+
+- Core GM support removed, depends on FakePermissions if you use GM.
+
+#### \[Version 0.16]
+
+- Fixed Permissions, will work with GroupManager, Permissions 2.0, or Permissions 2.1
+- Left-clicking to activate a stargate works again
+
+#### \[Version 0.15]
+
+- Built against b424jnks -- As such nothing lower is supported at the moment.
+- Moved gate destruction code to onBlockBreak since onBlockDamage no longer handles breaking blocks.
+- Removed long constructor.
+
+#### \[Version 0.14]
+
+- Fixed infinite loop in fixed gates.
+- Fixed gate destination will not open when dialed into.
+
+#### \[Version 0.13]
+
+- Fixed gates no longer show in destination list.
+
+#### \[Version 0.12]
+
+- Implemented fixed destination block using * in .gate file. This is the recommended method of doing an exit point for
+ custom gates, as the automatic method doesn't work in a lot of cases.
+- Split networks up in memory, can now use same name in different networks. As a result, fixed gates must now specify a
+ network.
+- Added the ability to have a private gate, which only you can activate. Use the 'P' option to create.
+- Fixed but not AlwaysOn gates now open the destination gate.
+- Fixed gates now show their network. Existing fixed gates are added to the default network (Sorry! It had to be done)
+
+#### \[Version 0.11]
+
+- Fuuuu- Some code got undid and broke everything. Fixed.
+
+#### \[Version 0.10]
+
+- Hopefully fixed the "No position found" bug.
+- If dest > origin, any blocks past origin.size will drop you at dest[0]
+- Switched to scheduler instead of our own thread for closing gates and deactivating signs
+- No longer depend on Permissions, use it as an option. isOp() used as defaults.
+
+#### \[Version 0.09]
+
+- Gates can now be any shape
+
+#### \[Version 0.08]
+
+- Gates can now consist of any material.
+- You can left-click or right-click the button to open a gate
+- Gates are now initialized on sign placement, not more right-clicking!
+
+#### \[Version 0.07]
+
+- Fixed where the default gate is saved to.
+
+#### \[Version 0.06]
+
+- Forgot to make gates load from new location, oops
+
+#### \[Version 0.05]
+
+- Moved Stargate files into the plugins/Stargate/ folder
+- Added migration code so old gates/portals are ported to new folder structure
+- Create default config.yml if it doesn't exist
+- Fixed removing a gate, it is now completely removed
+
+#### \[Version 0.04]
+
+- Updated to multi-world Bukkit
+
+#### \[Version 0.03]
+
+- Changed package to net.TheDgtl.*
+- Everything now uses Blox instead of Block objects
+- Started on vehicle code, but it's still buggy
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 1f0d47c..2e1bead 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,60 +1,154 @@
-
- 4.0.0
- org.TheDgtl
- Stargate
- 0.8.0.3
-
- UTF-8
-
-
-
- spigot-repo
- https://hub.spigotmc.org/nexus/content/groups/public/
-
-
- vault-repo
- https://nexus.hc.to/content/repositories/pub_releases
-
-
-
-
- org.spigotmc
- spigot-api
- 1.16.2-R0.1-SNAPSHOT
-
-
- net.milkbowl.vault
- VaultAPI
- 1.7
-
-
-
- src
-
-
- net/TheDgtl/Stargate/resources
- src/net/TheDgtl/Stargate/resources
-
- *.txt
-
-
-
- src
-
- *.yml
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.6.1
-
- 1.8
- 1.8
-
-
-
-
+
+ 4.0.0
+
+ net.knarcraft
+ Stargate
+ 0.9.4.3-SNAPSHOT
+
+
+
+ GNU Lesser General Public License
+ https://www.gnu.org/licenses/lgpl-3.0.en.html
+
+
+
+
+ UTF-8
+ 16
+
+
+
+
+ knarcraft-repo
+ https://git.knarcraft.net/api/packages/EpicKnarvik97/maven
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/groups/public/
+
+
+ vault-repo
+ https://nexus.hc.to/content/repositories/pub_releases
+
+
+ dynmap
+ https://repo.mikeprimm.com/
+
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
+
+
+
+
+ knarcraft-repo
+ https://git.knarcraft.net/api/packages/EpicKnarvik97/maven
+
+
+ knarcraft-repo
+ https://git.knarcraft.net/api/packages/EpicKnarvik97/maven
+
+
+
+
+
+ org.spigotmc
+ spigot-api
+ 1.19.3-R0.1-SNAPSHOT
+ provided
+
+
+ net.milkbowl.vault
+ VaultAPI
+ 1.7
+ provided
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.9.0
+ test
+
+
+ com.github.seeseemelk
+ MockBukkit-v1.18
+ 2.85.2
+ test
+
+
+ org.jetbrains
+ annotations
+ 23.0.0
+ provided
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ us.dynmap
+ dynmap-api
+ 3.1-beta-2
+ provided
+
+
+ net.knarcraft
+ knarlib
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ ${java.version}
+ ${java.version}
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+ false
+
+
+ net.knarcraft:knarlib
+
+ net/knarcraft/knarlib/**
+
+
+
+
+ *.MF
+ *.yml
+
+
+
+
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
\ No newline at end of file
diff --git a/src/config.yml b/src/config.yml
deleted file mode 100644
index 65f2b73..0000000
--- a/src/config.yml
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/src/main/java/net/knarcraft/stargate/Stargate.java b/src/main/java/net/knarcraft/stargate/Stargate.java
new file mode 100644
index 0000000..2cb40b1
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/Stargate.java
@@ -0,0 +1,437 @@
+package net.knarcraft.stargate;
+
+import net.knarcraft.knarlib.util.UpdateChecker;
+import net.knarcraft.stargate.command.CommandStarGate;
+import net.knarcraft.stargate.command.StarGateTabCompleter;
+import net.knarcraft.stargate.config.EconomyConfig;
+import net.knarcraft.stargate.config.MessageSender;
+import net.knarcraft.stargate.config.StargateConfig;
+import net.knarcraft.stargate.config.StargateGateConfig;
+import net.knarcraft.stargate.container.BlockChangeRequest;
+import net.knarcraft.stargate.container.ChunkUnloadRequest;
+import net.knarcraft.stargate.listener.BlockEventListener;
+import net.knarcraft.stargate.listener.EntityEventListener;
+import net.knarcraft.stargate.listener.EntitySpawnListener;
+import net.knarcraft.stargate.listener.PlayerEventListener;
+import net.knarcraft.stargate.listener.PluginEventListener;
+import net.knarcraft.stargate.listener.PortalEventListener;
+import net.knarcraft.stargate.listener.TeleportEventListener;
+import net.knarcraft.stargate.listener.VehicleEventListener;
+import net.knarcraft.stargate.listener.WorldEventListener;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.PortalRegistry;
+import net.knarcraft.stargate.thread.BlockChangeThread;
+import net.knarcraft.stargate.thread.ChunkUnloadThread;
+import net.knarcraft.stargate.thread.StarGateThread;
+import org.bukkit.Server;
+import org.bukkit.command.PluginCommand;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.plugin.PluginDescriptionFile;
+import org.bukkit.plugin.PluginManager;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.plugin.java.JavaPluginLoader;
+import org.bukkit.scheduler.BukkitScheduler;
+
+import 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
+Copyright (C) 2015-2020 Michael Smith (PseudoKnight)
+Copyright (C) 2021-2022 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 .
+ */
+
+/**
+ * The main class of the Stargate plugin
+ */
+@SuppressWarnings("unused")
+public class Stargate extends JavaPlugin {
+
+ private static final Queue blockChangeRequestQueue = new LinkedList<>();
+ private static final Queue 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 The plugin loader to be used.
+ * @param descriptionFile The description file to be used.
+ * @param dataFolder The data folder to be used.
+ * @param file The file to be used
+ */
+ protected Stargate(JavaPluginLoader loader, PluginDescriptionFile descriptionFile, File dataFolder, File file) {
+ super(loader, descriptionFile, dataFolder, file);
+ }
+
+ /**
+ * Stores information about an available update
+ *
+ * If a non-null version is given, joining admins will be alerted about the new update.
+ *
+ * @param version The version of the new update available
+ */
+ public static void setUpdateAvailable(String version) {
+ updateAvailable = version;
+ }
+
+ /**
+ * Gets information about an available update
+ *
+ * @return The version number if an update is available. Null otherwise
+ */
+ public static String getUpdateAvailable() {
+ return updateAvailable;
+ }
+
+ /**
+ * Gets an instance of this plugin
+ *
+ * @return An instance of this plugin, or null if not instantiated
+ */
+ public static Stargate getInstance() {
+ return stargate;
+ }
+
+ /**
+ * Adds a block change request to the request queue
+ *
+ * @param request The request to add
+ */
+ public static void addBlockChangeRequest(BlockChangeRequest request) {
+ if (request != null) {
+ blockChangeRequestQueue.add(request);
+ }
+ }
+
+ /**
+ * Gets the queue containing block change requests
+ *
+ * @return A block change request queue
+ */
+ public static Queue getBlockChangeRequestQueue() {
+ return blockChangeRequestQueue;
+ }
+
+ /**
+ * Gets the sender for sending messages to players
+ *
+ * @return The sender for sending messages to players
+ */
+ public static MessageSender getMessageSender() {
+ return stargateConfig.getMessageSender();
+ }
+
+ /**
+ * Gets the object containing gate configuration values
+ *
+ * @return The object containing gate configuration values
+ */
+ public static StargateGateConfig getGateConfig() {
+ return stargateConfig.getStargateGateConfig();
+ }
+
+ /**
+ * Gets the version of this plugin
+ *
+ * @return This plugin's version
+ */
+ public static String getPluginVersion() {
+ return pluginVersion;
+ }
+
+ /**
+ * Gets the logger used for logging to the console
+ *
+ * @return The logger
+ */
+ public static Logger getConsoleLogger() {
+ return logger;
+ }
+
+ /**
+ * Gets the max length of portal names and networks
+ *
+ * @return The max portal name/network length
+ */
+ @SuppressWarnings("SameReturnValue")
+ public static int getMaxNameNetworkLength() {
+ return 13;
+ }
+
+ /**
+ * Sends a debug message
+ *
+ * @param route The class name/route where something happened
+ * @param message A message describing what happened
+ */
+ public static void debug(String route, String message) {
+ if (stargateConfig == null || stargateConfig.isDebuggingEnabled()) {
+ logger.info("[Stargate::" + route + "] " + message);
+ } else {
+ logger.log(Level.FINEST, "[Stargate::" + route + "] " + message);
+ }
+ }
+
+ /**
+ * Logs an info message to the console
+ *
+ * @param message The message to log
+ */
+ public static void logInfo(String message) {
+ logger.info(getBackupString("prefix") + message);
+ }
+
+ /**
+ * Logs a severe error message to the console
+ *
+ * @param message The message to log
+ */
+ public static void logSevere(String message) {
+ log(Level.SEVERE, message);
+ }
+
+ /**
+ * Logs a warning message to the console
+ *
+ * @param message The message to log
+ */
+ public static void logWarning(String message) {
+ log(Level.WARNING, message);
+ }
+
+ /**
+ * Logs a message to the console
+ *
+ * @param severity The severity of the event triggering the message
+ * @param message The message to log
+ */
+ private static void log(Level severity, String message) {
+ logger.log(severity, getBackupString("prefix") + message);
+ }
+
+ /**
+ * Gets the folder for saving created portals
+ *
+ * The returned String path is the full path to the folder
+ *
+ * @return The folder for storing the portal database
+ */
+ public static String getPortalFolder() {
+ return stargateConfig.getPortalFolder();
+ }
+
+ /**
+ * Gets the folder storing gate files
+ *
+ * The returned String path is the full path to the folder
+ *
+ * @return The folder storing gate files
+ */
+ public static String getGateFolder() {
+ return stargateConfig.getGateFolder();
+ }
+
+ /**
+ * Gets the default network for gates where a network is not specified
+ *
+ * @return The default network
+ */
+ public static String getDefaultNetwork() {
+ return stargateConfig.getStargateGateConfig().getDefaultPortalNetwork();
+ }
+
+ /**
+ * Gets a translated string given its string key
+ *
+ * The name/key is the string before the equals sign in the language files
+ *
+ * @param name The name/key of the string to get
+ * @return The full translated string
+ */
+ public static String getString(String name) {
+ return stargateConfig.getLanguageLoader().getString(name);
+ }
+
+ /**
+ * Gets a backup string given its string key
+ *
+ * The name/key is the string before the equals sign in the language files
+ *
+ * @param name The name/key of the string to get
+ * @return The full string in the backup language (English)
+ */
+ public static String getBackupString(String name) {
+ return stargateConfig.getLanguageLoader().getBackupString(name);
+ }
+
+ /**
+ * Replaces a variable in a string
+ *
+ * @param input The input containing the variables
+ * @param search The variable to replace
+ * @param value The replacement value
+ * @return The input string with the search replaced with value
+ */
+ public static String replaceVars(String input, String search, String value) {
+ return input.replace(search, value);
+ }
+
+ /**
+ * Gets this plugin's plugin manager
+ *
+ * @return A plugin manager
+ */
+ public static PluginManager getPluginManager() {
+ return pluginManager;
+ }
+
+ /**
+ * Gets the object containing economy config values
+ *
+ * @return The object containing economy config values
+ */
+ public static EconomyConfig getEconomyConfig() {
+ return stargateConfig.getEconomyConfig();
+ }
+
+ @Override
+ public void onDisable() {
+ PortalHandler.closeAllPortals();
+ PortalRegistry.clearPortals();
+ stargateConfig.clearManagedWorlds();
+ getServer().getScheduler().cancelTasks(this);
+ }
+
+ @Override
+ public void onEnable() {
+ PluginDescriptionFile pluginDescriptionFile = this.getDescription();
+ pluginManager = getServer().getPluginManager();
+ FileConfiguration newConfig = this.getConfig();
+ this.saveDefaultConfig();
+ newConfig.options().copyDefaults(true);
+
+ logger = Logger.getLogger("Minecraft");
+ Server server = getServer();
+ stargate = this;
+
+ stargateConfig = new StargateConfig(logger);
+ stargateConfig.finishSetup();
+
+ 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();
+
+ this.registerCommands();
+
+ //Check for any available updates
+ UpdateChecker.checkForUpdate(this, "https://api.spigotmc.org/legacy/update.php?resource=97784",
+ Stargate::getPluginVersion, Stargate::setUpdateAvailable);
+ }
+
+ /**
+ * 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 ChunkUnloadThread(), 0L, 100L);
+ }
+
+ /**
+ * Registers all event listeners
+ */
+ private void registerEventListeners() {
+ pluginManager.registerEvents(new PlayerEventListener(), this);
+ pluginManager.registerEvents(new BlockEventListener(), this);
+
+ pluginManager.registerEvents(new VehicleEventListener(), this);
+ pluginManager.registerEvents(new EntityEventListener(), this);
+ pluginManager.registerEvents(new PortalEventListener(), this);
+ pluginManager.registerEvents(new WorldEventListener(), this);
+ pluginManager.registerEvents(new PluginEventListener(this), this);
+ pluginManager.registerEvents(new TeleportEventListener(), this);
+ pluginManager.registerEvents(new EntitySpawnListener(), this);
+ }
+
+ /**
+ * Registers a command for this plugin
+ */
+ private void registerCommands() {
+ PluginCommand stargateCommand = this.getCommand("stargate");
+ if (stargateCommand != null) {
+ stargateCommand.setExecutor(new CommandStarGate());
+ stargateCommand.setTabCompleter(new StarGateTabCompleter());
+ }
+ }
+
+ /**
+ * Gets the chunk unload queue containing chunks to unload
+ *
+ * @return The chunk unload queue
+ */
+ public static Queue getChunkUnloadQueue() {
+ return chunkUnloadQueue;
+ }
+
+ /**
+ * Adds a new chunk unload request to the chunk unload queue
+ *
+ * @param request The new chunk unload request to add
+ */
+ public static void addChunkUnloadRequest(ChunkUnloadRequest request) {
+ chunkUnloadQueue.removeIf((item) -> item.getChunkToUnload().equals(request.getChunkToUnload()));
+ chunkUnloadQueue.add(request);
+ }
+
+ /**
+ * Gets the stargate configuration
+ *
+ * @return The stargate configuration
+ */
+ public static StargateConfig getStargateConfig() {
+ return stargateConfig;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/command/CommandAbout.java b/src/main/java/net/knarcraft/stargate/command/CommandAbout.java
new file mode 100644
index 0000000..f0acd50
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/command/CommandAbout.java
@@ -0,0 +1,32 @@
+package net.knarcraft.stargate.command;
+
+import net.knarcraft.stargate.Stargate;
+import net.md_5.bungee.api.ChatColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * 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;
+ commandSender.sendMessage(textColor + "Stargate Plugin originally created by " + highlightColor +
+ "Drakia" + textColor + ", and revived by " + highlightColor + "EpicKnarvik97");
+ commandSender.sendMessage(textColor + "Go to " + highlightColor +
+ "https://git.knarcraft.net/EpicKnarvik97/Stargate " + textColor + "for the official repository");
+ String author = Stargate.getStargateConfig().getLanguageLoader().getString("author");
+ if (!author.isEmpty()) {
+ commandSender.sendMessage(textColor + "Language created by " + highlightColor + author);
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/command/CommandConfig.java b/src/main/java/net/knarcraft/stargate/command/CommandConfig.java
new file mode 100644
index 0000000..6374518
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/command/CommandConfig.java
@@ -0,0 +1,429 @@
+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.DynmapManager;
+import net.knarcraft.stargate.config.OptionDataType;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalRegistry;
+import net.knarcraft.stargate.portal.PortalSignDrawer;
+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 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 (!player.hasPermission("stargate.admin.config")) {
+ Stargate.getMessageSender().sendErrorMessage(commandSender, "Permission Denied");
+ 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 The option which should be updated
+ * @param commandSender The command sender that changed the value
+ * @param value The new value of the config option
+ */
+ private void updateConfigValue(ConfigOption selectedOption, CommandSender commandSender, 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 The option which should be updated
+ * @param value The new value of the config option
+ * @param configuration The configuration file to save to
+ */
+ private void updateBooleanConfigValue(ConfigOption selectedOption, String value, 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 The option which should be updated
+ * @param commandSender The command sender that changed the value
+ * @param value The new value of the config option
+ */
+ private void updateStringConfigValue(ConfigOption selectedOption, CommandSender commandSender, 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 The option which should be updated
+ * @param commandSender The command sender that changed the value
+ * @param arguments The arguments for the new config option
+ */
+ private void updateListConfigValue(ConfigOption selectedOption, CommandSender commandSender, String[] arguments) {
+ FileConfiguration configuration = Stargate.getInstance().getConfig();
+
+ if (selectedOption == ConfigOption.PER_SIGN_COLORS) {
+ if (arguments.length < 4) {
+ Stargate.getMessageSender().sendErrorMessage(commandSender, "Usage: /sg config perSignColors " +
+ " ");
+ 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 The command sender that triggered the command
+ * @param arguments The arguments given by the user
+ * @return The per-sign color string to update with, or null if the input was invalid
+ */
+ private String parsePerSignColorInput(CommandSender commandSender, String[] arguments) {
+ //Make sure the sign type is an actual sign
+ if (Material.matchMaterial(arguments[1] + "_SIGN") == null) {
+ Stargate.getMessageSender().sendErrorMessage(commandSender, "The given sign type is invalid");
+ 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 {
+ Stargate.getMessageSender().sendErrorMessage(commandSender, errorMessage[i]);
+ return null;
+ }
+ }
+ colorString += String.join(",", newColors);
+ return colorString;
+ }
+
+ /**
+ * Updates the per-sign colors with the given input
+ *
+ * @param signType The sign type that is updated
+ * @param colorString The new color string to replace any previous value with
+ * @param configuration The file configuration to update with the new per-sign colors
+ */
+ private void updatePerSignColors(String signType, String colorString, FileConfiguration configuration) {
+ List 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 The color chosen by the user
+ * @return True if the given color is valid
+ */
+ private boolean validatePerSignColor(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 The config option that was changed
+ * @param commandSender The command sender that executed the config command
+ */
+ private void saveAndReload(ConfigOption selectedOption, CommandSender commandSender) {
+ //Save the config file and reload if necessary
+ Stargate.getInstance().saveConfig();
+
+ Stargate.getMessageSender().sendSuccessMessage(commandSender, "Config updated");
+
+ //Reload whatever is necessary
+ reloadIfNecessary(commandSender, selectedOption);
+ }
+
+ /**
+ * Registers the chat color if
+ *
+ * @param selectedOption The option to change
+ * @param commandSender The command sender to alert if the color is invalid
+ * @param value The new option value
+ */
+ private boolean registerColor(ConfigOption selectedOption, String value, 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 The value to parse
+ * @return The parsed color or null
+ */
+ private ChatColor parseColor(String value) {
+ try {
+ return ChatColor.of(value.toUpperCase());
+ } catch (IllegalArgumentException | NullPointerException ignored) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets an integer from a string
+ *
+ * @param commandSender The command sender that sent the config command
+ * @param selectedOption The option the command sender is trying to change
+ * @param value The value given
+ * @return An integer, or null if it was invalid
+ */
+ private Integer getInteger(CommandSender commandSender, ConfigOption selectedOption, 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 The command sender that sent the config command
+ * @param selectedOption The option the command sender is trying to change
+ * @param value The value given
+ * @return A double, or null if it was invalid
+ */
+ private Double getDouble(CommandSender commandSender, ConfigOption selectedOption, 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 The command sender initiating the reload
+ * @param configOption The changed config option
+ */
+ private void reloadIfNecessary(CommandSender commandSender, 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 The command sender that sent the command
+ * @param option The config option to print information about
+ */
+ private void printConfigOptionValue(CommandSender sender, 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 The command sender to display the config list to
+ */
+ private void displayConfigValues(CommandSender sender) {
+ sender.sendMessage(ChatColor.GREEN + Stargate.getBackupString("prefix") + ChatColor.GOLD +
+ "Config values:");
+ for (ConfigOption option : ConfigOption.values()) {
+ sender.sendMessage(getOptionDescription(option));
+ }
+ }
+
+ /**
+ * Gets the description of a single config option
+ *
+ * @param option The option to describe
+ * @return A string describing the config option
+ */
+ private String getOptionDescription(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 + ")";
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/command/CommandReload.java b/src/main/java/net/knarcraft/stargate/command/CommandReload.java
new file mode 100644
index 0000000..6d29304
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/command/CommandReload.java
@@ -0,0 +1,28 @@
+package net.knarcraft.stargate.command;
+
+import net.knarcraft.stargate.Stargate;
+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 (!player.hasPermission("stargate.admin.reload")) {
+ Stargate.getMessageSender().sendErrorMessage(commandSender, "Permission Denied");
+ return true;
+ }
+ }
+ Stargate.getStargateConfig().reload(commandSender);
+ return true;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/command/CommandStarGate.java b/src/main/java/net/knarcraft/stargate/command/CommandStarGate.java
new file mode 100644
index 0000000..2185b54
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/command/CommandStarGate.java
@@ -0,0 +1,40 @@
+package net.knarcraft.stargate.command;
+
+import net.knarcraft.stargate.Stargate;
+import net.md_5.bungee.api.ChatColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+
+/**
+ * This command represents any command which starts with stargate
+ *
+ * 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.
+ */
+public class CommandStarGate implements CommandExecutor {
+
+ @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.GOLD + "Stargate version " +
+ ChatColor.GREEN + Stargate.getPluginVersion());
+ return true;
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/command/ConfigTabCompleter.java b/src/main/java/net/knarcraft/stargate/command/ConfigTabCompleter.java
new file mode 100644
index 0000000..ff09aa9
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/command/ConfigTabCompleter.java
@@ -0,0 +1,236 @@
+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 signTypes;
+ private List booleans;
+ private List integers;
+ private List chatColors;
+ private List languages;
+ private List extendedColors;
+ private List doubles;
+
+ @Nullable
+ @Override
+ public List 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 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 The selected option
+ * @param typedText The beginning of the typed text, for filtering matching results
+ * @return Some or all of the valid values for the option
+ */
+ private List getPossibleOptionValues(ConfigOption selectedOption, String typedText) {
+ switch (selectedOption) {
+ case LANGUAGE:
+ //Return available languages
+ return filterMatchingStartsWith(languages, typedText);
+ case GATE_FOLDER:
+ case PORTAL_FOLDER:
+ case DEFAULT_GATE_NETWORK:
+ //Just return the default value as most values should be possible
+ if (typedText.trim().isEmpty()) {
+ return putStringInList((String) selectedOption.getDefaultValue());
+ } else {
+ return new ArrayList<>();
+ }
+ case MAIN_SIGN_COLOR:
+ case HIGHLIGHT_SIGN_COLOR:
+ case 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 The selected option
+ * @param args The arguments given by the user
+ * @return Some or all of the valid values for the option
+ */
+ private List getPossibleStringListOptionValues(ConfigOption selectedOption, String[] args) {
+ if (selectedOption == ConfigOption.PER_SIGN_COLORS) {
+ return getPerSignColorCompletion(args);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the tab completion values for completing the per-sign color text
+ *
+ * @param args The arguments given by the user
+ * @return The options to give the user
+ */
+ private List getPerSignColorCompletion(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<>();
+ }
+
+ /**
+ * Puts a single string value into a string list
+ *
+ * @param value The string to make into a list
+ * @return A list containing the string value
+ */
+ private List putStringInList(String value) {
+ List list = new ArrayList<>();
+ list.add(value);
+ return list;
+ }
+
+ /**
+ * 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 The available chat colors
+ */
+ private List getChatColors() {
+ List 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("de");
+ languages.add("en");
+ languages.add("es");
+ languages.add("fr");
+ languages.add("hu");
+ languages.add("it");
+ languages.add("ja");
+ languages.add("nb-no");
+ languages.add("nl");
+ languages.add("nn-no");
+ languages.add("pt-br");
+ languages.add("ru");
+ languages.add("zh_cn");
+ //TODO: Generate this list dynamically by listing the language files in the jar and adding the user's custom
+ // language files
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/command/StarGateTabCompleter.java b/src/main/java/net/knarcraft/stargate/command/StarGateTabCompleter.java
new file mode 100644
index 0000000..96ecd3d
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/command/StarGateTabCompleter.java
@@ -0,0 +1,57 @@
+package net.knarcraft.stargate.command;
+
+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
+ public @Nullable List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command,
+ @NotNull String s, @NotNull String[] args) {
+ if (args.length == 1) {
+ List commands = getAvailableCommands(commandSender);
+ List 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 The command sender to get available commands for
+ * @return The commands available to the command sender
+ */
+ private List getAvailableCommands(CommandSender commandSender) {
+ List commands = new ArrayList<>();
+ commands.add("about");
+ if (!(commandSender instanceof Player player) || player.hasPermission("stargate.admin.reload")) {
+ commands.add("reload");
+ }
+ if (!(commandSender instanceof Player player) || player.hasPermission("stargate.admin.config")) {
+ commands.add("config");
+ }
+ return commands;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/config/ConfigOption.java b/src/main/java/net/knarcraft/stargate/config/ConfigOption.java
new file mode 100644
index 0000000..e12ea59
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/config/ConfigOption.java
@@ -0,0 +1,290 @@
+package net.knarcraft.stargate.config;
+
+/**
+ * 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'"}),
+
+ /**
+ * 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),
+
+ /**
+ * 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);
+
+ private final String configNode;
+ private final String description;
+ private final Object defaultValue;
+ private final OptionDataType dataType;
+
+ /**
+ * Instantiates a new config option
+ *
+ * @param configNode The full path of this config option's config node
+ * @param description The description of what this config option does
+ * @param defaultValue The default value of this config option
+ */
+ ConfigOption(String configNode, String description, 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 The name of the config option to get
+ * @return The corresponding config option, or null if the name is invalid
+ */
+ public static ConfigOption getByName(String name) {
+ for (ConfigOption option : ConfigOption.values()) {
+ if (option.getName().equalsIgnoreCase(name)) {
+ return option;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the name of this config option
+ *
+ * @return The name of this config option
+ */
+ public 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 The data type used
+ */
+ public OptionDataType getDataType() {
+ return this.dataType;
+ }
+
+ /**
+ * Gets the config node of this config option
+ *
+ * @return This config option's config node
+ */
+ public String getConfigNode() {
+ return this.configNode;
+ }
+
+ /**
+ * Gets the description of what this config option does
+ *
+ * @return The description of this config option
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+ /**
+ * Gets this config option's default value
+ *
+ * @return This config option's default value
+ */
+ public Object getDefaultValue() {
+ return this.defaultValue;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/config/ConfigTag.java b/src/main/java/net/knarcraft/stargate/config/ConfigTag.java
new file mode 100644
index 0000000..024b205
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/config/ConfigTag.java
@@ -0,0 +1,96 @@
+package net.knarcraft.stargate.config;
+
+import java.util.Arrays;
+
+/**
+ * A config tag groups config values by a property
+ */
+public enum ConfigTag {
+
+ COLOR(new ConfigOption[]{ConfigOption.FREE_GATES_COLOR, ConfigOption.MAIN_SIGN_COLOR,
+ ConfigOption.HIGHLIGHT_SIGN_COLOR, ConfigOption.PER_SIGN_COLORS}),
+ FOLDER(new ConfigOption[]{ConfigOption.GATE_FOLDER, ConfigOption.PORTAL_FOLDER}),
+ DYNMAP(new ConfigOption[]{ConfigOption.ENABLE_DYNMAP, ConfigOption.DYNMAP_ICONS_DEFAULT_HIDDEN});
+
+ private final ConfigOption[] taggedOptions;
+
+ /**
+ * Instantiates a new config tag
+ *
+ * @param taggedOptions The config options included in this tag
+ */
+ ConfigTag(ConfigOption[] taggedOptions) {
+ this.taggedOptions = taggedOptions;
+ }
+
+ /**
+ * Checks whether a config tag includes the given config option
+ *
+ * @param option The config option to check
+ * @return True of the config option is tagged
+ */
+ public boolean isTagged(ConfigOption option) {
+ return Arrays.stream(taggedOptions).anyMatch((item) -> item == option);
+ }
+
+ /**
+ * Checks whether a given config option requires a "reload of colors" to take effect
+ *
+ * @param configOption The config option to check
+ * @return True if changing the config option requires a "reload of colors" to take effect
+ */
+ public static boolean requiresColorReload(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 The config option to check
+ * @return True if changing the config option requires a full reload to take effect
+ */
+ public static boolean requiresFullReload(ConfigOption option) {
+ return FOLDER.isTagged(option);
+ }
+
+ /**
+ * Checks whether a given config option requires a re-load of all Dynmap markers
+ *
+ * @param configOption The config option to check
+ * @return True if changing the config option requires a reload of all dynmap markers
+ */
+ public static boolean requiresDynmapReload(ConfigOption configOption) {
+ return DYNMAP.isTagged(configOption);
+ }
+
+ /**
+ * Checks whether a given config option requires a portal reload to take effect
+ *
+ * @param option The config option to check
+ * @return True if changing the config option requires a portal reload to take effect
+ */
+ public static boolean requiresPortalReload(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 The config option to check
+ * @return True if the language loader requires a reload
+ */
+ public static boolean requiresLanguageReload(ConfigOption option) {
+ return option == ConfigOption.LANGUAGE;
+ }
+
+ /**
+ * Checks whether a given config option requires economy to be reloaded
+ *
+ * @param option The config option to check
+ * @return True if economy requires a reload
+ */
+ public static boolean requiresEconomyReload(ConfigOption option) {
+ return option == ConfigOption.USE_ECONOMY;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/config/DynmapManager.java b/src/main/java/net/knarcraft/stargate/config/DynmapManager.java
new file mode 100644
index 0000000..5eaa0c9
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/config/DynmapManager.java
@@ -0,0 +1,134 @@
+package net.knarcraft.stargate.config;
+
+import net.knarcraft.stargate.Stargate;
+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;
+
+/**
+ * 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 A reference
+ */
+ public static void initialize(DynmapAPI dynmapAPI) {
+ if (dynmapAPI == null || 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 The portal to add a marker for
+ */
+ public static void addPortalMarker(Portal portal) {
+ if (markerSet == null || Stargate.getStargateConfig().isDynmapDisabled()) {
+ return;
+ }
+ World world = portal.getWorld();
+ if (portal.getOptions().isHidden() || world == null) {
+ return;
+ }
+
+ Location location = portal.getBlockAt(portal.getGate().getLayout().getExit());
+ Marker marker = markerSet.createMarker(getPortalMarkerId(portal), 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""",
+ getPortalMarkerId(portal), 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("Name: %s%s: %sDestination: " +
+ "%sOwner: %s ", 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 The portal to remove the marker for
+ */
+ public static void removePortalMarker(Portal portal) {
+ if (markerSet == null || Stargate.getStargateConfig().isDynmapDisabled()) {
+ return;
+ }
+ Marker marker = markerSet.findMarker(getPortalMarkerId(portal));
+ if (marker != null) {
+ marker.deleteMarker();
+ }
+ }
+
+ /**
+ * Gets the id used for the given portal's marker
+ *
+ * @param portal The portal to get a marker id for
+ * @return
+ */
+ private static String getPortalMarkerId(Portal portal) {
+ return portal.getNetwork() + "-:-" + portal.getName();
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/config/EconomyConfig.java b/src/main/java/net/knarcraft/stargate/config/EconomyConfig.java
new file mode 100644
index 0000000..1b8400c
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/config/EconomyConfig.java
@@ -0,0 +1,239 @@
+package net.knarcraft.stargate.config;
+
+import net.knarcraft.stargate.Stargate;
+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 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 configOptions;
+
+ /**
+ * Instantiates a new economy config
+ *
+ * @param configOptions The loaded config options to read
+ */
+ public EconomyConfig(Map 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 The gate use cost
+ */
+ public int getDefaultUseCost() {
+ return (Integer) configOptions.get(ConfigOption.USE_COST);
+ }
+
+ /**
+ * Gets whether economy is enabled
+ *
+ * @return Whether economy is enabled
+ */
+ public boolean isEconomyEnabled() {
+ return (boolean) configOptions.get(ConfigOption.USE_ECONOMY);
+ }
+
+ /**
+ * Gets the economy object to use for transactions
+ *
+ * @return An economy object, or null if economy is disabled or not initialized
+ */
+ public Economy getEconomy() {
+ return economy;
+ }
+
+ /**
+ * Gets an instance of the Vault plugin
+ *
+ * @return An instance of the Vault plugin, or null if Vault is not loaded
+ */
+ 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 Whether free portals should be colored
+ */
+ public boolean drawFreePortalsColored() {
+ return (boolean) configOptions.get(ConfigOption.FREE_GATES_COLORED);
+ }
+
+ /**
+ * Whether a gate whose destination is a free gate is still charged
+ *
+ * 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.
+ *
+ * @return Whether to charge for free destinations
+ */
+ 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 Whether to send payments to the portal owner
+ */
+ public boolean sendPaymentToOwner() {
+ return (boolean) configOptions.get(ConfigOption.TO_OWNER);
+ }
+
+ /**
+ * Gets the cost of creating a gate without a specified cost
+ *
+ * @return The gate creation cost
+ */
+ public int getDefaultCreateCost() {
+ return (Integer) configOptions.get(ConfigOption.CREATE_COST);
+ }
+
+ /**
+ * Gets the cost of destroying a gate without a specified cost
+ *
+ * @return The gate destruction cost
+ */
+ public int getDefaultDestroyCost() {
+ return (Integer) configOptions.get(ConfigOption.DESTROY_COST);
+ }
+
+ /**
+ * Checks whether the given player can afford the given fee
+ *
+ * @param player The player to check
+ * @param cost The fee to pay
+ * @return True if the player can afford to pay the fee
+ */
+ 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 The amount to display
+ * @return A formatted text string describing the amount
+ */
+ 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 The plugin manager to get plugins from
+ * @return True if economy was enabled
+ */
+ public boolean setupEconomy(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 economyProvider = servicesManager.getRegistration(Economy.class);
+ if (economyProvider != null) {
+ economy = economyProvider.getProvider();
+ this.vault = vault;
+ return true;
+ } else {
+ Stargate.logInfo(Stargate.getString("ecoLoadError"));
+ }
+ } else {
+ Stargate.logInfo(Stargate.getString("vaultLoadError"));
+ }
+ configOptions.put(ConfigOption.USE_ECONOMY, false);
+ return false;
+ }
+
+ /**
+ * Gets whether to use economy
+ *
+ * @return True if the user has turned on economy and economy is available
+ */
+ public boolean useEconomy() {
+ return isEconomyEnabled() && economy != null;
+ }
+
+ /**
+ * Gets the cost of creating the given gate
+ *
+ * @param player The player creating the gate
+ * @param gate The gate type used
+ * @return The cost of creating the gate
+ */
+ public int getCreateCost(Player player, Gate gate) {
+ if (isFree(player, "create")) {
+ return 0;
+ } else {
+ return gate.getCreateCost();
+ }
+ }
+
+ /**
+ * Gets the cost of destroying the given gate
+ *
+ * @param player The player creating the gate
+ * @param gate The gate type used
+ * @return The cost of destroying the gate
+ */
+ public int getDestroyCost(Player player, Gate gate) {
+ if (isFree(player, "destroy")) {
+ return 0;
+ } else {
+ return gate.getDestroyCost();
+ }
+ }
+
+ /**
+ * Determines if a player can do a gate action for free
+ *
+ * @param player The player to check
+ * @param permissionNode The free.permissionNode necessary to allow free gate {action}
+ * @return
+ */
+ private boolean isFree(Player player, String permissionNode) {
+ return !useEconomy() || PermissionHelper.hasPermission(player, "stargate.free." + permissionNode);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/config/LanguageLoader.java b/src/main/java/net/knarcraft/stargate/config/LanguageLoader.java
new file mode 100644
index 0000000..0383794
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/config/LanguageLoader.java
@@ -0,0 +1,257 @@
+package net.knarcraft.stargate.config;
+
+import net.knarcraft.knarlib.property.ColorConversion;
+import net.knarcraft.knarlib.util.FileHelper;
+import net.knarcraft.stargate.Stargate;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 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 loadedBackupStrings;
+ private String chosenLanguage;
+ private Map loadedStringTranslations;
+
+ /**
+ * Instantiates a new language loader
+ *
+ * This will only load the backup language. Set the chosen language and reload afterwards.
+ *
+ * @param languageFolder The folder containing the language files
+ */
+ public LanguageLoader(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.getConsoleLogger().severe("[stargate] 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 name/key
+ *
+ * @param name The name/key of the string to display
+ * @return The string in the user's preferred language
+ */
+ public String getString(String name) {
+ String value = null;
+ if (loadedStringTranslations != null) {
+ value = loadedStringTranslations.get(name);
+ }
+ if (value == null) {
+ value = getBackupString(name);
+ }
+ return value;
+ }
+
+ /**
+ * Gets the string to display given its name/key
+ *
+ * @param name The name/key of the string to display
+ * @return The string in the backup language (English)
+ */
+ public String getBackupString(String name) {
+ String value = null;
+ if (loadedBackupStrings != null) {
+ value = loadedBackupStrings.get(name);
+ }
+ if (value == null) {
+ return "";
+ }
+ return value;
+ }
+
+ /**
+ * Sets the chosen plugin language
+ *
+ * @param chosenLanguage The new plugin language
+ */
+ public void setChosenLanguage(String chosenLanguage) {
+ this.chosenLanguage = chosenLanguage;
+ }
+
+ /**
+ * Updates files in the plugin directory with contents from the compiled .jar
+ *
+ * @param language The language to update
+ */
+ private void updateLanguage(String language) {
+ Map currentLanguageValues = load(language);
+
+ InputStream inputStream = getClass().getResourceAsStream("/lang/" + language + ".txt");
+ if (inputStream == null) {
+ Stargate.logInfo(String.format("The language %s is not available. Falling back to english, You can add a " +
+ "custom language by creating a new text file in the lang directory.", language));
+ Stargate.debug("LanguageLoader::updateLanguage", String.format("Unable to load /lang/%s.txt", language));
+ return;
+ }
+
+ try {
+ readChangedLanguageStrings(inputStream, language, currentLanguageValues);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Reads language strings
+ *
+ * @param inputStream The input stream to read from
+ * @param language The selected language
+ * @param currentLanguageValues The current values of the loaded/processed language
+ * @throws IOException if unable to read a language file
+ */
+ private void readChangedLanguageStrings(InputStream inputStream, String language, Map currentLanguageValues) throws IOException {
+ //Get language values
+ BufferedReader bufferedReader = FileHelper.getBufferedReaderFromInputStream(inputStream);
+ Map internalLanguageValues = 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 newLanguageValues = new HashMap<>();
+ boolean updateNecessary = false;
+ for (String key : internalLanguageValues.keySet()) {
+ if (currentLanguageValues.get(key) == null) {
+ newLanguageValues.put(key, internalLanguageValues.get(key));
+ //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 The language to update
+ * @param languageStrings The updated language strings
+ * @param customLanguageStrings Any custom language strings not recognized
+ * @throws IOException If unable to write to the language file
+ */
+ private void updateLanguageFile(String language, Map languageStrings,
+ Map customLanguageStrings) throws IOException {
+ BufferedWriter bufferedWriter = FileHelper.getBufferedWriterFromString(languageFolder + language + ".txt");
+
+ //Output normal Language data
+ for (String key : languageStrings.keySet()) {
+ bufferedWriter.write(key + "=" + languageStrings.get(key));
+ bufferedWriter.newLine();
+ }
+ bufferedWriter.newLine();
+ //Output any custom language strings the user had
+ if (customLanguageStrings != null) {
+ for (String key : customLanguageStrings.keySet()) {
+ bufferedWriter.write(key + "=" + customLanguageStrings.get(key));
+ bufferedWriter.newLine();
+ }
+ }
+ bufferedWriter.close();
+ }
+
+ /**
+ * Loads the given language
+ *
+ * @param lang The language to load
+ * @return A mapping between loaded string indexes and the strings to display
+ */
+ private Map load(String lang) {
+ return load(lang, null);
+ }
+
+ /**
+ * Loads the given language
+ *
+ * @param lang The language to load
+ * @param inputStream An optional input stream to use. Defaults to using a file input stream
+ * @return A mapping between loaded string indexes and the strings to display
+ */
+ private Map load(String lang, InputStream inputStream) {
+ Map strings;
+ BufferedReader bufferedReader;
+ try {
+ if (inputStream == null) {
+ bufferedReader = FileHelper.getBufferedReaderFromString(languageFolder + lang + ".txt");
+ } else {
+ bufferedReader = FileHelper.getBufferedReaderFromInputStream(inputStream);
+ }
+ strings = FileHelper.readKeyValuePairs(bufferedReader, "=", ColorConversion.NORMAL);
+ } catch (Exception e) {
+ if (Stargate.getStargateConfig().isDebuggingEnabled()) {
+ Stargate.getConsoleLogger().info("[Stargate] Unable to load language " + lang);
+ }
+ return null;
+ }
+ return strings;
+ }
+
+ /**
+ * Prints debug output to the console for checking loaded language strings/translations
+ */
+ public void debug() {
+ if (loadedStringTranslations != null) {
+ Set keys = loadedStringTranslations.keySet();
+ for (String key : keys) {
+ Stargate.debug("LanguageLoader::Debug::loadedStringTranslations", key + " => " +
+ loadedStringTranslations.get(key));
+ }
+ }
+ if (loadedBackupStrings == null) {
+ return;
+ }
+ Set keys = loadedBackupStrings.keySet();
+ for (String key : keys) {
+ Stargate.debug("LanguageLoader::Debug::loadedBackupStrings", key + " => " +
+ loadedBackupStrings.get(key));
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/config/MessageSender.java b/src/main/java/net/knarcraft/stargate/config/MessageSender.java
new file mode 100644
index 0000000..bfc706a
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/config/MessageSender.java
@@ -0,0 +1,61 @@
+package net.knarcraft.stargate.config;
+
+import net.md_5.bungee.api.ChatColor;
+import org.bukkit.command.CommandSender;
+
+/**
+ * The message sender is responsible sending messages to players with correct coloring and formatting
+ */
+public final class MessageSender {
+
+ private final LanguageLoader languageLoader;
+
+ /**
+ * Instantiates a new message sender
+ *
+ * @param languageLoader The language loader to get translated strings from
+ */
+ public MessageSender(LanguageLoader languageLoader) {
+ this.languageLoader = languageLoader;
+ }
+
+ /**
+ * Sends an error message to a player
+ *
+ * @param player The player to send the message to
+ * @param message The message to send
+ */
+ public void sendErrorMessage(CommandSender player, String message) {
+ sendMessage(player, message, true);
+ }
+
+ /**
+ * Sends a success message to a player
+ *
+ * @param player The player to send the message to
+ * @param message The message to send
+ */
+ public void sendSuccessMessage(CommandSender player, String message) {
+ sendMessage(player, message, false);
+ }
+
+ /**
+ * Sends a message to a player
+ *
+ * @param sender The player to send the message to
+ * @param message The message to send
+ * @param error Whether the message sent is an error
+ */
+ private void sendMessage(CommandSender sender, String message, boolean error) {
+ if (message.isEmpty()) {
+ return;
+ }
+ message = ChatColor.translateAlternateColorCodes('&', message);
+ if (error) {
+ sender.sendMessage(ChatColor.RED + languageLoader.getString("prefix") + ChatColor.WHITE + message);
+ } else {
+ sender.sendMessage(ChatColor.GREEN + languageLoader.getString("prefix") + ChatColor.WHITE + message);
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/config/OptionDataType.java b/src/main/java/net/knarcraft/stargate/config/OptionDataType.java
new file mode 100644
index 0000000..25fcf76
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/config/OptionDataType.java
@@ -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
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/config/StargateConfig.java b/src/main/java/net/knarcraft/stargate/config/StargateConfig.java
new file mode 100644
index 0000000..f03b852
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/config/StargateConfig.java
@@ -0,0 +1,553 @@
+package net.knarcraft.stargate.config;
+
+import net.knarcraft.knarlib.property.ColorConversion;
+import net.knarcraft.knarlib.util.FileHelper;
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.BlockChangeRequest;
+import net.knarcraft.stargate.listener.BungeeCordListener;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.PortalRegistry;
+import net.knarcraft.stargate.portal.property.gate.GateHandler;
+import net.knarcraft.stargate.thread.BlockChangeThread;
+import net.knarcraft.stargate.utility.PortalFileHelper;
+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 java.io.File;
+import java.io.IOException;
+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;
+
+/**
+ * The stargate config is responsible for keeping track of all configuration values
+ */
+public final class StargateConfig {
+
+ private final Queue activePortalsQueue = new ConcurrentLinkedQueue<>();
+ private final Queue openPortalsQueue = new ConcurrentLinkedQueue<>();
+ private final HashSet managedWorlds = new HashSet<>();
+
+ private StargateGateConfig stargateGateConfig;
+ private MessageSender messageSender;
+ 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 final Map configOptions;
+
+ /**
+ * Instantiates a new stargate config
+ *
+ * @param logger The logger to use for logging errors
+ */
+ public StargateConfig(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
+ *
+ * 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.
+ *
+ * @return A reference to the config options map
+ */
+ public Map 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();
+
+ messageSender = new MessageSender(languageLoader);
+ if (isDebuggingEnabled()) {
+ languageLoader.debug();
+ }
+
+ this.createMissingFolders();
+ this.loadGates();
+ this.loadAllPortals();
+
+ //Set up vault economy if vault has been loaded
+ setupVaultEconomy();
+ DynmapAPI dynmapAPI = (DynmapAPI) Bukkit.getPluginManager().getPlugin("dynmap");
+ if (dynmapAPI != null) {
+ DynmapManager.initialize(dynmapAPI);
+ DynmapManager.addAllPortalMarkers();
+ }
+ }
+
+ /**
+ * Gets a copy of all loaded config options with its values
+ *
+ * @return The loaded config options
+ */
+ public Map getConfigOptions() {
+ return new HashMap<>(configOptions);
+ }
+
+ /**
+ * Gets the queue of open portals
+ *
+ * The open portals queue is used to close open portals after some time has passed
+ *
+ * @return The open portals queue
+ */
+ public Queue getOpenPortalsQueue() {
+ return openPortalsQueue;
+ }
+
+ /**
+ * Gets the queue of active portals
+ *
+ * The active portals queue is used to de-activate portals after some time has passed
+ *
+ * @return The active portals queue
+ */
+ public Queue getActivePortalsQueue() {
+ return activePortalsQueue;
+ }
+
+ /**
+ * Gets whether debugging is enabled
+ *
+ * @return Whether debugging is enabled
+ */
+ public boolean isDebuggingEnabled() {
+ return (boolean) configOptions.get(ConfigOption.DEBUG);
+ }
+
+ /**
+ * Gets whether permission debugging is enabled
+ *
+ * @return Whether permission debugging is enabled
+ */
+ public boolean isPermissionDebuggingEnabled() {
+ return (boolean) configOptions.get(ConfigOption.PERMISSION_DEBUG);
+ }
+
+ /**
+ * Gets whether Dynmap integration is disabled
+ *
+ * @return Whether Dynmap integration is disabled
+ */
+ public boolean isDynmapDisabled() {
+ return !((boolean) configOptions.get(ConfigOption.ENABLE_DYNMAP));
+ }
+
+ /**
+ * Gets whether Dynmap icons should be hidden by default
+ *
+ * @return Whether Dynmap icons should be hidden by default
+ */
+ public boolean hideDynmapIcons() {
+ return (boolean) configOptions.get(ConfigOption.DYNMAP_ICONS_DEFAULT_HIDDEN);
+ }
+
+ /**
+ * Gets the object containing economy config values
+ *
+ * @return The object containing economy config values
+ */
+ public EconomyConfig getEconomyConfig() {
+ return this.economyConfig;
+ }
+
+ /**
+ * Reloads all portals and files
+ *
+ * @param sender The sender of the reload request
+ */
+ public void reload(CommandSender sender) {
+ //Unload all saved data
+ unload();
+
+ //Perform all block change requests to prevent mismatch if a gate's open-material changes. Changing the
+ // closed-material still requires a restart.
+ BlockChangeRequest firstElement = Stargate.getBlockChangeRequestQueue().peek();
+ while (firstElement != null) {
+ BlockChangeThread.pollQueue();
+ firstElement = Stargate.getBlockChangeRequestQueue().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();
+
+ messageSender.sendErrorMessage(sender, languageLoader.getString("reloaded"));
+ }
+
+ /**
+ * 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();
+ PortalHandler.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 The managed worlds
+ */
+ public Set getManagedWorlds() {
+ return new HashSet<>(managedWorlds);
+ }
+
+ /**
+ * Adds a world to the managed worlds
+ *
+ * @param worldName The name of the world to manage
+ */
+ public void addManagedWorld(String worldName) {
+ managedWorlds.add(worldName);
+ }
+
+ /**
+ * Removes a world from the managed worlds
+ *
+ * @param worldName The name of the world to stop managing
+ */
+ public void removeManagedWorld(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, new BungeeCordListener());
+ } 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 Whether admins should be alerted about new updates
+ */
+ 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.freeGatesGreen") != null) {
+ migrateConfig(newConfig);
+ isMigrating = true;
+ }
+
+ //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 -> {
+ String value = newConfig.getString(configNode);
+ optionValue = value != null ? value.trim() : "";
+ }
+ case BOOLEAN -> optionValue = newConfig.getBoolean(configNode);
+ case INTEGER -> optionValue = newConfig.getInt(configNode);
+ case DOUBLE -> optionValue = newConfig.getDouble(configNode);
+ 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);
+ gateFolder = (String) configOptions.get(ConfigOption.GATE_FOLDER);
+
+ //If users have an outdated config, assume they also need to update their default gates
+ if (isMigrating) {
+ GateHandler.writeDefaultGatesToFolder(gateFolder);
+ }
+
+ //Load all gate config values
+ stargateGateConfig = new StargateGateConfig(configOptions);
+
+ //Load all economy config values
+ economyConfig = new EconomyConfig(configOptions);
+
+ Stargate.getInstance().saveConfig();
+ }
+
+ /**
+ * Gets the object containing configuration values regarding gates
+ *
+ * @return Gets the gate config
+ */
+ 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()));
+ }
+
+ /**
+ * Changes all configuration values from the old name to the new name
+ *
+ * @param newConfig The config to read from and write to
+ */
+ private void migrateConfig(FileConfiguration newConfig) {
+ //Save the old config just in case something goes wrong
+ try {
+ newConfig.save(dataFolderPath + "/config.yml.old");
+ } catch (IOException e) {
+ Stargate.debug("Stargate::migrateConfig", "Unable to save old backup and do migration");
+ e.printStackTrace();
+ return;
+ }
+
+ //Read all available config migrations
+ Map migrationFields;
+ try {
+ migrationFields = FileHelper.readKeyValuePairs(FileHelper.getBufferedReaderFromInputStream(
+ FileHelper.getInputStreamForInternalFile("/config-migrations.txt")), "=",
+ ColorConversion.NORMAL);
+ } catch (IOException e) {
+ Stargate.debug("Stargate::migrateConfig", "Unable to load config migration file");
+ e.printStackTrace();
+ return;
+ }
+
+ //Replace old config names with the new ones
+ for (String key : migrationFields.keySet()) {
+ if (newConfig.contains(key)) {
+ String newPath = migrationFields.get(key);
+ Object oldValue = newConfig.get(key);
+ if (!newPath.trim().isEmpty()) {
+ newConfig.set(newPath, oldValue);
+ }
+ newConfig.set(key, null);
+ }
+ }
+ }
+
+ /**
+ * Loads economy from Vault
+ */
+ private void setupVaultEconomy() {
+ EconomyConfig economyConfig = getEconomyConfig();
+ if (economyConfig.setupEconomy(Stargate.getPluginManager()) && economyConfig.getEconomy() != null) {
+ String vaultVersion = economyConfig.getVault().getDescription().getVersion();
+ Stargate.logInfo(Stargate.replaceVars(Stargate.getString("vaultLoaded"), "%version%", vaultVersion));
+ }
+ }
+
+ /**
+ * 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() {
+ File newPortalDir = new File(portalFolder);
+ if (!newPortalDir.exists()) {
+ if (!newPortalDir.mkdirs()) {
+ logger.severe("Unable to create portal directory");
+ }
+ }
+ File newFile = new File(portalFolder, Stargate.getInstance().getServer().getWorlds().get(0).getName() +
+ ".db");
+ if (!newFile.exists() && !newFile.getParentFile().exists()) {
+ if (!newFile.getParentFile().mkdirs()) {
+ logger.severe("Unable to create portal database folder: " + newFile.getParentFile().getPath());
+ }
+ }
+ }
+
+ /**
+ * Gets the folder all portals are stored in
+ *
+ * @return The portal folder
+ */
+ public String getPortalFolder() {
+ return portalFolder;
+ }
+
+ /**
+ * Gets the folder storing gate files
+ *
+ * The returned String path is the full path to the folder
+ *
+ * @return The folder storing gate files
+ */
+ public String getGateFolder() {
+ return gateFolder;
+ }
+
+ /**
+ * Gets the sender for sending messages to players
+ *
+ * @return The sender for sending messages to players
+ */
+ public MessageSender getMessageSender() {
+ return messageSender;
+ }
+
+ /**
+ * Gets the language loader containing translated strings
+ *
+ * @return The language loader
+ */
+ public LanguageLoader getLanguageLoader() {
+ return languageLoader;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/config/StargateGateConfig.java b/src/main/java/net/knarcraft/stargate/config/StargateGateConfig.java
new file mode 100644
index 0000000..f1dc7ca
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/config/StargateGateConfig.java
@@ -0,0 +1,328 @@
+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 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 configOptions;
+
+ /**
+ * Instantiates a new stargate config
+ *
+ * @param configOptions The loaded config options to use
+ */
+ public StargateGateConfig(Map configOptions) {
+ this.configOptions = configOptions;
+ loadGateConfig();
+ }
+
+ /**
+ * Gets the amount of seconds a portal should be open before automatically closing
+ *
+ * @return The open time of a gate
+ */
+ public int getOpenTime() {
+ return openTime;
+ }
+
+ /**
+ * Gets the amount of seconds a portal should be active before automatically deactivating
+ *
+ * @return The active time of a gate
+ */
+ public int getActiveTime() {
+ return activeTime;
+ }
+
+ /**
+ * Gets the maximum number of gates allowed on each network
+ *
+ * @return Maximum number of gates for each network
+ */
+ public int maxGatesEachNetwork() {
+ return (int) configOptions.get(ConfigOption.MAX_GATES_EACH_NETWORK);
+ }
+
+ /**
+ * Gets whether a portal's lastly used destination should be remembered
+ *
+ * @return Whether a portal's lastly used destination should be remembered
+ */
+ public boolean rememberDestination() {
+ return (boolean) configOptions.get(ConfigOption.REMEMBER_DESTINATION);
+ }
+
+ /**
+ * Gets whether vehicle teleportation should be handled in addition to player teleportation
+ *
+ * @return Whether vehicle teleportation should be handled
+ */
+ public boolean handleVehicles() {
+ return (boolean) configOptions.get(ConfigOption.HANDLE_VEHICLES);
+ }
+
+ /**
+ * Gets whether vehicles with no passengers should be handled
+ *
+ * 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.
+ *
+ * @return Whether vehicles without passengers should be handled
+ */
+ public boolean handleEmptyVehicles() {
+ return (boolean) configOptions.get(ConfigOption.HANDLE_EMPTY_VEHICLES);
+ }
+
+ /**
+ * Gets whether vehicles containing creatures should be handled
+ *
+ * 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.
+ *
+ * @return Whether vehicles with creatures should be handled
+ */
+ public boolean handleCreatureTransportation() {
+ return (boolean) configOptions.get(ConfigOption.HANDLE_CREATURE_TRANSPORTATION);
+ }
+
+ /**
+ * Gets whether vehicles containing a creature, but not a player should be handled
+ *
+ * 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.
+ *
+ * @return Whether non-empty vehicles without a player should be handled
+ */
+ public boolean handleNonPlayerVehicles() {
+ return (boolean) configOptions.get(ConfigOption.HANDLE_NON_PLAYER_VEHICLES);
+ }
+
+ /**
+ * Gets whether leashed creatures should be teleported with a teleporting player
+ *
+ * @return Whether leashed creatures should be handled
+ */
+ public boolean handleLeashedCreatures() {
+ return (boolean) configOptions.get(ConfigOption.HANDLE_LEASHED_CREATURES);
+ }
+
+ /**
+ * Gets whether the CraftBook vehicle removal fix is enabled
+ *
+ * If enabled, minecarts and boats should be re-created instead of teleported.
+ *
+ * @return True if the CraftBook vehicle removal fix is enabled
+ */
+ 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 The delay to use before adding a player as passenger of a teleported vehicle
+ */
+ 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 Whether network destinations should be sorted
+ */
+ public boolean sortNetworkDestinations() {
+ return (boolean) configOptions.get(ConfigOption.SORT_NETWORK_DESTINATIONS);
+ }
+
+ /**
+ * Gets whether portal entrances should be protected from block breaking
+ *
+ * @return Whether portal entrances should be protected
+ */
+ public boolean protectEntrance() {
+ return (boolean) configOptions.get(ConfigOption.PROTECT_ENTRANCE);
+ }
+
+ /**
+ * Gets whether BungeeCord support is enabled
+ *
+ * @return Whether bungee support is enabled
+ */
+ public boolean enableBungee() {
+ return (boolean) configOptions.get(ConfigOption.ENABLE_BUNGEE);
+ }
+
+ /**
+ * Gets whether all portals' integrity has to be verified on startup and reload
+ *
+ * @return Whether portals need to be verified
+ */
+ public boolean verifyPortals() {
+ return (boolean) configOptions.get(ConfigOption.VERIFY_PORTALS);
+ }
+
+ /**
+ * Gets whether portals should be destroyed by nearby explosions
+ *
+ * @return Whether portals should be destroyed by explosions
+ */
+ 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 The default portal network
+ */
+ 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 The relative exit velocity
+ */
+ 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> 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 The sign color specification to parse
+ * @param defaultColors The specified default colors
+ * @param colorMaps The list of color maps to save the resulting colors to
+ */
+ private void parsePerSignColors(Object signColorSpecification, ChatColor[] defaultColors,
+ List> 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 The colors specified in the config file
+ * @param colorIndex The index of the color to load
+ * @param defaultColors The specified default colors
+ * @param signMaterials The materials to load this color for
+ * @param colorMaps The list of color maps to save the resulting color to
+ */
+ private void loadPerSignColor(String[] colors, int colorIndex, ChatColor[] defaultColors, Material[] signMaterials,
+ List> 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 A string representing the main sign color
+ */
+ private void loadPerSignColor(String mainSignColor, 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. 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. Defaulting to WHITE");
+ PortalSignDrawer.setHighlightColor(ChatColor.WHITE);
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/container/BlockChangeRequest.java b/src/main/java/net/knarcraft/stargate/container/BlockChangeRequest.java
new file mode 100644
index 0000000..55e5269
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/container/BlockChangeRequest.java
@@ -0,0 +1,55 @@
+package net.knarcraft.stargate.container;
+
+import org.bukkit.Axis;
+import org.bukkit.Material;
+
+/**
+ * Represents a request for changing a block into another material
+ */
+public class BlockChangeRequest {
+
+ private final BlockLocation blockLocation;
+ private final Material newMaterial;
+ private final Axis newAxis;
+
+ /**
+ * Instantiates a new block change request
+ *
+ * @param blockLocation The location of the block to change
+ * @param material The new material to change the block to
+ * @param axis The new axis to orient the block along
+ */
+ public BlockChangeRequest(BlockLocation blockLocation, Material material, Axis axis) {
+ this.blockLocation = blockLocation;
+ newMaterial = material;
+ newAxis = axis;
+ }
+
+ /**
+ * Gets the location of the block to change
+ *
+ * @return The location of the block
+ */
+ public BlockLocation getBlockLocation() {
+ return blockLocation;
+ }
+
+ /**
+ * Gets the material to change the block into
+ *
+ * @return The material to change the block into
+ */
+ public Material getMaterial() {
+ return newMaterial;
+ }
+
+ /**
+ * Gets the axis to orient the block along
+ *
+ * @return The axis to orient the block along
+ */
+ public Axis getAxis() {
+ return newAxis;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/container/BlockLocation.java b/src/main/java/net/knarcraft/stargate/container/BlockLocation.java
new file mode 100644
index 0000000..bb8b533
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/container/BlockLocation.java
@@ -0,0 +1,227 @@
+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.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;
+
+/**
+ * This class represents a block location
+ *
+ * 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.
+ */
+public class BlockLocation extends Location {
+
+ private BlockLocation parent = null;
+
+ /**
+ * Creates a new block location
+ *
+ * @param world The world the block exists in
+ * @param x The x coordinate of the block
+ * @param y The y coordinate of the block
+ * @param z The z coordinate of the block
+ */
+ public BlockLocation(World world, int x, int y, int z) {
+ super(world, x, y, z);
+ }
+
+ /**
+ * Creates a block location from a block
+ *
+ * @param block The block to get the location of
+ */
+ public BlockLocation(Block block) {
+ super(block.getWorld(), block.getX(), block.getY(), block.getZ());
+ }
+
+ /**
+ * Gets a block location from a string
+ *
+ * @param world The world the block exists in
+ * @param string A comma separated list of x, y and z coordinates as integers
+ */
+ public BlockLocation(World world, 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 The number of blocks to move in the x-direction
+ * @param y The number of blocks to move in the y-direction
+ * @param z The number of blocks to move in the z-direction
+ * @return A new block location
+ */
+ 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 The number of blocks to move in the x-direction
+ * @param y The number of blocks to move in the y-direction
+ * @param z The z position relative to this block's position
+ * @param yaw The number of blocks to move in the z-direction
+ * @return A new location
+ */
+ 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 The relative block vector describing the relative location
+ * @param yaw The yaw pointing outwards from a portal (in the relative vector's out direction)
+ * @return A location relative to this location
+ */
+ public BlockLocation getRelativeLocation(RelativeBlockVector relativeVector, double yaw) {
+ Vector realVector = DirectionHelper.getCoordinateVectorFromRelativeVector(relativeVector.getRight(),
+ relativeVector.getDown(), relativeVector.getOut(), yaw);
+ return makeRelativeBlockLocation(realVector.getBlockX(), realVector.getBlockY(), realVector.getBlockZ());
+ }
+
+ /**
+ * Makes a location relative to the current location according to given parameters
+ *
+ * Out goes in the direction of the yaw. Right goes in the direction of (yaw - 90) degrees.
+ * Depth goes downwards following the -y direction.
+ *
+ * @param right The amount of blocks to go right when looking towards a portal
+ * @param down The amount of blocks to go downwards when looking towards a portal
+ * @param out The amount of blocks to go outwards when looking towards a portal
+ * @param portalYaw The yaw when looking out from the portal
+ * @return A new location relative to this block location
+ */
+ 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 The block's material type
+ */
+ public Material getType() {
+ return this.getBlock().getType();
+ }
+
+ /**
+ * Sets the type of block at this location
+ *
+ * @param type The block's new material type
+ */
+ public void setType(Material type) {
+ this.getBlock().setType(type);
+ }
+
+ /**
+ * Gets the location representing this block location
+ *
+ * @return The location representing this block location
+ */
+ public Location getLocation() {
+ return this.clone();
+ }
+
+ /**
+ * Gets this block location's parent block
+ *
+ * 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.
+ *
+ * @return This block location's parent block
+ */
+ public Block getParent() {
+ if (parent == null) {
+ findParent();
+ }
+ if (parent == null) {
+ return null;
+ }
+ return parent.getBlock();
+ }
+
+ /**
+ * Tries to find the parent block location
+ *
+ * If this block location is a sign, the parent is the block location of the block the sign is connected to.
+ */
+ private void findParent() {
+ int offsetX = 0;
+ int offsetY = 0;
+ int offsetZ = 0;
+
+ BlockData blockData = getBlock().getBlockData();
+ if (blockData instanceof Directional) {
+ //Get the offset of the block "behind" this block
+ BlockFace facing = ((Directional) blockData).getFacing().getOppositeFace();
+ offsetX = facing.getModX();
+ offsetZ = facing.getModZ();
+ } else if (blockData instanceof Sign) {
+ //Get offset the block beneath the sign
+ offsetY = -1;
+ } else {
+ return;
+ }
+ parent = this.makeRelativeBlockLocation(offsetX, offsetY, offsetZ);
+ }
+
+ @Override
+ 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(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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/knarcraft/stargate/container/ChunkUnloadRequest.java b/src/main/java/net/knarcraft/stargate/container/ChunkUnloadRequest.java
new file mode 100644
index 0000000..4c47d15
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/container/ChunkUnloadRequest.java
@@ -0,0 +1,55 @@
+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 {
+
+ private final Long unloadNanoTime;
+ private final Chunk chunkToUnload;
+
+ /**
+ * Instantiates a new chunk unloading request
+ *
+ * @param chunkToUnload The chunk to request the unloading of
+ * @param timeUntilUnload The time in milliseconds to wait before unloading the chunk
+ */
+ public ChunkUnloadRequest(Chunk chunkToUnload, Long timeUntilUnload) {
+ this.chunkToUnload = chunkToUnload;
+ long systemNanoTime = System.nanoTime();
+ this.unloadNanoTime = systemNanoTime + (timeUntilUnload * 1000000);
+ }
+
+ /**
+ * Gets the chunk to unload
+ *
+ * @return The chunk to unload
+ */
+ public Chunk getChunkToUnload() {
+ return this.chunkToUnload;
+ }
+
+ /**
+ * Gets the system nano time denoting at which time the unload request should be executed
+ *
+ * @return The system nano time denoting when the chunk is to be unloaded
+ */
+ public Long getUnloadNanoTime() {
+ return this.unloadNanoTime;
+ }
+
+ @Override
+ 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);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/container/FromTheEndTeleportation.java b/src/main/java/net/knarcraft/stargate/container/FromTheEndTeleportation.java
new file mode 100644
index 0000000..54c7171
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/container/FromTheEndTeleportation.java
@@ -0,0 +1,60 @@
+package net.knarcraft.stargate.container;
+
+import net.knarcraft.stargate.portal.Portal;
+import org.bukkit.entity.Player;
+
+/**
+ * This class represents a player teleporting from the end to the over-world using an artificial end portal
+ *
+ * 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.
+ */
+public class FromTheEndTeleportation {
+
+ private final Player teleportingPlayer;
+ private final Portal exitPortal;
+
+ /**
+ * Instantiates a new teleportation from the end
+ *
+ * @param teleportingPlayer The teleporting player
+ * @param exitPortal The portal to exit from
+ */
+ public FromTheEndTeleportation(Player teleportingPlayer, Portal exitPortal) {
+ this.teleportingPlayer = teleportingPlayer;
+ this.exitPortal = exitPortal;
+ }
+
+ /**
+ * Gets the teleporting player
+ *
+ * @return The teleporting player
+ */
+ public Player getPlayer() {
+ return this.teleportingPlayer;
+ }
+
+ /**
+ * Gets the portal to exit from
+ *
+ * @return The portal to exit from
+ */
+ public Portal getExit() {
+ return this.exitPortal;
+ }
+
+ @Override
+ public int hashCode() {
+ return teleportingPlayer.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof FromTheEndTeleportation otherTeleportation)) {
+ return false;
+ }
+ return teleportingPlayer.equals(otherTeleportation.teleportingPlayer);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/container/RelativeBlockVector.java b/src/main/java/net/knarcraft/stargate/container/RelativeBlockVector.java
new file mode 100644
index 0000000..847c7ff
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/container/RelativeBlockVector.java
@@ -0,0 +1,122 @@
+package net.knarcraft.stargate.container;
+
+/**
+ * This stores a block location as a vector relative to a position
+ *
+ * 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.
+ */
+public class RelativeBlockVector {
+
+ private final int right;
+ private final int down;
+ private final int out;
+
+ /**
+ * A specifier for one of the relative block vector's three properties
+ */
+ public enum Property {
+ /**
+ * Specifies the relative block vector's right property
+ */
+ RIGHT,
+ /**
+ * Specifies the relative block vector's down property
+ */
+ DOWN,
+ /**
+ * Specifies the relative block vector's out property
+ */
+ OUT
+ }
+
+ /**
+ * Instantiates a new relative block vector
+ *
+ * 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.
+ *
+ * @param right The distance rightward relative to the origin
+ * @param down The distance downward relative to the origin
+ * @param out The distance outward relative to the origin
+ */
+ public RelativeBlockVector(int right, int down, int out) {
+ this.right = right;
+ this.down = down;
+ this.out = out;
+ }
+
+ /**
+ * Adds a value to one of the properties of this relative block vector
+ *
+ * @param propertyToAddTo The property to add to
+ * @param valueToAdd The value to add to the property (negative to move in the opposite direction)
+ * @return A new relative block vector with the property altered
+ */
+ public RelativeBlockVector addToVector(Property propertyToAddTo, int valueToAdd) {
+ return switch (propertyToAddTo) {
+ case RIGHT -> new RelativeBlockVector(this.right + valueToAdd, this.down, this.out);
+ case DOWN -> new RelativeBlockVector(this.right, this.down + valueToAdd, this.out);
+ case OUT -> new RelativeBlockVector(this.right, this.down, this.out + valueToAdd);
+ };
+ }
+
+ /**
+ * Gets a relative block vector which is this inverted (pointing in the opposite direction)
+ *
+ * @return This vector, but inverted
+ */
+ public RelativeBlockVector invert() {
+ return new RelativeBlockVector(-this.right, -this.down, -this.out);
+ }
+
+ /**
+ * Gets the distance to the right relative to the origin
+ *
+ * @return The distance to the right relative to the origin
+ */
+ public int getRight() {
+ return right;
+ }
+
+ /**
+ * Gets the distance downward relative to the origin
+ *
+ * @return The distance downward relative to the origin
+ */
+ public int getDown() {
+ return down;
+ }
+
+ /**
+ * Gets the distance outward relative to the origin
+ *
+ * @return The distance outward relative to the origin
+ */
+ public int getOut() {
+ return out;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(right = %d, down = %d, out = %d)", right, down, out);
+ }
+
+ @Override
+ public boolean equals(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;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/container/SignData.java b/src/main/java/net/knarcraft/stargate/container/SignData.java
new file mode 100644
index 0000000..1b4bd31
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/container/SignData.java
@@ -0,0 +1,67 @@
+package net.knarcraft.stargate.container;
+
+import net.knarcraft.knarlib.util.ColorHelper;
+import net.md_5.bungee.api.ChatColor;
+import org.bukkit.DyeColor;
+import org.bukkit.block.Sign;
+
+/**
+ * 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 The sign the colors belong to
+ * @param mainSignColor The main color to use for the sign
+ * @param highlightSignColor The highlighting color to use for the sign
+ */
+ public SignData(Sign sign, ChatColor mainSignColor, ChatColor highlightSignColor) {
+ this.sign = sign;
+ this.mainSignColor = mainSignColor;
+ this.highlightSignColor = highlightSignColor;
+ this.dyedColor = sign.getColor();
+ }
+
+ /**
+ * Gets the sign of this sign colors object
+ *
+ * @return The sign of this sign colors object
+ */
+ public Sign getSign() {
+ return sign;
+ }
+
+ /**
+ * Gets the main color of the sign
+ *
+ * @return The main color of the sign
+ */
+ public ChatColor getMainSignColor() {
+ if (dyedColor != DyeColor.BLACK) {
+ return ColorHelper.fromColor(dyedColor.getColor());
+ } else {
+ return mainSignColor;
+ }
+ }
+
+ /**
+ * Gets the highlighting color of the sign
+ *
+ * @return The highlighting color of the sign
+ */
+ public ChatColor getHighlightSignColor() {
+ if (dyedColor != DyeColor.BLACK) {
+ return ColorHelper.fromColor(ColorHelper.invert(dyedColor.getColor()));
+ } else {
+ return highlightSignColor;
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateAccessEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateAccessEvent.java
new file mode 100644
index 0000000..5b64a14
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateAccessEvent.java
@@ -0,0 +1,66 @@
+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
+ *
+ * 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.
+ */
+@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 The player involved in the event
+ * @param portal The portal involved in the event
+ * @param deny Whether the stargate access should be denied
+ */
+ public StargateAccessEvent(Player player, Portal portal, boolean deny) {
+ super(portal, player);
+
+ this.deny = deny;
+ }
+
+ /**
+ * Gets whether the player should be denied access
+ *
+ * @return Whether the player should be denied access
+ */
+ public boolean getDeny() {
+ return this.deny;
+ }
+
+ /**
+ * Sets whether to deny access to the player
+ *
+ * @param deny Whether to deny access to the player
+ */
+ public void setDeny(boolean deny) {
+ this.deny = deny;
+ }
+
+ /**
+ * Gets a handler-list containing all event handlers
+ *
+ * @return A handler-list with all event handlers
+ */
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @Override
+ @NotNull
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateActivateEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateActivateEvent.java
new file mode 100644
index 0000000..3d09661
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateActivateEvent.java
@@ -0,0 +1,89 @@
+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
+ *
+ * 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.
+ */
+@SuppressWarnings("unused")
+public class StargateActivateEvent extends StargatePlayerEvent {
+
+ private static final HandlerList handlers = new HandlerList();
+ private List destinations;
+ private String destination;
+
+ /**
+ * Instantiates a new stargate activate event
+ *
+ * @param portal The activated portal
+ * @param player The player activating the portal
+ * @param destinations The destinations available to the player using the portal
+ * @param destination The currently selected destination
+ */
+ public StargateActivateEvent(Portal portal, Player player, List destinations, String destination) {
+ super(portal, player);
+
+ this.destinations = destinations;
+ this.destination = destination;
+ }
+
+ /**
+ * Gets the destinations available for the portal
+ *
+ * @return The destinations available for the portal
+ */
+ public List getDestinations() {
+ return destinations;
+ }
+
+ /**
+ * Sets the destinations available to the player using the portal
+ *
+ * @param destinations The new list of available destinations
+ */
+ public void setDestinations(List destinations) {
+ this.destinations = destinations;
+ }
+
+ /**
+ * Gets the selected destination
+ *
+ * @return The selected destination
+ */
+ public String getDestination() {
+ return destination;
+ }
+
+ /**
+ * Sets (changes) the selected destination
+ *
+ * @param destination The new selected destination
+ */
+ public void setDestination(String destination) {
+ this.destination = destination;
+ }
+
+ /**
+ * Gets a handler-list containing all event handlers
+ *
+ * @return A handler-list with all event handlers
+ */
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @Override
+ @NotNull
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateCloseEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateCloseEvent.java
new file mode 100644
index 0000000..3f2ff07
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateCloseEvent.java
@@ -0,0 +1,64 @@
+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
+ *
+ * This event can be used to overwrite whether the stargate should be forced to close, even if it's set as
+ * always-on.
+ */
+@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 The portal to close
+ * @param force Whether to force the gate to close, even if set as always-on
+ */
+ public StargateCloseEvent(Portal portal, boolean force) {
+ super(portal);
+
+ this.force = force;
+ }
+
+ /**
+ * Gets whether to force the stargate to close
+ *
+ * @return Whether to force the stargate to close
+ */
+ public boolean getForce() {
+ return force;
+ }
+
+ /**
+ * Sets whether the stargate should be forced to close
+ *
+ * @param force Whether the stargate should be forced to close
+ */
+ public void setForce(boolean force) {
+ this.force = force;
+ }
+
+ /**
+ * Gets a handler-list containing all event handlers
+ *
+ * @return A handler-list with all event handlers
+ */
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateCreateEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateCreateEvent.java
new file mode 100644
index 0000000..1f66e60
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateCreateEvent.java
@@ -0,0 +1,120 @@
+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
+ *
+ * This event can be used to deny or change the cost of a stargate creation.
+ */
+@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 Thg player creating the stargate
+ * @param portal The created portal
+ * @param lines The lines of the sign creating the star gate
+ * @param deny Whether to deny the creation of the new gate
+ * @param denyReason The reason stargate creation was denied
+ * @param cost The cost of creating the new star gate
+ */
+ public StargateCreateEvent(Player player, Portal portal, String[] lines, boolean deny, 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 The line number to get
+ * @return The text on the given line
+ * @throws IndexOutOfBoundsException If given a line index less than zero or above three
+ */
+ public String getLine(int index) throws IndexOutOfBoundsException {
+ return lines[index];
+ }
+
+ /**
+ * Gets whether the stargate creation should be denied
+ *
+ * @return Whether the stargate creation should be denied
+ */
+ public boolean getDeny() {
+ return deny;
+ }
+
+ /**
+ * Sets whether the stargate creation should be denied
+ *
+ * @param deny Whether the stargate creation should be denied
+ */
+ public void setDeny(boolean deny) {
+ this.deny = deny;
+ }
+
+ /**
+ * Gets the reason the stargate creation was denied
+ *
+ * @return The reason the stargate creation was denied
+ */
+ public String getDenyReason() {
+ return denyReason;
+ }
+
+ /**
+ * Sets the reason the stargate creation was denied
+ *
+ * @param denyReason The new reason why the stargate creation was denied
+ */
+ public void setDenyReason(String denyReason) {
+ this.denyReason = denyReason;
+ }
+
+ /**
+ * Gets the cost of creating the stargate
+ *
+ * @return The cost of creating the stargate
+ */
+ public int getCost() {
+ return cost;
+ }
+
+ /**
+ * Sets the cost of creating the stargate
+ *
+ * @param cost The new cost of creating the stargate
+ */
+ public void setCost(int cost) {
+ this.cost = cost;
+ }
+
+ /**
+ * Gets a handler-list containing all event handlers
+ *
+ * @return A handler-list with all event handlers
+ */
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateDeactivateEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateDeactivateEvent.java
new file mode 100644
index 0000000..3314ac1
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateDeactivateEvent.java
@@ -0,0 +1,42 @@
+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
+ *
+ * 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.
+ */
+@SuppressWarnings("unused")
+public class StargateDeactivateEvent extends StargateEvent {
+
+ private static final HandlerList handlers = new HandlerList();
+
+ /**
+ * Instantiates a new stargate deactivation event
+ *
+ * @param portal The portal which was deactivated
+ */
+ public StargateDeactivateEvent(Portal portal) {
+ super(portal);
+ }
+
+ /**
+ * Gets a handler-list containing all event handlers
+ *
+ * @return A handler-list with all event handlers
+ */
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateDestroyEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateDestroyEvent.java
new file mode 100644
index 0000000..bfcc606
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateDestroyEvent.java
@@ -0,0 +1,106 @@
+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
+ *
+ * This event can be used to deny or change the cost of a stargate destruction.
+ */
+@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 The destroyed portal
+ * @param player The player destroying the portal
+ * @param deny Whether the event should be denied (cancelled)
+ * @param denyMsg The message to display if the event is denied
+ * @param cost The cost of destroying the portal
+ */
+ public StargateDestroyEvent(Portal portal, Player player, boolean deny, String denyMsg, int cost) {
+ super(portal, player);
+ this.deny = deny;
+ this.denyReason = denyMsg;
+ this.cost = cost;
+ }
+
+ /**
+ * Gets whether this event should be denied
+ *
+ * @return Whether this event should be denied
+ */
+ public boolean getDeny() {
+ return deny;
+ }
+
+ /**
+ * Sets whether this event should be denied
+ *
+ * @param deny Whether this event should be denied
+ */
+ public void setDeny(boolean deny) {
+ this.deny = deny;
+ }
+
+ /**
+ * Gets the reason the event was denied
+ *
+ * @return The reason the event was denied
+ */
+ public String getDenyReason() {
+ return denyReason;
+ }
+
+ /**
+ * Sets the reason the event was denied
+ *
+ * @param denyReason The reason the event was denied
+ */
+ public void setDenyReason(String denyReason) {
+ this.denyReason = denyReason;
+ }
+
+ /**
+ * Gets the cost of destroying the portal
+ *
+ * @return The cost of destroying the portal
+ */
+ public int getCost() {
+ return cost;
+ }
+
+ /**
+ * Sets the cost of destroying the portal
+ *
+ * @param cost The cost of destroying the portal
+ */
+ public void setCost(int cost) {
+ this.cost = cost;
+ }
+
+ /**
+ * Gets a handler-list containing all event handlers
+ *
+ * @return A handler-list with all event handlers
+ */
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateEntityPortalEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateEntityPortalEvent.java
new file mode 100644
index 0000000..cd34504
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateEntityPortalEvent.java
@@ -0,0 +1,90 @@
+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
+ *
+ * This event can be used to overwrite the location the entity is teleported to.
+ */
+@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 The entity travelling through this portal
+ * @param portal The portal the entity entered from
+ * @param destination The destination the entity should exit from
+ * @param exit The exit location of the destination portal the entity will be teleported to
+ */
+ public StargateEntityPortalEvent(Entity travellingEntity, Portal portal, Portal destination, Location exit) {
+ super(portal);
+
+ this.travellingEntity = travellingEntity;
+ this.destination = destination;
+ this.exit = exit;
+ }
+
+ /**
+ * Return the non-player entity teleporting
+ *
+ * @return The non-player teleporting
+ */
+ public Entity getEntity() {
+ return travellingEntity;
+ }
+
+ /**
+ * Return the destination portal
+ *
+ * @return The destination portal
+ */
+ public Portal getDestination() {
+ return destination;
+ }
+
+ /**
+ * Return the location of the players exit point
+ *
+ * @return Location of the exit point
+ */
+ @Override
+ public Location getExit() {
+ return exit;
+ }
+
+ /**
+ * Set the location of the entity's exit point
+ *
+ * @param location The new location of the entity's exit point
+ */
+ public void setExit(Location location) {
+ this.exit = location;
+ }
+
+ /**
+ * Gets a handler-list containing all event handlers
+ *
+ * @return A handler-list with all event handlers
+ */
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @Override
+ @NotNull
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateEvent.java
new file mode 100644
index 0000000..f67a388
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateEvent.java
@@ -0,0 +1,45 @@
+package net.knarcraft.stargate.event;
+
+import net.knarcraft.stargate.portal.Portal;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+
+/**
+ * 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 The portal involved in this stargate event
+ */
+ StargateEvent(Portal portal) {
+ this.portal = portal;
+ this.cancelled = false;
+ }
+
+ /**
+ * Gets the portal involved in this stargate event
+ *
+ * @return The portal involved in this stargate event
+ */
+ public Portal getPortal() {
+ return portal;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateOpenEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateOpenEvent.java
new file mode 100644
index 0000000..ce10534
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateOpenEvent.java
@@ -0,0 +1,65 @@
+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 opens a stargate
+ *
+ * This event can be used to overwrite whether the stargate should be forced to open, even if it's already open.
+ */
+@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 The player opening the stargate
+ * @param portal The opened portal
+ * @param force Whether to force the portal open
+ */
+ public StargateOpenEvent(Player player, Portal portal, boolean force) {
+ super(portal, player);
+
+ this.force = force;
+ }
+
+ /**
+ * Gets whether the portal should be forced open
+ *
+ * @return Whether the portal should be forced open
+ */
+ public boolean getForce() {
+ return force;
+ }
+
+ /**
+ * Sets whether the portal should be forced open
+ *
+ * @param force Whether the portal should be forced open
+ */
+ public void setForce(boolean force) {
+ this.force = force;
+ }
+
+ /**
+ * Gets a handler-list containing all event handlers
+ *
+ * @return A handler-list with all event handlers
+ */
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @Override
+ @NotNull
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargatePlayerEvent.java b/src/main/java/net/knarcraft/stargate/event/StargatePlayerEvent.java
new file mode 100644
index 0000000..14514f6
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargatePlayerEvent.java
@@ -0,0 +1,33 @@
+package net.knarcraft.stargate.event;
+
+import net.knarcraft.stargate.portal.Portal;
+import org.bukkit.entity.Player;
+
+/**
+ * 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 The portal involved in this stargate event
+ */
+ StargatePlayerEvent(Portal portal, Player player) {
+ super(portal);
+ this.player = player;
+ }
+
+ /**
+ * Gets the player creating the star gate
+ *
+ * @return The player creating the star gate
+ */
+ public Player getPlayer() {
+ return player;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargatePlayerPortalEvent.java b/src/main/java/net/knarcraft/stargate/event/StargatePlayerPortalEvent.java
new file mode 100644
index 0000000..3d2526a
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargatePlayerPortalEvent.java
@@ -0,0 +1,79 @@
+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
+ *
+ * This event can be used to overwrite the location the player is teleported to.
+ */
+@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 The player teleporting
+ * @param portal The portal the player entered from
+ * @param destination The destination the player should exit from
+ * @param exit The exit location of the destination portal the user will be teleported to
+ */
+ public StargatePlayerPortalEvent(Player player, Portal portal, Portal destination, Location exit) {
+ super(portal, player);
+
+ this.destination = destination;
+ this.exit = exit;
+ }
+
+ /**
+ * Return the destination portal
+ *
+ * @return The destination portal
+ */
+ public Portal getDestination() {
+ return destination;
+ }
+
+ /**
+ * Return the location of the players exit point
+ *
+ * @return Location of the exit point
+ */
+ @Override
+ public Location getExit() {
+ return exit;
+ }
+
+ /**
+ * Set the location of the player's exit point
+ *
+ * @param location The new location of the player's exit point
+ */
+ public void setExit(Location location) {
+ this.exit = location;
+ }
+
+ /**
+ * Gets a handler-list containing all event handlers
+ *
+ * @return A handler-list with all event handlers
+ */
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @Override
+ @NotNull
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/StargateTeleportEvent.java b/src/main/java/net/knarcraft/stargate/event/StargateTeleportEvent.java
new file mode 100644
index 0000000..e440828
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/StargateTeleportEvent.java
@@ -0,0 +1,18 @@
+package net.knarcraft.stargate.event;
+
+import org.bukkit.Location;
+import org.bukkit.event.Cancellable;
+
+/**
+ * A generic teleportation event
+ */
+public interface StargateTeleportEvent extends Cancellable {
+
+ /**
+ * Return the location of the players exit point
+ *
+ * @return Location of the exit point
+ */
+ Location getExit();
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/event/package-info.java b/src/main/java/net/knarcraft/stargate/event/package-info.java
new file mode 100644
index 0000000..abebf8b
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/event/package-info.java
@@ -0,0 +1,25 @@
+/**
+ * Events for any plugins wanting to interact with the Stargate plugin
+ *
+ * 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:
+ *
+ *
+ * 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.
+ * 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.
+ * The StargateCloseEvent is called whenever a stargate is closed. Forcing the stargate closed can be toggled.
+ * The StargateCreateEvent is called whenever a new stargate is created. Its deny value can be overridden, the
+ * cost can be changed
+ * The StargateDeactivateEvent is called whenever a stargate is deactivated.
+ * The StargateDestroyEvent is called whenever a stargate is destroyed. Its deny value can be overridden or the
+ * cost can be changed.
+ * The StargateEntityPortalEvent is called whenever an entity teleports through a stargate. The exit location
+ * can be changed.
+ * The StargateOpenEvent is called whenever a stargate is opened. Forcing the stargate open can be toggled.
+ * The StargatePlayerPortalEvent is called whenever a player teleports through a stargate. The exit location can
+ * be changed.
+ *
+ */
+package net.knarcraft.stargate.event;
\ No newline at end of file
diff --git a/src/main/java/net/knarcraft/stargate/listener/BlockEventListener.java b/src/main/java/net/knarcraft/stargate/listener/BlockEventListener.java
new file mode 100644
index 0000000..f443569
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/BlockEventListener.java
@@ -0,0 +1,280 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.BlockChangeRequest;
+import net.knarcraft.stargate.event.StargateDestroyEvent;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalCreator;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.PortalRegistry;
+import net.knarcraft.stargate.utility.EconomyHelper;
+import net.knarcraft.stargate.utility.MaterialHelper;
+import net.knarcraft.stargate.utility.PermissionHelper;
+import net.knarcraft.stargate.utility.PortalFileHelper;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.block.data.type.WallSign;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Snowman;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockFromToEvent;
+import org.bukkit.event.block.BlockPhysicsEvent;
+import org.bukkit.event.block.BlockPistonEvent;
+import org.bukkit.event.block.BlockPistonExtendEvent;
+import org.bukkit.event.block.BlockPistonRetractEvent;
+import org.bukkit.event.block.BlockPlaceEvent;
+import org.bukkit.event.block.EntityBlockFormEvent;
+import org.bukkit.event.block.SignChangeEvent;
+
+import java.util.List;
+
+/**
+ * This class is responsible for listening to relevant block events related to creating and breaking portals
+ */
+@SuppressWarnings("unused")
+public class BlockEventListener implements Listener {
+
+ /**
+ * Detects snowmen ruining portals
+ *
+ * If entrance protection or portal verification is enabled, the snowman will be prevented from placing snow in
+ * the portal entrance.
+ *
+ * @param event The triggered event
+ */
+ @EventHandler
+ public void onBlockFormedByEntity(EntityBlockFormEvent event) {
+ if (event.isCancelled() || (!Stargate.getGateConfig().protectEntrance() &&
+ !Stargate.getGateConfig().verifyPortals())) {
+ return;
+ }
+ //We are only interested in snowman events
+ if (!(event.getEntity() instanceof Snowman)) {
+ return;
+ }
+ //Cancel the event if a snowman is trying to place snow in the portal's entrance
+ if (PortalHandler.getByEntrance(event.getBlock()) != null) {
+ event.setCancelled(true);
+ }
+ }
+
+ /**
+ * Detects sign changes to detect if the user is creating a new gate
+ *
+ * @param event The triggered event
+ */
+ @EventHandler
+ public void onSignChange(SignChangeEvent event) {
+ if (event.isCancelled()) {
+ return;
+ }
+ Player player = event.getPlayer();
+ Block block = event.getBlock();
+ //Ignore normal signs
+ if (!(block.getBlockData() instanceof WallSign)) {
+ return;
+ }
+
+ final Portal portal = new PortalCreator(event, player).createPortal();
+ //Not creating a gate, just placing a sign
+ if (portal == null) {
+ return;
+ }
+
+ //Remove the sign if the no sign option is enabled
+ if (portal.getOptions().hasNoSign()) {
+ Material replaceMaterial = PortalFileHelper.decideRemovalMaterial(portal.getSignLocation(), portal);
+ BlockChangeRequest request = new BlockChangeRequest(portal.getSignLocation(), replaceMaterial, null);
+ Stargate.addBlockChangeRequest(request);
+ }
+
+ Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("createMsg"));
+ Stargate.debug("onSignChange", "Initialized stargate: " + portal.getName());
+ Stargate.getInstance().getServer().getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(),
+ portal::drawSign, 1);
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onBlockPlace(BlockPlaceEvent event) {
+ if (event.isCancelled() || !Stargate.getGateConfig().protectEntrance()) {
+ return;
+ }
+ Block block = event.getBlock();
+ Player player = event.getPlayer();
+
+ Portal portal = PortalHandler.getByEntrance(block);
+ if (portal != null) {
+ //Prevent blocks from being placed in the entrance, if protectEntrance is enabled, as breaking the block
+ // would destroy the portal
+ event.setCancelled(true);
+ }
+ }
+
+ /**
+ * Detects block breaking to detect if the user is destroying a gate
+ *
+ * @param event The triggered event
+ */
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onBlockBreak(BlockBreakEvent event) {
+ if (event.isCancelled()) {
+ return;
+ }
+ Block block = event.getBlock();
+ Player player = event.getPlayer();
+
+ //Decide if a portal is broken
+ Portal portal = PortalHandler.getByBlock(block);
+ if (portal == null && Stargate.getGateConfig().protectEntrance()) {
+ portal = PortalHandler.getByEntrance(block);
+ }
+ if (portal == null) {
+ return;
+ }
+
+ boolean deny = false;
+ String denyMessage = "";
+
+ //Decide if the user can destroy the portal
+ if (!PermissionHelper.canDestroyPortal(player, portal)) {
+ denyMessage = Stargate.getString("denyMsg");
+ deny = true;
+ Stargate.logInfo(String.format("%s tried to destroy gate", player.getName()));
+ }
+
+ int cost = Stargate.getEconomyConfig().getDestroyCost(player, portal.getGate());
+
+ //Create and call a StarGateDestroyEvent
+ StargateDestroyEvent destroyEvent = new StargateDestroyEvent(portal, player, deny, denyMessage, cost);
+ Stargate.getInstance().getServer().getPluginManager().callEvent(destroyEvent);
+ if (destroyEvent.isCancelled()) {
+ event.setCancelled(true);
+ return;
+ }
+
+ //Destroy denied
+ if (destroyEvent.getDeny()) {
+ if (!destroyEvent.getDenyReason().trim().isEmpty()) {
+ Stargate.getMessageSender().sendErrorMessage(player, destroyEvent.getDenyReason());
+ }
+ event.setCancelled(true);
+ return;
+ }
+
+ //Take care of payment transactions
+ if (!handleEconomyPayment(destroyEvent, player, portal, event)) {
+ return;
+ }
+
+ PortalRegistry.unregisterPortal(portal, true);
+ Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("destroyMsg"));
+ }
+
+ /**
+ * Handles economy payment for breaking the portal
+ *
+ * @param destroyEvent The destroy event
+ * @param player The player which triggered the event
+ * @param portal The broken portal
+ * @param event The break event
+ * @return True if the payment was successful. False if the event was cancelled
+ */
+ private boolean handleEconomyPayment(StargateDestroyEvent destroyEvent, Player player, Portal portal,
+ BlockBreakEvent event) {
+ int cost = destroyEvent.getCost();
+ if (cost != 0) {
+ String portalName = portal.getName();
+ //Cannot pay
+ if (!EconomyHelper.chargePlayerIfNecessary(player, cost)) {
+ Stargate.debug("onBlockBreak", "Insufficient Funds");
+ EconomyHelper.sendInsufficientFundsMessage(portalName, player, cost);
+ event.setCancelled(true);
+ return false;
+ }
+ //Tell the player they've paid or deceived money
+ if (cost > 0) {
+ EconomyHelper.sendDeductMessage(portalName, player, cost);
+ } else {
+ EconomyHelper.sendRefundMessage(portalName, player, cost);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Prevents any block physics events which may damage parts of the portal
+ *
+ * @param event The event to check and possibly cancel
+ */
+ @EventHandler
+ public void onBlockPhysics(BlockPhysicsEvent event) {
+ Block block = event.getBlock();
+ Portal portal = null;
+
+ if (block.getType() == Material.NETHER_PORTAL) {
+ portal = PortalHandler.getByEntrance(block);
+ } else if (MaterialHelper.isButtonCompatible(block.getType())) {
+ portal = PortalHandler.getByControl(block);
+ }
+ if (portal != null) {
+ event.setCancelled(true);
+ }
+ }
+
+ /**
+ * Cancels any block move events which may cause a block to enter the opening of a portal
+ *
+ * @param event The event to check and possibly cancel
+ */
+ @EventHandler
+ public void onBlockFromTo(BlockFromToEvent event) {
+ Portal portal = PortalHandler.getByEntrance(event.getBlock());
+
+ if (portal != null) {
+ event.setCancelled((event.getBlock().getY() == event.getToBlock().getY()));
+ }
+ }
+
+ /**
+ * Cancels any piston extend events if the target block is part of a portal
+ *
+ * @param event The event to check and possibly cancel
+ */
+ @EventHandler
+ public void onPistonExtend(BlockPistonExtendEvent event) {
+ cancelPistonEvent(event, event.getBlocks());
+ }
+
+ /**
+ * Cancels any piston retract events if the target block is part of a portal
+ *
+ * @param event The event to check and possibly cancel
+ */
+ @EventHandler
+ public void onPistonRetract(BlockPistonRetractEvent event) {
+ if (!event.isSticky()) {
+ return;
+ }
+ cancelPistonEvent(event, event.getBlocks());
+ }
+
+ /**
+ * Cancels a piston event if it would destroy a portal
+ *
+ * @param event The event to cancel
+ * @param blocks The blocks included in the event
+ */
+ private void cancelPistonEvent(BlockPistonEvent event, List blocks) {
+ for (Block block : blocks) {
+ Portal portal = PortalHandler.getByBlock(block);
+ if (portal != null) {
+ event.setCancelled(true);
+ return;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/listener/BungeeCordListener.java b/src/main/java/net/knarcraft/stargate/listener/BungeeCordListener.java
new file mode 100644
index 0000000..fba579d
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/BungeeCordListener.java
@@ -0,0 +1,42 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.stargate.utility.BungeeHelper;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.messaging.PluginMessageListener;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * This listener teleports a user if a valid message is received from BungeeCord
+ *
+ * Specifically, if a string starts with SGBungee encoded to be readable by readUTF followed by
+ * [PlayerUUID]delimiter[DestinationPortal] is received on the BungeeCord channel, this listener will teleport the
+ * player to the destination portal.
+ */
+public class BungeeCordListener implements PluginMessageListener {
+
+ /**
+ * Receives plugin messages
+ *
+ * @param channel The channel the message was received on
+ * @param unused Unused.
+ * @param message The message received from the plugin
+ */
+ @Override
+ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player unused, byte[] message) {
+ //Ignore plugin messages if some other plugin message is received
+ if (!channel.equals(BungeeHelper.getBungeeChannel())) {
+ return;
+ }
+
+ //Try to read the plugin message
+ String receivedMessage = BungeeHelper.readPluginMessage(message);
+ if (receivedMessage == null) {
+ return;
+ }
+
+ //Use the message to initiate teleportation
+ BungeeHelper.handleTeleportMessage(receivedMessage);
+ }
+
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/listener/EntityEventListener.java b/src/main/java/net/knarcraft/stargate/listener/EntityEventListener.java
new file mode 100644
index 0000000..cc181c2
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/EntityEventListener.java
@@ -0,0 +1,67 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.PortalRegistry;
+import net.knarcraft.stargate.utility.EntityHelper;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityExplodeEvent;
+import org.bukkit.event.entity.EntityPortalEvent;
+
+/**
+ * This listener listens for any relevant events on portal entities
+ */
+@SuppressWarnings("unused")
+public class EntityEventListener implements Listener {
+
+ /**
+ * This event handler prevents sending entities to the normal nether instead of the stargate target
+ *
+ * @param event The event to check and possibly cancel
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onPortalEvent(EntityPortalEvent event) {
+ if (event.isCancelled()) {
+ return;
+ }
+
+ Entity entity = event.getEntity();
+ //Cancel normal portal event is near a stargate
+ if (PortalHandler.getByAdjacentEntrance(event.getFrom(), EntityHelper.getEntityMaxSizeInt(entity)) != null) {
+ event.setCancelled(true);
+ }
+ }
+
+ /**
+ * This method catches any explosion events
+ *
+ * If destroyed by explosions is enabled, any portals destroyed by the explosion will be unregistered. If not,
+ * the explosion will be cancelled.
+ *
+ * @param event The triggered explosion event
+ */
+ @EventHandler
+ public void onEntityExplode(EntityExplodeEvent event) {
+ if (event.isCancelled()) {
+ return;
+ }
+ for (Block block : event.blockList()) {
+ Portal portal = PortalHandler.getByBlock(block);
+ if (portal == null) {
+ continue;
+ }
+ if (Stargate.getGateConfig().destroyedByExplosion()) {
+ PortalRegistry.unregisterPortal(portal, true);
+ } else {
+ event.setCancelled(true);
+ break;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/listener/EntitySpawnListener.java b/src/main/java/net/knarcraft/stargate/listener/EntitySpawnListener.java
new file mode 100644
index 0000000..b4c0b2c
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/EntitySpawnListener.java
@@ -0,0 +1,25 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.portal.PortalHandler;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+
+/**
+ * A listener that listens for any relevant events causing entities to spawn
+ */
+public class EntitySpawnListener implements Listener {
+
+ @EventHandler
+ public void onCreatureSpawn(CreatureSpawnEvent event) {
+ //Prevent Zombified Piglins and other creatures form spawning at stargates
+ if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.NETHER_PORTAL) {
+ if (PortalHandler.getByEntrance(event.getLocation()) != null) {
+ event.setCancelled(true);
+ Stargate.debug("EntitySpawnListener", "Prevented creature from spawning at Stargate");
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java b/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java
new file mode 100644
index 0000000..6fd69b0
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java
@@ -0,0 +1,401 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.knarlib.util.UpdateChecker;
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.config.MessageSender;
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalActivator;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
+import net.knarcraft.stargate.portal.teleporter.VehicleTeleporter;
+import net.knarcraft.stargate.utility.BungeeHelper;
+import net.knarcraft.stargate.utility.MaterialHelper;
+import net.knarcraft.stargate.utility.PermissionHelper;
+import net.knarcraft.stargate.utility.TeleportHelper;
+import net.knarcraft.stargate.utility.UUIDMigrationHelper;
+import net.md_5.bungee.api.ChatColor;
+import org.bukkit.Bukkit;
+import org.bukkit.GameMode;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.block.data.type.WallSign;
+import org.bukkit.entity.AbstractHorse;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Vehicle;
+import org.bukkit.event.Event;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.inventory.EquipmentSlot;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.util.Vector;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This listener listens to any player-related events related to stargates
+ */
+@SuppressWarnings("unused")
+public class PlayerEventListener implements Listener {
+
+ private static final Map previousEventTimes = new HashMap<>();
+
+ /**
+ * This event handler handles detection of any player teleporting through a bungee gate
+ *
+ * @param event The event to check for a teleporting player
+ */
+ @EventHandler
+ public void onPlayerJoin(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() &&
+ player.hasPermission("stargate.admin")) {
+ String updateMessage = UpdateChecker.getUpdateAvailableString(availableUpdate, Stargate.getPluginVersion());
+ Stargate.getMessageSender().sendErrorMessage(player, updateMessage);
+ }
+
+ 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) {
+ return;
+ }
+
+ Portal portal = PortalHandler.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 moves into a portal
+ *
+ * @param event The player move event which was triggered
+ */
+ @EventHandler
+ public void onPlayerMove(PlayerMoveEvent event) {
+ if (event.isCancelled() || event.getTo() == null) {
+ return;
+ }
+
+ BlockLocation fromLocation = new BlockLocation(event.getFrom().getBlock());
+ BlockLocation toLocation = new BlockLocation(event.getTo().getBlock());
+ Player player = event.getPlayer();
+
+ //Check whether the event needs to be considered
+ if (!isRelevantMoveEvent(event, player, fromLocation, toLocation)) {
+ return;
+ }
+ Portal entrancePortal = PortalHandler.getByEntrance(toLocation);
+ //Check an additional block away in case the portal is a bungee portal using END_PORTAL
+ if (entrancePortal == null) {
+ entrancePortal = PortalHandler.getByAdjacentEntrance(toLocation);
+ }
+
+ Portal destination = entrancePortal.getPortalActivator().getDestination(player);
+
+ Entity playerVehicle = player.getVehicle();
+ //If the player is in a vehicle, but vehicle handling is disabled, just ignore the player
+ if (playerVehicle == null || (playerVehicle instanceof LivingEntity &&
+ Stargate.getGateConfig().handleVehicles())) {
+ teleportPlayer(playerVehicle, player, entrancePortal, destination, event);
+ }
+ }
+
+ /**
+ * Teleports a player, also teleports the player's vehicle if it's a living entity
+ *
+ * @param playerVehicle The vehicle the player is currently sitting in
+ * @param player The player which moved
+ * @param entrancePortal The entrance the player entered
+ * @param destination The destination of the entrance portal
+ * @param event The move event causing the teleportation to trigger
+ */
+ private void teleportPlayer(Entity playerVehicle, Player player, Portal entrancePortal, Portal destination,
+ PlayerMoveEvent event) {
+ if (playerVehicle instanceof LivingEntity) {
+ //Make sure any horses are properly tamed
+ if (playerVehicle instanceof AbstractHorse horse && !horse.isTamed()) {
+ horse.setTamed(true);
+ horse.setOwner(player);
+ }
+ //Teleport the player's vehicle
+ 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().isSilent()) {
+ Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("teleportMsg"));
+ }
+ entrancePortal.getPortalOpener().closePortal(false);
+ }
+
+ /**
+ * Checks whether a player move event is relevant for this plugin
+ *
+ * @param event The player move event to check
+ * @param player The player which moved
+ * @param fromLocation The location the player is moving from
+ * @param toLocation The location the player is moving to
+ * @return True if the event is relevant
+ */
+ private boolean isRelevantMoveEvent(PlayerMoveEvent event, Player player, BlockLocation fromLocation,
+ BlockLocation toLocation) {
+ //Check to see if the player moved to another block
+ if (fromLocation.equals(toLocation)) {
+ return false;
+ }
+
+ //Check if the player moved from a portal
+ Portal entrancePortal = PortalHandler.getByEntrance(toLocation);
+ if (entrancePortal == null) {
+ //Check an additional block away for BungeeCord portals using END_PORTAL as its material
+ entrancePortal = PortalHandler.getByAdjacentEntrance(toLocation);
+ if (entrancePortal == null || !entrancePortal.getOptions().isBungee() ||
+ entrancePortal.getGate().getPortalOpenBlock() != Material.END_PORTAL) {
+ return false;
+ }
+ }
+
+ Portal destination = entrancePortal.getPortalActivator().getDestination(player);
+
+ //Catch always open portals without a valid destination to prevent the user for being teleported and denied
+ if (!entrancePortal.getOptions().isBungee() && destination == null) {
+ return false;
+ }
+
+ //Decide if the anything stops the player from teleport
+ if (PermissionHelper.playerCannotTeleport(entrancePortal, destination, player, event)) {
+ return false;
+ }
+
+ //Decide if the user should be teleported to another bungee server
+ if (entrancePortal.getOptions().isBungee()) {
+ if (BungeeHelper.bungeeTeleport(player, entrancePortal, event) && !entrancePortal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("teleportMsg"));
+ }
+ return false;
+ }
+
+ //Make sure to check if the player has any leashed creatures, even though leashed teleportation is disabled
+ return TeleportHelper.noLeashedCreaturesPreventTeleportation(player);
+ }
+
+ /**
+ * This event handler detects if a player clicks a button or a sign
+ *
+ * @param event The player interact event which was triggered
+ */
+ @EventHandler
+ public void onPlayerInteract(PlayerInteractEvent event) {
+ Player player = event.getPlayer();
+ Block block = event.getClickedBlock();
+
+ if (block == null) {
+ return;
+ }
+
+ if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
+ 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 The event causing the click
+ * @param player The player clicking the sign
+ * @param block The block that was clicked
+ * @param leftClick Whether the player performed a left click as opposed to a right click
+ */
+ private void handleSignClick(PlayerInteractEvent event, Player player, Block block, boolean leftClick) {
+ Portal portal = PortalHandler.getByBlock(block);
+ if (portal == null) {
+ return;
+ }
+
+ //Allow players with permissions to apply dye to signs
+ EquipmentSlot hand = event.getHand();
+ if (hand != null && (PermissionHelper.hasPermission(player, "stargate.admin.dye") ||
+ portal.isOwner(player))) {
+ ItemStack item = player.getInventory().getItem(hand);
+ if (item != null) {
+ String itemName = item.getType().toString();
+ if (itemName.endsWith("DYE") || itemName.endsWith("INK_SAC")) {
+ event.setUseInteractedBlock(Event.Result.ALLOW);
+ Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), portal::drawSign, 1);
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Check if a player should be denied from accessing (using) a portal
+ *
+ * @param player The player trying to access the portal
+ * @param portal The portal the player is trying to use
+ * @return True if the player should be denied
+ */
+ private boolean cannotAccessPortal(Player player, Portal portal) {
+ boolean deny = PermissionHelper.cannotAccessNetwork(player, portal.getCleanNetwork());
+
+ if (PermissionHelper.portalAccessDenied(player, portal, deny)) {
+ if (!portal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This method handles right-clicking of a sign or button belonging to a stargate
+ *
+ * @param event The event triggering the right-click
+ * @param player The player doing the right-click
+ * @param block The block the player clicked
+ * @param hand The hand the player used to interact with the stargate
+ */
+ private void handleRightClickBlock(PlayerInteractEvent event, Player player, Block block, 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(), block)) {
+ return;
+ }
+
+ if (MaterialHelper.isButtonCompatible(block.getType())) {
+ Portal portal = PortalHandler.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
+ *
+ * This will only display portal info if the portal has no sign and is not silent.
+ *
+ * @param block The clicked block
+ * @param player The player that clicked the block
+ */
+ private void displayPortalInfo(Block block, Player player) {
+ Portal portal = PortalHandler.getByBlock(block);
+ if (portal == null) {
+ return;
+ }
+
+ //Display portal information as a portal without a sign does not display any
+ if (portal.getOptions().hasNoSign() && (!portal.getOptions().isSilent() || player.isSneaking())) {
+ MessageSender sender = Stargate.getMessageSender();
+ sender.sendSuccessMessage(player, ChatColor.GOLD + Stargate.getString("portalInfoTitle"));
+ sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoName"),
+ "%name%", portal.getName()));
+ sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoDestination"),
+ "%destination%", portal.getDestinationName()));
+ if (portal.getOptions().isBungee()) {
+ sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoServer"),
+ "%server%", portal.getNetwork()));
+ } else {
+ sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoNetwork"),
+ "%network%", portal.getNetwork()));
+ }
+ }
+ }
+
+ /**
+ * This function decides if a right click of a block is caused by a Spigot bug
+ *
+ * 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.
+ *
+ * @param player The player performing the right-click
+ * @param block The block to check
+ * @return True if the click is a bug and should be cancelled
+ */
+ private boolean clickIsBug(Player player, Block block) {
+ 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;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/listener/PluginEventListener.java b/src/main/java/net/knarcraft/stargate/listener/PluginEventListener.java
new file mode 100644
index 0000000..2f70de3
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/PluginEventListener.java
@@ -0,0 +1,53 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.stargate.Stargate;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.server.PluginDisableEvent;
+import org.bukkit.event.server.PluginEnableEvent;
+
+/**
+ * This listener listens for any plugins being enabled or disabled to catch the loading of vault
+ */
+@SuppressWarnings("unused")
+public class PluginEventListener implements Listener {
+
+ private final Stargate stargate;
+
+ /**
+ * Instantiates a new plugin event listener
+ *
+ * @param stargate A reference to the stargate plugin to
+ */
+ public PluginEventListener(Stargate stargate) {
+ this.stargate = stargate;
+ }
+
+ /**
+ * This event listens for and announces that the vault plugin was detected and enabled
+ *
+ * Each time this event is called, the economy handler will try to enable vault
+ *
+ * @param ignored The actual event called. This is currently not used
+ */
+ @EventHandler
+ public void onPluginEnable(PluginEnableEvent ignored) {
+ if (Stargate.getEconomyConfig().setupEconomy(stargate.getServer().getPluginManager())) {
+ String vaultVersion = Stargate.getEconomyConfig().getVault().getDescription().getVersion();
+ Stargate.logInfo(Stargate.replaceVars(Stargate.getString("vaultLoaded"), "%version%", vaultVersion));
+ }
+ }
+
+ /**
+ * This event listens for the vault plugin being disabled and notifies the console
+ *
+ * @param event The event caused by disabling a plugin
+ */
+ @EventHandler
+ public void onPluginDisable(PluginDisableEvent event) {
+ if (event.getPlugin().equals(Stargate.getEconomyConfig().getVault())) {
+ Stargate.logInfo("Vault plugin lost.");
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/listener/PortalEventListener.java b/src/main/java/net/knarcraft/stargate/listener/PortalEventListener.java
new file mode 100644
index 0000000..c81640f
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/PortalEventListener.java
@@ -0,0 +1,122 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.FromTheEndTeleportation;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
+import net.knarcraft.stargate.utility.PermissionHelper;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityPortalEnterEvent;
+import org.bukkit.event.player.PlayerRespawnEvent;
+import org.bukkit.event.world.PortalCreateEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Listens for and cancels relevant portal events
+ */
+public class PortalEventListener implements Listener {
+
+ private static final List playersFromTheEnd = new ArrayList<>();
+
+ /**
+ * Listens for and aborts vanilla portal creation caused by stargate creation
+ *
+ * @param event The triggered event
+ */
+ @EventHandler
+ public void onPortalCreation(PortalCreateEvent event) {
+ if (event.isCancelled()) {
+ return;
+ }
+ //Unnecessary nether portal creation is only triggered by nether pairing
+ if (event.getReason() == PortalCreateEvent.CreateReason.NETHER_PAIR) {
+ //If an entity is standing in a Stargate entrance, it can be assumed that the creation is a mistake
+ Entity entity = event.getEntity();
+ if (entity != null && PortalHandler.getByAdjacentEntrance(entity.getLocation()) != null) {
+ Stargate.debug("PortalEventListener::onPortalCreation",
+ "Cancelled nether portal create event");
+ event.setCancelled(true);
+ }
+ }
+ }
+
+ /**
+ * Listen for entities entering an artificial end portal
+ *
+ * @param event The triggered event
+ */
+ @EventHandler
+ public void onEntityPortalEnter(EntityPortalEnterEvent event) {
+ Location location = event.getLocation();
+ World world = location.getWorld();
+ Entity entity = event.getEntity();
+ //Hijack normal portal teleportation if teleporting from a stargate
+ if (entity instanceof Player player && location.getBlock().getType() == Material.END_PORTAL && world != null &&
+ world.getEnvironment() == World.Environment.THE_END) {
+ Portal portal = PortalHandler.getByAdjacentEntrance(location);
+ if (portal == null) {
+ return;
+ }
+
+ Stargate.debug("PortalEventListener::onEntityPortalEnter",
+ "Found player " + player + " entering END_PORTAL " + portal);
+
+ //Remove any old player teleportations in case weird things happen
+ playersFromTheEnd.removeIf((teleportation -> teleportation.getPlayer() == player));
+ //Decide if the anything stops the player from teleporting
+ if (PermissionHelper.playerCannotTeleport(portal, portal.getPortalActivator().getDestination(), player, null) ||
+ portal.getOptions().isBungee()) {
+ //Teleport the player back to the portal they came in, just in case
+ playersFromTheEnd.add(new FromTheEndTeleportation(player, portal));
+ Stargate.debug("PortalEventListener::onEntityPortalEnter",
+ "Sending player back to the entrance");
+ } else {
+ playersFromTheEnd.add(new FromTheEndTeleportation(player, portal.getPortalActivator().getDestination()));
+ 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 The triggered event
+ */
+ @EventHandler
+ public void onRespawn(PlayerRespawnEvent event) {
+ Player respawningPlayer = event.getPlayer();
+ int playerIndex = playersFromTheEnd.indexOf(new FromTheEndTeleportation(respawningPlayer, null));
+ if (playerIndex == -1) {
+ return;
+ }
+ FromTheEndTeleportation teleportation = playersFromTheEnd.get(playerIndex);
+ playersFromTheEnd.remove(playerIndex);
+
+ Portal exitPortal = teleportation.getExit();
+ //Overwrite respawn location to respawn in front of the portal
+ 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);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/listener/TeleportEventListener.java b/src/main/java/net/knarcraft/stargate/listener/TeleportEventListener.java
new file mode 100644
index 0000000..21a0118
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/TeleportEventListener.java
@@ -0,0 +1,36 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.stargate.portal.PortalHandler;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerTeleportEvent;
+
+/**
+ * This listener listens to teleportation-related events
+ */
+@SuppressWarnings("unused")
+public class TeleportEventListener implements Listener {
+
+ /**
+ * This event handler handles some special teleportation events
+ *
+ * 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.
+ *
+ * @param event The event to check and possibly cancel
+ */
+ @EventHandler
+ public void onPlayerTeleport(PlayerTeleportEvent event) {
+ PlayerTeleportEvent.TeleportCause cause = event.getCause();
+
+ //Block normal portal teleportation if teleporting from a stargate
+ if (!event.isCancelled() && (cause == PlayerTeleportEvent.TeleportCause.NETHER_PORTAL ||
+ cause == PlayerTeleportEvent.TeleportCause.END_GATEWAY ||
+ cause == PlayerTeleportEvent.TeleportCause.END_PORTAL)
+ && PortalHandler.getByAdjacentEntrance(event.getFrom()) != null) {
+ event.setCancelled(true);
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/listener/VehicleEventListener.java b/src/main/java/net/knarcraft/stargate/listener/VehicleEventListener.java
new file mode 100644
index 0000000..42c1102
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/VehicleEventListener.java
@@ -0,0 +1,147 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.teleporter.VehicleTeleporter;
+import net.knarcraft.stargate.utility.EconomyHelper;
+import net.knarcraft.stargate.utility.EntityHelper;
+import net.knarcraft.stargate.utility.TeleportHelper;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Vehicle;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.vehicle.VehicleMoveEvent;
+
+import java.util.List;
+
+/**
+ * This listener listens for the vehicle move event to teleport vehicles through portals
+ */
+@SuppressWarnings("unused")
+public class VehicleEventListener implements Listener {
+
+ /**
+ * Check for a vehicle moving through a portal
+ *
+ * @param event The triggered move event
+ */
+ @EventHandler
+ public void onVehicleMove(VehicleMoveEvent event) {
+ if (!Stargate.getGateConfig().handleVehicles()) {
+ return;
+ }
+ List passengers = event.getVehicle().getPassengers();
+ Vehicle vehicle = event.getVehicle();
+
+ Portal entrancePortal;
+ int entitySize = EntityHelper.getEntityMaxSizeInt(vehicle);
+ if (EntityHelper.getEntityMaxSize(vehicle) > 1) {
+ entrancePortal = PortalHandler.getByAdjacentEntrance(event.getTo(), entitySize - 1);
+ } else {
+ entrancePortal = PortalHandler.getByEntrance(event.getTo());
+ }
+
+ //Return if the portal cannot be teleported through
+ if (entrancePortal == null || !entrancePortal.isOpen() || entrancePortal.getOptions().isBungee()) {
+ return;
+ }
+
+ teleportVehicle(passengers, entrancePortal, vehicle);
+ }
+
+ /**
+ * Teleports a vehicle through a stargate
+ *
+ * @param passengers The passengers inside the vehicle
+ * @param entrancePortal The portal the vehicle is entering
+ * @param vehicle The vehicle passing through
+ */
+ private static void teleportVehicle(List passengers, Portal entrancePortal, 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.getWorld() + " " +
+ destinationPortal.getSignLocation());
+ new VehicleTeleporter(destinationPortal, vehicle).teleportEntity(entrancePortal);
+ }
+ }
+
+ /**
+ * Teleports a player and the vehicle the player sits in
+ *
+ * @param entrancePortal The portal the minecart entered
+ * @param vehicle The vehicle to teleport
+ */
+ private static void teleportPlayerAndVehicle(Portal entrancePortal, Vehicle vehicle) {
+ Entity rootEntity = vehicle;
+ while (rootEntity.getVehicle() != null) {
+ rootEntity = rootEntity.getVehicle();
+ }
+ List players = TeleportHelper.getPlayers(rootEntity.getPassengers());
+ Portal destinationPortal = null;
+
+ 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) {
+ destinationPortal = possibleDestinationPortal;
+ }
+ }
+
+ //Cancel the teleport if no players activated the portal, or if any players are denied access
+ boolean cancelTeleport = false;
+ for (Player player : players) {
+ if (destinationPortal == null) {
+ cancelTeleport = true;
+ if (!entrancePortal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("invalidMsg"));
+ }
+ } else if (!TeleportHelper.playerCanTeleport(player, entrancePortal, destinationPortal)) {
+ cancelTeleport = true;
+ }
+ }
+ if (cancelTeleport) {
+ return;
+ }
+
+ //Take payment from all players
+ 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
+ int cost = EconomyHelper.getUseCost(player, entrancePortal, destinationPortal);
+ if (cost > 0) {
+ if (EconomyHelper.cannotPayTeleportFee(entrancePortal, player, cost)) {
+ return;
+ }
+ }
+ }
+
+ //Teleport the vehicle and inform the user if the vehicle was teleported
+ boolean teleported = new VehicleTeleporter(destinationPortal, vehicle).teleportEntity(entrancePortal);
+ if (teleported) {
+ if (!entrancePortal.getOptions().isSilent()) {
+ for (Player player : players) {
+ Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("teleportMsg"));
+ }
+ }
+ entrancePortal.getPortalOpener().closePortal(false);
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/listener/WorldEventListener.java b/src/main/java/net/knarcraft/stargate/listener/WorldEventListener.java
new file mode 100644
index 0000000..6329d6d
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/listener/WorldEventListener.java
@@ -0,0 +1,50 @@
+package net.knarcraft.stargate.listener;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.config.StargateConfig;
+import net.knarcraft.stargate.portal.PortalRegistry;
+import net.knarcraft.stargate.utility.PortalFileHelper;
+import org.bukkit.World;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.world.WorldLoadEvent;
+import org.bukkit.event.world.WorldUnloadEvent;
+
+/**
+ * This listener listens for the loading and unloading of worlds to load and unload stargates
+ */
+@SuppressWarnings("unused")
+public class WorldEventListener implements Listener {
+
+ /**
+ * This listener listens for the loading of a world and loads all gates from the world if not already loaded
+ *
+ * @param event The triggered world load event
+ */
+ @EventHandler
+ public void onWorldLoad(WorldLoadEvent event) {
+ StargateConfig config = Stargate.getStargateConfig();
+ if (!config.getManagedWorlds().contains(event.getWorld().getName()) &&
+ PortalFileHelper.loadAllPortals(event.getWorld())) {
+ config.addManagedWorld(event.getWorld().getName());
+ }
+ }
+
+ /**
+ * This listener listens for the unloading of a world
+ *
+ * @param event The triggered world unload event
+ */
+ @EventHandler
+ public void onWorldUnload(WorldUnloadEvent event) {
+ Stargate.debug("onWorldUnload", "Reloading all Stargates");
+ World world = event.getWorld();
+ String worldName = world.getName();
+ StargateConfig config = Stargate.getStargateConfig();
+ if (config.getManagedWorlds().contains(worldName)) {
+ config.removeManagedWorld(worldName);
+ PortalRegistry.clearPortals(world);
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/package-info.java b/src/main/java/net/knarcraft/stargate/package-info.java
new file mode 100644
index 0000000..c0f22f3
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * The root package of the Stargate plugin. Contains the main Stargate.java file
+ */
+package net.knarcraft.stargate;
\ No newline at end of file
diff --git a/src/main/java/net/knarcraft/stargate/portal/Portal.java b/src/main/java/net/knarcraft/stargate/portal/Portal.java
new file mode 100644
index 0000000..f42ddb2
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/Portal.java
@@ -0,0 +1,349 @@
+package net.knarcraft.stargate.portal;
+
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import net.knarcraft.stargate.portal.property.PortalLocation;
+import net.knarcraft.stargate.portal.property.PortalOption;
+import net.knarcraft.stargate.portal.property.PortalOptions;
+import net.knarcraft.stargate.portal.property.PortalOwner;
+import net.knarcraft.stargate.portal.property.PortalStructure;
+import net.knarcraft.stargate.portal.property.gate.Gate;
+import net.md_5.bungee.api.ChatColor;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+
+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 Object containing locations of all relevant blocks
+ * @param button The location of the portal's open button
+ * @param destination The destination defined on the sign's destination line. "" for non-fixed gates
+ * @param name The name of the portal defined on the sign's first line
+ * @param network The network the portal belongs to, defined on the sign's third
+ * @param gate The gate type to use for this portal
+ * @param portalOwner The portal's owner
+ * @param options A map containing all possible portal options, with true for the ones enabled
+ */
+ public Portal(PortalLocation portalLocation, BlockLocation button, String destination, String name, String network,
+ Gate gate, PortalOwner portalOwner, Map options) {
+ this.location = portalLocation;
+ this.network = network;
+ this.name = name;
+ this.portalOwner = portalOwner;
+ this.options = new PortalOptions(options, destination.length() > 0);
+ this.signDrawer = new PortalSignDrawer(this);
+ this.portalOpener = new PortalOpener(this, 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 True if this portal is registered
+ */
+ public boolean isRegistered() {
+ return isRegistered;
+ }
+
+ /**
+ * Sets whether this portal is registered
+ *
+ * @param isRegistered True if this portal is registered
+ */
+ public void setRegistered(boolean isRegistered) {
+ this.isRegistered = isRegistered;
+ }
+
+ /**
+ * Gets the location data for this portal
+ *
+ * @return This portal's location data
+ */
+ public PortalLocation getLocation() {
+ return this.location;
+ }
+
+ /**
+ * Gets the structure of this portal
+ *
+ * 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.
+ *
+ * @return This portal's structure
+ */
+ public PortalStructure getStructure() {
+ return this.structure;
+ }
+
+ /**
+ * Gets this portal's activator
+ *
+ * The activator is responsible for activating/de-activating the portal and contains information about
+ * available destinations and which player activated the portal.
+ *
+ * @return This portal's activator
+ */
+ 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 This portal's portal options
+ */
+ public PortalOptions getOptions() {
+ return this.options;
+ }
+
+ /**
+ * Gets whether this portal is currently open
+ *
+ * @return Whether this portal is open
+ */
+ public boolean isOpen() {
+ return portalOpener.isOpen();
+ }
+
+ /**
+ * Gets the player currently using this portal
+ *
+ * @return The player currently using this portal
+ */
+ public Player getActivePlayer() {
+ return portalActivator.getActivePlayer();
+ }
+
+ /**
+ * Gets the network this portal belongs to
+ *
+ * @return The network this portal belongs to
+ */
+ public String getNetwork() {
+ return network;
+ }
+
+ /**
+ * Gets the clean name of the network this portal belongs to
+ *
+ * @return The clean network name
+ */
+ public String getCleanNetwork() {
+ return cleanNetwork;
+ }
+
+ /**
+ * Gets the time this portal was triggered (activated/opened)
+ *
+ * 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.
+ *
+ * @return The time this portal was triggered (activated/opened)
+ */
+ public long getTriggeredTime() {
+ return portalOpener.getTriggeredTime();
+ }
+
+ /**
+ * Gets the name of this portal
+ *
+ * @return The name of this portal
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Gets the clean name of this portal
+ *
+ * @return The clean name of this portal
+ */
+ public String getCleanName() {
+ return cleanName;
+ }
+
+ /**
+ * Gets the portal opener used by this portal
+ *
+ * The portal opener is responsible for opening and closing this portal.
+ *
+ * @return This portal's portal opener
+ */
+ public PortalOpener getPortalOpener() {
+ return portalOpener;
+ }
+
+ /**
+ * Gets the name of this portal's destination portal
+ *
+ * @return The name of this portal's destination portal
+ */
+ public String getDestinationName() {
+ return portalOpener.getPortalActivator().getDestinationName();
+ }
+
+ /**
+ * Gets the gate type used by this portal
+ *
+ * @return The gate type used by this portal
+ */
+ public Gate getGate() {
+ return structure.getGate();
+ }
+
+ /**
+ * Gets this portal's owner
+ *
+ * The owner is the player which created the portal.
+ *
+ * @return This portal's owner
+ */
+ public PortalOwner getOwner() {
+ return portalOwner;
+ }
+
+ /**
+ * Checks whether a given player is the owner of this portal
+ *
+ * @param player The player to check
+ * @return True if the player is the owner of this portal
+ */
+ public boolean isOwner(Player player) {
+ if (this.portalOwner.getUUID() != null) {
+ return player.getUniqueId().compareTo(this.portalOwner.getUUID()) == 0;
+ } else {
+ return player.getName().equalsIgnoreCase(this.portalOwner.getName());
+ }
+ }
+
+ /**
+ * Gets the world this portal belongs to
+ *
+ * @return The world this portal belongs to
+ */
+ public World getWorld() {
+ return location.getWorld();
+ }
+
+ /**
+ * Gets the location of this portal's sign
+ *
+ * @return The location of this portal's sign
+ */
+ public BlockLocation getSignLocation() {
+ return this.location.getSignLocation();
+ }
+
+ /**
+ * Gets the rotation (yaw) of this portal
+ *
+ * The yaw is used to calculate all kinds of directions. See DirectionHelper to see how the yaw is used to
+ * calculate to/from other direction types.
+ *
+ * @return The rotation (yaw) of this portal
+ */
+ public float getYaw() {
+ return this.location.getYaw();
+ }
+
+ /**
+ * Gets the location of the top-left block of the portal
+ *
+ * @return The location of the top-left portal block
+ */
+ public BlockLocation getTopLeft() {
+ return this.location.getTopLeft();
+ }
+
+ /**
+ * Gets the block at the given location relative to this portal's top-left block
+ *
+ * @param vector The relative block vector explaining the position of the block
+ * @return The block at the given relative position
+ */
+ public BlockLocation getBlockAt(RelativeBlockVector vector) {
+ return getTopLeft().getRelativeLocation(vector, getYaw());
+ }
+
+ /**
+ * Cleans a string by removing color codes, lower-casing and replacing spaces with underscores
+ *
+ * @param string The string to clean
+ * @return The clean string
+ */
+ public static String cleanString(String string) {
+ return ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', string)).toLowerCase();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Portal [id=%s, network=%s name=%s, type=%s]", getSignLocation(), 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(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);
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalActivator.java b/src/main/java/net/knarcraft/stargate/portal/PortalActivator.java
new file mode 100644
index 0000000..c0fd01e
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/PortalActivator.java
@@ -0,0 +1,288 @@
+package net.knarcraft.stargate.portal;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.event.StargateActivateEvent;
+import net.knarcraft.stargate.event.StargateDeactivateEvent;
+import org.bukkit.entity.Player;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * The portal activator activates/de-activates portals and keeps track of a portal's destinations
+ *
+ * The portal activator is responsible for activating/de-activating the portal and contains information about
+ * available destinations and which player activated the portal.
+ */
+public class PortalActivator {
+
+ private final Portal portal;
+ private final PortalOpener opener;
+
+ private List destinations = new ArrayList<>();
+ private String destination;
+ private String lastDestination = "";
+ private Player activePlayer;
+
+ /**
+ * Instantiates a new portal destinations object
+ *
+ * @param portal The portal which this this object stores destinations for
+ * @param portalOpener The portal opener to trigger when the activation causes the portal to open
+ * @param destination The fixed destination specified on the portal's sign
+ */
+ public PortalActivator(Portal portal, PortalOpener portalOpener, String destination) {
+ this.portal = portal;
+ this.opener = portalOpener;
+ this.destination = destination;
+ }
+
+ /**
+ * Gets the player which this activator's portal is currently activated for
+ *
+ * @return The player this activator's portal is currently activated for
+ */
+ public Player getActivePlayer() {
+ return activePlayer;
+ }
+
+ /**
+ * Gets the available portal destinations
+ *
+ * @return The available portal destinations
+ */
+ public List getDestinations() {
+ return new ArrayList<>(this.destinations);
+ }
+
+ /**
+ * Gets the portal destination given a player
+ *
+ * @param player Used for random gates to determine which destinations are available
+ * @return The destination portal the player should teleport to
+ */
+ public Portal getDestination(Player player) {
+ String portalNetwork = portal.getCleanNetwork();
+ if (portal.getOptions().isRandom()) {
+ //Find possible destinations
+ List destinations = PortalHandler.getDestinations(portal, player, portalNetwork);
+ if (destinations.size() == 0) {
+ return null;
+ }
+ //Get one random destination
+ String destination = destinations.get((new Random()).nextInt(destinations.size()));
+ return PortalHandler.getByName(Portal.cleanString(destination), portalNetwork);
+ } else {
+ //Just return the normal fixed destination
+ return PortalHandler.getByName(Portal.cleanString(destination), portalNetwork);
+ }
+ }
+
+ /**
+ * Gets the portal's destination
+ *
+ * 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.
+ *
+ * @return The portal destination
+ */
+ public Portal getDestination() {
+ return getDestination(null);
+ }
+
+ /**
+ * Sets the destination of this portal activator's portal
+ *
+ * @param destination The new destination of this portal activator's portal
+ */
+ public void setDestination(Portal destination) {
+ setDestination(destination.getName());
+ }
+
+ /**
+ * Sets the destination of this portal activator's portal
+ *
+ * @param destination The new destination of this portal activator's portal
+ */
+ public void setDestination(String destination) {
+ this.destination = destination;
+ }
+
+ /**
+ * Gets the name of the selected destination
+ *
+ * @return The name of the selected destination
+ */
+ public String getDestinationName() {
+ return destination;
+ }
+
+ /**
+ * Activates this activator's portal for the given player
+ *
+ * @param player The player to activate the portal for
+ * @return True if the portal was activated
+ */
+ boolean activate(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 = PortalHandler.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
+ *
+ * The event may also end up changing destinations.
+ *
+ * @param player The player trying to activate this activator's portal
+ * @return True if the portal was activated. False otherwise
+ */
+ private boolean triggerStargateActivationEvent(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 Whether this portal activator's portal is active
+ */
+ public boolean isActive() {
+ return portal.getOptions().isFixed() || (destinations.size() > 0);
+ }
+
+ /**
+ * Cycles destination for a non-fixed gate by one forwards step
+ *
+ * @param player The player to cycle the gate for
+ */
+ public void cycleDestination(Player player) {
+ cycleDestination(player, 1);
+ }
+
+ /**
+ * Cycles destination for a non-fixed gate
+ *
+ * @param player The player cycling destinations
+ * @param direction The direction of the cycle (+1 for next, -1 for previous)
+ */
+ public void cycleDestination(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;
+
+ Stargate.debug("cycleDestination", "Network Size: " +
+ PortalHandler.getNetwork(portal.getCleanNetwork()).size());
+ Stargate.debug("cycleDestination", "Player has access to: " + destinations.size());
+ }
+
+ //If no destinations are available, just tell the player and quit
+ if (destinations.size() == 0) {
+ if (!portal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("destEmpty"));
+ }
+ 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 The direction of the cycle (+1 for next, -1 for previous)
+ */
+ 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;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalCreator.java b/src/main/java/net/knarcraft/stargate/portal/PortalCreator.java
new file mode 100644
index 0000000..4ec865c
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/PortalCreator.java
@@ -0,0 +1,327 @@
+package net.knarcraft.stargate.portal;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import net.knarcraft.stargate.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.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.PermissionHelper;
+import net.knarcraft.stargate.utility.PortalFileHelper;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.entity.Player;
+import org.bukkit.event.block.SignChangeEvent;
+
+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 The sign change event which initialized the creation
+ * @param player The player creating the portal
+ */
+ public PortalCreator(SignChangeEvent event, Player player) {
+ this.event = event;
+ this.player = player;
+ }
+
+ /**
+ * Creates a new portal
+ *
+ * @return The created portal
+ */
+ public Portal createPortal() {
+ BlockLocation signLocation = new BlockLocation(event.getBlock());
+ Block signControlBlock = signLocation.getParent();
+
+ //Return early if the sign is not placed on a block, or the block is not a control block
+ if (signControlBlock == null || GateHandler.getGatesByControlBlock(signControlBlock).length == 0) {
+ Stargate.debug("createPortal", "Control block not registered");
+ return null;
+ }
+
+ //The control block is already part of another portal
+ if (PortalHandler.getByBlock(signControlBlock) != null) {
+ Stargate.debug("createPortal", "idParent belongs to existing stargate");
+ return null;
+ }
+
+ //Get necessary information from the gate's sign
+ String portalName = PortalHandler.filterName(event.getLine(0));
+ String destinationName = PortalHandler.filterName(event.getLine(1));
+ String network = PortalHandler.filterName(event.getLine(2));
+ String options = PortalHandler.filterName(event.getLine(3)).toLowerCase();
+
+ //Get portal options available to the player creating the portal
+ Map portalOptions = PortalHandler.getPortalOptions(player, destinationName, options);
+
+ //Get the yaw
+ float yaw = DirectionHelper.getYawFromLocationDifference(signControlBlock.getLocation(),
+ signLocation.getLocation());
+
+ //Get the direction the button should be facing
+ BlockFace buttonFacing = DirectionHelper.getBlockFaceFromYaw(yaw);
+
+ PortalLocation portalLocation = new PortalLocation();
+ portalLocation.setButtonFacing(buttonFacing).setYaw(yaw).setSignLocation(signLocation);
+
+ Stargate.debug("createPortal", "Finished getting all portal info");
+
+ //Try and find a gate matching the new portal
+ Gate gate = PortalHandler.findMatchingGate(portalLocation, player.getWorld());
+ if ((gate == null) || (portalLocation.getButtonVector() == null)) {
+ Stargate.debug("createPortal", "Could not find matching gate layout");
+ return null;
+ }
+
+ //If the portal is a bungee portal and invalid, abort here
+ if (!PortalHandler.isValidBungeePortal(portalOptions, player, destinationName, network)) {
+ Stargate.debug("createPortal", "Portal is an invalid bungee portal");
+ return null;
+ }
+
+ //Debug
+ StringBuilder builder = new StringBuilder();
+ for (PortalOption option : portalOptions.keySet()) {
+ builder.append(option.getCharacterRepresentation()).append(" = ").append(portalOptions.get(option)).append(" ");
+ }
+ Stargate.debug("createPortal", builder.toString());
+
+ //Use default network if a proper alternative is not set
+ if (!portalOptions.get(PortalOption.BUNGEE) && (network.length() < 1 || network.length() >
+ getMaxNameNetworkLength())) {
+ network = Stargate.getDefaultNetwork();
+ }
+
+ boolean deny = false;
+ String denyMessage = "";
+
+ //Check if the player can create portals on this network. If not, create a personal portal
+ if (!portalOptions.get(PortalOption.BUNGEE) && !PermissionHelper.canCreateNetworkGate(player, network)) {
+ Stargate.debug("createPortal", "Player doesn't have create permissions on network. Trying personal");
+ if (PermissionHelper.canCreatePersonalPortal(player)) {
+ network = player.getName();
+ if (network.length() > getMaxNameNetworkLength()) {
+ network = network.substring(0, getMaxNameNetworkLength());
+ }
+ Stargate.debug("createPortal", "Creating personal portal");
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createPersonal"));
+ } else {
+ Stargate.debug("createPortal", "Player does not have access to network");
+ deny = true;
+ denyMessage = Stargate.getString("createNetDeny");
+ }
+ }
+
+ //Check if the player can create this gate layout
+ String gateName = gate.getFilename();
+ gateName = gateName.substring(0, gateName.indexOf('.'));
+ if (!deny && !PermissionHelper.canCreatePortal(player, gateName)) {
+ Stargate.debug("createPortal", "Player does not have access to gate layout");
+ deny = true;
+ denyMessage = Stargate.getString("createGateDeny");
+ }
+
+ //Check if the user can create portals to this world.
+ if (!portalOptions.get(PortalOption.BUNGEE) && !deny && destinationName.length() > 0) {
+ Portal portal = PortalHandler.getByName(destinationName, network);
+ if (portal != null) {
+ String world = portal.getWorld().getName();
+ if (PermissionHelper.cannotAccessWorld(player, world)) {
+ Stargate.debug("canCreateNetworkGate", "Player does not have access to destination world");
+ deny = true;
+ denyMessage = Stargate.getString("createWorldDeny");
+ }
+ }
+ }
+
+ //Check if a conflict exists
+ if (conflictsWithExistingPortal(gate, portalLocation.getTopLeft(), yaw, player)) {
+ return null;
+ }
+
+ PortalOwner owner = new PortalOwner(player);
+ this.portal = new Portal(portalLocation, null, destinationName, portalName, network, gate, owner,
+ portalOptions);
+ return validatePortal(denyMessage, event.getLines(), deny);
+ }
+
+ /**
+ * Validates the newly created portal assigned to this portal validator
+ *
+ * @param denyMessage The deny message to displayed if the creation has already been denied
+ * @param lines The lines on the sign causing the portal to be created
+ * @param deny Whether the portal creation has already been denied
+ * @return The portal or null if its creation was denied
+ */
+ public Portal validatePortal(String denyMessage, 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()) {
+ Stargate.getMessageSender().sendErrorMessage(player, stargateCreateEvent.getDenyReason());
+ }
+ 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.getButtonFacing());
+ }
+
+ //Register the new portal
+ PortalHandler.registerPortal(portal);
+ updateNewPortalOpenState(destinationName);
+
+ //Update portals pointing at this one if it's not a bungee portal
+ if (!portal.getOptions().isBungee()) {
+ PortalHandler.updatePortalsPointingAtNewPortal(portal);
+ }
+
+ PortalFileHelper.saveAllPortals(portal.getWorld());
+
+ return portal;
+ }
+
+ /**
+ * Checks whether the newly created, but unregistered portal is valid
+ *
+ * @param cost The cost of creating the portal
+ * @param portalName The name of the newly created portal
+ * @return True if the portal is completely valid
+ */
+ private boolean checkIfNewPortalIsValid(int cost, String portalName) {
+ //Check if the portal name can fit on the sign with padding (>name<)
+ if (portal.getCleanName().length() < 1 || portal.getCleanName().length() > getMaxNameNetworkLength()) {
+ Stargate.debug("createPortal", String.format("Name length error. %s is too long.",
+ portal.getCleanName()));
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createNameLength"));
+ return false;
+ }
+
+ if (portal.getOptions().isBungee()) {
+ //Check if the bungee portal's name has been duplicated
+ if (PortalHandler.getBungeePortals().get(portal.getCleanName()) != null) {
+ Stargate.debug("createPortal::Bungee", "Gate name duplicate");
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createExists"));
+ return false;
+ }
+ } else {
+ //Check if the portal name has been duplicated on the network
+ if (PortalHandler.getByName(portal.getCleanName(), portal.getCleanNetwork()) != null) {
+ Stargate.debug("createPortal", "Gate name duplicate");
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createExists"));
+ return false;
+ }
+
+ //Check if the number of portals in the network has been surpassed
+ List networkList = PortalHandler.getAllPortalNetworks().get(portal.getCleanNetwork());
+ int maxGates = Stargate.getGateConfig().maxGatesEachNetwork();
+ if (maxGates > 0 && networkList != null && networkList.size() >= maxGates) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createFull"));
+ return false;
+ }
+ }
+
+ if (cost > 0) {
+ //Deduct the required fee from the player
+ if (!EconomyHelper.chargePlayerIfNecessary(player, cost)) {
+ EconomyHelper.sendInsufficientFundsMessage(portalName, player, cost);
+ Stargate.debug("createPortal", "Insufficient Funds");
+ return false;
+ } else {
+ EconomyHelper.sendDeductMessage(portalName, player, cost);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Updates the open state of the newly created portal
+ *
+ * @param destinationName The name of the destination portal. Only used if set as always on
+ */
+ private void updateNewPortalOpenState(String destinationName) {
+ portal.drawSign();
+ if (portal.getOptions().isRandom() || portal.getOptions().isBungee()) {
+ //Open the implicitly always on portal
+ portal.getPortalOpener().openPortal(true);
+ } else if (portal.getOptions().isAlwaysOn()) {
+ //For a normal always-on portal, open both the portal and the destination
+ Portal destinationPortal = PortalHandler.getByName(destinationName, portal.getCleanNetwork());
+ 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
+ for (BlockLocation entrance : portal.getStructure().getEntrances()) {
+ entrance.setType(portal.getGate().getPortalClosedBlock());
+ }
+ }
+ }
+
+ /**
+ * Checks whether the new portal conflicts with an existing portal
+ *
+ * @param gate The gate type of the new portal
+ * @param topLeft The top-left block of the new portal
+ * @param yaw The yaw when looking directly outwards from the portal
+ * @param player The player creating the new portal
+ * @return True if a conflict was found. False otherwise
+ */
+ private static boolean conflictsWithExistingPortal(Gate gate, BlockLocation topLeft, double yaw, Player player) {
+ for (RelativeBlockVector borderVector : gate.getLayout().getBorder()) {
+ BlockLocation borderBlockLocation = topLeft.getRelativeLocation(borderVector, yaw);
+ if (PortalHandler.getByBlock(borderBlockLocation.getBlock()) != null) {
+ Stargate.debug("createPortal", "Gate conflicts with existing gate");
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createConflict"));
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalHandler.java b/src/main/java/net/knarcraft/stargate/portal/PortalHandler.java
new file mode 100644
index 0000000..44ffb81
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/PortalHandler.java
@@ -0,0 +1,452 @@
+package net.knarcraft.stargate.portal;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import net.knarcraft.stargate.portal.property.PortalLocation;
+import net.knarcraft.stargate.portal.property.PortalOption;
+import net.knarcraft.stargate.portal.property.PortalStructure;
+import net.knarcraft.stargate.portal.property.gate.Gate;
+import net.knarcraft.stargate.portal.property.gate.GateHandler;
+import net.knarcraft.stargate.utility.PermissionHelper;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Player;
+
+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 class PortalHandler {
+
+ private PortalHandler() {
+
+ }
+
+ /**
+ * Gets a copy of all portal networks
+ *
+ * @return A copy of all portal networks
+ */
+ public static Map> getAllPortalNetworks() {
+ return PortalRegistry.getAllPortalNetworks();
+ }
+
+ /**
+ * Gets a copy of all bungee portals
+ *
+ * @return A copy of all bungee portals
+ */
+ public static Map getBungeePortals() {
+ return PortalRegistry.getBungeePortals();
+ }
+
+ /**
+ * Gets names of all portals within a network
+ *
+ * @param network The network to get portals from
+ * @return A list of portal names
+ */
+ public static List getNetwork(String network) {
+ return PortalRegistry.getNetwork(network);
+ }
+
+ /**
+ * Gets all destinations in the network viewable by the given player
+ *
+ * @param entrancePortal The portal the user is entering from
+ * @param player The player who wants to see destinations
+ * @param network The network to get destinations from
+ * @return All destinations the player can go to
+ */
+ public static List getDestinations(Portal entrancePortal, Player player, String network) {
+ List destinations = new ArrayList<>();
+ for (String destination : PortalRegistry.getAllPortalNetworks().get(network)) {
+ Portal portal = getByName(destination, network);
+ if (portal == null) {
+ continue;
+ }
+ //Check if destination is a random portal
+ if (portal.getOptions().isRandom()) {
+ continue;
+ }
+ //Check if destination is always open (Don't show if so)
+ if (portal.getOptions().isAlwaysOn() && !portal.getOptions().isShown()) {
+ continue;
+ }
+ //Check if destination is this portal
+ if (destination.equals(entrancePortal.getCleanName())) {
+ continue;
+ }
+ //Check if destination is a fixed portal not pointing to this portal
+ if (portal.getOptions().isFixed() &&
+ !Portal.cleanString(portal.getDestinationName()).equals(entrancePortal.getCleanName())) {
+ continue;
+ }
+ //Allow random use by non-players (Minecarts)
+ if (player == null) {
+ destinations.add(portal.getName());
+ continue;
+ }
+ //Check if this player can access the destination world
+ if (PermissionHelper.cannotAccessWorld(player, portal.getWorld().getName())) {
+ continue;
+ }
+ //The portal is visible to the player
+ if (PermissionHelper.canSeePortal(player, portal)) {
+ destinations.add(portal.getName());
+ }
+ }
+ return destinations;
+ }
+
+ /**
+ * Registers a portal
+ *
+ * @param portal The portal to register
+ */
+ public static void registerPortal(Portal portal) {
+ PortalRegistry.registerPortal(portal);
+ }
+
+ /**
+ * Checks if the new portal is a valid bungee portal
+ *
+ * @param portalOptions The enabled portal options
+ * @param player The player trying to create the new portal
+ * @param destinationName The name of the portal's destination
+ * @param network The name of the portal's network
+ * @return False if the portal is an invalid bungee portal. True otherwise
+ */
+ static boolean isValidBungeePortal(Map portalOptions, Player player,
+ String destinationName, String network) {
+ if (portalOptions.get(PortalOption.BUNGEE)) {
+ if (!PermissionHelper.hasPermission(player, "stargate.admin.bungee")) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeDeny"));
+ return false;
+ } else if (!Stargate.getGateConfig().enableBungee()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeDisabled"));
+ return false;
+ } else if (destinationName.isEmpty() || network.isEmpty()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeEmpty"));
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Tries to find a gate matching the portal the user is trying to create
+ *
+ * @param portalLocation The location data for the new portal
+ * @param world The world the player is located in
+ * @return The matching gate type, or null if no such gate could be found
+ */
+ static Gate findMatchingGate(PortalLocation portalLocation, World world) {
+ Block signParent = portalLocation.getSignLocation().getParent();
+ BlockLocation parent = new BlockLocation(world, signParent.getX(), signParent.getY(),
+ signParent.getZ());
+
+ //Get all gates with the used type of control blocks
+ Gate[] possibleGates = GateHandler.getGatesByControlBlock(signParent);
+ double yaw = portalLocation.getYaw();
+ Gate gate = null;
+
+ for (Gate possibleGate : possibleGates) {
+ //Get gate controls
+ RelativeBlockVector[] vectors = possibleGate.getLayout().getControls();
+
+ portalLocation.setButtonVector(null);
+ for (RelativeBlockVector controlVector : vectors) {
+ //Assuming the top-left location is pointing to the gate's top-left location, check if it's a match
+ BlockLocation possibleTopLocation = parent.getRelativeLocation(controlVector.invert(), yaw);
+ if (possibleGate.matches(possibleTopLocation, portalLocation.getYaw(), true)) {
+ gate = possibleGate;
+ portalLocation.setTopLeft(possibleTopLocation);
+ } else {
+ portalLocation.setButtonVector(controlVector);
+ }
+ }
+ }
+
+ return gate;
+ }
+
+ /**
+ * Updates the sign and open state of portals pointing at the newly created portal
+ *
+ * @param portal The newly created portal
+ */
+ static void updatePortalsPointingAtNewPortal(Portal portal) {
+ for (String originName : PortalRegistry.getAllPortalNetworks().get(portal.getCleanNetwork())) {
+ Portal origin = getByName(originName, portal.getCleanNetwork());
+ if (origin == null ||
+ !Portal.cleanString(origin.getDestinationName()).equals(portal.getCleanName()) ||
+ !origin.getStructure().isVerified()) {
+ 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 The player creating the portal
+ * @param destinationName The destination of the portal
+ * @param options The string on the option line of the sign
+ * @return A map containing all portal options and their values
+ */
+ static Map getPortalOptions(Player player, String destinationName, String options) {
+ Map 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.length() == 0) {
+ 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 The name of the portal
+ * @param network The network the portal is connected to
+ * @return The portal with the given name or null
+ */
+ public static Portal getByName(String name, String network) {
+ Map> lookupMap = PortalRegistry.getPortalLookupByNetwork();
+ if (!lookupMap.containsKey(network.toLowerCase())) {
+ return null;
+ }
+ return lookupMap.get(network.toLowerCase()).get(name.toLowerCase());
+
+ }
+
+ /**
+ * Gets a portal given its entrance
+ *
+ * @param location The location of the portal's entrance
+ * @return The portal at the given location
+ */
+ public static Portal getByEntrance(Location location) {
+ return PortalRegistry.getLookupEntrances().get(new BlockLocation(location.getWorld(), location.getBlockX(),
+ location.getBlockY(), location.getBlockZ()));
+ }
+
+ /**
+ * Gets a portal given its entrance
+ *
+ * @param block The block at the portal's entrance
+ * @return The portal at the given block's location
+ */
+ public static Portal getByEntrance(Block block) {
+ return PortalRegistry.getLookupEntrances().get(new BlockLocation(block));
+ }
+
+ /**
+ * Gets a portal given a location adjacent to its entrance
+ *
+ * @param location A location adjacent to the portal's entrance
+ * @return The portal adjacent to the given location
+ */
+ public static Portal getByAdjacentEntrance(Location location) {
+ return getByAdjacentEntrance(location, 1);
+ }
+
+ /**
+ * Gets a portal given a location adjacent to its entrance
+ *
+ * @param location A location adjacent to the portal's entrance
+ * @param range The range to scan for portals
+ * @return The portal adjacent to the given location
+ */
+ public static Portal getByAdjacentEntrance(Location location, int range) {
+ List adjacentPositions = new ArrayList<>();
+ BlockLocation centerLocation = new BlockLocation(location.getBlock());
+ adjacentPositions.add(centerLocation);
+
+ for (int index = 1; index <= range; index++) {
+ adjacentPositions.add(centerLocation.makeRelativeBlockLocation(index, 0, 0));
+ adjacentPositions.add(centerLocation.makeRelativeBlockLocation(-index, 0, 0));
+ adjacentPositions.add(centerLocation.makeRelativeBlockLocation(0, 0, index));
+ adjacentPositions.add(centerLocation.makeRelativeBlockLocation(0, 0, -index));
+ if (index < range) {
+ adjacentPositions.add(centerLocation.makeRelativeBlockLocation(index, 0, index));
+ adjacentPositions.add(centerLocation.makeRelativeBlockLocation(-index, 0, -index));
+ adjacentPositions.add(centerLocation.makeRelativeBlockLocation(index, 0, -index));
+ adjacentPositions.add(centerLocation.makeRelativeBlockLocation(-index, 0, index));
+ }
+ }
+
+ for (BlockLocation adjacentPosition : adjacentPositions) {
+ Portal portal = PortalRegistry.getLookupEntrances().get(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 The portal's control block
+ * @return The portal with the given control block
+ */
+ public static Portal getByControl(Block block) {
+ return PortalRegistry.getLookupControls().get(new BlockLocation(block));
+ }
+
+ /**
+ * Gets a portal given a block
+ *
+ * @param block One of the loaded lookup blocks
+ * @return The portal corresponding to the block
+ */
+ public static Portal getByBlock(Block block) {
+ return PortalRegistry.getLookupBlocks().get(new BlockLocation(block));
+ }
+
+ /**
+ * Gets a bungee portal given its name
+ *
+ * @param name The name of the bungee portal to get
+ * @return A bungee portal
+ */
+ public static Portal getBungeePortal(String name) {
+ return PortalRegistry.getBungeePortals().get(name.toLowerCase());
+ }
+
+ /**
+ * Gets all portal options stored in the portal data
+ *
+ * @param portalData The string list containing all information about a portal
+ * @return A map between portal options and booleans
+ */
+ public static Map getPortalOptions(String[] portalData) {
+ Map 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 The number of always open portals enabled
+ */
+ 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 invalidPortals = new ArrayList<>();
+ for (Portal portal : PortalRegistry.getAllPortals()) {
+ //Try and verify the portal. Invalidate it if it cannot be validated
+ PortalStructure structure = portal.getStructure();
+ if (!structure.wasVerified() && (!structure.isVerified() || !structure.checkIntegrity())) {
+ 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 The portal of the star portal
+ */
+ private static void unregisterInvalidPortal(Portal portal) {
+ //Show debug information
+ for (RelativeBlockVector control : portal.getGate().getLayout().getControls()) {
+ Block block = portal.getBlockAt(control).getBlock();
+ //Log control blocks not matching the gate layout
+ if (!block.getType().equals(portal.getGate().getControlBlock())) {
+ Stargate.debug("PortalHandler::destroyInvalidPortal", "Control Block Type == " +
+ block.getType().name());
+ }
+ }
+ PortalRegistry.unregisterPortal(portal, false);
+ Stargate.logInfo(String.format("Destroying 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 The name to filter
+ * @return The filtered name
+ */
+ public static String filterName(String input) {
+ if (input == null) {
+ return "";
+ }
+ return input.replaceAll("[|:#]", "").trim();
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalOpener.java b/src/main/java/net/knarcraft/stargate/portal/PortalOpener.java
new file mode 100644
index 0000000..8390c05
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/PortalOpener.java
@@ -0,0 +1,232 @@
+package net.knarcraft.stargate.portal;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.BlockChangeRequest;
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.event.StargateCloseEvent;
+import net.knarcraft.stargate.event.StargateOpenEvent;
+import net.knarcraft.stargate.portal.property.PortalOptions;
+import org.bukkit.Axis;
+import org.bukkit.Material;
+import org.bukkit.block.data.Orientable;
+import org.bukkit.entity.Player;
+
+/**
+ * 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 The portal this portal opener should open
+ * @param destination The fixed destination defined on the portal's sign
+ */
+ public PortalOpener(Portal portal, String destination) {
+ this.portal = portal;
+ this.portalActivator = new PortalActivator(portal, this, destination);
+ }
+
+ /**
+ * Gets whether this portal opener's portal is currently open
+ *
+ * @return Whether this portal opener's portal is open
+ */
+ public boolean isOpen() {
+ return isOpen || portal.getOptions().isAlwaysOn();
+ }
+
+ /**
+ * Sets the time when this portal was triggered (activated/opened)
+ *
+ * @param triggeredTime Unix timestamp when portal was triggered
+ */
+ public void setTriggeredTime(long triggeredTime) {
+ this.triggeredTime = triggeredTime;
+ }
+
+ /**
+ * Gets the portal activator belonging to this portal opener
+ *
+ * @return The portal activator belonging to this portal opener
+ */
+ public PortalActivator getPortalActivator() {
+ return this.portalActivator;
+ }
+
+ /**
+ * Open this portal opener's portal
+ *
+ * @param force Whether to force the portal open, even if it's already open for some player
+ */
+ public void openPortal(boolean force) {
+ openPortal(null, force);
+ }
+
+ /**
+ * Open this portal opener's portal
+ *
+ * @param openFor The player to open the portal for
+ * @param force Whether to force the portal open, even if it's already open for some player
+ */
+ public void openPortal(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
+ Material openType = portal.getGate().getPortalOpenBlock();
+ //Adjust orientation if applicable
+ Axis axis = (openType.createBlockData() instanceof Orientable) ? portal.getLocation().getRotationAxis() : null;
+
+ //Change the entrance blocks to the correct type
+ for (BlockLocation inside : portal.getStructure().getEntrances()) {
+ Stargate.addBlockChangeRequest(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 The player to open this portal opener's portal for
+ */
+ private void updatePortalOpenState(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's verified
+ if (destination.getStructure().isVerified()) {
+ destination.drawSign();
+ }
+ }
+ }
+
+ /**
+ * Closes this portal opener's portal
+ *
+ * @param force Whether to force the portal closed, even if it's set as always on
+ */
+ 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
+ Material closedType = portal.getGate().getPortalClosedBlock();
+ for (BlockLocation entrance : portal.getStructure().getEntrances()) {
+ Stargate.addBlockChangeRequest(new BlockChangeRequest(entrance, closedType, null));
+ }
+
+ //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 The player to check portal state for
+ * @return True if this portal opener's portal is open to the given player
+ */
+ public boolean isOpenFor(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 The time this portal opener's portal was triggered
+ */
+ public long getTriggeredTime() {
+ return triggeredTime;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalRegistry.java b/src/main/java/net/knarcraft/stargate/portal/PortalRegistry.java
new file mode 100644
index 0000000..fe07562
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/PortalRegistry.java
@@ -0,0 +1,297 @@
+package net.knarcraft.stargate.portal;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.config.DynmapManager;
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.utility.PortalFileHelper;
+import org.bukkit.World;
+
+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 lookupBlocks = new HashMap<>();
+ private static final Map lookupEntrances = new HashMap<>();
+ private static final Map lookupControls = new HashMap<>();
+
+ private static final Map> portalLookupByNetwork = new HashMap<>();
+ private static final Map> allPortalNetworks = new HashMap<>();
+ private static final Map bungeePortals = new HashMap<>();
+
+ private static final List 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 The world containing the portals to clear
+ */
+ public static void clearPortals(World world) {
+ //Storing the portals to clear is necessary to avoid a concurrent modification exception
+ List portalsToRemove = new ArrayList<>();
+ allPortals.forEach((portal) -> {
+ if (portal.getWorld().equals(world)) {
+ portalsToRemove.add(portal);
+ }
+ });
+
+ clearPortals(portalsToRemove);
+ }
+
+ /**
+ * Clears a given list of portals from all relevant variables
+ *
+ * @param portalsToRemove A list of portals to remove
+ */
+ private static void clearPortals(List portalsToRemove) {
+ //Store the names of the portals to remove as some maps require the name, not the object
+ List 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 A copy of the list of all portals
+ */
+ public static List getAllPortals() {
+ return new ArrayList<>(allPortals);
+ }
+
+ /**
+ * Gets a copy of the lookup map for finding a portal by its frame
+ *
+ * @return A copy of the frame block lookup map
+ */
+ public static Map getLookupBlocks() {
+ return new HashMap<>(lookupBlocks);
+ }
+
+ /**
+ * Gets a copy of the lookup map for finding a portal by its control block
+ *
+ * @return A copy of the control block lookup map
+ */
+ public static Map getLookupControls() {
+ return new HashMap<>(lookupControls);
+ }
+
+ /**
+ * Gets a copy of the lookup map for finding all portals in a network
+ *
+ * @return A copy of the network portal lookup map
+ */
+ public static Map> getPortalLookupByNetwork() {
+ return new HashMap<>(portalLookupByNetwork);
+ }
+
+ /**
+ * Gets a copy of all portal entrances available for lookup
+ *
+ * @return A copy of all entrances to portal mappings
+ */
+ public static Map getLookupEntrances() {
+ return new HashMap<>(lookupEntrances);
+ }
+
+ /**
+ * Gets a copy of all portal networks
+ *
+ * @return A copy of all portal networks
+ */
+ public static Map> getAllPortalNetworks() {
+ return new HashMap<>(allPortalNetworks);
+ }
+
+ /**
+ * Gets a copy of all bungee portals
+ *
+ * @return A copy of all bungee portals
+ */
+ public static Map getBungeePortals() {
+ return new HashMap<>(bungeePortals);
+ }
+
+ /**
+ * Gets names of all portals within a network
+ *
+ * @param network The network to get portals from
+ * @return A list of portal names
+ */
+ public static List getNetwork(String network) {
+ return allPortalNetworks.get(network.toLowerCase());
+ }
+
+ /**
+ * Un-registers the given portal
+ *
+ * @param portal The portal to un-register
+ * @param removeAll Whether to remove the portal from the list of all portals
+ */
+ public static void unregisterPortal(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();
+
+ //Remove portal from lookup blocks
+ for (BlockLocation block : portal.getStructure().getFrame()) {
+ lookupBlocks.remove(block);
+ }
+
+ //Remove registered info about the lookup controls and blocks
+ lookupBlocks.remove(portal.getSignLocation());
+ lookupControls.remove(portal.getSignLocation());
+
+ BlockLocation button = portal.getStructure().getButton();
+ if (button != null) {
+ lookupBlocks.remove(button);
+ lookupControls.remove(button);
+ }
+
+ //Remove entrances
+ for (BlockLocation entrance : portal.getStructure().getEntrances()) {
+ lookupEntrances.remove(entrance);
+ }
+
+ //Remove the portal from the list of all portals
+ if (removeAll) {
+ allPortals.remove(portal);
+ }
+
+ 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 = PortalHandler.getByName(originName, portal.getCleanNetwork());
+ if (origin == null || !origin.getDestinationName().equalsIgnoreCase(portalName) ||
+ !origin.getStructure().isVerified()) {
+ 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.getWorld());
+ portal.setRegistered(false);
+ DynmapManager.removePortalMarker(portal);
+ }
+
+ /**
+ * Registers a portal
+ *
+ * @param portal The portal to register
+ */
+ static void registerPortal(Portal portal) {
+ portal.getOptions().setFixed(portal.getDestinationName().length() > 0 || 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 (BlockLocation block : portal.getStructure().getFrame()) {
+ lookupBlocks.put(block, portal);
+ }
+ //Register the sign and button to the lookup lists
+ if (!portal.getOptions().hasNoSign()) {
+ lookupBlocks.put(portal.getSignLocation(), portal);
+ lookupControls.put(portal.getSignLocation(), portal);
+ }
+
+ BlockLocation button = portal.getStructure().getButton();
+ if (button != null) {
+ lookupBlocks.put(button, portal);
+ lookupControls.put(button, portal);
+ }
+
+ //Register entrances to the lookup list
+ for (BlockLocation entrance : portal.getStructure().getEntrances()) {
+ lookupEntrances.put(entrance, portal);
+ }
+
+ allPortals.add(portal);
+ portal.setRegistered(true);
+ DynmapManager.addPortalMarker(portal);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalSignDrawer.java b/src/main/java/net/knarcraft/stargate/portal/PortalSignDrawer.java
new file mode 100644
index 0000000..3dabc9e
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/PortalSignDrawer.java
@@ -0,0 +1,406 @@
+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.container.SignData;
+import net.knarcraft.stargate.portal.property.PortalLocation;
+import net.knarcraft.stargate.utility.PermissionHelper;
+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 java.util.Map;
+
+/**
+ * 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 perSignMainColors;
+ private static Map perSignHighlightColors;
+
+ /**
+ * Instantiates a new portal sign drawer
+ *
+ * @param portal The portal whose sign this portal sign drawer is responsible for drawing
+ */
+ public PortalSignDrawer(Portal portal) {
+ this.portal = portal;
+ }
+
+ /**
+ * Sets the highlighting sign color
+ *
+ * The highlighting color is used for the markings around portal names and network names ('>','<','-',')','(').
+ *
+ * @param newHighlightColor The new highlight color
+ */
+ public static void setHighlightColor(ChatColor newHighlightColor) {
+ highlightColor = newHighlightColor;
+ }
+
+ /**
+ * Sets the main sign color
+ *
+ * The main sign color is used for most text on the sign.
+ *
+ * @param newMainColor The new main sign color
+ */
+ public static void setMainColor(ChatColor newMainColor) {
+ mainColor = newMainColor;
+ }
+
+ /**
+ * Sets the color to use for marking free stargates
+ *
+ * @param freeColor The new color to use for marking free stargates
+ */
+ public static void setFreeColor(ChatColor freeColor) {
+ PortalSignDrawer.freeColor = freeColor;
+ }
+
+ /**
+ * Sets the per-sign main colors
+ *
+ * @param signMainColors The per-sign main colors
+ */
+ public static void setPerSignMainColors(Map signMainColors) {
+ PortalSignDrawer.perSignMainColors = signMainColors;
+ }
+
+ /**
+ * Sets the per-sign highlight colors
+ *
+ * @param signHighlightColors The per-sign highlight colors
+ */
+ public static void setPerSignHighlightColors(Map signHighlightColors) {
+ PortalSignDrawer.perSignHighlightColors = signHighlightColors;
+ }
+
+ /**
+ * Gets the currently used main sign color
+ *
+ * @return The currently used main sign color
+ */
+ public static ChatColor getMainColor() {
+ return mainColor;
+ }
+
+ /**
+ * Gets the currently used highlighting sign color
+ *
+ * @return The currently used highlighting sign color
+ */
+ 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;
+ }
+
+ SignData signData = new SignData(sign, getMainColor(sign.getType()), getHighlightColor(sign.getType()));
+ drawSign(signData);
+ }
+
+ /**
+ * Gets the sign for this sign drawer's portal
+ *
+ * @return The sign of this sign drawer's portal
+ */
+ private Sign getSign() {
+ Block signBlock = portal.getSignLocation().getBlock();
+ 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 All necessary sign information
+ */
+ private void drawSign(SignData signData) {
+ Sign sign = signData.getSign();
+ ChatColor highlightColor = signData.getHighlightSignColor();
+ ChatColor mainColor = signData.getMainSignColor();
+ //Clear sign
+ clearSign(sign);
+ setLine(signData, 0, highlightColor + "-" + mainColor + translateAllColorCodes(portal.getName()) +
+ highlightColor + "-");
+
+ if (!portal.getPortalActivator().isActive()) {
+ //Default sign text
+ drawInactiveSign(signData);
+ } else {
+ if (portal.getOptions().isBungee()) {
+ //Bungee sign
+ drawBungeeSign(signData);
+ } else if (portal.getOptions().isFixed()) {
+ //Sign pointing at one other portal
+ drawFixedSign(signData);
+ } else {
+ //Networking stuff
+ drawNetworkSign(signData);
+ }
+ }
+
+ sign.update();
+ }
+
+ /**
+ * Clears all lines of a sign, but does not update the sign
+ *
+ * @param sign The sign to clear
+ */
+ private void clearSign(Sign sign) {
+ for (int index = 0; index <= 3; index++) {
+ sign.setLine(index, "");
+ }
+ }
+
+ /**
+ * Marks this sign drawer's portal as unregistered
+ */
+ public void drawUnregisteredSign() {
+ Sign sign = getSign();
+ if (sign == null) {
+ return;
+ }
+ clearSign(sign);
+ sign.setLine(0, translateAllColorCodes(portal.getName()));
+ sign.update();
+ }
+
+ /**
+ * Draws a sign with choose-able network locations
+ *
+ * @param signData All necessary sign information
+ */
+ private void drawNetworkSign(SignData signData) {
+ 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);
+ }
+ //Not first entry. Draw the previous entry
+ if (destinationIndex > 0) {
+ drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex - 1);
+ }
+ //Draw the chosen entry (line 2 or 3)
+ drawNetworkSignChosenLine(signData, freeGatesColored, ++signLineIndex);
+ //Has another entry and space on the sign
+ if ((maxIndex >= destinationIndex + 1)) {
+ drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex + 1);
+ }
+ //Has another entry and space on the sign
+ if ((maxIndex >= destinationIndex + 2) && (++signLineIndex <= 3)) {
+ drawNetworkSignLine(signData, freeGatesColored, signLineIndex, destinationIndex + 2);
+ }
+ }
+
+ /**
+ * Draws the chosen destination on one sign line
+ *
+ * @param signData All necessary sign information
+ * @param freeGatesColored Whether to display free gates in a different color
+ * @param signLineIndex The line to draw on
+ */
+ private void drawNetworkSignChosenLine(SignData signData, boolean freeGatesColored, int signLineIndex) {
+ ChatColor highlightColor = signData.getHighlightSignColor();
+ ChatColor mainColor = signData.getMainSignColor();
+ if (freeGatesColored) {
+ Portal destination = PortalHandler.getByName(portal.getDestinationName(), portal.getNetwork());
+ boolean free = PermissionHelper.isFree(portal.getActivePlayer(), portal, destination);
+ ChatColor nameColor = (free ? freeColor : highlightColor);
+ setLine(signData, signLineIndex, nameColor + ">" + (free ? freeColor : mainColor) +
+ translateAllColorCodes(portal.getDestinationName()) + nameColor + "<");
+ } else {
+ setLine(signData, signLineIndex, highlightColor + ">" + mainColor +
+ translateAllColorCodes(portal.getDestinationName()) + highlightColor + "<");
+ }
+ }
+
+ /**
+ * Sets a line on a sign, adding the chosen sign color
+ *
+ * @param signData All necessary sign information
+ * @param index The index of the sign line to change
+ * @param text The new text on the sign
+ */
+ public void setLine(SignData signData, int index, String text) {
+ ChatColor mainColor = signData.getMainSignColor();
+ signData.getSign().setLine(index, mainColor + text);
+ }
+
+ /**
+ * Draws one network destination on one sign line
+ *
+ * @param signData All necessary sign information
+ * @param freeGatesColored Whether to display free gates in a different color
+ * @param signLineIndex The line to draw on
+ * @param destinationIndex The index of the destination to draw
+ */
+ private void drawNetworkSignLine(SignData signData, boolean freeGatesColored, int signLineIndex, int destinationIndex) {
+ ChatColor mainColor = signData.getMainSignColor();
+ PortalActivator destinations = portal.getPortalActivator();
+ String destinationName = destinations.getDestinations().get(destinationIndex);
+ if (freeGatesColored) {
+ Portal destination = PortalHandler.getByName(destinationName, portal.getNetwork());
+ boolean free = PermissionHelper.isFree(portal.getActivePlayer(), portal, destination);
+ setLine(signData, signLineIndex, (free ? freeColor : mainColor) + translateAllColorCodes(destinationName));
+ } else {
+ setLine(signData, signLineIndex, mainColor + translateAllColorCodes(destinationName));
+ }
+ }
+
+ /**
+ * Draws the sign of a BungeeCord portal
+ *
+ * @param signData All necessary sign information
+ */
+ private void drawBungeeSign(SignData signData) {
+ ChatColor highlightColor = signData.getHighlightSignColor();
+ ChatColor mainColor = signData.getMainSignColor();
+ setLine(signData, 1, Stargate.getString("bungeeSign"));
+ setLine(signData, 2, highlightColor + ">" + mainColor + translateAllColorCodes(portal.getDestinationName()) +
+ highlightColor + "<");
+ setLine(signData, 3, highlightColor + "[" + mainColor + translateAllColorCodes(portal.getNetwork()) +
+ highlightColor + "]");
+ }
+
+ /**
+ * Draws the sign of an in-active portal
+ *
+ * The sign for an in-active portal should display the right-click prompt and the network.
+ *
+ * @param signData All necessary sign information
+ */
+ private void drawInactiveSign(SignData signData) {
+ ChatColor highlightColor = signData.getHighlightSignColor();
+ ChatColor mainColor = signData.getMainSignColor();
+ setLine(signData, 1, Stargate.getString("signRightClick"));
+ setLine(signData, 2, Stargate.getString("signToUse"));
+ if (!portal.getOptions().isNoNetwork()) {
+ setLine(signData, 3, highlightColor + "(" + mainColor + translateAllColorCodes(portal.getNetwork()) +
+ highlightColor + ")");
+ } else {
+ setLine(signData, 3, "");
+ }
+ }
+
+ /**
+ * Draws a sign pointing to a fixed location
+ *
+ * @param signData All necessary sign information
+ */
+ private void drawFixedSign(SignData signData) {
+ ChatColor highlightColor = signData.getHighlightSignColor();
+ ChatColor mainColor = signData.getMainSignColor();
+ Portal destinationPortal = PortalHandler.getByName(Portal.cleanString(portal.getDestinationName()),
+ portal.getCleanNetwork());
+ String destinationName = portal.getOptions().isRandom() ? Stargate.getString("signRandom") :
+ (destinationPortal != null ? destinationPortal.getName() : portal.getDestinationName());
+ setLine(signData, 1, highlightColor + ">" + mainColor + translateAllColorCodes(destinationName) +
+ highlightColor + "<");
+
+ if (portal.getOptions().isNoNetwork()) {
+ setLine(signData, 2, "");
+ } else {
+ setLine(signData, 2, highlightColor + "(" + mainColor +
+ translateAllColorCodes(portal.getNetwork()) + highlightColor + ")");
+ }
+ Portal destination = PortalHandler.getByName(Portal.cleanString(portal.getDestinationName()),
+ portal.getNetwork());
+ if (destination == null && !portal.getOptions().isRandom()) {
+ setLine(signData, 3, errorColor + Stargate.getString("signDisconnected"));
+ } else {
+ setLine(signData, 3, "");
+ }
+ }
+
+ /**
+ * Marks a portal with an invalid gate by changing its sign and writing to the console
+ *
+ * @param portalLocation The location of the portal with an invalid gate
+ * @param gateName The name of the invalid gate type
+ * @param lineIndex The index of the line the invalid portal was found at
+ */
+ public static void markPortalWithInvalidGate(PortalLocation portalLocation, String gateName, int lineIndex) {
+ BlockState blockState = portalLocation.getSignLocation().getBlock().getState();
+ if (!(blockState instanceof Sign sign)) {
+ return;
+ }
+ sign.setLine(3, errorColor + Stargate.getString("signInvalidGate"));
+ 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 The sign type to get the main color for
+ * @return The main color for the given sign type
+ */
+ private ChatColor getMainColor(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 The sign type to get the highlight color for
+ * @return The highlight color for the given sign type
+ */
+ private ChatColor getHighlightColor(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 The input to translate color codes for
+ * @return The input with color codes converted translated from & to §
+ */
+ private String translateAllColorCodes(String input) {
+ return ColorHelper.translateColorCodes(input, ColorConversion.RGB);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/property/PortalLocation.java b/src/main/java/net/knarcraft/stargate/portal/property/PortalLocation.java
new file mode 100644
index 0000000..49c09ff
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/property/PortalLocation.java
@@ -0,0 +1,145 @@
+package net.knarcraft.stargate.portal.property;
+
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import org.bukkit.Axis;
+import org.bukkit.World;
+import org.bukkit.block.BlockFace;
+
+/**
+ * Keeps track of location related data for a portal
+ */
+@SuppressWarnings("UnusedReturnValue")
+public class PortalLocation {
+
+ private BlockLocation topLeft;
+ private float yaw;
+ private BlockLocation signLocation;
+ private RelativeBlockVector buttonVector;
+ private BlockFace buttonFacing;
+
+ /**
+ * Gets the top-left block of the portal
+ *
+ * @return The top-left block of the portal
+ */
+ public BlockLocation getTopLeft() {
+ return topLeft;
+ }
+
+ /**
+ * Gets the yaw for looking outwards from the portal
+ *
+ * @return The portal's yaw
+ */
+ public float getYaw() {
+ return yaw;
+ }
+
+ /**
+ * Gets the location of the portal's sign
+ *
+ * @return The location of the portal's sign
+ */
+ public BlockLocation getSignLocation() {
+ return signLocation;
+ }
+
+ /**
+ * The relative block vector pointing to the portal's button
+ *
+ * @return The relative location of the portal's button
+ */
+ public RelativeBlockVector getButtonVector() {
+ return buttonVector;
+ }
+
+ /**
+ * Gets the block face determining the button's direction
+ *
+ * @return The button's block face
+ */
+ public BlockFace getButtonFacing() {
+ return buttonFacing;
+ }
+
+ /**
+ * Gets the rotation axis, which is the axis along which the gate is placed
+ * The portal's rotation axis is the cross axis of the button's axis
+ *
+ * @return The portal's rotation axis
+ */
+ public Axis getRotationAxis() {
+ return getYaw() == 0.0F || getYaw() == 180.0F ? Axis.X : Axis.Z;
+ }
+
+ /**
+ * Gets the world this portal resides in
+ *
+ * @return The world this portal resides in
+ */
+ public World getWorld() {
+ return topLeft.getWorld();
+ }
+
+ /**
+ * Sets the portal's top-left location
+ *
+ * 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.
+ *
+ * @param topLeft The new top-left block of the portal's square structure
+ * @return The portal location Object
+ */
+ public PortalLocation setTopLeft(BlockLocation topLeft) {
+ this.topLeft = topLeft;
+ return this;
+ }
+
+ /**
+ * Sets the portal's yaw
+ *
+ * The portal's yaw is the yaw a player would get when looking directly out from the portal
+ *
+ * @param yaw The portal's new yaw
+ * @return The portal location Object
+ */
+ public PortalLocation setYaw(float yaw) {
+ this.yaw = yaw;
+ return this;
+ }
+
+ /**
+ * Sets the location of the portal's sign
+ *
+ * @param signLocation The new sign location
+ * @return The portal location Object
+ */
+ public PortalLocation setSignLocation(BlockLocation signLocation) {
+ this.signLocation = signLocation;
+ return this;
+ }
+
+ /**
+ * Sets the relative location of the portal's button
+ *
+ * @param buttonVector The new relative button location
+ * @return The portal location Object
+ */
+ public PortalLocation setButtonVector(RelativeBlockVector buttonVector) {
+ this.buttonVector = buttonVector;
+ return this;
+ }
+
+ /**
+ * Sets the block face for the direction the portal button is facing
+ *
+ * @param buttonFacing The new block face of the portal's button
+ * @return The portal location Object
+ */
+ public PortalLocation setButtonFacing(BlockFace buttonFacing) {
+ this.buttonFacing = buttonFacing;
+ return this;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/property/PortalOption.java b/src/main/java/net/knarcraft/stargate/portal/property/PortalOption.java
new file mode 100644
index 0000000..9b4cce3
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/property/PortalOption.java
@@ -0,0 +1,106 @@
+package net.knarcraft.stargate.portal.property;
+
+/**
+ * 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', "stargate.option.hidden", 11),
+
+ /**
+ * This option allows a portal that's always on and does not need to be activated or opened each time
+ */
+ ALWAYS_ON('a', "stargate.option.alwayson", 12),
+
+ /**
+ * This option allows a portal that's private to the stargate's owner
+ */
+ PRIVATE('p', "stargate.option.private", 13),
+
+ /**
+ * This option allows a portal that's free even if stargates usually are not
+ */
+ FREE('f', "stargate.option.free", 15),
+
+ /**
+ * This option allows a portal where players exit through the back of the portal
+ */
+ BACKWARDS('b', "stargate.option.backwards", 16),
+
+ /**
+ * This option shows the gate in the network list even if it's always on
+ */
+ SHOW('s', "stargate.option.show", 17),
+
+ /**
+ * This option hides the network name on the sign
+ */
+ NO_NETWORK('n', "stargate.option.nonetwork", 18),
+
+ /**
+ * This option allows a portal where players teleport to a random exit portal in the network
+ */
+ RANDOM('r', "stargate.option.random", 19),
+
+ /**
+ * This option allows a portal to teleport to another server connected through BungeeCord
+ */
+ BUNGEE('u', "stargate.admin.bungee", 20),
+
+ /**
+ * This option allows a portal which does not display a teleportation message, for better immersion
+ */
+ SILENT('i', "stargate.option.silent", 21),
+
+ /**
+ * This option causes a fixed portal's sign to be removed after creation
+ */
+ NO_SIGN('e', "stargate.option.nosign", 22);
+
+ private final char characterRepresentation;
+ private final String permissionString;
+ private final int saveIndex;
+
+ /**
+ * Instantiates a new portal options
+ *
+ * @param characterRepresentation The character representation used on the sign to allow this option
+ * @param permissionString The permission necessary to use this option
+ */
+ PortalOption(final char characterRepresentation, String permissionString, int saveIndex) {
+ this.characterRepresentation = characterRepresentation;
+ this.permissionString = permissionString;
+ this.saveIndex = saveIndex;
+ }
+
+ /**
+ * Gets the character representation used to enable this setting on the sign
+ *
+ * @return The character representation of this option
+ */
+ public char getCharacterRepresentation() {
+ return this.characterRepresentation;
+ }
+
+ /**
+ * Gets the permission necessary to use this option
+ *
+ * @return The permission necessary for this option
+ */
+ public String getPermissionString() {
+ return this.permissionString;
+ }
+
+ /**
+ * Gets the index of the save file this option is stored at
+ *
+ * @return This option's save index
+ */
+ public int getSaveIndex() {
+ return this.saveIndex;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/property/PortalOptions.java b/src/main/java/net/knarcraft/stargate/portal/property/PortalOptions.java
new file mode 100644
index 0000000..03cf40f
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/property/PortalOptions.java
@@ -0,0 +1,196 @@
+package net.knarcraft.stargate.portal.property;
+
+import net.knarcraft.stargate.Stargate;
+
+import java.util.Map;
+
+/**
+ * Keeps track of all options for one portal
+ */
+public class PortalOptions {
+
+ private final Map options;
+ private boolean isFixed;
+
+ /**
+ * Instantiates a new portal options object
+ *
+ * @param options All options to keep track of
+ * @param hasDestination Whether the portal has a fixed destination
+ */
+ public PortalOptions(Map 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.NO_SIGN, false);
+ Stargate.debug("PortalOptions", "Gate marked with no sign, but not fixed. Setting NoSign = false");
+ }
+ }
+
+ /**
+ * Gets whether this portal is fixed
+ *
+ * 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.
+ *
+ * @return Whether this portal is fixed
+ */
+ public boolean isFixed() {
+ return this.isFixed;
+ }
+
+ /**
+ * Sets whether this portal is fixed
+ *
+ * @param fixed Whether this gate should be fixed
+ */
+ public void setFixed(boolean fixed) {
+ this.isFixed = fixed;
+ }
+
+ /**
+ * Gets whether this portal is always on
+ *
+ * An always on portal is always open for everyone, and always uses the open-block. It never needs to be
+ * activated or opened manually.
+ *
+ * @return Whether this portal is always on
+ */
+ public boolean isAlwaysOn() {
+ return this.options.get(PortalOption.ALWAYS_ON);
+ }
+
+ /**
+ * Gets whether this portal is hidden
+ *
+ * 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.
+ *
+ * @return Whether this portal is hidden
+ */
+ public boolean isHidden() {
+ return this.options.get(PortalOption.HIDDEN);
+ }
+
+ /**
+ * Gets whether this portal is private
+ *
+ * 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.
+ *
+ * @return Whether this portal is private
+ */
+ public boolean isPrivate() {
+ return this.options.get(PortalOption.PRIVATE);
+ }
+
+ /**
+ * Gets whether this portal is free
+ *
+ * A free portal is exempt from any fees which would normally occur from using the portal. It does nothing if
+ * economy is disabled.
+ *
+ * @return Whether this portal is free
+ */
+ public boolean isFree() {
+ return this.options.get(PortalOption.FREE);
+ }
+
+ /**
+ * Gets whether this portal is backwards
+ *
+ * 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.
+ *
+ * @return Whether this portal is backwards
+ */
+ public boolean isBackwards() {
+ return this.options.get(PortalOption.BACKWARDS);
+ }
+
+ /**
+ * Gets whether this portal is shown on the network even if it's always on
+ *
+ * Normally, always-on portals are not selectable on a network, but enabling this option allows the portal to be
+ * shown.
+ *
+ * @return Whether portal gate is shown
+ */
+ public boolean isShown() {
+ return this.options.get(PortalOption.SHOW);
+ }
+
+ /**
+ * Gets whether this portal shows no network
+ *
+ * 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".
+ *
+ * @return 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
+ *
+ *
A random portal is always on and will teleport to a random destination within the same network.
+ *
+ * @return Whether this portal goes to a random location
+ */
+ public boolean isRandom() {
+ return this.options.get(PortalOption.RANDOM);
+ }
+
+ /**
+ * Gets whether this portal is a bungee portal
+ *
+ * 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.
+ *
+ * @return Whether this portal is a bungee portal
+ */
+ public boolean isBungee() {
+ return this.options.get(PortalOption.BUNGEE);
+ }
+
+ /**
+ * Gets whether this portal is silent
+ *
+ * A silent 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).
+ *
+ * @return Whether this portal is silent
+ */
+ public boolean isSilent() {
+ return this.options.get(PortalOption.SILENT);
+ }
+
+ /**
+ * Gets whether this portal has no sign
+ *
+ * An always-on portal is allowed to not have a sign as it will never be interacted with anyway.
+ *
+ * @return Whether this portal has no sign
+ */
+ public boolean hasNoSign() {
+ return this.options.get(PortalOption.NO_SIGN);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/property/PortalOwner.java b/src/main/java/net/knarcraft/stargate/portal/property/PortalOwner.java
new file mode 100644
index 0000000..307621e
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/property/PortalOwner.java
@@ -0,0 +1,118 @@
+package net.knarcraft.stargate.portal.property;
+
+import net.knarcraft.stargate.Stargate;
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.entity.Player;
+
+import 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 A UUID, or a username for legacy support
+ */
+ public PortalOwner(String ownerIdentifier) {
+ parseIdentifier(ownerIdentifier);
+ }
+
+ /**
+ * Instantiates a new portal owner
+ *
+ * @param player The player which is the owner of the portal
+ */
+ public PortalOwner(Player player) {
+ this.ownerUUID = player.getUniqueId();
+ this.ownerName = player.getName();
+ }
+
+ /**
+ * Gets the UUID of this owner
+ *
+ * @return The UUID of this owner, or null if a UUID is not available
+ */
+ public UUID getUUID() {
+ return ownerUUID;
+ }
+
+ /**
+ * Sets the unique id for a portal owner without one
+ *
+ * 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.
+ *
+ * @param uniqueId The new unique id for the portal owner
+ */
+ public void setUUID(UUID uniqueId) {
+ if (ownerUUID == null) {
+ ownerUUID = uniqueId;
+ } else {
+ throw new IllegalArgumentException("An existing UUID cannot be overwritten.");
+ }
+ }
+
+ /**
+ * Gets the name of this owner
+ *
+ * @return The name of this owner
+ */
+ public String getName() {
+ return ownerName;
+ }
+
+ /**
+ * Gets the one identifier used for saving the owner
+ *
+ * If the UUID is available, a string representation of the UUID will be returned. If not, the owner's name will
+ * be returned.
+ *
+ * @return The owner's identifier
+ */
+ public String getIdentifier() {
+ if (ownerUUID != null) {
+ return ownerUUID.toString();
+ } else {
+ return ownerName;
+ }
+ }
+
+ /**
+ * Parses the identifier of a portal's owner
+ *
+ * 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.
+ *
+ * @param ownerIdentifier The identifier for a portal's owner
+ */
+ private void parseIdentifier(String ownerIdentifier) {
+ UUID ownerUUID = null;
+ String ownerName;
+ if (ownerIdentifier.length() > 16) {
+ //If more than 16 characters, the string cannot be a username, so it's probably a UUID
+ try {
+ ownerUUID = UUID.fromString(ownerIdentifier);
+ OfflinePlayer offlineOwner = Bukkit.getServer().getOfflinePlayer(ownerUUID);
+ ownerName = offlineOwner.getName();
+ } catch (IllegalArgumentException ex) {
+ //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;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/property/PortalStructure.java b/src/main/java/net/knarcraft/stargate/portal/property/PortalStructure.java
new file mode 100644
index 0000000..c5c2986
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/property/PortalStructure.java
@@ -0,0 +1,150 @@
+package net.knarcraft.stargate.portal.property;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.property.gate.Gate;
+
+/**
+ * The portal structure is responsible for the physical properties of a portal
+ *
+ * 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.
+ */
+public class PortalStructure {
+
+ private final Portal portal;
+ private final Gate gate;
+ private BlockLocation button;
+ private BlockLocation[] frame;
+ private BlockLocation[] entrances;
+ private boolean verified;
+
+ /**
+ * Instantiates a new portal structure
+ *
+ * @param portal The portal whose structure to store
+ * @param gate The gate type used by this portal structure
+ * @param button The real location of the portal's button
+ */
+ public PortalStructure(Portal portal, Gate gate, BlockLocation button) {
+ this.portal = portal;
+ this.gate = gate;
+ this.verified = false;
+ this.button = button;
+ }
+
+ /**
+ * Gets the gate used by this portal structure
+ *
+ * @return The gate used by this portal structure
+ */
+ public Gate getGate() {
+ return gate;
+ }
+
+ /**
+ * Gets the location of this portal's button
+ *
+ * @return The location of this portal's button
+ */
+ public BlockLocation getButton() {
+ return button;
+ }
+
+ /**
+ * Sets the location of this portal's button
+ *
+ * @param button The location of this portal's button
+ */
+ public void setButton(BlockLocation button) {
+ this.button = button;
+ }
+
+ /**
+ * Verifies that all control blocks in this portal follows its gate template
+ *
+ * @return True if all control blocks were verified
+ */
+ public boolean isVerified() {
+ boolean verified = true;
+ if (!Stargate.getGateConfig().verifyPortals()) {
+ return true;
+ }
+ for (RelativeBlockVector control : gate.getLayout().getControls()) {
+ verified = verified && portal.getBlockAt(control).getBlock().getType().equals(gate.getControlBlock());
+ }
+ this.verified = verified;
+ return verified;
+ }
+
+ /**
+ * Gets the result of the last portal verification
+ *
+ * @return True if this portal was verified
+ */
+ public boolean wasVerified() {
+ if (!Stargate.getGateConfig().verifyPortals()) {
+ return true;
+ }
+ return verified;
+ }
+
+ /**
+ * Checks if all blocks in a gate matches the gate template
+ *
+ * @return True if all blocks match the gate template
+ */
+ public boolean checkIntegrity() {
+ if (Stargate.getGateConfig().verifyPortals()) {
+ return gate.matches(portal.getTopLeft(), portal.getYaw());
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Gets a list of block locations from a list of relative block vectors
+ *
+ * The block locations will be calculated by using this portal's top-left block as the origin for the relative
+ * vectors..
+ *
+ * @param vectors The relative block vectors to convert
+ * @return A list of block locations
+ */
+ private BlockLocation[] relativeBlockVectorsToBlockLocations(RelativeBlockVector[] vectors) {
+ BlockLocation[] locations = new BlockLocation[vectors.length];
+ for (int i = 0; i < vectors.length; i++) {
+ locations[i] = portal.getBlockAt(vectors[i]);
+ }
+ return locations;
+ }
+
+ /**
+ * Gets the locations of this portal's entrances
+ *
+ * @return The locations of this portal's entrances
+ */
+ public BlockLocation[] 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 The locations of this portal's frame
+ */
+ public BlockLocation[] 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;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/property/gate/Gate.java b/src/main/java/net/knarcraft/stargate/portal/property/gate/Gate.java
new file mode 100644
index 0000000..bfde5e4
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/property/gate/Gate.java
@@ -0,0 +1,356 @@
+package net.knarcraft.stargate.portal.property.gate;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import org.bukkit.Material;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A gate describes the physical structure of a stargate
+ *
+ * While the portal class represents a portal in space, the Gate class represents the physical gate/portal entrance.
+ */
+public class Gate {
+
+ private final String filename;
+ private final GateLayout layout;
+ private final Map characterMaterialMap;
+ //Gate materials
+ private final Material portalOpenBlock;
+ private final Material portalClosedBlock;
+ private final Material portalButton;
+ //Economy information
+ private final int useCost;
+ private final int createCost;
+ private final int destroyCost;
+ private final boolean toOwner;
+
+ /**
+ * Instantiates a new gate
+ *
+ * @param filename The name of the gate file, including extension
+ * @param layout The gate layout defined in the gate file
+ * @param characterMaterialMap The material types the different layout characters represent
+ * @param portalOpenBlock The material to set the opening to when the portal is open
+ * @param portalClosedBlock The material to set the opening to when the portal is closed
+ * @param portalButton The material to use for the portal button
+ * @param useCost The cost of using a portal with this gate layout (-1 to disable)
+ * @param createCost The cost of creating a portal with this gate layout (-1 to disable)
+ * @param destroyCost The cost of destroying a portal with this gate layout (-1 to disable)
+ * @param toOwner Whether any payment should go to the owner of the gate, as opposed to just disappearing
+ */
+ public Gate(String filename, GateLayout layout, Map characterMaterialMap, Material portalOpenBlock,
+ Material portalClosedBlock, Material portalButton, int useCost, int createCost, int destroyCost,
+ boolean toOwner) {
+ this.filename = filename;
+ this.layout = layout;
+ this.characterMaterialMap = characterMaterialMap;
+ this.portalOpenBlock = portalOpenBlock;
+ this.portalClosedBlock = portalClosedBlock;
+ this.portalButton = portalButton;
+ this.useCost = useCost;
+ this.createCost = createCost;
+ this.destroyCost = destroyCost;
+ this.toOwner = toOwner;
+ }
+
+ /**
+ * Gets this gate's layout
+ *
+ * @return This gate's layout
+ */
+ public GateLayout getLayout() {
+ return layout;
+ }
+
+ /**
+ * Gets a copy of the character to material mapping for this gate
+ *
+ * @return The character to material map
+ */
+ public Map getCharacterMaterialMap() {
+ return new HashMap<>(characterMaterialMap);
+ }
+
+ /**
+ * Gets the material type used for this gate's control blocks
+ *
+ * @return The material type used for control blocks
+ */
+ public Material getControlBlock() {
+ return characterMaterialMap.get(GateHandler.getControlBlockCharacter());
+ }
+
+ /**
+ * Gets the filename of this gate's file
+ *
+ * @return The filename of this gate's file
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Gets the block type to use for the opening when a portal using this gate is open
+ *
+ * @return The block type to use for the opening when open
+ */
+ public Material getPortalOpenBlock() {
+ return portalOpenBlock;
+ }
+
+ /**
+ * Gets the block type to use for the opening when a portal using this gate is closed
+ *
+ * @return The block type to use for the opening when closed
+ */
+ public Material getPortalClosedBlock() {
+ return portalClosedBlock;
+ }
+
+ /**
+ * Gets the material to use for a portal's button if using this gate type
+ *
+ * @return The material to use for a portal's button if using this gate type
+ */
+ public Material getPortalButton() {
+ return portalButton;
+ }
+
+ /**
+ * Gets the cost of using a portal with this gate
+ *
+ * @return The cost of using a portal with this gate
+ */
+ public int getUseCost() {
+ return useCost < 0 ? Stargate.getEconomyConfig().getDefaultUseCost() : useCost;
+ }
+
+ /**
+ * Gets the cost of creating a portal with this gate
+ *
+ * @return The cost of creating a portal with this gate
+ */
+ public Integer getCreateCost() {
+ return createCost < 0 ? Stargate.getEconomyConfig().getDefaultCreateCost() : createCost;
+ }
+
+ /**
+ * Gets the cost of destroying a portal with this gate
+ *
+ * @return The cost of destroying a portal with this gate
+ */
+ public Integer getDestroyCost() {
+ return destroyCost < 0 ? Stargate.getEconomyConfig().getDefaultDestroyCost() : destroyCost;
+ }
+
+ /**
+ * Gets whether portal payments go to this portal's owner
+ *
+ * @return Whether portal payments go to the owner
+ */
+ public Boolean getToOwner() {
+ return toOwner;
+ }
+
+ /**
+ * Checks whether a portal's gate matches this gate type
+ *
+ * @param topLeft The top-left block of the portal's gate
+ * @param yaw The yaw when looking directly outwards
+ * @return True if this gate matches the portal
+ */
+ public boolean matches(BlockLocation topLeft, double yaw) {
+ return matches(topLeft, yaw, false);
+ }
+
+ /**
+ * Checks whether a portal's gate matches this gate type
+ *
+ * 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.
+ *
+ * @param topLeft The top-left block of the portal's gate
+ * @param yaw The yaw when looking directly outwards
+ * @param onCreate Whether this is used in the context of creating a new gate
+ * @return True if this gate matches the portal
+ */
+ public boolean matches(BlockLocation topLeft, double yaw, boolean onCreate) {
+ return verifyGateEntrancesMatch(topLeft, yaw, onCreate) && verifyGateBorderMatches(topLeft, yaw);
+ }
+
+ /**
+ * Verifies that all border blocks of a portal matches this gate type
+ *
+ * @param topLeft The top-left block of the portal
+ * @param yaw The yaw when looking directly outwards from the portal
+ * @return True if all border blocks of the gate match the layout
+ */
+ private boolean verifyGateBorderMatches(BlockLocation topLeft, double yaw) {
+ Map characterMaterialMap = new HashMap<>(this.characterMaterialMap);
+ for (RelativeBlockVector borderVector : layout.getBorder()) {
+ int rowIndex = borderVector.getRight();
+ int lineIndex = borderVector.getDown();
+ Character key = layout.getLayout()[lineIndex][rowIndex];
+
+ Material materialInLayout = characterMaterialMap.get(key);
+ Material materialAtLocation = topLeft.getRelativeLocation(borderVector, yaw).getType();
+
+ if (materialInLayout == null) {
+ /* This generally should not happen with proper checking, but just in case a material character is not
+ * recognized, but still allowed in previous checks, verify the gate as long as all such instances of
+ * the character correspond to the same material in the physical gate. All subsequent gates will also
+ * need to match the first verified gate. */
+ characterMaterialMap.put(key, materialAtLocation);
+ Stargate.debug("Gate::Matches", String.format("Missing layout material in %s. Using %s from the" +
+ " physical portal.", getFilename(), materialAtLocation));
+ } else if (materialAtLocation != materialInLayout) {
+ Stargate.debug("Gate::Matches", String.format("Block Type Mismatch: %s != %s",
+ materialAtLocation, materialInLayout));
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Verifies that all entrances of a portal gate matches this gate type
+ *
+ * @param topLeft The top-left block of this portal
+ * @param yaw The yaw when looking directly outwards
+ * @param onCreate Whether this is used in the context of creating a new gate
+ * @return Whether this is used in the context of creating a new gate
+ */
+ private boolean verifyGateEntrancesMatch(BlockLocation topLeft, double yaw, boolean onCreate) {
+ Stargate.debug("verifyGateEntrancesMatch", String.valueOf(topLeft));
+ for (RelativeBlockVector entranceVector : layout.getEntrances()) {
+ Stargate.debug("verifyGateEntrancesMatch", String.valueOf(entranceVector));
+ Material type = topLeft.getRelativeLocation(entranceVector, yaw).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 (type != portalClosedBlock && type != portalOpenBlock) {
+ Stargate.debug("Gate::Matches", "Entrance/Exit Material Mismatch: " + type);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Saves this gate to a file
+ *
+ * This method will save the gate to its filename in the given folder.
+ *
+ * @param gateFolder The folder to save the gate file in
+ */
+ public void save(String gateFolder) {
+ try {
+ BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(gateFolder + filename));
+
+ //Save main material names
+ writeConfig(bufferedWriter, "portal-open", portalOpenBlock.name());
+ writeConfig(bufferedWriter, "portal-closed", portalClosedBlock.name());
+ writeConfig(bufferedWriter, "button", portalButton.name());
+
+ //Save the values necessary for economy
+ saveEconomyValues(bufferedWriter);
+
+ //Store material types to use for frame blocks
+ saveFrameBlockTypes(bufferedWriter);
+
+ bufferedWriter.newLine();
+
+ //Save the gate layout
+ layout.saveLayout(bufferedWriter);
+
+ bufferedWriter.close();
+ } catch (IOException ex) {
+ Stargate.logSevere(String.format("Could not save Gate %s - %s", filename, ex.getMessage()));
+ }
+ }
+
+ /**
+ * Saves current economy related values using a buffered writer
+ *
+ * @param bufferedWriter The buffered writer to write to
+ * @throws IOException If unable to write to the buffered writer
+ */
+ private void saveEconomyValues(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 The buffered writer to write to
+ * @throws IOException If unable to write to the buffered writer
+ */
+ private void saveFrameBlockTypes(BufferedWriter bufferedWriter) throws IOException {
+ for (Map.Entry entry : characterMaterialMap.entrySet()) {
+ Character type = entry.getKey();
+ Material value = entry.getValue();
+ //Skip characters not part of the frame
+ if (type.equals(GateHandler.getAnythingCharacter()) ||
+ type.equals(GateHandler.getEntranceCharacter()) ||
+ type.equals(GateHandler.getExitCharacter())) {
+ continue;
+ }
+
+ bufferedWriter.append(type);
+ bufferedWriter.append('=');
+ if (value != null) {
+ bufferedWriter.append(value.toString());
+ }
+ bufferedWriter.newLine();
+ }
+ }
+
+ /**
+ * Writes a formatted string to a buffered writer
+ *
+ * @param bufferedWriter The buffered writer to write the formatted string to
+ * @param key The config key to save
+ * @param value The config value to save
+ * @throws IOException If unable to write to the buffered writer
+ */
+ private void writeConfig(BufferedWriter bufferedWriter, String key, 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();
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/property/gate/GateHandler.java b/src/main/java/net/knarcraft/stargate/portal/property/gate/GateHandler.java
new file mode 100644
index 0000000..9558175
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/property/gate/GateHandler.java
@@ -0,0 +1,347 @@
+package net.knarcraft.stargate.portal.property.gate;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.utility.GateReader;
+import net.knarcraft.stargate.utility.MaterialHelper;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+
+import 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 HashMap gates = new HashMap<>();
+ private static final HashMap> controlBlocks = new HashMap<>();
+
+ private GateHandler() {
+
+ }
+
+ /**
+ * Gets the character used for blocks that are not part of the gate
+ *
+ * @return The character used for blocks that are not part of the gate
+ */
+ public static Character getAnythingCharacter() {
+ return ANYTHING;
+ }
+
+ /**
+ * Gets the character used for defining the entrance
+ *
+ * @return The character used for defining the entrance
+ */
+ public static Character getEntranceCharacter() {
+ return ENTRANCE;
+ }
+
+ /**
+ * Gets the character used for defining the exit
+ *
+ * @return The character used for defining the exit
+ */
+ public static Character getExitCharacter() {
+ return EXIT;
+ }
+
+
+ /**
+ * Gets the character used for defining control blocks
+ *
+ * @return The character used for defining control blocks
+ */
+ public static Character getControlBlockCharacter() {
+ return CONTROL_BLOCK;
+ }
+
+ /**
+ * Register a gate into the list of available gates
+ *
+ * @param gate The gate to register
+ */
+ private static void registerGate(Gate gate) {
+ gates.put(gate.getFilename(), gate);
+
+ Material blockID = gate.getControlBlock();
+
+ if (!controlBlocks.containsKey(blockID)) {
+ controlBlocks.put(blockID, new ArrayList<>());
+ }
+
+ controlBlocks.get(blockID).add(gate);
+ }
+
+ /**
+ * Loads a gate from a file
+ *
+ * @param file The file containing the gate data
+ * @return The loaded gate, or null if unable to load the gate
+ */
+ private static Gate loadGate(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 The name of the file containing the gate data
+ * @param parentFolder The parent folder of the gate data file
+ * @param scanner The scanner to use for reading the gate data
+ * @return The loaded gate or null if unable to load the gate
+ */
+ private static Gate loadGate(String fileName, String parentFolder, Scanner scanner) {
+ List> design = new ArrayList<>();
+ Map characterMaterialMap = new HashMap<>();
+ Map config = new HashMap<>();
+ Set frameTypes = new HashSet<>();
+
+ //Initialize character to material map
+ characterMaterialMap.put(ENTRANCE, Material.AIR);
+ characterMaterialMap.put(EXIT, Material.AIR);
+ characterMaterialMap.put(ANYTHING, Material.AIR);
+
+ //Read the file into appropriate lists and maps
+ int columns = readGateFile(scanner, characterMaterialMap, fileName, design, frameTypes, 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 The config map to get configuration values from
+ * @param fileName The name of the saved gate config file
+ * @param layout The layout matrix of the new gate
+ * @param characterMaterialMap A map between layout characters and the material to use
+ * @return A new gate, or null if the config is invalid
+ */
+ private static Gate createGate(Map config, String fileName, Character[][] layout,
+ Map characterMaterialMap) {
+ //Read relevant material types
+ Material portalOpenBlock = readGateConfig(config, fileName, "portal-open", defaultPortalBlockOpen);
+ Material portalClosedBlock = readGateConfig(config, fileName, "portal-closed", defaultPortalBlockClosed);
+ Material portalButton = readGateConfig(config, fileName, "button", defaultButton);
+
+ //Read economy values
+ int useCost = GateReader.readGateConfig(config, fileName, "usecost");
+ int createCost = GateReader.readGateConfig(config, fileName, "createcost");
+ int destroyCost = GateReader.readGateConfig(config, fileName, "destroycost");
+ boolean toOwner = (config.containsKey("toowner") ? Boolean.parseBoolean(config.get("toowner")) :
+ Stargate.getEconomyConfig().sendPaymentToOwner());
+
+ //Create the new gate
+ Gate gate = new Gate(fileName, new GateLayout(layout), characterMaterialMap, portalOpenBlock, portalClosedBlock,
+ portalButton, useCost, createCost, destroyCost, toOwner);
+
+ if (!validateGate(gate, fileName)) {
+ return null;
+ }
+ return gate;
+ }
+
+ /**
+ * Validates that a gate is valid
+ *
+ * @param gate The gate to validate
+ * @param fileName The filename of the loaded gate file
+ * @return True if the gate is valid. False otherwise
+ */
+ private static boolean validateGate(Gate gate, 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 (!MaterialHelper.isButtonCompatible(gate.getPortalButton())) {
+ Stargate.logSevere(String.format(failString, "Gate button must be a type of button."));
+ return false;
+ }
+
+ if (!gate.getPortalOpenBlock().isBlock()) {
+ Stargate.logSevere(String.format(failString, "Gate open block must be a type of block."));
+ return false;
+ }
+
+ if (!gate.getPortalClosedBlock().isBlock()) {
+ Stargate.logSevere(String.format(failString, "Gate closed block must be a type of block."));
+ return false;
+ }
+
+ for (Material material : gate.getCharacterMaterialMap().values()) {
+ if (!material.isBlock()) {
+ Stargate.logSevere(String.format(failString, "Every gate border block must be a type of block."));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Loads all gates inside the given folder
+ *
+ * @param gateFolder The folder containing the gates
+ */
+ public static void loadGates(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 The folder containing gate config files
+ */
+ public static void writeDefaultGatesToFolder(String gateFolder) {
+ loadGateFromJar("nethergate.gate", gateFolder);
+ loadGateFromJar("watergate.gate", gateFolder);
+ loadGateFromJar("endgate.gate", gateFolder);
+ loadGateFromJar("squarenetherglowstonegate.gate", gateFolder);
+ }
+
+ /**
+ * Loads the given gate file from within the Jar's resources directory
+ *
+ * @param gateFile The name of the gate file
+ * @param gateFolder The folder containing gates
+ */
+ private static void loadGateFromJar(String gateFile, 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
+ *
+ * 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.
+ *
+ * @param block The control block to check
+ * @return A list of gates using the given control block
+ */
+ public static Gate[] getGatesByControlBlock(Block block) {
+ return getGatesByControlBlock(block.getType());
+ }
+
+ /**
+ * Gets the gates with the given control block
+ *
+ * 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.
+ *
+ * @param type The type of the control block to check
+ * @return A list of gates using the given material for control block
+ */
+ public static Gate[] getGatesByControlBlock(Material type) {
+ Gate[] result = new Gate[0];
+ List lookup = controlBlocks.get(type);
+
+ if (lookup != null) {
+ result = lookup.toArray(result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Gets a portal given its filename
+ *
+ * @param fileName The filename of the gate to get
+ * @return The gate with the given filename
+ */
+ public static Gate getGateByName(String fileName) {
+ return gates.get(fileName);
+ }
+
+ /**
+ * Gets the number of loaded gate configurations
+ *
+ * @return The number of loaded gate configurations
+ */
+ public static int getGateCount() {
+ return gates.size();
+ }
+
+ /**
+ * Clears all loaded gates and control blocks
+ */
+ public static void clearGates() {
+ gates.clear();
+ controlBlocks.clear();
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/property/gate/GateLayout.java b/src/main/java/net/knarcraft/stargate/portal/property/gate/GateLayout.java
new file mode 100644
index 0000000..540983e
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/property/gate/GateLayout.java
@@ -0,0 +1,209 @@
+package net.knarcraft.stargate.portal.property.gate;
+
+import net.knarcraft.stargate.container.RelativeBlockVector;
+
+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
+ *
+ * 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.
+ */
+public class GateLayout {
+
+ private final Character[][] layout;
+ private final List exits = new ArrayList<>();
+ private RelativeBlockVector[] entrances = new RelativeBlockVector[0];
+ private RelativeBlockVector[] border = new RelativeBlockVector[0];
+ private RelativeBlockVector[] controls = new RelativeBlockVector[0];
+ private RelativeBlockVector exitBlock = null;
+
+ /**
+ * Instantiates a new gate layout
+ *
+ * @param layout A character matrix describing the layout
+ */
+ public GateLayout(Character[][] layout) {
+ this.layout = layout;
+ readLayout();
+ }
+
+ /**
+ * Gets the character array describing this layout
+ *
+ * @return The character array describing this layout
+ */
+ public Character[][] getLayout() {
+ return this.layout;
+ }
+
+ /**
+ * Gets the locations of all entrances for this gate
+ *
+ * Entrances contain both the portal entrance blocks and the portal exit blocks.
+ *
+ * @return The locations of entrances for this gate
+ */
+ public RelativeBlockVector[] getEntrances() {
+ return entrances;
+ }
+
+ /**
+ * Gets the locations of border blocks for the gate described by this layout
+ *
+ * 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.
+ *
+ * @return The locations of border blocks for this gate
+ */
+ public RelativeBlockVector[] getBorder() {
+ return border;
+ }
+
+ /**
+ * Gets the exit block defined in the layout
+ *
+ * @return The exit block defined in the layout
+ */
+ public RelativeBlockVector getExit() {
+ return exitBlock;
+ }
+
+ /**
+ * Gets all possible exit locations defined in the layout
+ *
+ * This returns all blocks usable as exits. This basically means it returns the lowest block in each opening of
+ * the gate layout.
+ *
+ * @return All possible exits
+ */
+ public List getExits() {
+ return exits;
+ }
+
+ /**
+ * Gets the locations of the control blocks for this gate
+ *
+ * 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.
+ *
+ * @return The locations of the control blocks for this gate
+ */
+ public RelativeBlockVector[] getControls() {
+ return controls;
+ }
+
+ /**
+ * Saves the gate layout using a buffered writer
+ *
+ * @param bufferedWriter The buffered writer to write to
+ * @throws IOException If unable to write to the buffered writer
+ */
+ public void saveLayout(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
+ *
+ * This methods reads the layout and stores exits, entrances, border blocks and control blocks.
+ */
+ private void readLayout() {
+ List entranceList = new ArrayList<>();
+ List borderList = new ArrayList<>();
+ List 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 The list of control blocks to save to
+ * @param entranceList The list of entrances to save to
+ * @param borderList The list of border blocks to save to
+ */
+ private void readLayout(List controlList, List entranceList,
+ List 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 RelativeBlockVector(x, exitDepths[x], 0));
+ }
+ }
+ }
+
+ /**
+ * Parses one character of the layout
+ *
+ * @param key The read character
+ * @param columnIndex The column containing the read character
+ * @param rowIndex The row containing the read character
+ * @param exitDepths The list of exit depths to save to
+ * @param controlList The list of control blocks to save to
+ * @param entranceList The list of entrances to save to
+ * @param borderList The list of border blocks to save to
+ */
+ private void parseLayoutCharacter(Character key, int columnIndex, int rowIndex, int[] exitDepths,
+ List controlList, List entranceList,
+ List borderList) {
+ //Add control blocks to the control block list
+ if (key.equals(GateHandler.getControlBlockCharacter())) {
+ controlList.add(new RelativeBlockVector(columnIndex, rowIndex, 0));
+ }
+
+ if (isOpening(key)) {
+ //Register entrance
+ entranceList.add(new RelativeBlockVector(columnIndex, rowIndex, 0));
+ //Overwrite the lowest exit location for this column/x-coordinate
+ exitDepths[columnIndex] = rowIndex;
+ //Register exit if found
+ if (key.equals(GateHandler.getExitCharacter())) {
+ this.exitBlock = new RelativeBlockVector(columnIndex, rowIndex, 0);
+ }
+ } else if (!key.equals(GateHandler.getAnythingCharacter())) {
+ //Register border block
+ borderList.add(new RelativeBlockVector(columnIndex, rowIndex, 0));
+ }
+ }
+
+ /**
+ * Checks whether the given character represents a gate opening
+ *
+ * @param character The character to check
+ * @return True if the character represents an opening
+ */
+ private boolean isOpening(Character character) {
+ return character.equals(GateHandler.getEntranceCharacter()) || character.equals(GateHandler.getExitCharacter());
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/teleporter/EntityTeleporter.java b/src/main/java/net/knarcraft/stargate/portal/teleporter/EntityTeleporter.java
new file mode 100644
index 0000000..e963c92
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/teleporter/EntityTeleporter.java
@@ -0,0 +1,34 @@
+package net.knarcraft.stargate.portal.teleporter;
+
+import net.knarcraft.stargate.event.StargateEntityPortalEvent;
+import net.knarcraft.stargate.portal.Portal;
+import org.bukkit.entity.Entity;
+
+/**
+ * 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 The portal which is the target of the teleportation
+ */
+ public EntityTeleporter(Portal targetPortal, Entity teleportingEntity) {
+ super(targetPortal, teleportingEntity);
+ this.teleportingEntity = teleportingEntity;
+ }
+
+ /**
+ * Teleports an entity to this teleporter's portal
+ *
+ * @param origin The portal the entity is teleporting from
+ * @return True if the entity was teleported. False otherwise
+ */
+ public boolean teleportEntity(Portal origin) {
+ return teleport(origin, new StargateEntityPortalEvent(teleportingEntity, origin, portal, exit));
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/teleporter/PlayerTeleporter.java b/src/main/java/net/knarcraft/stargate/portal/teleporter/PlayerTeleporter.java
new file mode 100644
index 0000000..3460340
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/teleporter/PlayerTeleporter.java
@@ -0,0 +1,78 @@
+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 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 The portal which is the target of the teleportation
+ * @param player The teleporting player
+ */
+ public PlayerTeleporter(Portal targetPortal, Player player) {
+ super(targetPortal, player);
+ this.player = player;
+ }
+
+ /**
+ * Teleports a player to this teleporter's portal
+ *
+ * @param origin The portal the player teleports from
+ * @param event The player move event triggering the event
+ */
+ public void teleportPlayer(Portal origin, PlayerMoveEvent event) {
+ double velocity = player.getVelocity().length();
+ List 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.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);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/teleporter/Teleporter.java b/src/main/java/net/knarcraft/stargate/portal/teleporter/Teleporter.java
new file mode 100644
index 0000000..7e1f7ee
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/teleporter/Teleporter.java
@@ -0,0 +1,319 @@
+package net.knarcraft.stargate.portal.teleporter;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.container.ChunkUnloadRequest;
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import net.knarcraft.stargate.event.StargateTeleportEvent;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.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.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.Vector;
+
+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 The portal which is the target of the teleportation
+ * @param teleportedEntity The entity teleported by this teleporter
+ */
+ public Teleporter(Portal portal, Entity teleportedEntity) {
+ this.portal = portal;
+ this.scheduler = Stargate.getInstance().getServer().getScheduler();
+ this.teleportedEntity = teleportedEntity;
+ this.exit = getExit(teleportedEntity);
+ }
+
+ /**
+ * Teleports an entity
+ *
+ * @param origin The portal the entity teleported from
+ * @param stargateTeleportEvent The event to call to make sure the teleportation is valid
+ * @return True if the teleportation was successfully performed
+ */
+ public boolean teleport(Portal origin, StargateTeleportEvent stargateTeleportEvent) {
+ List passengers = teleportedEntity.getPassengers();
+
+ //Call the StargateEntityPortalEvent to allow plugins to change destination
+ if (!origin.equals(portal)) {
+ 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 The exit location of this teleporter
+ */
+ public Location getExit() {
+ return exit.clone();
+ }
+
+ /**
+ * Triggers the entity portal event to allow plugins to change the exit location
+ *
+ * @param origin The origin portal teleported from
+ * @param stargateTeleportEvent The exit location to teleport the entity to
+ * @return The location the entity should be teleported to, or null if the event was cancelled
+ */
+ protected Location triggerPortalEvent(Portal origin, 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 The location the entity will exit from
+ */
+ protected void adjustExitLocationRotation(Location exit) {
+ int adjust = 0;
+ if (portal.getOptions().isBackwards()) {
+ adjust = 180;
+ }
+ float newYaw = (portal.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 The relative exit defined as the portal's exit
+ * @param exitLocation The currently calculated portal exit
+ * @param entity The travelling entity
+ * @return A location which won't suffocate the entity inside the portal
+ */
+ private Location preventExitSuffocation(RelativeBlockVector relativeExit, Location exitLocation, 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.getRight() - openingLeft.getRight() + 1;
+ int existingOffset = relativeExit.getRight() - openingLeft.getRight();
+ double newOffset = (openingWidth - existingOffset) / 2D;
+
+ //Remove the half offset for better centering
+ if (openingWidth > 1) {
+ newOffset -= 0.5;
+ }
+ exitLocation = DirectionHelper.moveLocation(exitLocation, newOffset, 0, 0, portal.getYaw());
+
+ //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 The current exit location to adjust
+ * @param entity The entity to adjust the exit location for
+ * @return The adjusted exit location
+ */
+ private Location moveExitLocationOutwards(Location exitLocation, 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.getYaw());
+ }
+ return exitLocation;
+ }
+
+ /**
+ * Gets one of the edges of a portal's opening/exit
+ *
+ * @param relativeExit The known exit to start from
+ * @param direction The direction to move (+1 for right, -1 for left)
+ * @return The right or left edge of the opening
+ */
+ private RelativeBlockVector getPortalExitEdge(RelativeBlockVector relativeExit, int direction) {
+ RelativeBlockVector openingEdge = relativeExit;
+
+ do {
+ RelativeBlockVector possibleOpening = new RelativeBlockVector(openingEdge.getRight() + direction,
+ openingEdge.getDown(), openingEdge.getOut());
+ 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
+ *
+ * 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.
+ *
+ * @param entity The travelling entity
+ * @param exitLocation The exit location generated
+ * @return The location the travelling entity should be teleported to
+ */
+ private Location adjustExitLocationHeight(Entity entity, 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 The entity to teleport (used to determine distance from portal to avoid suffocation)
+ * @return The location the entity should be teleported to.
+ */
+ private Location getExit(Entity entity) {
+ Location exitLocation = null;
+ RelativeBlockVector relativeExit = portal.getGate().getLayout().getExit();
+ if (relativeExit != null) {
+ BlockLocation exit = portal.getBlockAt(relativeExit);
+
+ //Move one block out to prevent exiting inside the portal
+ float portalYaw = portal.getYaw();
+ if (portal.getOptions().isBackwards()) {
+ portalYaw += 180;
+ }
+ exitLocation = exit.getRelativeLocation(0D, 0D, 1, portalYaw);
+
+ if (entity != null) {
+ double entitySize = EntityHelper.getEntityMaxSize(entity);
+ //Prevent exit suffocation for players riding horses or similar
+ if (entitySize > 1) {
+ exitLocation = preventExitSuffocation(relativeExit, exitLocation, entity);
+ }
+ }
+ } else {
+ Stargate.logWarning(String.format("Missing destination point in .gate file %s",
+ portal.getGate().getFilename()));
+ }
+
+ //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 A list of chunks to load
+ */
+ private List getChunksToLoad() {
+ List chunksToLoad = new ArrayList<>();
+ for (RelativeBlockVector vector : portal.getGate().getLayout().getEntrances()) {
+ BlockLocation 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;
+ Location fiveBlocksForward = DirectionHelper.moveLocation(entranceLocation, 0, 0, blockOffset,
+ portal.getYaw());
+ //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;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/portal/teleporter/VehicleTeleporter.java b/src/main/java/net/knarcraft/stargate/portal/teleporter/VehicleTeleporter.java
new file mode 100644
index 0000000..4f3e42c
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/portal/teleporter/VehicleTeleporter.java
@@ -0,0 +1,183 @@
+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 java.util.List;
+
+/**
+ * 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 The targetPortal which is the target of the teleportation
+ * @param teleportingVehicle The teleporting vehicle
+ */
+ public VehicleTeleporter(Portal targetPortal, Vehicle teleportingVehicle) {
+ super(targetPortal, teleportingVehicle);
+ this.teleportingVehicle = teleportingVehicle;
+ }
+
+ /**
+ * Teleports a vehicle to this teleporter's portal
+ *
+ * It is assumed that if a vehicle contains any players, their permissions have already been validated before
+ * calling this method.
+ *
+ * @param origin The portal the vehicle is teleporting from
+ * @return True if the vehicle was teleported. False otherwise
+ */
+ @Override
+ public boolean teleportEntity(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.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 The location the vehicle should be teleported to
+ * @param newVelocity The velocity to give the vehicle right after teleportation
+ * @param origin The portal the vehicle teleported from
+ * @return True if the vehicle was teleported. False otherwise
+ */
+ private boolean teleportVehicle(Location exit, Vector newVelocity, Portal origin) {
+ //Load chunks to make sure not to teleport to the void
+ loadChunks();
+
+ List 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 The passengers to teleport
+ * @return True if the passengers are allowed to teleport
+ */
+ private boolean vehiclePassengersAllowed(List 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 The passengers of the vehicle
+ * @param exit The location the vehicle will exit
+ * @param newVelocity The new velocity of the teleported vehicle
+ * @param origin The portal the vehicle teleported from
+ */
+ private void teleportVehicle(List passengers, Location exit, Vector newVelocity, 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
+ *
+ * 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.
+ *
+ * @param passengers A list of all passengers in the vehicle
+ * @param exit The exit location to spawn the new vehicle on
+ * @param newVelocity The new velocity of the new vehicle
+ * @param origin The portal the vehicle teleported from
+ */
+ private void putPassengersInNewVehicle(List passengers, Location exit,
+ Vector newVelocity, Portal origin) {
+ World vehicleWorld = exit.getWorld();
+ if (vehicleWorld == null) {
+ Stargate.logWarning("Unable to get the world to teleport the vehicle to");
+ return;
+ }
+ //Spawn a new vehicle
+ Vehicle newVehicle = vehicleWorld.spawn(exit, teleportingVehicle.getClass());
+ if (teleportingVehicle instanceof Boat boat) {
+ ((Boat) newVehicle).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);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/thread/BlockChangeThread.java b/src/main/java/net/knarcraft/stargate/thread/BlockChangeThread.java
new file mode 100644
index 0000000..faa14fc
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/thread/BlockChangeThread.java
@@ -0,0 +1,83 @@
+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;
+
+/**
+ * This thread changes gate blocks to display a gate as open or closed
+ *
+ * This thread fetches some entries from blockPopulateQueue each time it's called.
+ */
+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 True if the queue is empty and it's safe to quit
+ */
+ public static boolean pollQueue() {
+ //Abort if there's no work to be done
+ BlockChangeRequest blockChangeRequest = Stargate.getBlockChangeRequestQueue().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 The block to fix
+ */
+ private static void fixEndGatewayGate(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 The block to orient
+ * @param axis The axis to use for orienting the block
+ */
+ private static void orientBlock(Block block, Axis axis) {
+ Orientable orientable = (Orientable) block.getBlockData();
+ orientable.setAxis(axis);
+ block.setBlockData(orientable);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/thread/ChunkUnloadThread.java b/src/main/java/net/knarcraft/stargate/thread/ChunkUnloadThread.java
new file mode 100644
index 0000000..598a191
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/thread/ChunkUnloadThread.java
@@ -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 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();
+ }
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/thread/StarGateThread.java b/src/main/java/net/knarcraft/stargate/thread/StarGateThread.java
new file mode 100644
index 0000000..0c02f50
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/thread/StarGateThread.java
@@ -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 The current time
+ */
+ private void closeOpenPortals(long time) {
+ List closedPortals = new ArrayList<>();
+ Queue 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 The current time
+ */
+ private void deactivateActivePortals(long time) {
+ List deactivatedPortals = new ArrayList<>();
+ Queue 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);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/BungeeHelper.java b/src/main/java/net/knarcraft/stargate/utility/BungeeHelper.java
new file mode 100644
index 0000000..677e80f
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/BungeeHelper.java
@@ -0,0 +1,221 @@
+package net.knarcraft.stargate.utility;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
+import net.md_5.bungee.api.ChatColor;
+import org.bukkit.entity.Player;
+import org.bukkit.event.player.PlayerMoveEvent;
+
+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 bungeeQueue = new HashMap<>();
+
+ private BungeeHelper() {
+
+ }
+
+ /**
+ * Get the plugin message channel use for BungeeCord messages
+ *
+ * @return The bungee plugin channel
+ */
+ public static String getBungeeChannel() {
+ return bungeeChannel;
+ }
+
+ /**
+ * Removes a player from the queue of players teleporting through BungeeCord
+ *
+ * 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.
+ *
+ * @param playerUUID The UUID of the player to remove
+ * @return The name of the destination portal the player should be teleported to
+ */
+ public static String removeFromQueue(UUID playerUUID) {
+ return bungeeQueue.remove(playerUUID);
+ }
+
+ /**
+ * Sends a plugin message to BungeeCord allowing the target server to catch it
+ *
+ * @param player The teleporting player
+ * @param entrancePortal The portal the player is teleporting from
+ * @return True if the message was successfully sent
+ */
+ public static boolean sendTeleportationMessage(Player player, Portal entrancePortal) {
+ try {
+ //Build the teleportation message, format is delimiter
+ 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(stripColor(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 ex) {
+ Stargate.logSevere("Error sending BungeeCord teleport packet");
+ ex.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sends the bungee message necessary to make a player connect to another server
+ *
+ * @param player The player to teleport
+ * @param entrancePortal The bungee portal the player is teleporting from
+ * @return True if the plugin message was sent successfully
+ */
+ public static boolean changeServer(Player player, Portal entrancePortal) {
+ try {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
+
+ //Send a connect-message to connect the player to the server defined in the entrance portal's network line
+ dataOutputStream.writeUTF("Connect");
+ dataOutputStream.writeUTF(stripColor(entrancePortal.getNetwork()));
+
+ //Send the plugin message
+ player.sendPluginMessage(Stargate.getInstance(), bungeeChannel, byteArrayOutputStream.toByteArray());
+ } catch (IOException ex) {
+ Stargate.logSevere("Error sending BungeeCord connect packet");
+ ex.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Reads a plugin message byte array to a string if it's sent from another stargate plugin
+ *
+ * @param message The byte array to read
+ * @return The message contained in the byte array, or null on failure
+ */
+ 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 ex) {
+ Stargate.logSevere("Error receiving BungeeCord message");
+ ex.printStackTrace();
+ return null;
+ }
+ return new String(data);
+ }
+
+ /**
+ * Handles the receival of a teleport message
+ *
+ * @param receivedMessage The received teleport message
+ */
+ public static void handleTeleportMessage(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 = PortalHandler.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 The player to teleport
+ * @param entrancePortal The gate the player is entering from
+ * @param event The event causing the teleportation
+ * @return True if the teleportation was successful
+ */
+ public static boolean bungeeTeleport(Player player, Portal entrancePortal, PlayerMoveEvent event) {
+ //Check if bungee is actually enabled
+ if (!Stargate.getGateConfig().enableBungee()) {
+ if (!entrancePortal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeDisabled"));
+ }
+ 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;
+ }
+
+ /**
+ * Strips all color tags from a string
+ *
+ * @param string The string to strip color from
+ * @return The string without color codes
+ */
+ private static String stripColor(String string) {
+ return ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', string));
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/DirectionHelper.java b/src/main/java/net/knarcraft/stargate/utility/DirectionHelper.java
new file mode 100644
index 0000000..c42fe6d
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/DirectionHelper.java
@@ -0,0 +1,147 @@
+package net.knarcraft.stargate.utility;
+
+import org.bukkit.Location;
+import org.bukkit.block.BlockFace;
+import org.bukkit.util.Vector;
+
+/**
+ * This class helps with direction-related calculations
+ */
+public final class DirectionHelper {
+
+ private DirectionHelper() {
+
+ }
+
+ /**
+ * Gets a yaw by comparing two locations
+ *
+ * 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.
+ *
+ * @param location1 The first location, which works as the origin
+ * @param location2 The second location, which the yaw will point towards
+ * @return The yaw pointing from the first location to the second location
+ */
+ public static float getYawFromLocationDifference(Location location1, 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 given a yaw value
+ *
+ * The supplied yaw must be a value such that (yaw mod 90) = 0. If not, an exception is thrown.
+ *
+ * @param yaw The yaw value to convert
+ * @return The block face the yaw corresponds to
+ */
+ public static BlockFace getBlockFaceFromYaw(double yaw) {
+ //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 direction vector given a yaw
+ *
+ * @param yaw The yaw to convert to a direction vector
+ * @return The direction vector pointing in the same direction as the yaw
+ */
+ public static Vector getDirectionVectorFromYaw(double yaw) {
+ //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
+ *
+ * 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.
+ *
+ * @param location The location to start at
+ * @param right The amount to go right
+ * @param down The amount to go downward
+ * @param out The amount to go outward
+ * @param yaw The yaw when looking directly outwards from a portal
+ * @return A location relative to the given location
+ */
+ public static Location moveLocation(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 The amount of rightward steps from the top-left origin
+ * @param down The amount of downward steps from the top-left origin
+ * @param out The distance outward from the top-left origin
+ * @param yaw The yaw when looking directly outwards from a portal
+ * @return A normal vector
+ */
+ public static Vector getCoordinateVectorFromRelativeVector(double right, double down, double out, double 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));
+ }
+ }
+
+ /**
+ * Normalizes a yaw to make it positive and no larger than 360 degrees
+ *
+ * @param yaw The yaw to normalize
+ * @return The normalized yaw
+ */
+ private static double normalizeYaw(double yaw) {
+ while (yaw < 0) {
+ yaw += 360;
+ }
+ yaw = yaw % 360;
+ return yaw;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/EconomyHelper.java b/src/main/java/net/knarcraft/stargate/utility/EconomyHelper.java
new file mode 100644
index 0000000..6c58e52
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/EconomyHelper.java
@@ -0,0 +1,248 @@
+package net.knarcraft.stargate.utility;
+
+import net.knarcraft.knarlib.formatting.StringFormatter;
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.config.EconomyConfig;
+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.entity.Player;
+
+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 The portal the player is entering
+ * @param player The player wishing to teleport
+ * @param cost The cost of teleportation
+ * @return False if payment was successful. True if the payment was unsuccessful
+ */
+ public static boolean cannotPayTeleportFee(Portal entrancePortal, 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 The name of the used portal
+ * @param portalOwner The owner of the portal
+ * @param earnings The amount the owner earned
+ */
+ public static void sendObtainMessage(String portalName, Player portalOwner, int earnings) {
+ String obtainedMsg = Stargate.getString("ecoObtain");
+ obtainedMsg = replacePlaceholders(obtainedMsg, portalName, earnings);
+ Stargate.getMessageSender().sendSuccessMessage(portalOwner, obtainedMsg);
+ }
+
+ /**
+ * Sends a message telling the user how much they paid for interacting with a portal
+ *
+ * @param portalName The name of the portal interacted with
+ * @param player The interacting player
+ * @param cost The cost of the interaction
+ */
+ public static void sendDeductMessage(String portalName, Player player, int cost) {
+ String deductMsg = Stargate.getString("ecoDeduct");
+ deductMsg = replacePlaceholders(deductMsg, portalName, cost);
+ Stargate.getMessageSender().sendSuccessMessage(player, deductMsg);
+ }
+
+ /**
+ * Sends a message telling the user they don't have enough funds to do a portal interaction
+ *
+ * @param portalName The name of the portal interacted with
+ * @param player The interacting player
+ * @param cost The cost of the interaction
+ */
+ public static void sendInsufficientFundsMessage(String portalName, Player player, int cost) {
+ String inFundMsg = Stargate.getString("ecoInFunds");
+ inFundMsg = replacePlaceholders(inFundMsg, portalName, cost);
+ Stargate.getMessageSender().sendErrorMessage(player, inFundMsg);
+ }
+
+ /**
+ * Sends a message telling the user how much they are refunded for breaking their portal
+ *
+ * @param portalName The name of the broken portal
+ * @param player The player breaking the portal
+ * @param cost The amount the user has to pay for destroying the portal. (expects a negative value)
+ */
+ public static void sendRefundMessage(String portalName, Player player, int cost) {
+ String refundMsg = Stargate.getString("ecoRefund");
+ refundMsg = replacePlaceholders(refundMsg, portalName, -cost);
+ Stargate.getMessageSender().sendSuccessMessage(player, refundMsg);
+ }
+
+ /**
+ * Determines the cost of using a gate
+ *
+ * @param player The player trying to use the gate
+ * @param source The source/entry portal
+ * @param destination The destination portal
+ * @return The cost of using the portal
+ */
+ public static int getUseCost(Player player, Portal source, 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, "stargate.free.use")) {
+ return 0;
+ }
+
+ return source.getGate().getUseCost();
+ }
+
+ /**
+ * Charges the player for an action, if required
+ *
+ * @param player The player to take money from
+ * @param target The target to pay
+ * @param cost The cost of the transaction
+ * @return True if the player was charged successfully
+ */
+ public static boolean chargePlayerIfNecessary(Player player, UUID target, int cost) {
+ if (skipPayment(cost)) {
+ return true;
+ }
+ //Charge player
+ return chargePlayer(player, target, cost);
+ }
+
+ /**
+ * Charges a player
+ *
+ * @param player The player to charge
+ * @param amount The amount to charge
+ * @return True if the payment succeeded, or if no payment was necessary
+ */
+ private static boolean chargePlayer(Player player, double amount) {
+ Economy economy = Stargate.getEconomyConfig().getEconomy();
+ if (Stargate.getEconomyConfig().isEconomyEnabled() && economy != null) {
+ if (!economy.has(player, amount)) {
+ return false;
+ }
+ economy.withdrawPlayer(player, amount);
+ }
+ return true;
+ }
+
+ /**
+ * Charges the player for an action, if required
+ *
+ * @param player The player to take money from
+ * @param cost The cost of the transaction
+ * @return True if the player was charged successfully
+ */
+ public static boolean chargePlayerIfNecessary(Player player, int cost) {
+ if (skipPayment(cost)) {
+ return true;
+ }
+ //Charge player
+ return chargePlayer(player, cost);
+ }
+
+ /**
+ * Checks whether a payment transaction should be skipped
+ *
+ * @param cost The cost of the transaction
+ * @return True if the transaction should be skipped
+ */
+ private static boolean skipPayment(int cost) {
+ return cost == 0 || !Stargate.getEconomyConfig().useEconomy();
+ }
+
+ /**
+ * Charges a player, giving the charge to a target
+ *
+ * @param player The player to charge
+ * @param target The UUID of the player to pay
+ * @param amount The amount to charge
+ * @return True if the payment succeeded, or if no payment was necessary
+ */
+ private static boolean chargePlayer(Player player, 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 string
+ *
+ * @param message The message to replace variables in
+ * @param portalName The name of the relevant portal
+ * @param cost The cost for a given interaction
+ * @return The same string with cost and portal variables replaced
+ */
+ private static String replacePlaceholders(String message, String portalName, int cost) {
+ return StringFormatter.replacePlaceholders(message, new String[]{"%cost%", "%portal%"},
+ new String[]{Stargate.getEconomyConfig().format(cost), portalName});
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/EntityHelper.java b/src/main/java/net/knarcraft/stargate/utility/EntityHelper.java
new file mode 100644
index 0000000..04ed19f
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/EntityHelper.java
@@ -0,0 +1,38 @@
+package net.knarcraft.stargate.utility;
+
+import org.bukkit.entity.Entity;
+
+/**
+ * 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
+ *
+ * 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.
+ *
+ * @param entity The entity to get max size for
+ * @return The max size of the entity
+ */
+ public static int getEntityMaxSizeInt(Entity entity) {
+ return (int) Math.ceil((float) getEntityMaxSize(entity));
+ }
+
+ /**
+ * Gets the max size of an entity along its x and z axis
+ *
+ * @param entity The entity to get max size for
+ * @return The max size of the entity
+ */
+ public static double getEntityMaxSize(Entity entity) {
+ return Math.max(entity.getBoundingBox().getWidthX(), entity.getBoundingBox().getWidthZ());
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/GateReader.java b/src/main/java/net/knarcraft/stargate/utility/GateReader.java
new file mode 100644
index 0000000..0334019
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/GateReader.java
@@ -0,0 +1,210 @@
+package net.knarcraft.stargate.utility;
+
+import net.knarcraft.stargate.Stargate;
+import org.bukkit.Material;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+
+/**
+ * Helper class for reading gate files
+ */
+public final class GateReader {
+
+ private GateReader() {
+
+ }
+
+ /**
+ * Reads a gate file
+ *
+ * @param scanner The scanner to read from
+ * @param characterMaterialMap The map of characters to store valid symbols in
+ * @param fileName The filename of the loaded gate config file
+ * @param design The list to store the loaded design/layout to
+ * @param frameTypes The set to store frame/border materials to
+ * @param config The map of config values to store to
+ * @return The column count/width of the loaded gate
+ */
+ public static int readGateFile(Scanner scanner, Map characterMaterialMap, String fileName,
+ List> design, Set frameTypes, Map config) {
+ boolean designing = false;
+ int columns = 0;
+ try {
+ 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, frameTypes, 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;
+ } finally {
+ if (scanner != null) {
+ scanner.close();
+ }
+ }
+ return columns;
+ }
+
+ /**
+ * Reads one design line of the gate layout file
+ *
+ * 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.
+ *
+ * @param line The line to read
+ * @param maxColumns The current max columns value of the design
+ * @param characterMaterialMap The map between characters and the corresponding materials to use
+ * @param fileName The filename of the loaded gate config file
+ * @param design The two-dimensional list to store the loaded design to
+ * @return The new max columns value of the design
+ */
+ private static int readGateDesignLine(String line, int maxColumns, Map characterMaterialMap,
+ String fileName, List> design) {
+ List 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 The line to read
+ * @param characterMaterialMap The character to material map to store to
+ * @param frameTypes The set to store gate frame/border types to
+ * @param config The config value map to store to
+ * @throws Exception If an invalid material is encountered
+ */
+ private static void readGateConfigValue(String line, Map characterMaterialMap,
+ Set frameTypes, Map config) throws Exception {
+ 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);
+ Material material = Material.getMaterial(value);
+ if (material == null) {
+ throw new Exception("Invalid material in line: " + line);
+ }
+ //Register the map between the read symbol and the corresponding material
+ characterMaterialMap.put(symbol, material);
+ //Save the material as one of the frame materials used for this kind of gate
+ frameTypes.add(material);
+ } else {
+ //Read a normal config value
+ config.put(key, value);
+ }
+ }
+
+ /**
+ * Reads an integer configuration value
+ *
+ * @param config The configuration to read
+ * @param fileName The filename of the config file
+ * @param key The config key to read
+ * @return The read value, or -1 if it could not be read
+ */
+ public static int readGateConfig(Map config, String fileName, String key) {
+ if (config.containsKey(key)) {
+ try {
+ return Integer.parseInt(config.get(key));
+ } catch (NumberFormatException ex) {
+ Stargate.logWarning(String.format("%s reading %s: %s is not numeric", ex.getClass().getName(),
+ fileName, key));
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Reads a material configuration value
+ *
+ * @param config The configuration to read
+ * @param fileName The filename of the config file
+ * @param key The config key to read
+ * @param defaultMaterial The default material to use, in case the config is invalid
+ * @return The material specified in the config, or the default material if it could not be read
+ */
+ public static Material readGateConfig(Map config, String fileName, String key,
+ Material defaultMaterial) {
+ if (config.containsKey(key)) {
+ Material material = Material.getMaterial(config.get(key));
+ if (material != null) {
+ return material;
+ } else {
+ Stargate.logWarning(String.format("Error reading %s: %s is not a material", fileName, key));
+ }
+ }
+ return defaultMaterial;
+ }
+
+ /**
+ * Generates a matrix containing the gate layout
+ *
+ * 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.
+ *
+ * @param design The design of the gate layout
+ * @param columns The largest amount of columns in the design
+ * @return A matrix containing the gate's layout
+ */
+ public static Character[][] generateLayoutMatrix(List> design, int columns) {
+ Character[][] layout = new Character[design.size()][columns];
+ for (int lineIndex = 0; lineIndex < design.size(); lineIndex++) {
+ List 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;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/MaterialHelper.java b/src/main/java/net/knarcraft/stargate/utility/MaterialHelper.java
new file mode 100644
index 0000000..1d00239
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/MaterialHelper.java
@@ -0,0 +1,52 @@
+package net.knarcraft.stargate.utility;
+
+import org.bukkit.Material;
+import org.bukkit.Tag;
+
+/**
+ * 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 The material to check
+ * @return True if the material is a wall coral
+ */
+ public static boolean isWallCoral(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 The material to check
+ * @return True if the material is a container
+ */
+ public static boolean isContainer(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 The material to check
+ * @return True if the material can be used as a button
+ */
+ public static boolean isButtonCompatible(Material material) {
+ return Tag.BUTTONS.isTagged(material) || isWallCoral(material) || isContainer(material);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java b/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java
new file mode 100644
index 0000000..f87dc41
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java
@@ -0,0 +1,417 @@
+package net.knarcraft.stargate.utility;
+
+import net.knarcraft.stargate.Stargate;
+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 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 The player opening the portal
+ * @param portal The portal to open
+ */
+ public static void openPortal(Player player, 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().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("invalidMsg"));
+ }
+ 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;
+ }
+
+ //Deny access if another player has activated the portal, and it's still in use
+ if (!portal.getOptions().isFixed() && portal.getPortalActivator().isActive() &&
+ portal.getActivePlayer() != player) {
+ if (!portal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
+ }
+ return;
+ }
+
+ //Check if the player can use the private gate
+ if (portal.getOptions().isPrivate() && !PermissionHelper.canUsePrivatePortal(player, portal)) {
+ if (!portal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
+ }
+ return;
+ }
+
+ //Destination is currently in use by another player, blocking teleportation
+ if (destination.isOpen() && !destination.getOptions().isAlwaysOn()) {
+ if (!portal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("blockMsg"));
+ }
+ return;
+ }
+
+ //Open the portal
+ portal.getPortalOpener().openPortal(player, false);
+ }
+
+ /**
+ * Creates a StargateAccessEvent and gets the updated deny value
+ *
+ * The event is used for other plugins to bypass the permission checks.
+ *
+ * @param player The player trying to use the portal
+ * @param portal The portal the player is trying to use
+ * @param deny Whether the player's access has already been denied by a previous check
+ * @return False if the player should be allowed through the portal
+ */
+ public static boolean portalAccessDenied(Player player, 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 The player to check
+ * @param entrancePortal The portal the user wants to enter
+ * @param destination The portal the user wants to exit from
+ * @return False if the user is allowed to access the portal
+ */
+ public static boolean cannotAccessPortal(Player player, Portal entrancePortal, Portal destination) {
+ boolean deny = false;
+
+ if (entrancePortal.getOptions().isBungee()) {
+ if (!PermissionHelper.canAccessServer(player, entrancePortal.getCleanNetwork())) {
+ //If the portal is a bungee portal, and the player cannot access the server, deny
+ Stargate.debug("cannotAccessPortal", "Cannot access server");
+ deny = true;
+ }
+ } else if (PermissionHelper.cannotAccessNetwork(player, entrancePortal.getCleanNetwork())) {
+ //If the player does not have access to the network, deny
+ Stargate.debug("cannotAccessPortal", "Cannot access network");
+ deny = true;
+ } else if (PermissionHelper.cannotAccessWorld(player, destination.getWorld().getName())) {
+ //If the player does not have access to the portal's world, deny
+ Stargate.debug("cannotAccessPortal", "Cannot access world");
+ 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
+ *
+ * This is the same as player.hasPermission(), but this function allows for printing permission debugging info.
+ *
+ * @param player The player to check
+ * @param permission The permission to check
+ * @return True if the player has the permission
+ */
+ public static boolean hasPermission(Player player, String permission) {
+ if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
+ Stargate.debug("hasPerm::Permission(" + player.getName() + ")", permission + " => " +
+ player.hasPermission(permission));
+ }
+ return player.hasPermission(permission);
+ }
+
+ /**
+ * Check if a player has been given a permission implicitly
+ *
+ * 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.
+ *
+ * @param player The player to check
+ * @param permission The permission to check
+ * @return True if the player has the permission implicitly or explicitly
+ */
+ public static boolean hasPermissionImplicit(Player player, String permission) {
+ if (!player.isPermissionSet(permission)) {
+ if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
+ Stargate.debug("hasPermissionImplicit::Permission", permission + " => implicitly true");
+ }
+ return true;
+ }
+ if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
+ Stargate.debug("hasPermissionImplicit::Permission", permission + " => " +
+ player.hasPermission(permission));
+ }
+ return player.hasPermission(permission);
+ }
+
+ /**
+ * Checks whether a player can access the given world
+ *
+ * @param player The player trying to access the world
+ * @param world The world the player is trying to access
+ * @return False if the player should be allowed to access the world
+ */
+ public static boolean cannotAccessWorld(Player player, String world) {
+ //The player can access all worlds
+ if (hasPermission(player, "stargate.world")) {
+ //Check if the world permission has been explicitly denied
+ return !hasPermissionImplicit(player, "stargate.world." + world);
+ }
+ //The player can access the destination world
+ return !hasPermission(player, "stargate.world." + world);
+ }
+
+ /**
+ * Checks whether a player can access the given network
+ *
+ * @param player The player to check
+ * @param network The network to check
+ * @return True if the player is denied from accessing the network
+ */
+ public static boolean cannotAccessNetwork(Player player, String network) {
+ //The player can access all networks
+ if (hasPermission(player, "stargate.network")) {
+ //Check if the world permission has been explicitly denied
+ return !hasPermissionImplicit(player, "stargate.network." + network);
+ }
+ //Check if the player can access this network
+ if (hasPermission(player, "stargate.network." + network)) {
+ return false;
+ }
+ //Is able to create personal gates (Assumption is made they can also access them)
+ String playerName = player.getName();
+ if (playerName.length() > getMaxNameNetworkLength()) {
+ playerName = playerName.substring(0, getMaxNameNetworkLength());
+ }
+ return !network.equals(playerName) || !hasPermission(player, "stargate.create.personal");
+ }
+
+ /**
+ * Checks whether a player can access the given bungee server
+ *
+ * @param player The player trying to teleport
+ * @param server The server the player is trying to connect to
+ * @return True if the player is allowed to access the given server
+ */
+ public static boolean canAccessServer(Player player, String server) {
+ //The player can access all servers
+ if (hasPermission(player, "stargate.server")) {
+ //Check if the server permission has been explicitly denied
+ return hasPermissionImplicit(player, "stargate.server." + server);
+ }
+ //The player can access the destination server
+ return hasPermission(player, "stargate.server." + server);
+ }
+
+ /**
+ * Checks whether the given player can teleport the given stretch for free
+ *
+ * @param player The player trying to teleport
+ * @param src The portal the player is entering
+ * @param dest The portal the player wants to teleport to
+ * @return True if the player can travel for free
+ */
+ public static boolean isFree(Player player, Portal src, Portal dest) {
+ //This portal is free
+ if (src.getOptions().isFree()) {
+ return true;
+ }
+ //Player can use this portal for free
+ if (hasPermission(player, "stargate.free.use")) {
+ 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)
+ *
+ * This decides if the player can see the gate on the network selection screen
+ *
+ * @param player The player to check
+ * @param portal The portal to check
+ * @return True if the given player can see the given portal
+ */
+ public static boolean canSeePortal(Player player, Portal portal) {
+ //The portal is not hidden
+ if (!portal.getOptions().isHidden()) {
+ return true;
+ }
+ //The player can see all hidden portals
+ if (hasPermission(player, "stargate.admin.hidden")) {
+ 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 The player trying to use the portal
+ * @param portal The private portal used
+ * @return True if the player is allowed to use the portal
+ */
+ public static boolean canUsePrivatePortal(Player player, Portal portal) {
+ //Check if the player is the owner of the gate
+ if (portal.isOwner(player)) {
+ return true;
+ }
+ //The player is an admin with the ability to use private gates
+ return hasPermission(player, "stargate.admin.private");
+ }
+
+ /**
+ * Checks if the given player has access to the given portal option
+ *
+ * @param player The player trying to use the option
+ * @param option The option the player is trying to use
+ * @return True if the player is allowed to create a portal with the given option
+ */
+ public static boolean canUseOption(Player player, PortalOption option) {
+ return hasPermission(player, option.getPermissionString());
+ }
+
+ /**
+ * Checks if the given player is allowed to create gates on the given network
+ *
+ * @param player The player trying to create a new gate
+ * @param network The network the player is trying to create a gate on
+ * @return True if the player is allowed to create the new gate
+ */
+ public static boolean canCreateNetworkGate(Player player, String network) {
+ //Check if the player is allowed to create a portal on any network
+ if (hasPermission(player, "stargate.create.network")) {
+ //Check if the network has been explicitly denied
+ return hasPermissionImplicit(player, "stargate.create.network." + network);
+ }
+ //Check if the player is allowed to create on this specific network
+ return hasPermission(player, "stargate.create.network." + network);
+ }
+
+ /**
+ * Checks whether the given player is allowed to create a personal gate
+ *
+ * @param player The player trying to create the new gate
+ * @return True if the player is allowed
+ */
+ public static boolean canCreatePersonalPortal(Player player) {
+ return hasPermission(player, "stargate.create.personal");
+ }
+
+ /**
+ * Checks if the given player can create a portal with the given gate layout
+ *
+ * @param player The player trying to create a portal
+ * @param gate The gate type of the new portal
+ * @return True if the player is allowed to create a portal with the given gate layout
+ */
+ public static boolean canCreatePortal(Player player, String gate) {
+ //Check if the player is allowed to create all gates
+ if (hasPermission(player, "stargate.create.gate")) {
+ //Check if the gate type has been explicitly denied
+ return hasPermissionImplicit(player, "stargate.create.gate." + gate);
+ }
+ //Check if the player can create the specific gate type
+ return hasPermission(player, "stargate.create.gate." + gate);
+ }
+
+ /**
+ * Checks if the given player can destroy the given portal
+ *
+ * @param player The player trying to destroy the portal
+ * @param portal The portal to destroy
+ * @return True if the player is allowed to destroy the portal
+ */
+ public static boolean canDestroyPortal(Player player, Portal portal) {
+ String network = portal.getCleanNetwork();
+
+ //Use a special check for bungee portals
+ if (portal.getOptions().isBungee()) {
+ return hasPermission(player, "stargate.admin.bungee");
+ }
+
+ //Check if the player is allowed to destroy on all networks
+ if (hasPermission(player, "stargate.destroy.network")) {
+ //Check if the network has been explicitly denied
+ return hasPermissionImplicit(player, "stargate.destroy.network." + network);
+ }
+ //Check if the player is allowed to destroy on the network
+ if (hasPermission(player, "stargate.destroy.network." + network)) {
+ return true;
+ }
+ //Check if personal portal and if the player is allowed to destroy it
+ return portal.isOwner(player) && hasPermission(player, "stargate.destroy.personal");
+ }
+
+ /**
+ * Decide of the player can teleport through a portal
+ *
+ * @param entrancePortal The portal the player is entering from
+ * @param destination The destination of the portal the player is inside
+ * @param player The player wanting to teleport
+ * @param event The move event causing the teleportation
+ * @return True if the player cannot teleport. False otherwise
+ */
+ public static boolean playerCannotTeleport(Portal entrancePortal, Portal destination, Player player, 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().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
+ }
+ new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
+ return true;
+ }
+
+ //No destination
+ if (!entrancePortal.getOptions().isBungee() && destination == null) {
+ return true;
+ }
+
+ //Player cannot access portal
+ if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destination)) {
+ if (!entrancePortal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
+ }
+ new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
+ 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;
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/PortalFileHelper.java b/src/main/java/net/knarcraft/stargate/utility/PortalFileHelper.java
new file mode 100644
index 0000000..74741fc
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/PortalFileHelper.java
@@ -0,0 +1,401 @@
+package net.knarcraft.stargate.utility;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.BlockChangeRequest;
+import net.knarcraft.stargate.container.BlockLocation;
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.PortalRegistry;
+import net.knarcraft.stargate.portal.property.PortalLocation;
+import net.knarcraft.stargate.portal.property.PortalOptions;
+import net.knarcraft.stargate.portal.property.PortalOwner;
+import net.knarcraft.stargate.portal.property.gate.Gate;
+import net.knarcraft.stargate.portal.property.gate.GateHandler;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.block.BlockFace;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.block.data.Directional;
+import org.bukkit.block.data.Waterlogged;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+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 The world to save portals for
+ */
+ public static void saveAllPortals(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.getWorld().getName();
+ if (!worldName.equalsIgnoreCase(world.getName())) {
+ continue;
+ }
+ //Save the portal
+ savePortal(bufferedWriter, portal);
+ }
+
+ bufferedWriter.close();
+ } catch (Exception e) {
+ Stargate.logSevere(String.format("Exception while writing stargates to %s: %s", saveFileLocation, e));
+ }
+ }
+
+ /**
+ * Saves one portal
+ *
+ * @param bufferedWriter The buffered writer to write to
+ * @param portal The portal to save
+ * @throws IOException If unable to write to the buffered writer
+ */
+ private static void savePortal(BufferedWriter bufferedWriter, Portal portal) throws IOException {
+ StringBuilder builder = new StringBuilder();
+ BlockLocation button = portal.getStructure().getButton();
+
+ //WARNING: Because of the primitive save format, any change in order will break everything!
+ builder.append(portal.getName()).append(':');
+ builder.append(portal.getSignLocation().toString()).append(':');
+ builder.append((button != null) ? button.toString() : "").append(':');
+
+ //Add removes config values to keep indices consistent
+ builder.append(0).append(':');
+ builder.append(0).append(':');
+
+ builder.append(portal.getYaw()).append(':');
+ builder.append(portal.getTopLeft().toString()).append(':');
+ builder.append(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 The portal to save
+ * @param builder The string builder to append to
+ */
+ private static void savePortalOptions(Portal portal, StringBuilder builder) {
+ PortalOptions options = portal.getOptions();
+ builder.append(':');
+ builder.append(options.isHidden()).append(':');
+ builder.append(options.isAlwaysOn()).append(':');
+ builder.append(options.isPrivate()).append(':');
+ builder.append(portal.getWorld().getName()).append(':');
+ builder.append(options.isFree()).append(':');
+ builder.append(options.isBackwards()).append(':');
+ builder.append(options.isShown()).append(':');
+ builder.append(options.isNoNetwork()).append(':');
+ builder.append(options.isRandom()).append(':');
+ builder.append(options.isBungee()).append(':');
+ builder.append(options.isSilent()).append(':');
+ builder.append(options.hasNoSign());
+ }
+
+ /**
+ * Loads all portals for the given world
+ *
+ * @param world The world to load portals for
+ * @return True if portals could be loaded
+ */
+ public static boolean loadAllPortals(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 The world to load portals for
+ * @param database The database file containing the portals
+ * @return True if the portals were loaded successfully
+ */
+ private static boolean loadPortals(World world, 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 e) {
+ Stargate.logSevere(String.format("Exception while reading stargates from %s: %d", database.getName(),
+ lineIndex));
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ /**
+ * Reads one file line containing information about one portal
+ *
+ * @param scanner The scanner to read
+ * @param lineIndex The index of the read line
+ * @param world The world for which portals are currently being read
+ * @return True if the read portal has changed and the world's database needs to be saved
+ */
+ private static boolean readPortalLine(Scanner scanner, int lineIndex, 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
+ *
+ * This will open always on portals, print info about loaded stargates and re-draw portal signs for loaded
+ * portals.
+ *
+ * @param world The world portals have been loaded for
+ * @param needsToSaveDatabase Whether the portal database's file needs to be updated
+ */
+ private static void doPostLoadTasks(World world, boolean needsToSaveDatabase) {
+ //Open any always-on portals. Do this here as it should be more efficient than in the loop.
+ PortalHandler.verifyAllPortals();
+ int portalCount = PortalRegistry.getAllPortals().size();
+ int openCount = PortalHandler.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
+ for (Portal portal : PortalRegistry.getAllPortals()) {
+ if (portal.isRegistered()) {
+ portal.drawSign();
+ updatePortalButton(portal);
+ }
+ }
+ //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 The array describing the portal
+ * @param world The world to create the portal in
+ * @param lineIndex The line index to report in case the user needs to fix an error
+ * @return True if the portal's data has changed and its database needs to be updated
+ */
+ private static boolean loadPortal(String[] portalData, World world, int lineIndex) {
+ //Load min. required portal data
+ String name = portalData[0];
+ BlockLocation button = (portalData[2].length() > 0) ? new BlockLocation(world, portalData[2]) : null;
+
+ //Load the portal's location
+ PortalLocation portalLocation = new PortalLocation();
+ portalLocation.setSignLocation(new BlockLocation(world, portalData[1]));
+ portalLocation.setYaw(Float.parseFloat(portalData[5]));
+ portalLocation.setTopLeft(new BlockLocation(world, portalData[6]));
+
+ //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
+ Portal portal = new Portal(portalLocation, button, destination, name, network, gate, owner,
+ PortalHandler.getPortalOptions(portalData));
+
+ //Register the portal, and close it in case it wasn't properly closed when the server stopped
+ boolean buttonLocationChanged = updateButtonVector(portal);
+ PortalHandler.registerPortal(portal);
+ portal.getPortalOpener().closePortal(true);
+ return buttonLocationChanged;
+ }
+
+ /**
+ * Updates a portal's button if it does not match the correct material
+ *
+ * @param portal The portal update the button of
+ */
+ private static void updatePortalButton(Portal portal) {
+ BlockLocation buttonLocation = getButtonLocation(portal);
+ if (portal.getOptions().isAlwaysOn()) {
+ //Clear button if not already air or water
+ if (MaterialHelper.isButtonCompatible(buttonLocation.getType())) {
+ Material newMaterial = decideRemovalMaterial(buttonLocation, portal);
+ Stargate.addBlockChangeRequest(new BlockChangeRequest(buttonLocation, newMaterial, null));
+ }
+ } else {
+ //Replace button if the material does not match
+ if (buttonLocation.getType() != portal.getGate().getPortalButton()) {
+ generatePortalButton(portal, DirectionHelper.getBlockFaceFromYaw(portal.getYaw()));
+ }
+ }
+ }
+
+ /**
+ * Decides the material to use for removing a portal's button/sign
+ *
+ * @param location The location of the button/sign to replace
+ * @param portal The portal the button/sign belongs to
+ * @return The material to use for removing the button/sign
+ */
+ public static Material decideRemovalMaterial(BlockLocation location, Portal portal) {
+ //Get the blocks to each side of the location
+ Location leftLocation = location.getRelativeLocation(-1, 0, 0, portal.getYaw());
+ Location rightLocation = location.getRelativeLocation(1, 0, 0, portal.getYaw());
+
+ //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
+ *
+ * If the location has a water block, or a block which is waterlogged, it will be considered underwater.
+ *
+ * @param location The location to check
+ * @return True if the location is underwater
+ */
+ private static boolean isUnderwater(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
+ *
+ * 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.
+ *
+ * @param portal The portal to update the button vector for
+ * @return True if the calculated button location is not the same as the one in the portal file
+ */
+ private static boolean updateButtonVector(Portal portal) {
+ for (RelativeBlockVector control : portal.getGate().getLayout().getControls()) {
+ BlockLocation controlLocation = portal.getLocation().getTopLeft().getRelativeLocation(control,
+ portal.getYaw());
+ BlockLocation buttonLocation = controlLocation.getRelativeLocation(
+ new RelativeBlockVector(0, 0, 1), portal.getYaw());
+ if (!buttonLocation.equals(portal.getLocation().getSignLocation())) {
+ portal.getLocation().setButtonVector(control);
+
+ BlockLocation oldButtonLocation = portal.getStructure().getButton();
+ if (oldButtonLocation != null && !oldButtonLocation.equals(buttonLocation)) {
+ Stargate.addBlockChangeRequest(new BlockChangeRequest(oldButtonLocation, Material.AIR, null));
+ portal.getStructure().setButton(buttonLocation);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Generates a button for a portal
+ *
+ * @param portal The portal to generate button for
+ * @param buttonFacing The direction the button should be facing
+ */
+ public static void generatePortalButton(Portal portal, BlockFace buttonFacing) {
+ //Go one block outwards to find the button's location rather than the control block's location
+ BlockLocation button = getButtonLocation(portal);
+
+ Directional buttonData = (Directional) Bukkit.createBlockData(portal.getGate().getPortalButton());
+ buttonData.setFacing(buttonFacing);
+ button.getBlock().setBlockData(buttonData);
+ portal.getStructure().setButton(button);
+ }
+
+ /**
+ * Gets the location of a portal's button
+ *
+ * @param portal The portal to find the button for
+ * @return The location of the portal's button
+ */
+ private static BlockLocation getButtonLocation(Portal portal) {
+ BlockLocation topLeft = portal.getTopLeft();
+ RelativeBlockVector buttonVector = portal.getLocation().getButtonVector();
+ return topLeft.getRelativeLocation(buttonVector.addToVector(RelativeBlockVector.Property.OUT, 1),
+ portal.getYaw());
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/TeleportHelper.java b/src/main/java/net/knarcraft/stargate/utility/TeleportHelper.java
new file mode 100644
index 0000000..97d7868
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/TeleportHelper.java
@@ -0,0 +1,247 @@
+package net.knarcraft.stargate.utility;
+
+import net.knarcraft.stargate.Stargate;
+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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class with methods for various teleportation tasks
+ *
+ * The teleport helper mainly helps with passengers and leashed creatures
+ */
+public final class TeleportHelper {
+
+ private TeleportHelper() {
+
+ }
+
+ /**
+ * Checks whether a player has leashed creatures that block the teleportation
+ *
+ * @param player The player trying to teleport
+ * @return False if the player has leashed any creatures that cannot go through the portal
+ */
+ public static boolean noLeashedCreaturesPreventTeleportation(Player player) {
+ //Find any nearby leashed entities to teleport with the player
+ List nearbyCreatures = getLeashedCreatures(player);
+
+ //Disallow creatures with passengers to prevent smuggling
+ for (Creature creature : nearbyCreatures) {
+ if (!creature.getPassengers().isEmpty()) {
+ return false;
+ }
+ }
+ //TODO: Improve this to account for any players sitting on any of the lead creatures
+
+ //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 The player to check
+ * @return A list of all creatures the player is holding in a leash (lead)
+ */
+ public static List getLeashedCreatures(Player player) {
+ List leashedCreatures = new ArrayList<>();
+ //Find any nearby leashed entities to teleport with the player
+ List 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
+ *
+ * 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.
+ *
+ * @param targetVehicle The entity to add the passenger to
+ * @param passenger The passenger to teleport and add
+ * @param exitDirection The direction of any passengers exiting the stargate
+ * @param newVelocity The new velocity of the teleported passenger
+ */
+ public static void teleportAndAddPassenger(Entity targetVehicle, Entity passenger, Vector exitDirection,
+ 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 The passengers to handle
+ * @param entity The entity the passengers should be put into
The portal the entity teleported from
+ * @param target The portal the entity is teleporting to
+ * @param exitRotation The rotation of any passengers exiting the stargate
+ * @param newVelocity The new velocity of the teleported passengers
+ */
+ public static void handleEntityPassengers(List passengers, Entity entity, Portal origin, Portal target,
+ Vector exitRotation, Vector newVelocity) {
+ for (Entity passenger : passengers) {
+ List 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
+ *
+ * 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.
+ *
+ * @param player The player which is teleported
+ * @param origin The portal the player is teleporting from
+ * @param target The portal the player is teleporting to
+ */
+ public static void teleportLeashedCreatures(Player player, Portal origin, 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 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 The list of entities to check
+ * @return True if at least one entity is not a player
+ */
+ public static boolean containsNonPlayer(List 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 The list of entities to check
+ * @return True if at least one player is present among the passengers
+ */
+ public static boolean containsPlayer(List 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 The entities to check for players
+ * @return The found players
+ */
+ public static List getPlayers(List entities) {
+ List 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 The player trying to teleport
+ * @param entrancePortal The portal the player is entering
+ * @param destinationPortal The portal the player is to exit from
+ * @return True if the player is allowed to teleport and is able to pay necessary fees
+ */
+ public static boolean playerCanTeleport(Player player, Portal entrancePortal, Portal destinationPortal) {
+ //Make sure the user can access the portal
+ if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destinationPortal)) {
+ if (!entrancePortal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
+ }
+ entrancePortal.getPortalOpener().closePortal(false);
+ return false;
+ }
+
+ //Check if the player is able to afford the teleport fee
+ int cost = EconomyHelper.getUseCost(player, entrancePortal, destinationPortal);
+ boolean canAffordFee = cost <= 0 || Stargate.getEconomyConfig().canAffordFee(player, cost);
+ if (!canAffordFee) {
+ if (!entrancePortal.getOptions().isSilent()) {
+ Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("ecoInFunds"));
+ }
+ return false;
+ }
+
+ return TeleportHelper.noLeashedCreaturesPreventTeleportation(player);
+ }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/utility/UUIDMigrationHelper.java b/src/main/java/net/knarcraft/stargate/utility/UUIDMigrationHelper.java
new file mode 100644
index 0000000..4cb4472
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/utility/UUIDMigrationHelper.java
@@ -0,0 +1,115 @@
+package net.knarcraft.stargate.utility;
+
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.portal.Portal;
+import net.knarcraft.stargate.portal.PortalHandler;
+import net.knarcraft.stargate.portal.PortalRegistry;
+import net.knarcraft.stargate.portal.property.PortalOwner;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.World;
+
+import 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> playerNamesToMigrate;
+
+ /**
+ * Migrates the player's name to a UUID
+ *
+ * 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.
+ *
+ * @param player The player to migrate
+ */
+ public static void migrateUUID(OfflinePlayer player) {
+ Map> 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 portalsOwned = playersToMigrate.get(playerName);
+ if (portalsOwned == null) {
+ return;
+ }
+
+ migratePortalsToUUID(portalsOwned, player.getUniqueId());
+
+ //Remove the player to prevent the migration to happen every time the player joins
+ playersToMigrate.remove(playerName);
+ }
+
+ /**
+ * Migrates a list of portals to use UUID instead of only player name
+ *
+ * @param portals The portals to migrate
+ * @param uniqueId The unique ID of the portals' owner
+ */
+ private static void migratePortalsToUUID(List portals, UUID uniqueId) {
+ Set worldsToSave = new HashSet<>();
+
+ //Get the real portal from the copy and set UUID
+ for (Portal portalCopy : portals) {
+ Portal portal = PortalHandler.getByName(portalCopy.getCleanName(), portalCopy.getCleanNetwork());
+ if (portal != null) {
+ portal.getOwner().setUUID(uniqueId);
+ worldsToSave.add(portal.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 The player names to migrate
+ */
+ private static Map> 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 portalList = playerNamesToMigrate.get(ownerName);
+ if (portalList == null) {
+ List newList = new ArrayList<>();
+ newList.add(portal);
+ playerNamesToMigrate.put(ownerName, newList);
+ } else {
+ portalList.add(portal);
+ }
+ }
+ }
+ return playerNamesToMigrate;
+ }
+
+}
diff --git a/src/main/resources/config-migrations.txt b/src/main/resources/config-migrations.txt
new file mode 100644
index 0000000..cb2628f
--- /dev/null
+++ b/src/main/resources/config-migrations.txt
@@ -0,0 +1,49 @@
+lang=language
+defaultNetwork=defaultGateNetwork
+use-mysql=
+ignoreEntrance=
+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.signColor
+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=taxAccount
+usevault=
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..b8d6dbc
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,107 @@
+# stargate Configuration File
+# Main stargate config
+
+# language - The language file to load for messages (de,en,es,fr,hu,it,ja,nb-no,nl,nn-no,pt-br,ru,zh_cn)
+language: en
+# adminUpdateAlert - Whether to alert admins about new plugin updates
+adminUpdateAlert: true
+folders:
+ # portalFolder - The folder for storing portals
+ portalFolder: plugins/Stargate/portals/
+ # gateFolder - The folder for storing gate layouts
+ gateFolder: plugins/Stargate/gates/
+gates:
+ # maxGatesEachNetwork - The maximum number of gates allowed on a network - 0 for unlimited
+ maxGatesEachNetwork: 0
+ # defaultGateNetwork - The default gate network
+ defaultGateNetwork: central
+ # exitVelocity - The velocity to give players exiting stargates, relative to the entry velocity
+ exitVelocity: 0.1
+ cosmetic:
+ # rememberDestination - Whether to remember the cursor location between uses
+ rememberDestination: false
+ # sortNetworkDestinations - Whether to sort network lists alphabetically
+ sortNetworkDestinations: false
+ # mainSignColor - The color used for drawing signs (Default: BLACK).
+ mainSignColor: BLACK
+ # highlightSignColor - The color used for sign markings (Default: WHITE)
+ highlightSignColor: WHITE
+ 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'
+ integrity:
+ # destroyedByExplosion - Whether to destroy gates with explosions (Creeper, TNT, etc.)
+ destroyedByExplosion: false
+ # verifyPortals - Whether all the non-sign blocks are checked to match the gate layout when a stargate is loaded.
+ verifyPortals: false
+ # protectEntrance - Whether to protect gate entrance material (More resource intensive. Only enable if using
+ # destroyable open/closed material)
+ protectEntrance: false
+ functionality:
+ enableBungee: false
+ # handleVehicles - Whether to allow vehicles through gates. This overrides other vehicle settings
+ handleVehicles: true
+ # handleEmptyVehicles - Whether to allow empty vehicles through gates (chest/hopper/tnt/furnace minecarts included)
+ handleEmptyVehicles: true
+ # handleCreatureTransportation - Whether to allow players to transport creatures by sending vehicles (minecarts,
+ # boats) through gates
+ handleCreatureTransportation: true
+ # handleNonPlayerVehicles - Whether to allow vehicles with a passenger which is not a player through gates.
+ # handleCreatureTransportation must be enabled
+ handleNonPlayerVehicles: true
+ # handleLeashedCreatures - Whether to allow creatures lead by a player to teleport with the player
+ handleLeashedCreatures: true
+ # 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
+ enableCraftBookRemoveOnEjectFix: false
+
+# ######################## #
+# stargate economy options #
+# ######################## #
+economy:
+ # useEconomy - Whether to use an economy plugin
+ useEconomy: false
+ # createCost - The cost to create a gate
+ createCost: 0
+ # destroyCost - The cost to destroy a gate
+ destroyCost: 0
+ # useCost - The cost to use a gate
+ useCost: 0
+ # toOwner - Whether the charge for using a gate goes to the gate's owner
+ toOwner: false
+ # chargeFreeDestination - Whether a gate whose destination is a free gate is still charged
+ chargeFreeDestination: true
+ # freeGatesColored - Whether a free gate in the destination list is marked with a color
+ freeGatesColored: false
+ # freeGatesColor - The color to use for marking free gates
+ freeGatesColor: DARK_GREEN
+
+# ############# #
+# Debug options #
+# ############# #
+debugging:
+ # debug - Debug -- Only enable if you have issues, massive console output
+ debug: false
+ # permissionDebug - This will output any and all Permissions checks to console, used for permissions debugging
+ # (Requires debug: true)
+ permissionDebug: false
+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.
+ waitForPlayerAfterTeleportDelay: 6
+
+# ############## #
+# Dynmap options #
+# ############## #
+dynmap:
+ # enableDynmap - Whether to display Stargates in Dynmap's map
+ enableDynmap: true
+ # dynmapIconsHiddenByDefault - Whether to hide the set of Stargate icons by default, requiring users to
+ # manually enable them with a checkbox.
+ dynmapIconsHiddenByDefault: true
\ No newline at end of file
diff --git a/src/main/resources/gates/endgate.gate b/src/main/resources/gates/endgate.gate
new file mode 100644
index 0000000..a2d1687
--- /dev/null
+++ b/src/main/resources/gates/endgate.gate
@@ -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
diff --git a/src/main/resources/gates/nethergate.gate b/src/main/resources/gates/nethergate.gate
new file mode 100644
index 0000000..b74cc4d
--- /dev/null
+++ b/src/main/resources/gates/nethergate.gate
@@ -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
diff --git a/src/main/resources/gates/squarenetherglowstonegate.gate b/src/main/resources/gates/squarenetherglowstonegate.gate
new file mode 100644
index 0000000..091dabc
--- /dev/null
+++ b/src/main/resources/gates/squarenetherglowstonegate.gate
@@ -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
diff --git a/src/main/resources/gates/watergate.gate b/src/main/resources/gates/watergate.gate
new file mode 100644
index 0000000..72db218
--- /dev/null
+++ b/src/main/resources/gates/watergate.gate
@@ -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
diff --git a/src/net/TheDgtl/Stargate/resources/de.txt b/src/main/resources/lang/de.txt
similarity index 82%
rename from src/net/TheDgtl/Stargate/resources/de.txt
rename to src/main/resources/lang/de.txt
index 4bcfbb4..c2c2f61 100644
--- a/src/net/TheDgtl/Stargate/resources/de.txt
+++ b/src/main/resources/lang/de.txt
@@ -1,28 +1,28 @@
-author=EduardBaer
-prefix=[Stargate]
-teleportMsg=Du wurdest Teleportiert.
-destroyMsg=Gate zerstört
-invalidMsg=Ungültiges Ziel
-blockMsg=Ziel blockiert
-destEmpty=Zielliste leer
-denyMsg=Zugriff verweigert
-
-ecoDeduct=%cost% abgezogen
-ecoRefund=%cost% zurückerstattet
-ecoObtain=%cost% von Stargate %portal% erhalten
-ecoInFunds=Das kannst du dir nicht leisten.
-
-createMsg=Gate erstellt.
-createNetDeny=Du hast keinen Zugriff auf dieses Netzwerk.
-createGateDeny=Du hast keinen Zugriff auf dieses Gate-Layout.
-createPersonal=Gate im persönlichen Netzwerk erstellt.
-createNameLength=Name zu kurz oder zu lang.
-createExists=Ein Gate mit diesem Name existiert bereits.
-createFull=Dieses Netzwerk ist voll.
-createWorldDeny=Du hast keinen Zugriff auf diese Welt.
-createConflict=Dieses Gate steht im Konflikt mit einem bereits existierenden.
-
-signRightClick=Right click
-signToUse=to use gate
-signRandom=Random
+author=EduardBaer
+prefix=[Stargate]
+teleportMsg=Du wurdest Teleportiert.
+destroyMsg=Gate zerstört
+invalidMsg=Ungültiges Ziel
+blockMsg=Ziel blockiert
+destEmpty=Zielliste leer
+denyMsg=Zugriff verweigert
+
+ecoDeduct=%cost% abgezogen
+ecoRefund=%cost% zurückerstattet
+ecoObtain=%cost% von Stargate %portal% erhalten
+ecoInFunds=Das kannst du dir nicht leisten.
+
+createMsg=Gate erstellt.
+createNetDeny=Du hast keinen Zugriff auf dieses Netzwerk.
+createGateDeny=Du hast keinen Zugriff auf dieses Gate-Layout.
+createPersonal=Gate im persönlichen Netzwerk erstellt.
+createNameLength=Name zu kurz oder zu lang.
+createExists=Ein Gate mit diesem Name existiert bereits.
+createFull=Dieses Netzwerk ist voll.
+createWorldDeny=Du hast keinen Zugriff auf diese Welt.
+createConflict=Dieses Gate steht im Konflikt mit einem bereits existierenden.
+
+signRightClick=Right click
+signToUse=to use gate
+signRandom=Random
signDisconnected=Disconnected
\ No newline at end of file
diff --git a/src/net/TheDgtl/Stargate/resources/en.txt b/src/main/resources/lang/en.txt
similarity index 70%
rename from src/net/TheDgtl/Stargate/resources/en.txt
rename to src/main/resources/lang/en.txt
index 8b50bd0..c27e099 100644
--- a/src/net/TheDgtl/Stargate/resources/en.txt
+++ b/src/main/resources/lang/en.txt
@@ -1,32 +1,43 @@
-prefix=[Stargate]
-teleportMsg=Teleported
-destroyMsg=Gate Destroyed
-invalidMsg=Invalid Destination
-blockMsg=Destination Blocked
-destEmpty=Destination List Empty
-denyMsg=Access Denied
-
-ecoDeduct=Deducted %cost%
-ecoRefund=Refunded %cost%
-ecoObtain=Obtained %cost% from Stargate %portal%
-ecoInFunds=Insufficient Funds
-
-createMsg=Gate Created
-createNetDeny=You do not have access to that network
-createGateDeny=You do not have access to that gate layout
-createPersonal=Creating gate on personal network
-createNameLength=Name too short or too long.
-createExists=A gate by that name already exists
-createFull=This network is full
-createWorldDeny=You do not have access to that world
-createConflict=Gate conflicts with existing gate
-
-signRightClick=Right click
-signToUse=to use gate
-signRandom=Random
-signDisconnected=Disconnected
-
-bungeeDisabled=BungeeCord support is disabled.
-bungeeDeny=You do not have permission to create BungeeCord gates.
-bungeeEmpty=BungeeCord gates require both a destination and network.
-bungeeSign=Teleport to
+prefix=[Stargate]
+teleportMsg=Teleported
+destroyMsg=Gate Destroyed
+invalidMsg=Invalid Destination
+blockMsg=Destination Blocked
+destEmpty=Destination List Empty
+denyMsg=Access Denied
+reloaded=Stargate Reloaded
+
+ecoDeduct=Deducted %cost%
+ecoRefund=Refunded %cost%
+ecoObtain=Obtained %cost% from Stargate %portal%
+ecoInFunds=Insufficient Funds
+ecoLoadError=Vault was loaded, but no economy plugin could be hooked into
+vaultLoadError=Economy is enabled but Vault could not be loaded. Economy disabled
+vaultLoaded=Vault v%version% found
+
+createMsg=Gate Created
+createNetDeny=You do not have access to that network
+createGateDeny=You do not have access to that gate layout
+createPersonal=Creating gate on personal network
+createNameLength=Name too short or too long.
+createExists=A gate by that name already exists
+createFull=This network is full
+createWorldDeny=You do not have access to that world
+createConflict=Gate conflicts with existing gate
+
+signRightClick=Right click
+signToUse=to use gate
+signRandom=Random
+signDisconnected=Disconnected
+signInvalidGate=Invalid gate
+
+bungeeDisabled=BungeeCord support is disabled.
+bungeeDeny=You do not have permission to create BungeeCord gates.
+bungeeEmpty=BungeeCord gates require both a destination and network.
+bungeeSign=Teleport to
+
+portalInfoTitle=[STARGATE INFO]
+portalInfoName=Name: %name%
+portalInfoDestination=Destination: %destination%
+portalInfoNetwork=Network: %network%
+portalInfoServer=Server: %server%
\ No newline at end of file
diff --git a/src/net/TheDgtl/Stargate/resources/es.txt b/src/main/resources/lang/es.txt
similarity index 82%
rename from src/net/TheDgtl/Stargate/resources/es.txt
rename to src/main/resources/lang/es.txt
index 5fe853b..5c5338f 100644
--- a/src/net/TheDgtl/Stargate/resources/es.txt
+++ b/src/main/resources/lang/es.txt
@@ -1,10 +1,10 @@
author=Manuestaire
prefix=[Stargate]
teleportMsg=Teletransportado
-destroyMsg=Portal Destruído
+destroyMsg=Portal DestruÃdo
invalidMsg=Elige Destino
blockMsg=Destino Bloqueado
-destEmpty=La lista de destinos está vacía
+destEmpty=La lista de destinos está vacÃa
denyMsg=Acceso denegado
ecoDeduct=Pagaste %cost%
@@ -14,11 +14,11 @@ ecoInFunds=No tienes suficiente dinero
createMsg=Portal creado
createNetDeny=No tienes acceso a esta red
-createGateDeny=No tienes acceso a este diseño de portal
+createGateDeny=No tienes acceso a este diseño de portal
createPersonal=Creando el portal en una red personal
createNameLength=Nombre demasiado largo o demasiado corto
createExists=Ya existe una puerta con este nombre
-createFull=Esta red está llena
+createFull=Esta red está llena
createWorldDeny=No tienes permisos para acceder a ese mundo
createConflict=El portal entra en conflicto con un portal ya existente
diff --git a/src/net/TheDgtl/Stargate/resources/fr.txt b/src/main/resources/lang/fr.txt
similarity index 97%
rename from src/net/TheDgtl/Stargate/resources/fr.txt
rename to src/main/resources/lang/fr.txt
index 8b945a4..66fd9ca 100644
--- a/src/net/TheDgtl/Stargate/resources/fr.txt
+++ b/src/main/resources/lang/fr.txt
@@ -1,4 +1,4 @@
-author=Dauphin14
+author=Dauphin14
prefix=[Stargate]
teleportMsg=Téléportation Réussie.
destroyMsg=Portail detruit.
diff --git a/src/net/TheDgtl/Stargate/resources/hu.txt b/src/main/resources/lang/hu.txt
similarity index 100%
rename from src/net/TheDgtl/Stargate/resources/hu.txt
rename to src/main/resources/lang/hu.txt
diff --git a/src/net/TheDgtl/Stargate/resources/it.txt b/src/main/resources/lang/it.txt
similarity index 100%
rename from src/net/TheDgtl/Stargate/resources/it.txt
rename to src/main/resources/lang/it.txt
diff --git a/src/main/resources/lang/ja.txt b/src/main/resources/lang/ja.txt
new file mode 100644
index 0000000..56c2b1d
--- /dev/null
+++ b/src/main/resources/lang/ja.txt
@@ -0,0 +1,44 @@
+author=furplag
+prefix=[Stargate]
+teleportMsg=テレãƒãƒ¼ãƒˆ
+destroyMsg=ゲートãŒç ´å£Šã•れã¾ã—ãŸ
+invalidMsg=無効ãªè¡Œãå…ˆ
+blockMsg=ブãƒãƒƒã‚¯ã•れãŸè¡Œãå…ˆ
+destEmpty=行ã先リストãŒç©ºã§ã™
+denyMsg=ã‚¢ã‚¯ã‚»ã‚¹ãŒæ‹’å¦ã•れã¾ã—ãŸ
+reloaded= Stargate をリãƒãƒ¼ãƒ‰ã—ã¾ã—ãŸ
+
+ecoDeduct=ï¼…costï¼… ã®å€¤å¼•ã
+ecoRefund=ï¼…costï¼… ã®è¿”金
+ecoObtain= Stargate ï¼…portalï¼… ã‹ã‚‰ ï¼…costï¼… ã‚’å¾—ã¾ã—ãŸ
+ecoInFunds=資金ã®ä¸è¶³
+ecoLoadError= Vault ãŒèªã¿è¾¼ã¾ã‚Œã¾ã—ãŸãŒã€Economy プラグインをフックã§ãã¾ã›ã‚“ã§ã—ãŸ
+vaultLoadError=Economy ã¯æœ‰åйã«ãªã£ã¦ã„ã¾ã™ãŒã€Vault ã‚’ãƒãƒ¼ãƒ‰ã§ããªã„ãŸã‚ Economy ã¯ç„¡åŠ¹åŒ–ã•れã¾ã—ãŸ
+vaultLoaded= Vault vï¼…versionï¼… ãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸ
+
+createMsg=ゲートãŒä½œæˆã•れã¾ã—ãŸ
+createNetDeny=対象ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã›ã‚“
+createGateDeny=対象ã®ã‚²ãƒ¼ãƒˆãƒ¬ã‚¤ã‚¢ã‚¦ãƒˆã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã›ã‚“
+createPersonal=パーソナルãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ä¸Šã«ã‚²ãƒ¼ãƒˆã‚’作æˆã™ã‚‹
+createNameLength=ゲートåãŒçŸã™ãŽã‚‹ã‹é•·ã™ãŽã¾ã™
+createExists=ã™ã§ã«å˜åœ¨ã™ã‚‹ã‚²ãƒ¼ãƒˆåã§ã™
+createFull=対象ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã¯ã„ã£ã±ã„ã§ã™
+createWorldDeny=ã‚ãªãŸã¯ãã®ä¸–界ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã›ã‚“
+createConflict=ã‚²ãƒ¼ãƒˆãŒæ—¢å˜ã®ã‚²ãƒ¼ãƒˆã¨ç«¶åˆã—ã¦ã„ã¾ã™
+
+signRightClick=å³ã‚¯ãƒªãƒƒã‚¯
+signToUse=ゲートを使用ã™ã‚‹
+signRandom=ランダãƒ
+signDisconnected=切æ–
+signInvalidGate=無効ãªã‚²ãƒ¼ãƒˆ
+
+bungeeDisabled=BungeeCord サãƒãƒ¼ãƒˆã¯ç„¡åйã«ãªã£ã¦ã„ã¾ã™
+bungeeDeny=BungeeCord ゲートを作æˆã™ã‚‹æ¨©é™ãŒã‚りã¾ã›ã‚“
+bungeeEmpty=BungeeCord ゲートã«ã¯ã€è¡Œãå…ˆã¨ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã®ä¸¡æ–¹ãŒå¿…è¦ã§ã™
+bungeeSign=テレãƒãƒ¼ãƒˆå…ˆ:
+
+portalInfoTitle=[STARGATE INFO]
+portalInfoName=ゲートå: ï¼…nameï¼…
+portalInfoDestination=行ãå…ˆ: ï¼…destinationï¼…
+portalInfoNetwork=ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯: ï¼…networkï¼…
+portalInfoServer=サーãƒãƒ¼: ï¼…serverï¼…
diff --git a/src/main/resources/lang/nb-no.txt b/src/main/resources/lang/nb-no.txt
new file mode 100644
index 0000000..0aff97f
--- /dev/null
+++ b/src/main/resources/lang/nb-no.txt
@@ -0,0 +1,43 @@
+author=EpicKnarvik97
+prefix=[Stjerneport]
+teleportMsg=Teleporterte
+destroyMsg=Port Ødelagt
+invalidMsg=Ugyldig Destinasjon
+blockMsg=Destinasjon Blokkert
+destEmpty=Destinasjonslisten Er Tom
+denyMsg=Tilgang Avslått
+reloaded=Stjerneport Ble Lastet Inn PÃ¥ Nytt
+
+ecoDeduct=Fratrekk %cost%
+ecoRefund=Refundert %cost%
+ecoObtain=Fikk %cost% fra Stjerneport %portal%
+ecoInFunds=Manglende Midler
+ecoLoadError=Vault ble lastet inn men ingen brukbar økonomi-utvidelse ble funnet
+vaultLoadError=Økonomi er skrudd på, men Vault kunne ikke lastes inn. Økonomi er skrudd av
+vaultLoaded=Vault v%version% funnet
+
+createMsg=Port opprettet
+createNetDeny=Du har ikke tilgang til det nettverket
+createGateDeny=Du har ikke tilgang til den portutformingen
+createPersonal=Oppretter port på personlig nettverk
+createNameLength=Navnet er for kort eller for langt.
+createExists=En port ved det navnet eksisterer allerede
+createFull=Dette nettverket er fullt
+createWorldDeny=Du har ikke tilgang til den verdenen
+createConflict=Port er i konflikt med en eksisterende port
+
+signRightClick=Høyreklikk
+signToUse=for å bruke port
+signRandom=Tilfeldig
+signDisconnected=Koblet fra
+
+bungeeDisabled=BungeeCord støtte er slått av.
+bungeeDeny=Du har ikke tillatelse til å opprette BungeeCord porter.
+bungeeEmpty=BungeeCord porter behøver bade en destinasjon og et nettverk.
+bungeeSign=Teleporter til
+
+portalInfoTitle=[STJERNEPORT INFO]
+portalInfoName=Navn: %name%
+portalInfoDestination=Destinasjon: %destination%
+portalInfoNetwork=Nettverk: %network%
+portalInfoServer=Server: %server%
\ No newline at end of file
diff --git a/src/net/TheDgtl/Stargate/resources/nl.txt b/src/main/resources/lang/nl.txt
similarity index 94%
rename from src/net/TheDgtl/Stargate/resources/nl.txt
rename to src/main/resources/lang/nl.txt
index 57f3db3..2e29b3c 100644
--- a/src/net/TheDgtl/Stargate/resources/nl.txt
+++ b/src/main/resources/lang/nl.txt
@@ -16,7 +16,7 @@ createMsg=Gate gemaakt
createNetDeny=Je hebt geen toegang tot dat netwerk.
createGateDeny=Je mag die Gate-Layout niet gebruiken
createPersonal=Gate op persoonlijk netwerk gemaakt.
-createNameLength=Naam te lang of te kort.
+createNameLength=Naam te chosenLanguage of te kort.
createExists=Er bestaat al een gate met die naam
createFull=Dit netwerk is vol.
createWorldDeny=Je mag niet in die wereld komen.
diff --git a/src/main/resources/lang/nn-no.txt b/src/main/resources/lang/nn-no.txt
new file mode 100644
index 0000000..7e07a23
--- /dev/null
+++ b/src/main/resources/lang/nn-no.txt
@@ -0,0 +1,43 @@
+author=EpicKnarvik97
+prefix=[Stjerneport]
+teleportMsg=Teleporterte
+destroyMsg=Port Øydelagd
+invalidMsg=Ugyldig Destinasjon
+blockMsg=Destinasjon Blokkert
+destEmpty=Destinasjonslista Er Tom
+denyMsg=Tilgang Avslått
+reloaded=Stjerneport Vart Lasta Inn PÃ¥ Nytt
+
+ecoDeduct=Fråtrekk %cost%
+ecoRefund=Refundert %cost%
+ecoObtain=Mottok %cost% frå Stjerneport %portal%
+ecoInFunds=Manglande Midlar
+ecoLoadError=Vault vart lasta inn men inga brukbar økonomi-utviding vart funnen
+vaultLoadError=Økonomi er skrudd på, men Vault kunne ikkje lastas inn. Økonomi er skrudd av
+vaultLoaded=Vault v%version% funnen
+
+createMsg=Port oppretta
+createNetDeny=Du har ikkje tilgang til det nettverket
+createGateDeny=Du har ikkje tilgang til den portutforminga
+createPersonal=Opprettar port på personleg nettverk
+createNameLength=Namnet er for kort eller for langt.
+createExists=Ein port med det namnet eksisterar allereie
+createFull=Dette nettverket er fullt
+createWorldDeny=Du har ikkje tilgang til den verda
+createConflict=Port er i konflikt med ein eksisterande port
+
+signRightClick=Høgreklikk
+signToUse=for å bruke port
+signRandom=Tilfeldig
+signDisconnected=Kopla frå
+
+bungeeDisabled=BungeeCord støtte er slått av.
+bungeeDeny=Du har ikkje løyve til å opprette BungeeCord portar.
+bungeeEmpty=BungeeCord portar treng bade ein destinasjon og eit nettverk.
+bungeeSign=Teleporter til
+
+portalInfoTitle=[STJERNEPORT INFO]
+portalInfoName=Namn: %name%
+portalInfoDestination=Destinasjon: %destination%
+portalInfoNetwork=Nettverk: %network%
+portalInfoServer=Server: %server%
\ No newline at end of file
diff --git a/src/net/TheDgtl/Stargate/resources/pt-br.txt b/src/main/resources/lang/pt-br.txt
similarity index 94%
rename from src/net/TheDgtl/Stargate/resources/pt-br.txt
rename to src/main/resources/lang/pt-br.txt
index 5022e25..594e96c 100644
--- a/src/net/TheDgtl/Stargate/resources/pt-br.txt
+++ b/src/main/resources/lang/pt-br.txt
@@ -17,7 +17,7 @@ createNetDeny=Voce nao tem acesso a essa rede.
createGateDeny=Voce nao tem acesso a esse portal layout.
createPersonal=Criando portal em rede pessoal.
createNameLength=Nome muito curto ou muito grande.
-createExists=Já existe um portal com esse nome.
+createExists=Já existe um portal com esse nome.
createFull=Esta rede esta cheia.
createWorldDeny=Voce nao tem acesso a esse mundo.
createConflict=Portal em conflito com um portal ja existente.
diff --git a/src/net/TheDgtl/Stargate/resources/ru.txt b/src/main/resources/lang/ru.txt
similarity index 98%
rename from src/net/TheDgtl/Stargate/resources/ru.txt
rename to src/main/resources/lang/ru.txt
index 6431462..afef3c7 100644
--- a/src/net/TheDgtl/Stargate/resources/ru.txt
+++ b/src/main/resources/lang/ru.txt
@@ -1,4 +1,4 @@
-author=ckr@jk
+author=ckr@jk
prefix=[Портал]
teleportMsg=Вы перемещены
destroyMsg=Портал уничтожен
diff --git a/src/main/resources/lang/zh_cn.txt b/src/main/resources/lang/zh_cn.txt
new file mode 100644
index 0000000..095fa24
--- /dev/null
+++ b/src/main/resources/lang/zh_cn.txt
@@ -0,0 +1,39 @@
+author=YKDZ
+signRightClick=å³é”®
+ecoLoadError=Vault å·²åŠ è½½, 但未检测到åˆé€‚çš„ç»æµŽæ’ä»¶
+createConflict=星门与现有星门冲çª
+invalidMsg=æ— æ•ˆçš„ç›®çš„åœ°
+prefix=[星门]
+ecoObtain=从星门 %portal% æ”¶å–了 %cost%
+vaultLoaded=检测到 Vault v%version%
+reloaded=星门æ’ä»¶å·²é‡è½½
+bungeeDeny=ä½ æ²¡æœ‰åˆ›å»ºè·¨æœæ˜Ÿé—¨çš„æƒé™.
+signToUse=以使用星门
+signInvalidGate=未知星门
+bungeeEmpty=è·¨æœæ˜Ÿé—¨éœ€è¦æä¾›ç›®çš„地和网络.
+createMsg=星门已创建
+bungeeDisabled=è·¨æœåŠŸèƒ½å·²è¢«ç¦ç”¨.
+blockMsg=目的地被阻挡
+ecoInFunds=ä½™é¢ä¸è¶³
+createNameLength=åç§°è¿‡çŸæˆ–过长.
+vaultLoadError=未检测到Vault. ç»æµŽæ¨¡å—å·²ç¦ç”¨
+denyMsg=访问被拒
+ecoDeduct=花费 %cost%
+signDisconnected=已喿¶ˆé“¾æŽ¥
+createNetDeny=ä½ æ²¡æœ‰è¿™ä¸ªæ˜Ÿé—¨ç½‘ç»œçš„è®¸å¯
+bungeeSign=ä¼ é€åˆ°
+portalInfoName=åç§°: %name%
+destroyMsg=æ˜Ÿé—¨å·²è¢«ç ´å
+portalInfoTitle=[星门信æ¯]
+createExists=与已有星门é‡å
+teleportMsg=å·²ä¼ é€
+createGateDeny=ä½ æ²¡æœ‰ä½¿ç”¨è¿™ä¸ªæ˜Ÿé—¨ç»“æž„çš„æƒé™
+signRandom=éšæœº
+portalInfoServer=æœåС噍: %server%
+createWorldDeny=ä½ æ²¡æœ‰é“¾æŽ¥è¿™ä¸ªä¸–ç•Œçš„æƒé™
+portalInfoDestination=目的地: %destination%
+portalInfoNetwork=星门网络: %network%
+destEmpty=目的地列表为空
+createPersonal=在ç§äººç½‘络ä¸åˆ›å»ºæ˜Ÿé—¨
+ecoRefund=退款 %cost%
+createFull=æ¤æ˜Ÿé—¨ç½‘络已满
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..283fe85
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,159 @@
+name: Stargate
+main: net.knarcraft.stargate.Stargate
+version: '${project.version}'
+description: Stargate mod for Bukkit Revived
+author: EpicKnarvik97
+authors: [ Drakia, PseudoKnight, EpicKnarvik97 ]
+website: https://git.knarcraft.net/EpicKnarvik97/Stargate
+api-version: 1.18
+softdepend: [ Vault, dynmap ]
+commands:
+ stargate:
+ aliases:
+ - sg
+ description: Used to see stargate info, reloading and editing config values
+ usage: / - Used to see stargate info, reload the plugin or change config values
+permissions:
+ stargate.*:
+ description: Wildcard permission which gives all Stargate permissions
+ default: false
+ children:
+ stargate.admin: true
+ stargate.use: true
+ stargate.create: true
+ stargate.destroy: true
+ stargate.free: true
+ stargate.option: true
+ stargate.server: true
+ stargate.reload:
+ description: Allows reloading the plugin
+ default: false
+ stargate.use:
+ description: Allow use of all stargates linking to any world in any network
+ default: true
+ children:
+ stargate.world: true
+ stargate.network: true
+ stargate.server: true
+ stargate.world:
+ description: Allow use of stargates in any world
+ default: false
+ stargate.network:
+ description: Allows use of stargates in any network
+ default: false
+ stargate.create:
+ description: Allow creating stargates on any network using any gate type
+ default: op
+ children:
+ stargate.create.personal: true
+ stargate.create.network: true
+ stargate.create.gate: true
+ stargate.create.personal:
+ description: Allows the creation of a personal stargate if a player is missing permission for the given network
+ default: false
+ stargate.create.network:
+ description: Allows the creation of a stargate on any network
+ default: false
+ stargate.create.gate:
+ description: Allows the creation of a stargate using any gate type
+ default: false
+ stargate.destroy:
+ description: Allows the destruction of all stargates
+ default: op
+ children:
+ stargate.destroy.network: true
+ stargate.destroy.personal: true
+ stargate.destroy.network:
+ description: Allows the destruction of stargates on any network
+ default: false
+ stargate.destroy.personal:
+ description: Allows the destruction of any personal stargates the player has created
+ default: false
+ stargate.free:
+ description: Allow free use/creation/destruction of stargates
+ default: op
+ children:
+ stargate.free.use: true
+ stargate.free.create: true
+ stargate.free.destroy: true
+ stargate.free.use:
+ description: Allows free usage of all stargates
+ default: false
+ stargate.free.create:
+ description: Allows creating stargates for free
+ default: false
+ stargate.free.destroy:
+ description: Allows destroying stargates for free
+ default: false
+ stargate.option:
+ description: Allows use of all options
+ default: op
+ children:
+ stargate.option.hidden: true
+ stargate.option.alwayson: true
+ stargate.option.private: true
+ stargate.option.free: true
+ stargate.option.backwards: true
+ stargate.option.show: true
+ stargate.option.nonetwork: true
+ stargate.option.random: true
+ stargate.option.silent: true
+ stargate.option.nosign: true
+ stargate.option.hidden:
+ description: Allows the creation of a hidden stargate
+ default: false
+ stargate.option.alwayson:
+ description: Allows the creation of an always open stargate
+ default: false
+ stargate.option.private:
+ description: Allows the creation of a private stargate
+ default: false
+ stargate.option.free:
+ description: Allows the creation of a stargate which is free regardless of any set prices
+ default: false
+ stargate.option.backwards:
+ description: Allows the creation of a stargate where players will exit through the back
+ default: false
+ stargate.option.show:
+ description: Allows the creation of a stargate which is shown on the network, even if always on
+ default: false
+ stargate.option.nonetwork:
+ description: Allows the creation of a stargate with a hidden network name
+ default: false
+ stargate.option.random:
+ description: Allows the creation of a stargate with a random destination
+ default: false
+ stargate.option.silent:
+ description: Allows the creation of a stargate which does not output anything to the chat
+ default: false
+ stargate.option.nosign:
+ description: Allows the creation of a stargate which has no sign
+ default: false
+ stargate.admin.hidden:
+ description: Allows this player to see all hidden stargates
+ default: false
+ stargate.admin.private:
+ description: Allows this player to use all private stargates
+ default: false
+ stargate.admin.bungee:
+ description: Allows the creation and destruction of a stargate between BungeeCord servers
+ default: false
+ stargate.admin.config:
+ description: Allows the player to change config values from the chat
+ default: false
+ stargate.admin.dye:
+ description: Allows this player to change the dye of any stargate's sign
+ default: false
+ stargate.server:
+ description: Allows the creation of a BungeeCord stargate going to any server
+ default: false
+ stargate.admin:
+ description: Allow all admin features and commands (Hidden/Private bypass, BungeeCord, Reload, Config)
+ default: op
+ children:
+ stargate.admin.reload: true
+ stargate.admin.hidden: true
+ stargate.admin.private: true
+ stargate.admin.bungee: true
+ stargate.admin.config: true
+ stargate.admin.dye: true
\ No newline at end of file
diff --git a/src/net/TheDgtl/Stargate/Blox.java b/src/net/TheDgtl/Stargate/Blox.java
deleted file mode 100644
index f45c023..0000000
--- a/src/net/TheDgtl/Stargate/Blox.java
+++ /dev/null
@@ -1,173 +0,0 @@
-package net.TheDgtl.Stargate;
-
-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.type.Sign;
-import org.bukkit.block.data.type.WallSign;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011 Shaun (sturmeh)
- * Copyright (C) 2011 Dinnerbone
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class Blox {
- private final int x;
- private final int y;
- private final int z;
- private final World world;
- private Blox parent = null;
-
- public Blox (World world, int x, int y, int z) {
- this.x = x;
- this.y = y;
- this.z = z;
- this.world = world;
- }
-
- public Blox (Block block) {
- this.x = block.getX();
- this.y = block.getY();
- this.z = block.getZ();
- this.world = block.getWorld();
- }
-
- public Blox (Location location) {
- this.x = location.getBlockX();
- this.y = location.getBlockY();
- this.z = location.getBlockZ();
- this.world = location.getWorld();
- }
-
- public Blox (World world, String string) {
- String[] split = string.split(",");
- this.x = Integer.parseInt(split[0]);
- this.y = Integer.parseInt(split[1]);
- this.z = Integer.parseInt(split[2]);
- this.world = world;
- }
-
- public Blox makeRelative(int x, int y, int z) {
- return new Blox(this.world, this.x + x, this.y + y, this.z + z);
- }
-
- public Location makeRelativeLoc(double x, double y, double z, float rotX, float rotY) {
- return new Location(this.world, (double)this.x + x, (double)this.y + y, (double)this.z + z, rotX, rotY);
- }
-
- public Blox modRelative(int right, int depth, int distance, int modX, int modY, int modZ) {
- return makeRelative(-right * modX + distance * modZ, -depth * modY, -right * modZ + -distance * modX);
- }
-
- public Location modRelativeLoc(double right, double depth, double distance, float rotX, float rotY, int modX, int modY, int modZ) {
- return makeRelativeLoc(0.5 + -right * modX + distance * modZ, depth, 0.5 + -right * modZ + -distance * modX, rotX, 0);
- }
-
- public void setType(Material type) {
- world.getBlockAt(x, y, z).setType(type);
- }
-
- public Material getType() {
- return world.getBlockAt(x, y, z).getType();
- }
-
- public Block getBlock() {
- return world.getBlockAt(x, y, z);
- }
-
- public Location getLocation() {
- return new Location(world, x, y, z);
- }
-
- public int getX() {
- return x;
- }
-
- public int getY() {
- return y;
- }
-
- public int getZ() {
- return z;
- }
-
- public World getWorld() {
- return world;
- }
-
- public Block getParent() {
- if (parent == null) findParent();
- if (parent == null) return null;
- return parent.getBlock();
- }
-
- private void findParent() {
- int offsetX = 0;
- int offsetY = 0;
- int offsetZ = 0;
-
- BlockData blk = getBlock().getBlockData();
- if (blk instanceof WallSign) {
- BlockFace facing = ((WallSign) blk).getFacing().getOppositeFace();
- offsetX = facing.getModX();
- offsetZ = facing.getModZ();
- } else if (blk instanceof Sign) {
- offsetY = -1;
- } else {
- return;
- }
- parent = new Blox(world, getX() + offsetX, getY() + offsetY, getZ() + offsetZ);
- }
-
- public String toString() {
- StringBuilder builder = new StringBuilder();
- //builder.append(world.getName());
- //builder.append(',');
- builder.append(x);
- builder.append(',');
- builder.append(y);
- builder.append(',');
- builder.append(z);
- return builder.toString();
- }
-
- @Override
- public int hashCode() {
- int result = 18;
-
- result = result * 27 + x;
- result = result * 27 + y;
- result = result * 27 + z;
- result = result * 27 + world.getName().hashCode();
-
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
-
- Blox blox = (Blox) obj;
- return (x == blox.x) && (y == blox.y) && (z == blox.z) && (world.getName().equals(blox.world.getName()));
- }
-}
\ No newline at end of file
diff --git a/src/net/TheDgtl/Stargate/BloxPopulator.java b/src/net/TheDgtl/Stargate/BloxPopulator.java
deleted file mode 100644
index 88290fb..0000000
--- a/src/net/TheDgtl/Stargate/BloxPopulator.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package net.TheDgtl.Stargate;
-
-import org.bukkit.Axis;
-import org.bukkit.Material;
-
-public class BloxPopulator {
- private Blox blox;
- private Material nextMat;
- private Axis nextAxis;
-
- public BloxPopulator(Blox b, Material m) {
- blox = b;
- nextMat = m;
- nextAxis = null;
- }
-
- public BloxPopulator(Blox b, Material m, Axis a) {
- blox = b;
- nextMat = m;
- nextAxis = a;
- }
-
- public void setBlox(Blox b) {
- blox = b;
- }
-
- public void setMat(Material m) {
- nextMat = m;
- }
-
- public void setAxis(Axis a) {
- nextAxis = a;
- }
-
- public Blox getBlox() {
- return blox;
- }
-
- public Material getMat() {
- return nextMat;
- }
-
- public Axis getAxis() {
- return nextAxis;
- }
-
-}
diff --git a/src/net/TheDgtl/Stargate/EconomyHandler.java b/src/net/TheDgtl/Stargate/EconomyHandler.java
deleted file mode 100644
index 36963d7..0000000
--- a/src/net/TheDgtl/Stargate/EconomyHandler.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package net.TheDgtl.Stargate;
-
-import net.milkbowl.vault.economy.Economy;
-
-import org.bukkit.Bukkit;
-import org.bukkit.entity.Player;
-import org.bukkit.plugin.Plugin;
-import org.bukkit.plugin.PluginManager;
-import org.bukkit.plugin.RegisteredServiceProvider;
-
-import java.util.UUID;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class EconomyHandler {
- public static boolean economyEnabled = false;
- public static Economy economy = null;
- public static Plugin vault = null;
-
- public static int useCost = 0;
- public static int createCost = 0;
- public static int destroyCost = 0;
- public static boolean toOwner = false;
- public static boolean chargeFreeDestination = true;
- public static boolean freeGatesGreen = false;
-
- public static double getBalance(Player player) {
- if (!economyEnabled) return 0;
- return economy.getBalance(player);
- }
-
- public static boolean chargePlayer(Player player, String target, double amount) {
- if (!economyEnabled) return true;
- if(player.getName().equals(target)) return true;
- if(economy != null) {
- if(!economy.has(player, amount)) return false;
- economy.withdrawPlayer(player, amount);
- economy.depositPlayer(target, amount);
- }
- return true;
- }
-
- public static boolean chargePlayer(Player player, UUID target, double amount) {
- if (!economyEnabled) return true;
- if(player.getUniqueId().compareTo(target) == 0) return true;
- if(economy != null) {
- if(!economy.has(player, amount)) return false;
- economy.withdrawPlayer(player, amount);
- economy.depositPlayer(Bukkit.getOfflinePlayer(target), amount);
- }
- return true;
- }
-
- public static boolean chargePlayer(Player player, double amount) {
- if (!economyEnabled) return true;
- if(economy != null) {
- if(!economy.has(player, amount)) return false;
- economy.withdrawPlayer(player, amount);
- }
- return true;
- }
-
- public static String format(int amt) {
- if (economyEnabled) {
- return economy.format(amt);
- }
- return "";
- }
-
- public static boolean setupEconomy(PluginManager pm) {
- if (!economyEnabled) return false;
- // Check for Vault
- Plugin p = pm.getPlugin("Vault");
- if (p != null && p.isEnabled()) {
- RegisteredServiceProvider economyProvider = Stargate.server.getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class);
- if (economyProvider != null) {
- economy = economyProvider.getProvider();
- vault = p;
- return true;
- }
- }
- economyEnabled = false;
- return false;
- }
-
- public static boolean useEconomy() {
- return economyEnabled && economy != null;
- }
-
-}
diff --git a/src/net/TheDgtl/Stargate/Gate.java b/src/net/TheDgtl/Stargate/Gate.java
deleted file mode 100644
index 31bb93f..0000000
--- a/src/net/TheDgtl/Stargate/Gate.java
+++ /dev/null
@@ -1,513 +0,0 @@
-package net.TheDgtl.Stargate;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.logging.Level;
-
-import org.bukkit.Material;
-import org.bukkit.Tag;
-import org.bukkit.block.Block;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011 Shaun (sturmeh)
- * Copyright (C) 2011 Dinnerbone
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class Gate {
- private static final Character ANYTHING = ' ';
- private static final Character ENTRANCE = '.';
- private static final Character EXIT = '*';
- private static final HashMap gates = new HashMap<>();
- private static final HashMap> controlBlocks = new HashMap<>();
- private static final HashSet frameBlocks = new HashSet<>();
-
- private final String filename;
- private final Character[][] layout;
- private final HashMap types;
- private RelativeBlockVector[] entrances = new RelativeBlockVector[0];
- private RelativeBlockVector[] border = new RelativeBlockVector[0];
- private RelativeBlockVector[] controls = new RelativeBlockVector[0];
- private RelativeBlockVector exitBlock = null;
- private final HashMap exits = new HashMap<>();
- private Material portalBlockOpen = Material.NETHER_PORTAL;
- private Material portalBlockClosed = Material.AIR;
- private Material button = Material.STONE_BUTTON;
-
- // Economy information
- private int useCost = -1;
- private int createCost = -1;
- private int destroyCost = -1;
- private boolean toOwner = false;
-
- public Gate(String filename, Character[][] layout, HashMap types) {
- this.filename = filename;
- this.layout = layout;
- this.types = types;
-
- populateCoordinates();
- }
-
- private void populateCoordinates() {
- ArrayList entranceList = new ArrayList<>();
- ArrayList borderList = new ArrayList<>();
- ArrayList controlList = new ArrayList<>();
- RelativeBlockVector[] relativeExits = new RelativeBlockVector[layout[0].length];
- int[] exitDepths = new int[layout[0].length];
- RelativeBlockVector lastExit = null;
-
- for (int y = 0; y < layout.length; y++) {
- for (int x = 0; x < layout[y].length; x++) {
- Character key = layout[y][x];
- if (key.equals('-')) {
- controlList.add(new RelativeBlockVector(x, y, 0));
- }
-
- if (key.equals(ENTRANCE) || key.equals(EXIT)) {
- entranceList.add(new RelativeBlockVector(x, y, 0));
- exitDepths[x] = y;
- if (key.equals(EXIT)) {
- this.exitBlock = new RelativeBlockVector(x, y, 0);
- }
- } else if (!key.equals(ANYTHING)) {
- borderList.add(new RelativeBlockVector(x, y, 0));
- }
- }
- }
-
- for (int x = 0; x < exitDepths.length; x++) {
- relativeExits[x] = new RelativeBlockVector(x, exitDepths[x], 0);
- }
-
- for (int x = relativeExits.length - 1; x >= 0; x--) {
- if (relativeExits[x] != null) {
- lastExit = relativeExits[x];
- } else {
- relativeExits[x] = lastExit;
- }
-
- if (exitDepths[x] > 0) this.exits.put(relativeExits[x], x);
- }
-
- this.entrances = entranceList.toArray(this.entrances);
- this.border = borderList.toArray(this.border);
- this.controls = controlList.toArray(this.controls);
- }
-
- public void save(String gateFolder) {
- try {
- BufferedWriter bw = new BufferedWriter(new FileWriter(gateFolder + filename));
-
- writeConfig(bw, "portal-open", portalBlockOpen.name());
- writeConfig(bw, "portal-closed", portalBlockClosed.name());
- writeConfig(bw, "button", button.name());
- if (useCost != -1)
- writeConfig(bw, "usecost", useCost);
- if (createCost != -1)
- writeConfig(bw, "createcost", createCost);
- if (destroyCost != -1)
- writeConfig(bw, "destroycost", destroyCost);
- writeConfig(bw, "toowner", toOwner);
-
- for (Map.Entry entry : types.entrySet()) {
- Character type = entry.getKey();
- Material value = entry.getValue();
- // Skip control values
- if (type.equals(ANYTHING) || type.equals(ENTRANCE) || type.equals(EXIT)) {
- continue;
- }
-
- bw.append(type);
- bw.append('=');
- if(value != null) {
- bw.append(value.toString());
- }
- bw.newLine();
- }
-
- bw.newLine();
-
- for(Character[] aLayout : layout) {
- for(Character symbol : aLayout) {
- bw.append(symbol);
- }
- bw.newLine();
- }
-
- bw.close();
- } catch (IOException ex) {
- Stargate.log.log(Level.SEVERE, "Could not save Gate " + filename + " - " + ex.getMessage());
- }
- }
-
- private void writeConfig(BufferedWriter bw, String key, int value) throws IOException {
- bw.append(String.format("%s=%d", key, value));
- bw.newLine();
- }
-
- private void writeConfig(BufferedWriter bw, String key, boolean value) throws IOException {
- bw.append(String.format("%s=%b", key, value));
- bw.newLine();
- }
-
- private void writeConfig(BufferedWriter bw, String key, String value) throws IOException {
- bw.append(String.format("%s=%s", key, value));
- bw.newLine();
- }
-
- public Character[][] getLayout() {
- return layout;
- }
-
- public HashMap getTypes() {
- return types;
- }
-
- public RelativeBlockVector[] getEntrances() {
- return entrances;
- }
-
- public RelativeBlockVector[] getBorder() {
- return border;
- }
-
- public RelativeBlockVector[] getControls() {
- return controls;
- }
-
- public HashMap getExits() {
- return exits;
- }
- public RelativeBlockVector getExit() {
- return exitBlock;
- }
-
- public Material getControlBlock() {
- return types.get('-');
- }
-
- public String getFilename() {
- return filename;
- }
-
- public Material getPortalBlockOpen() {
- return portalBlockOpen;
- }
-
- public void setPortalBlockOpen(Material type) {
- portalBlockOpen = type;
- }
-
- public Material getPortalBlockClosed() {
- return portalBlockClosed;
- }
-
- public void setPortalBlockClosed(Material type) {
- portalBlockClosed = type;
- }
-
- public Material getButton() {
- return button;
- }
-
- public int getUseCost() {
- if (useCost < 0) return EconomyHandler.useCost;
- return useCost;
- }
-
- public Integer getCreateCost() {
- if (createCost < 0) return EconomyHandler.createCost;
- return createCost;
- }
-
- public Integer getDestroyCost() {
- if (destroyCost < 0) return EconomyHandler.destroyCost;
- return destroyCost;
- }
-
- public Boolean getToOwner() {
- return toOwner;
- }
-
- public boolean matches(Blox topleft, int modX, int modZ) {
- return matches(topleft, modX, modZ, false);
- }
-
- public boolean matches(Blox topleft, int modX, int modZ, boolean onCreate) {
- HashMap portalTypes = new HashMap<>(types);
- for (int y = 0; y < layout.length; y++) {
- for (int x = 0; x < layout[y].length; x++) {
- Character key = layout[y][x];
-
- if (key.equals(ENTRANCE) || key.equals(EXIT)) {
- if (Stargate.ignoreEntrance) continue;
-
- Material type = topleft.modRelative(x, y, 0, modX, 1, modZ).getType();
-
- // Ignore entrance if it's air and we're creating a new gate
- if (onCreate && type == Material.AIR) continue;
-
- if (type != portalBlockClosed && type != portalBlockOpen) {
- Stargate.debug("Gate::Matches", "Entrance/Exit Material Mismatch: " + type);
- return false;
- }
- } else if (!key.equals(ANYTHING)) {
- Material id = portalTypes.get(key);
- if(id == null) {
- portalTypes.put(key, topleft.modRelative(x, y, 0, modX, 1, modZ).getType());
- } else if(topleft.modRelative(x, y, 0, modX, 1, modZ).getType() != id) {
- Stargate.debug("Gate::Matches", "Block Type Mismatch: " + topleft.modRelative(x, y, 0, modX, 1, modZ).getType() + " != " + id);
- return false;
- }
- }
- }
- }
-
- return true;
- }
-
- public static void registerGate(Gate gate) {
- gates.put(gate.getFilename(), gate);
-
- Material blockID = gate.getControlBlock();
-
- if (!controlBlocks.containsKey(blockID)) {
- controlBlocks.put(blockID, new ArrayList<>());
- }
-
- controlBlocks.get(blockID).add(gate);
- }
-
- public static Gate loadGate(File file) {
- Scanner scanner = null;
- boolean designing = false;
- ArrayList> design = new ArrayList<>();
- HashMap types = new HashMap<>();
- HashMap config = new HashMap<>();
- HashSet frameTypes = new HashSet<>();
- int cols = 0;
-
- // Init types map
- types.put(ENTRANCE, Material.AIR);
- types.put(EXIT, Material.AIR);
- types.put(ANYTHING, Material.AIR);
-
- try {
- scanner = new Scanner(file);
-
- while (scanner.hasNextLine()) {
- String line = scanner.nextLine();
-
- if (designing) {
- ArrayList row = new ArrayList<>();
-
- if (line.length() > cols) {
- cols = line.length();
- }
-
- for (Character symbol : line.toCharArray()) {
- if ((symbol.equals('?')) || (!types.containsKey(symbol))) {
- Stargate.log.log(Level.SEVERE, "Could not load Gate " + file.getName() + " - Unknown symbol '" + symbol + "' in diagram");
- return null;
- }
- row.add(symbol);
- }
-
- design.add(row);
- } else {
- if ((line.isEmpty()) || (!line.contains("="))) {
- designing = true;
- } else {
- String[] split = line.split("=");
- String key = split[0].trim();
- String value = split[1].trim();
-
- if (key.length() == 1) {
- Character symbol = key.charAt(0);
- Material id = Material.getMaterial(value);
- if(id == null) {
- throw new Exception("Invalid material in line: " + line);
- }
- types.put(symbol, id);
- frameTypes.add(id);
- } else {
- config.put(key, value);
- }
- }
- }
- }
- } catch (Exception ex) {
- Stargate.log.log(Level.SEVERE, "Could not load Gate " + file.getName() + " - " + ex.getMessage());
- return null;
- } finally {
- if (scanner != null) scanner.close();
- }
-
- Character[][] layout = new Character[design.size()][cols];
-
- for (int y = 0; y < design.size(); y++) {
- ArrayList row = design.get(y);
- Character[] result = new Character[cols];
-
- for (int x = 0; x < cols; x++) {
- if (x < row.size()) {
- result[x] = row.get(x);
- } else {
- result[x] = ' ';
- }
- }
-
- layout[y] = result;
- }
-
- Gate gate = new Gate(file.getName(), layout, types);
-
- gate.portalBlockOpen = readConfig(config, gate, file, "portal-open", gate.portalBlockOpen);
- gate.portalBlockClosed = readConfig(config, gate, file, "portal-closed", gate.portalBlockClosed);
- gate.button = readConfig(config, gate, file, "button", gate.button);
- gate.useCost = readConfig(config, gate, file, "usecost", -1);
- gate.destroyCost = readConfig(config, gate, file, "destroycost", -1);
- gate.createCost = readConfig(config, gate, file, "createcost", -1);
- gate.toOwner = (config.containsKey("toowner") ? Boolean.valueOf(config.get("toowner")) : EconomyHandler.toOwner);
-
- if (gate.getControls().length != 2) {
- Stargate.log.log(Level.SEVERE, "Could not load Gate " + file.getName() + " - Gates must have exactly 2 control points.");
- return null;
- }
-
- if (!Tag.BUTTONS.isTagged(gate.button)) {
- Stargate.log.log(Level.SEVERE, "Could not load Gate " + file.getName() + " - Gate button must be a type of button.");
- return null;
- }
-
- // Merge frame types, add open mat to list
- frameBlocks.addAll(frameTypes);
-
- gate.save(file.getParent() + "/"); // Updates format for version changes
- return gate;
- }
-
- private static int readConfig(HashMap config, Gate gate, File file, String key, int def) {
- if (config.containsKey(key)) {
- try {
- return Integer.parseInt(config.get(key));
- } catch (NumberFormatException ex) {
- Stargate.log.log(Level.WARNING, String.format("%s reading %s: %s is not numeric", ex.getClass().getName(), file, key));
- }
- }
-
- return def;
- }
-
- private static Material readConfig(HashMap config, Gate gate, File file, String key, Material def) {
- if (config.containsKey(key)) {
- Material mat = Material.getMaterial(config.get(key));
- if(mat != null) {
- return mat;
- }
- Stargate.log.log(Level.WARNING, String.format("Error reading %s: %s is not a material", file, key));
- }
- return def;
- }
-
- public static void loadGates(String gateFolder) {
- File dir = new File(gateFolder);
- File[] files;
-
- if (dir.exists()) {
- files = dir.listFiles(new StargateFilenameFilter());
- } else {
- files = new File[0];
- }
-
- if (files == null || files.length == 0) {
- if (dir.mkdir()) {
- populateDefaults(gateFolder);
- }
- } else {
- for (File file : files) {
- Gate gate = loadGate(file);
- if (gate != null) registerGate(gate);
- }
- }
- }
-
- public static void populateDefaults(String gateFolder) {
- Character[][] layout = new Character[][] {
- {' ', 'X','X', ' '},
- {'X', '.', '.', 'X'},
- {'-', '.', '.', '-'},
- {'X', '*', '.', 'X'},
- {' ', 'X', 'X', ' '},
- };
- HashMap types = new HashMap<>();
- types.put(ENTRANCE, Material.AIR);
- types.put(EXIT, Material.AIR);
- types.put(ANYTHING, Material.AIR);
- types.put('X', Material.OBSIDIAN);
- types.put('-', Material.OBSIDIAN);
-
- Gate gate = new Gate("nethergate.gate", layout, types);
- gate.save(gateFolder);
- registerGate(gate);
- }
-
- public static Gate[] getGatesByControlBlock(Block block) {
- return getGatesByControlBlock(block.getType());
- }
-
- public static Gate[] getGatesByControlBlock(Material type) {
- Gate[] result = new Gate[0];
- ArrayList lookup = controlBlocks.get(type);
-
- if (lookup != null) result = lookup.toArray(result);
-
- return result;
- }
-
- public static Gate getGateByName(String name) {
- return gates.get(name);
- }
-
- public static int getGateCount() {
- return gates.size();
- }
-
- public static boolean isGateBlock(Material type) {
- return frameBlocks.contains(type);
- }
-
- static class StargateFilenameFilter implements FilenameFilter {
- public boolean accept(File dir, String name) {
- return name.endsWith(".gate");
- }
- }
-
- public static void clearGates() {
- gates.clear();
- controlBlocks.clear();
- frameBlocks.clear();
- }
-}
diff --git a/src/net/TheDgtl/Stargate/LangLoader.java b/src/net/TheDgtl/Stargate/LangLoader.java
deleted file mode 100644
index ad4a718..0000000
--- a/src/net/TheDgtl/Stargate/LangLoader.java
+++ /dev/null
@@ -1,226 +0,0 @@
-package net.TheDgtl.Stargate;
-
-import org.bukkit.ChatColor;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Set;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class LangLoader {
- private final String UTF8_BOM = "\uFEFF";
- // Variables
- private final String datFolder;
- private String lang;
- private HashMap strList;
- private final HashMap defList;
-
- public LangLoader(String datFolder, String lang) {
- this.lang = lang;
- this.datFolder = datFolder;
-
- File tmp = new File(datFolder, lang + ".txt");
- if (!tmp.exists()) {
- tmp.getParentFile().mkdirs();
- }
- updateLanguage(lang);
-
- strList = load(lang);
- // We have a default hashMap used for when new text is added.
- InputStream is = Stargate.class.getResourceAsStream("resources/" + lang + ".txt");
- if (is != null) {
- defList = load("en", is);
- } else {
- defList = null;
- Stargate.log.severe("[Stargate] Error loading backup language. There may be missing text ingame");
- }
- }
-
- public boolean reload() {
- // This extracts/updates the language as needed
- updateLanguage(lang);
- strList = load(lang);
- return true;
- }
-
- public String getString(String name) {
- String val = strList.get(name);
- if (val == null && defList != null) val = defList.get(name);
- if (val == null) return "";
- return val;
- }
-
- public void setLang(String lang) {
- this.lang = lang;
- }
-
- // This function updates on-disk language files
- // with missing lines from the in-JAR files
- private void updateLanguage(String lang) {
- // Load the current language file
- ArrayList keyList = new ArrayList<>();
- ArrayList valList = new ArrayList<>();
-
- HashMap curLang = load(lang);
-
- InputStream is = Stargate.class.getResourceAsStream("resources/" + lang + ".txt");
- if (is == null) return;
-
- boolean updated = false;
- FileOutputStream fos = null;
- try {
- // Input stuff
- InputStreamReader isr = new InputStreamReader(is);
- BufferedReader br = new BufferedReader(isr);
-
- String line = br.readLine();
- boolean firstLine = true;
- while (line != null) {
- // Strip UTF BOM
- if (firstLine) line = removeUTF8BOM(line);
- firstLine = false;
- // Split at first "="
- int eq = line.indexOf('=');
- if (eq == -1) {
- keyList.add("");
- valList.add("");
- line = br.readLine();
- continue;
- }
- String key = line.substring(0, eq);
- String val = line.substring(eq);
-
- if (curLang == null || curLang.get(key) == null) {
- keyList.add(key);
- valList.add(val);
- updated = true;
- } else {
- keyList.add(key);
- valList.add("=" + curLang.get(key).replace('\u00A7', '&'));
- curLang.remove(key);
- }
- line = br.readLine();
- }
- br.close();
-
- // Save file
- fos = new FileOutputStream(datFolder + lang + ".txt");
- OutputStreamWriter out = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
- BufferedWriter bw = new BufferedWriter(out);
-
- // Output normal Language data
- for (int i = 0; i < keyList.size(); i++) {
- bw.write(keyList.get(i) + valList.get(i));
- bw.newLine();
- }
- bw.newLine();
- // Output any custom language strings the user had
- if(curLang != null) {
- for (String key : curLang.keySet()) {
- bw.write(key + "=" + curLang.get(key));
- bw.newLine();
- }
- }
-
- bw.close();
- } catch (Exception ex) {
- ex.printStackTrace();
- } finally {
- if (fos != null) {
- try {fos.close();} catch (Exception ex) {}
- }
- }
- if (updated)
- Stargate.log.info("[Stargate] Your language file (" + lang + ".txt) has been updated");
- }
-
- private HashMap load(String lang) {
- return load(lang, null);
- }
-
- private HashMap load(String lang, InputStream is) {
- HashMap strings = new HashMap<>();
- FileInputStream fis = null;
- InputStreamReader isr = null;
- try {
- if (is == null) {
- fis = new FileInputStream(datFolder + lang + ".txt");
- isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
- } else {
- isr = new InputStreamReader(is, StandardCharsets.UTF_8);
- }
- BufferedReader br = new BufferedReader(isr);
- String line = br.readLine();
- boolean firstLine = true;
- while (line != null) {
- // Strip UTF BOM
- if (firstLine) line = removeUTF8BOM(line);
- firstLine = false;
- // Split at first "="
- int eq = line.indexOf('=');
- if (eq == -1) {
- line = br.readLine();
- continue;
- }
- String key = line.substring(0, eq);
- String val = ChatColor.translateAlternateColorCodes('&', line.substring(eq + 1));
- strings.put(key, val);
- line = br.readLine();
- }
- } catch (Exception ex) {
- return null;
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (Exception ex) {}
- }
- }
- return strings;
- }
-
- public void debug() {
- Set keys = strList.keySet();
- for (String key : keys) {
- Stargate.debug("LangLoader::Debug::strList", key + " => " + strList.get(key));
- }
- if (defList == null) return;
- keys = defList.keySet();
- for (String key : keys) {
- Stargate.debug("LangLoader::Debug::defList", key + " => " + defList.get(key));
- }
- }
-
- private String removeUTF8BOM(String s) {
- if (s.startsWith(UTF8_BOM)) {
- s = s.substring(1);
- }
- return s;
- }
-}
diff --git a/src/net/TheDgtl/Stargate/Portal.java b/src/net/TheDgtl/Stargate/Portal.java
deleted file mode 100644
index 2a74f76..0000000
--- a/src/net/TheDgtl/Stargate/Portal.java
+++ /dev/null
@@ -1,1495 +0,0 @@
-package net.TheDgtl.Stargate;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.util.*;
-import java.util.logging.Level;
-
-import net.TheDgtl.Stargate.event.StargateActivateEvent;
-import net.TheDgtl.Stargate.event.StargateCloseEvent;
-import net.TheDgtl.Stargate.event.StargateCreateEvent;
-import net.TheDgtl.Stargate.event.StargateDeactivateEvent;
-import net.TheDgtl.Stargate.event.StargateOpenEvent;
-import net.TheDgtl.Stargate.event.StargatePortalEvent;
-
-import org.bukkit.Axis;
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
-import org.bukkit.Location;
-import org.bukkit.Material;
-import org.bukkit.OfflinePlayer;
-import org.bukkit.World;
-import org.bukkit.block.Block;
-import org.bukkit.block.BlockFace;
-import org.bukkit.block.BlockState;
-import org.bukkit.block.Sign;
-import org.bukkit.block.data.Bisected;
-import org.bukkit.block.data.BlockData;
-import org.bukkit.block.data.Directional;
-import org.bukkit.block.data.Powerable;
-import org.bukkit.block.data.type.WallSign;
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.Player;
-import org.bukkit.entity.minecart.StorageMinecart;
-import org.bukkit.entity.Vehicle;
-import org.bukkit.event.block.SignChangeEvent;
-import org.bukkit.event.player.PlayerMoveEvent;
-import org.bukkit.util.Vector;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011 Shaun (sturmeh)
- * Copyright (C) 2011 Dinnerbone
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class Portal {
- // Static variables used to store portal lists
- private static final HashMap lookupBlocks = new HashMap<>();
- private static final HashMap lookupEntrances = new HashMap<>();
- private static final HashMap lookupControls = new HashMap<>();
- private static final ArrayList allPortals = new ArrayList<>();
- private static final HashMap> allPortalsNet = new HashMap<>();
- private static final HashMap> lookupNamesNet = new HashMap<>();
-
- // A list of Bungee gates
- private static final HashMap bungeePortals = new HashMap<>();
-
- // Gate location block info
- private final Blox topLeft;
- private final int modX;
- private final int modZ;
- private final float rotX;
- private final Axis rot;
-
- // Block references
- private final Blox id;
- private Blox button;
- private Blox[] frame;
- private Blox[] entrances;
-
- // Gate information
- private String name;
- private String destination;
- private String lastDest = "";
- private String network;
- private final Gate gate;
- private String ownerName = "";
- private UUID ownerUUID = null;
- private final World world;
- private boolean verified;
- private boolean fixed;
-
- // Options
- private boolean hidden = false;
- private boolean alwaysOn = false;
- private boolean priv = false;
- private boolean free = false;
- private boolean backwards = false;
- private boolean show = false;
- private boolean noNetwork = false;
- private boolean random = false;
- private boolean bungee = false;
-
- // In-use information
- private Player player;
- private Player activePlayer;
- private ArrayList destinations = new ArrayList<>();
- private boolean isOpen = false;
- private long openTime;
-
- private Portal(Blox topLeft, int modX, int modZ,
- float rotX, Blox id, Blox button,
- String dest, String name,
- boolean verified, String network, Gate gate, UUID ownerUUID, String ownerName,
- boolean hidden, boolean alwaysOn, boolean priv, boolean free, boolean backwards, boolean show, boolean noNetwork, boolean random, boolean bungee) {
- this.topLeft = topLeft;
- this.modX = modX;
- this.modZ = modZ;
- this.rotX = rotX;
- this.rot = rotX == 0.0F || rotX == 180.0F ? Axis.X : Axis.Z;
- this.id = id;
- this.destination = dest;
- this.button = button;
- this.verified = verified;
- this.network = network;
- this.name = name;
- this.gate = gate;
- this.ownerUUID = ownerUUID;
- this.ownerName = ownerName;
- this.hidden = hidden;
- this.alwaysOn = alwaysOn;
- this.priv = priv;
- this.free = free;
- this.backwards = backwards;
- this.show = show;
- this.noNetwork = noNetwork;
- this.random = random;
- this.bungee = bungee;
- this.world = topLeft.getWorld();
- this.fixed = dest.length() > 0 || this.random || this.bungee;
-
- if (this.isAlwaysOn() && !this.isFixed()) {
- this.alwaysOn = false;
- Stargate.debug("Portal", "Can not create a non-fixed always-on gate. Setting AlwaysOn = false");
- }
-
- if (this.random && !this.isAlwaysOn()) {
- this.alwaysOn = true;
- Stargate.debug("Portal", "Gate marked as random, set to always-on");
- }
-
- if (verified) {
- this.drawSign();
- }
- }
-
- /**
- * Option Check Functions
- */
- public boolean isOpen() {
- return isOpen || isAlwaysOn();
- }
-
- public boolean isAlwaysOn() {
- return alwaysOn;
- }
-
- public boolean isHidden() {
- return hidden;
- }
-
- public boolean isPrivate() {
- return priv;
- }
-
- public boolean isFree() {
- return free;
- }
-
- public boolean isBackwards() {
- return backwards;
- }
-
- public boolean isShown() {
- return show;
- }
-
- public boolean isNoNetwork() {
- return noNetwork;
- }
-
- public boolean isRandom() {
- return random;
- }
-
- public boolean isBungee() {
- return bungee;
- }
-
- public void setAlwaysOn(boolean alwaysOn) {
- this.alwaysOn = alwaysOn;
- }
-
- public void setHidden(boolean hidden) {
- this.hidden = hidden;
- }
-
- public void setPrivate(boolean priv) {
- this.priv = priv;
- }
-
- public void setFree(boolean free) {
- this.free = free;
- }
-
- public void setBackwards(boolean backwards) {
- this.backwards = backwards;
- }
-
- public void setShown(boolean show) {
- this.show = show;
- }
-
- public void setNoNetwork(boolean noNetwork) {
- this.noNetwork = noNetwork;
- }
-
- public void setRandom(boolean random) {
- this.random = random;
- }
-
- /**
- * Getters and Setters
- */
-
- public float getRotation() {
- return rotX;
- }
-
- public Axis getAxis() {
- return rot;
- }
-
- public Player getActivePlayer() {
- return activePlayer;
- }
-
- public String getNetwork() {
- return network;
- }
-
- public void setNetwork(String network) {
- this.network = network;
- }
-
- public long getOpenTime() {
- return openTime;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = filterName(name);
- drawSign();
- }
-
- public Portal getDestination(Player player) {
- if (isRandom()) {
- destinations = getDestinations(player, getNetwork());
- if (destinations.size() == 0) {
- return null;
- }
- String dest = destinations.get((new Random()).nextInt(destinations.size()));
- destinations.clear();
- return Portal.getByName(dest, getNetwork());
- }
- return Portal.getByName(destination, getNetwork());
- }
-
- public Portal getDestination() {
- return getDestination(null);
- }
-
- public void setDestination(Portal destination) {
- setDestination(destination.getName());
- }
-
- public void setDestination(String destination) {
- this.destination = destination;
- }
-
- public String getDestinationName() {
- return destination;
- }
-
- public Gate getGate() {
- return gate;
- }
-
- public String getOwnerName() {
- return ownerName;
- }
-
- public UUID getOwnerUUID() {
- return ownerUUID;
- }
-
- public void setOwner(UUID owner) {
- this.ownerUUID = owner;
- }
-
- public boolean isOwner(Player player) {
- if(this.ownerUUID != null) {
- return player.getUniqueId().compareTo(this.ownerUUID) == 0;
- } else {
- return player.getName().equalsIgnoreCase(this.ownerName);
- }
- }
-
- public Blox[] getEntrances() {
- if (entrances == null) {
- RelativeBlockVector[] space = gate.getEntrances();
- entrances = new Blox[space.length];
- int i = 0;
-
- for (RelativeBlockVector vector : space) {
- entrances[i++] = getBlockAt(vector);
- }
- }
- return entrances;
- }
-
- public Blox[] getFrame() {
- if (frame == null) {
- RelativeBlockVector[] border = gate.getBorder();
- frame = new Blox[border.length];
- int i = 0;
-
- for (RelativeBlockVector vector : border) {
- frame[i++] = getBlockAt(vector);
- }
- }
-
- return frame;
- }
-
- public Blox getSign() {
- return id;
- }
-
- public World getWorld() {
- return world;
- }
-
- public Blox getButton() {
- return button;
- }
-
- public void setButton(Blox button) {
- this.button = button;
- }
-
- public static ArrayList getNetwork(String network) {
- return allPortalsNet.get(network.toLowerCase());
- }
-
- public boolean open(boolean force) {
- return open(null, force);
- }
-
- public boolean open(Player openFor, boolean force) {
- // Call the StargateOpenEvent
- StargateOpenEvent event = new StargateOpenEvent(openFor, this, force);
- Stargate.server.getPluginManager().callEvent(event);
- if (event.isCancelled()) return false;
- force = event.getForce();
-
- if (isOpen() && !force) return false;
-
- Material openType = gate.getPortalBlockOpen();
- Axis ax = openType == Material.NETHER_PORTAL ? rot : null;
- for (Blox inside : getEntrances()) {
- Stargate.blockPopulatorQueue.add(new BloxPopulator(inside, openType, ax));
- }
-
- isOpen = true;
- openTime = System.currentTimeMillis() / 1000;
- Stargate.openList.add(this);
- Stargate.activeList.remove(this);
-
- // Open remote gate
- if (!isAlwaysOn()) {
- player = openFor;
-
- Portal end = getDestination();
- // Only open dest if it's not-fixed or points at this gate
- if (!random && end != null && (!end.isFixed() || end.getDestinationName().equalsIgnoreCase(getName())) && !end.isOpen()) {
- end.open(openFor, false);
- end.setDestination(this);
- if (end.isVerified()) end.drawSign();
- }
- }
-
- return true;
- }
-
- public void close(boolean force) {
- if (!isOpen) return;
- // Call the StargateCloseEvent
- StargateCloseEvent event = new StargateCloseEvent(this, force);
- Stargate.server.getPluginManager().callEvent(event);
- if (event.isCancelled()) return;
- force = event.getForce();
-
- if (isAlwaysOn() && !force) return; // Only close always-open if forced
-
- // Close this gate, then the dest gate.
- Material closedType = gate.getPortalBlockClosed();
- for (Blox inside : getEntrances()) {
- Stargate.blockPopulatorQueue.add(new BloxPopulator(inside, closedType));
- }
-
- player = null;
- isOpen = false;
- Stargate.openList.remove(this);
- Stargate.activeList.remove(this);
-
- if (!isAlwaysOn()) {
- Portal end = getDestination();
-
- if (end != null && end.isOpen()) {
- end.deactivate(); // Clear it's destination first.
- end.close(false);
- }
- }
-
- deactivate();
- }
-
- public boolean isOpenFor(Player player) {
- if (!isOpen) {
- return false;
- }
- if ((isAlwaysOn()) || (this.player == null)) {
- return true;
- }
- return (player != null) && (player.getName().equalsIgnoreCase(this.player.getName()));
- }
-
- public boolean isFixed() {
- return fixed;
- }
-
- public boolean isPowered() {
- RelativeBlockVector[] controls = gate.getControls();
-
- for (RelativeBlockVector vector : controls) {
- BlockData data = getBlockAt(vector).getBlock().getBlockData();
-
- if (data instanceof Powerable && ((Powerable) data).isPowered()) {
- return true;
- }
- }
-
- return false;
- }
-
- public void teleport(Player player, Portal origin, PlayerMoveEvent event) {
- Location traveller = player.getLocation();
- Location exit = getExit(traveller);
-
- // Handle backwards gates
- int adjust = 180;
- if (isBackwards() != origin.isBackwards())
- adjust = 0;
- exit.setYaw(traveller.getYaw() - origin.getRotation() + this.getRotation() + adjust);
-
- // Call the StargatePortalEvent to allow plugins to change destination
- if (!origin.equals(this)) {
- StargatePortalEvent pEvent = new StargatePortalEvent(player, origin, this, exit);
- Stargate.server.getPluginManager().callEvent(pEvent);
- // Teleport is cancelled
- if (pEvent.isCancelled()) {
- origin.teleport(player, origin, event);
- return;
- }
- // Update exit if needed
- exit = pEvent.getExit();
- }
-
- // If no event is passed in, assume it's a teleport, and act as such
- if (event == null) {
- exit.setYaw(this.getRotation());
- player.teleport(exit);
- } else {
- // The new method to teleport in a move event is set the "to" field.
- event.setTo(exit);
- }
- }
-
- public void teleport(final Vehicle vehicle) {
- Location traveller = new Location(this.world, vehicle.getLocation().getX(), vehicle.getLocation().getY(), vehicle.getLocation().getZ());
- Location exit = getExit(traveller);
-
- double velocity = vehicle.getVelocity().length();
-
- // Stop and teleport
- vehicle.setVelocity(new Vector());
-
- // Get new velocity
- final Vector newVelocity = new Vector(modX, 0.0F, modZ);
- newVelocity.multiply(velocity);
-
- List passengers = vehicle.getPassengers();
- if (!passengers.isEmpty()) {
- final Vehicle v = exit.getWorld().spawn(exit, vehicle.getClass());
- final Entity passenger = passengers.get(0);
- vehicle.eject();
- vehicle.remove();
- passenger.eject();
- passenger.teleport(exit);
- Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, () -> {
- v.addPassenger(passenger);
- v.setVelocity(newVelocity);
- }, 1);
- } else {
- Vehicle mc = exit.getWorld().spawn(exit, vehicle.getClass());
- if (mc instanceof StorageMinecart) {
- StorageMinecart smc = (StorageMinecart)mc;
- smc.getInventory().setContents(((StorageMinecart)vehicle).getInventory().getContents());
- }
- mc.setVelocity(newVelocity);
- vehicle.remove();
- }
- }
-
- public Location getExit(Location traveller) {
- Location loc = null;
- // Check if the gate has an exit block
- if (gate.getExit() != null) {
- Blox exit = getBlockAt(gate.getExit());
- int back = (isBackwards()) ? -1 : 1;
- loc = exit.modRelativeLoc(0D, 0D, 1D, traveller.getYaw(), traveller.getPitch(), modX * back, 1, modZ * back);
- } else {
- Stargate.log.log(Level.WARNING, "[Stargate] Missing destination point in .gate file " + gate.getFilename());
- }
-
- if (loc != null) {
- BlockData bd = getWorld().getBlockAt(loc).getBlockData();
- if (bd instanceof Bisected && ((Bisected) bd).getHalf() == Bisected.Half.BOTTOM) {
- loc.add(0, 0.5, 0);
- }
-
- loc.setPitch(traveller.getPitch());
- return loc;
- }
- return traveller;
- }
-
- public boolean isChunkLoaded() {
- return getWorld().isChunkLoaded(topLeft.getBlock().getChunk());
- }
-
- public boolean isVerified() {
- verified = true;
- if(!Stargate.verifyPortals) {
- return true;
- }
- for (RelativeBlockVector control : gate.getControls()) {
- verified = verified && getBlockAt(control).getBlock().getType().equals(gate.getControlBlock());
- }
- return verified;
- }
-
- public boolean wasVerified() {
- if(!Stargate.verifyPortals) {
- return true;
- }
- return verified;
- }
-
- public boolean checkIntegrity() {
- if(!Stargate.verifyPortals) {
- return true;
- }
- return gate.matches(topLeft, modX, modZ);
- }
-
- public ArrayList getDestinations(Player player, String network) {
- ArrayList destinations = new ArrayList<>();
- for (String dest : allPortalsNet.get(network.toLowerCase())) {
- Portal portal = getByName(dest, network);
- if (portal == null) continue;
- // Check if dest is a random gate
- if (portal.isRandom()) continue;
- // Check if dest is always open (Don't show if so)
- if (portal.isAlwaysOn() && !portal.isShown()) continue;
- // Check if dest is this portal
- if (dest.equalsIgnoreCase(getName())) continue;
- // Check if dest is a fixed gate not pointing to this gate
- if (portal.isFixed() && !portal.getDestinationName().equalsIgnoreCase(getName())) continue;
- // Allow random use by non-players (Minecarts)
- if (player == null) {
- destinations.add(portal.getName());
- continue;
- }
- // Check if this player can access the dest world
- if (!Stargate.canAccessWorld(player, portal.getWorld().getName())) continue;
- // Visible to this player.
- if (Stargate.canSee(player, portal)) {
- destinations.add(portal.getName());
- }
- }
- return destinations;
- }
-
- public boolean activate(Player player) {
- destinations.clear();
- destination = "";
- Stargate.activeList.add(this);
- activePlayer = player;
- String network = getNetwork();
- destinations = getDestinations(player, network);
- if (Stargate.sortLists) {
- Collections.sort(destinations);
- }
- if (Stargate.destMemory && !lastDest.isEmpty() && destinations.contains(lastDest)) {
- destination = lastDest;
- }
-
- StargateActivateEvent event = new StargateActivateEvent(this, player, destinations, destination);
- Stargate.server.getPluginManager().callEvent(event);
- if (event.isCancelled()) {
- Stargate.activeList.remove(this);
- return false;
- }
- destination = event.getDestination();
- destinations = event.getDestinations();
- drawSign();
- return true;
- }
-
- public void deactivate() {
- StargateDeactivateEvent event = new StargateDeactivateEvent(this);
- Stargate.server.getPluginManager().callEvent(event);
- if (event.isCancelled()) return;
-
- Stargate.activeList.remove(this);
- if (isFixed()) {
- return;
- }
- destinations.clear();
- destination = "";
- activePlayer = null;
- drawSign();
- }
-
- public boolean isActive() {
- return isFixed() || (destinations.size() > 0);
- }
-
- public void cycleDestination(Player player) {
- cycleDestination(player, 1);
- }
-
- public void cycleDestination(Player player, int dir) {
- boolean activate = false;
- if (!isActive() || getActivePlayer() != player) {
- // If the event is cancelled, return
- if (!activate(player)) {
- return;
- }
- Stargate.debug("cycleDestination", "Network Size: " + allPortalsNet.get(network.toLowerCase()).size());
- Stargate.debug("cycleDestination", "Player has access to: " + destinations.size());
- activate = true;
- }
-
- if (destinations.size() == 0) {
- Stargate.sendMessage(player, Stargate.getString("destEmpty"));
- return;
- }
-
- if (!Stargate.destMemory || !activate || lastDest.isEmpty()) {
- int index = destinations.indexOf(destination);
- index += dir;
- if (index >= destinations.size())
- index = 0;
- else if (index < 0)
- index = destinations.size() - 1;
- destination = destinations.get(index);
- lastDest = destination;
- }
- openTime = System.currentTimeMillis() / 1000;
- drawSign();
- }
-
- public final void drawSign() {
- BlockState state = id.getBlock().getState();
- if (!(state instanceof Sign)) {
- Stargate.log.warning("[Stargate] Sign block is not a Sign object");
- Stargate.debug("Portal::drawSign", "Block: " + id.getBlock().getType() + " @ " + id.getBlock().getLocation());
- return;
- }
- Sign sign = (Sign) state;
- Stargate.setLine(sign, 0, "-" + name + "-");
- int max = destinations.size() - 1;
- int done = 0;
-
- if (!isActive()) {
- Stargate.setLine(sign, ++done, Stargate.getString("signRightClick"));
- Stargate.setLine(sign, ++done, Stargate.getString("signToUse"));
- if (!noNetwork) {
- Stargate.setLine(sign, ++done, "(" + network + ")");
- }
- } else {
- // Awesome new logic for Bungee gates
- if (isBungee()) {
- Stargate.setLine(sign, ++done, Stargate.getString("bungeeSign"));
- Stargate.setLine(sign, ++done, ">" + destination + "<");
- Stargate.setLine(sign, ++done, "[" + network + "]");
- } else if (isFixed()) {
- if (isRandom()) {
- Stargate.setLine(sign, ++done, "> " + Stargate.getString("signRandom") + " <");
- } else {
- Stargate.setLine(sign, ++done, ">" + destination + "<");
- }
- if (noNetwork) {
- Stargate.setLine(sign, ++done, "");
- } else {
- Stargate.setLine(sign, ++done, "(" + network + ")");
- }
- Portal dest = Portal.getByName(destination, network);
- if (dest == null && !isRandom()) {
- Stargate.setLine(sign, ++done, Stargate.getString("signDisconnected"));
- } else {
- Stargate.setLine(sign, ++done, "");
- }
- } else {
- int index = destinations.indexOf(destination);
- if ((index == max) && (max > 1) && (++done <= 3)) {
- if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
- Portal dest = Portal.getByName(destinations.get(index - 2), network);
- boolean green = Stargate.isFree(activePlayer, this, dest);
- Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index - 2));
- } else {
- Stargate.setLine(sign, done, destinations.get(index - 2));
- }
- }
- if ((index > 0) && (++done <= 3)) {
- if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
- Portal dest = Portal.getByName(destinations.get(index - 1), network);
- boolean green = Stargate.isFree(activePlayer, this, dest);
- Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index - 1));
- } else {
- Stargate.setLine(sign, done, destinations.get(index - 1));
- }
- }
- if (++done <= 3) {
- if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
- Portal dest = Portal.getByName(destination, network);
- boolean green = Stargate.isFree(activePlayer, this, dest);
- Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + ">" + destination + "<");
- } else {
- Stargate.setLine(sign, done, " >" + destination + "< ");
- }
- }
- if ((max >= index + 1) && (++done <= 3)) {
- if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
- Portal dest = Portal.getByName(destinations.get(index + 1), network);
- boolean green = Stargate.isFree(activePlayer, this, dest);
- Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index + 1));
- } else {
- Stargate.setLine(sign, done, destinations.get(index + 1));
- }
- }
- if ((max >= index + 2) && (++done <= 3)) {
- if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
- Portal dest = Portal.getByName(destinations.get(index + 2), network);
- boolean green = Stargate.isFree(activePlayer, this, dest);
- Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index + 2));
- } else {
- Stargate.setLine(sign, done, destinations.get(index + 2));
- }
- }
- }
- }
-
- for (done++; done <= 3; done++) {
- sign.setLine(done, "");
- }
-
- sign.update();
- }
-
- public void unregister(boolean removeAll) {
- Stargate.debug("Unregister", "Unregistering gate " + getName());
- close(true);
-
- for (Blox block : getFrame()) {
- lookupBlocks.remove(block);
- }
- // Include the sign and button
- lookupBlocks.remove(id);
- if (button != null) {
- lookupBlocks.remove(button);
- }
-
- lookupControls.remove(id);
- if (button != null)
- lookupControls.remove(button);
-
- for (Blox entrance : getEntrances()) {
- lookupEntrances.remove(entrance);
- }
-
- if (removeAll)
- allPortals.remove(this);
-
- if (bungee) {
- bungeePortals.remove(getName().toLowerCase());
- } else {
- lookupNamesNet.get(getNetwork().toLowerCase()).remove(getName().toLowerCase());
- allPortalsNet.get(getNetwork().toLowerCase()).remove(getName().toLowerCase());
-
- for (String originName : allPortalsNet.get(getNetwork().toLowerCase())) {
- Portal origin = Portal.getByName(originName, getNetwork());
- if (origin == null) continue;
- if (!origin.getDestinationName().equalsIgnoreCase(getName())) continue;
- if (!origin.isVerified()) continue;
- if (origin.isFixed()) origin.drawSign();
- if (origin.isAlwaysOn()) origin.close(true);
- }
- }
-
- if (id.getBlock().getBlockData() instanceof WallSign) {
- Sign sign = (Sign)id.getBlock().getState();
- sign.setLine(0, getName());
- sign.setLine(1, "");
- sign.setLine(2, "");
- sign.setLine(3, "");
- sign.update();
- }
-
- saveAllGates(getWorld());
- }
-
- private Blox getBlockAt(RelativeBlockVector vector) {
- return topLeft.modRelative(vector.getRight(), vector.getDepth(), vector.getDistance(), modX, 1, modZ);
- }
-
- private void register() {
- fixed = destination.length() > 0 || random || bungee;
-
- // Bungee gates are stored in their own list
- if (isBungee()) {
- bungeePortals.put(getName().toLowerCase(), this);
- } else {
- // Check if network exists in our network list
- if (!lookupNamesNet.containsKey(getNetwork().toLowerCase())) {
- Stargate.debug("register", "Network " + getNetwork() + " not in lookupNamesNet, adding");
- lookupNamesNet.put(getNetwork().toLowerCase(), new HashMap<>());
- }
- lookupNamesNet.get(getNetwork().toLowerCase()).put(getName().toLowerCase(), this);
-
- // Check if this network exists
- if (!allPortalsNet.containsKey(getNetwork().toLowerCase())) {
- Stargate.debug("register", "Network " + getNetwork() + " not in allPortalsNet, adding");
- allPortalsNet.put(getNetwork().toLowerCase(), new ArrayList<>());
- }
- allPortalsNet.get(getNetwork().toLowerCase()).add(getName().toLowerCase());
- }
-
- for (Blox block : getFrame()) {
- lookupBlocks.put(block, this);
- }
- // Include the sign and button
- lookupBlocks.put(id, this);
- if (button != null) {
- lookupBlocks.put(button, this);
- }
-
- lookupControls.put(id, this);
- if (button != null)
- lookupControls.put(button, this);
-
- for (Blox entrance : getEntrances()) {
- lookupEntrances.put(entrance, this);
- }
-
- allPortals.add(this);
- }
-
- public static Portal createPortal(SignChangeEvent event, Player player) {
- Blox id = new Blox(event.getBlock());
- Block idParent = id.getParent();
- if (idParent == null) {
- return null;
- }
-
- if (Gate.getGatesByControlBlock(idParent).length == 0) return null;
-
- if (Portal.getByBlock(idParent) != null) {
- Stargate.debug("createPortal", "idParent belongs to existing gate");
- return null;
- }
-
- Blox parent = new Blox(player.getWorld(), idParent.getX(), idParent.getY(), idParent.getZ());
- Blox topleft = null;
- String name = filterName(event.getLine(0));
- String destName = filterName(event.getLine(1));
- String network = filterName(event.getLine(2));
- String options = filterName(event.getLine(3)).toLowerCase();
- boolean hidden = (options.indexOf('h') != -1);
- boolean alwaysOn = (options.indexOf('a') != -1);
- boolean priv = (options.indexOf('p') != -1);
- boolean free = (options.indexOf('f') != - 1);
- boolean backwards = (options.indexOf('b') != -1);
- boolean show = (options.indexOf('s') != -1);
- boolean noNetwork = (options.indexOf('n') != -1);
- boolean random = (options.indexOf('r') != -1);
- boolean bungee = (options.indexOf('u') != -1);
-
- // Check permissions for options.
- if (hidden && !Stargate.canOption(player, "hidden")) hidden = false;
- if (alwaysOn && !Stargate.canOption(player, "alwayson")) alwaysOn = false;
- if (priv && !Stargate.canOption(player, "private")) priv = false;
- if (free && !Stargate.canOption(player, "free")) free = false;
- if (backwards && !Stargate.canOption(player, "backwards")) backwards = false;
- if (show && !Stargate.canOption(player, "show")) show = false;
- if (noNetwork && !Stargate.canOption(player, "nonetwork")) noNetwork = false;
- if (random && !Stargate.canOption(player, "random")) random = false;
-
- // Can not create a non-fixed always-on gate.
- if (alwaysOn && destName.length() == 0) {
- alwaysOn = false;
- }
-
- // Show isn't useful if A is false
- if (show && !alwaysOn) {
- show = false;
- }
-
- // Random gates are always on and can't be shown
- if (random) {
- alwaysOn = true;
- show = false;
- }
-
- // Bungee gates are always on and don't support Random
- if (bungee) {
- alwaysOn = true;
- random = false;
- }
-
- // Moved the layout check so as to avoid invalid messages when not making a gate
- int modX = 0;
- int modZ = 0;
- float rotX = 0f;
- BlockFace buttonfacing = BlockFace.DOWN;
-
- if (idParent.getX() > id.getBlock().getX()) {
- modZ -= 1;
- rotX = 90f;
- buttonfacing = BlockFace.WEST;
- } else if (idParent.getX() < id.getBlock().getX()) {
- modZ += 1;
- rotX = 270f;
- buttonfacing = BlockFace.EAST;
- } else if (idParent.getZ() > id.getBlock().getZ()) {
- modX += 1;
- rotX = 180f;
- buttonfacing = BlockFace.NORTH;
- } else if (idParent.getZ() < id.getBlock().getZ()) {
- modX -= 1;
- rotX = 0f;
- buttonfacing = BlockFace.SOUTH;
- }
-
- Gate[] possibleGates = Gate.getGatesByControlBlock(idParent);
- Gate gate = null;
- RelativeBlockVector buttonVector = null;
-
- for (Gate possibility : possibleGates) {
- if ((gate == null) && (buttonVector == null)) {
- RelativeBlockVector[] vectors = possibility.getControls();
- RelativeBlockVector otherControl = null;
-
- for (RelativeBlockVector vector : vectors) {
- Blox tl = parent.modRelative(-vector.getRight(), -vector.getDepth(), -vector.getDistance(), modX, 1, modZ);
-
- if (gate == null) {
- if (possibility.matches(tl, modX, modZ, true)) {
- gate = possibility;
- topleft = tl;
-
- if (otherControl != null) {
- buttonVector = otherControl;
- }
- }
- } else if (otherControl != null) {
- buttonVector = vector;
- }
-
- otherControl = vector;
- }
- }
- }
-
- if ((gate == null) || (buttonVector == null)) {
- Stargate.debug("createPortal", "Could not find matching gate layout");
- return null;
- }
-
- // If the player is trying to create a Bungee gate without permissions, drop out here
- // Do this after the gate layout check, in the least
- if (bungee) {
- if (!Stargate.enableBungee) {
- Stargate.sendMessage(player, Stargate.getString("bungeeDisabled"));
- return null;
- } else if (!Stargate.hasPerm(player, "stargate.admin.bungee")) {
- Stargate.sendMessage(player, Stargate.getString("bungeeDeny"));
- return null;
- } else if (destName.isEmpty() || network.isEmpty()) {
- Stargate.sendMessage(player, Stargate.getString("bungeeEmpty"));
- return null;
- }
- }
-
- // Debug
- Stargate.debug("createPortal", "h = " + hidden + " a = " + alwaysOn + " p = " + priv + " f = " + free + " b = " + backwards + " s = " + show + " n = " + noNetwork + " r = " + random + " u = " + bungee);
-
- if (!bungee && (network.length() < 1 || network.length() > 11)) {
- network = Stargate.getDefaultNetwork();
- }
-
- boolean deny = false;
- String denyMsg = "";
-
- // Check if the player can create gates on this network
- if (!bungee && !Stargate.canCreate(player, network)) {
- Stargate.debug("createPortal", "Player doesn't have create permissions on network. Trying personal");
- if (Stargate.canCreatePersonal(player)) {
- network = player.getName();
- if (network.length() > 11) network = network.substring(0, 11);
- Stargate.debug("createPortal", "Creating personal portal");
- Stargate.sendMessage(player, Stargate.getString("createPersonal"));
- } else {
- Stargate.debug("createPortal", "Player does not have access to network");
- deny = true;
- denyMsg = Stargate.getString("createNetDeny");
- //return null;
- }
- }
-
- // Check if the player can create this gate layout
- String gateName = gate.getFilename();
- gateName = gateName.substring(0, gateName.indexOf('.'));
- if (!deny && !Stargate.canCreateGate(player, gateName)) {
- Stargate.debug("createPortal", "Player does not have access to gate layout");
- deny = true;
- denyMsg = Stargate.getString("createGateDeny");
- }
-
- // Check if the user can create gates to this world.
- if (!bungee && !deny && destName.length() > 0) {
- Portal p = Portal.getByName(destName, network);
- if (p != null) {
- String world = p.getWorld().getName();
- if (!Stargate.canAccessWorld(player, world)) {
- Stargate.debug("canCreate", "Player does not have access to destination world");
- deny = true;
- denyMsg = Stargate.getString("createWorldDeny");
- }
- }
- }
-
- // Bleh, gotta check to make sure none of this gate belongs to another gate. Boo slow.
- for (RelativeBlockVector v : gate.getBorder()) {
- Blox b = topleft.modRelative(v.getRight(), v.getDepth(), v.getDistance(), modX, 1, modZ);
- if (Portal.getByBlock(b.getBlock()) != null) {
- Stargate.debug("createPortal", "Gate conflicts with existing gate");
- Stargate.sendMessage(player, Stargate.getString("createConflict"));
- return null;
- }
- }
-
- Blox button = null;
- Portal portal = null;
- portal = new Portal(topleft, modX, modZ, rotX, id, button, destName, name, false, network, gate, player.getUniqueId(), player.getName(), hidden, alwaysOn, priv, free, backwards, show, noNetwork, random, bungee);
-
- int cost = Stargate.getCreateCost(player, gate);
-
- // Call StargateCreateEvent
- StargateCreateEvent cEvent = new StargateCreateEvent(player, portal, event.getLines(), deny, denyMsg, cost);
- Stargate.server.getPluginManager().callEvent(cEvent);
- if (cEvent.isCancelled()) {
- return null;
- }
- if (cEvent.getDeny()) {
- Stargate.sendMessage(player, cEvent.getDenyReason());
- return null;
- }
-
- cost = cEvent.getCost();
-
- // Name & Network can be changed in the event, so do these checks here.
- if (portal.getName().length() < 1 || portal.getName().length() > 11) {
- Stargate.debug("createPortal", "Name length error");
- Stargate.sendMessage(player, Stargate.getString("createNameLength"));
- return null;
- }
-
- // Don't do network checks for bungee gates
- if (portal.isBungee()) {
- if (bungeePortals.get(portal.getName().toLowerCase()) != null) {
- Stargate.debug("createPortal::Bungee", "Gate Exists");
- Stargate.sendMessage(player, Stargate.getString("createExists"));
- return null;
- }
- } else {
- if (getByName(portal.getName(), portal.getNetwork()) != null) {
- Stargate.debug("createPortal", "Name Error");
- Stargate.sendMessage(player, Stargate.getString("createExists"));
- return null;
- }
-
- // Check if there are too many gates in this network
- ArrayList netList = allPortalsNet.get(portal.getNetwork().toLowerCase());
- if (Stargate.maxGates > 0 && netList != null && netList.size() >= Stargate.maxGates) {
- Stargate.sendMessage(player, Stargate.getString("createFull"));
- return null;
- }
- }
-
- if (cost > 0) {
- if (!Stargate.chargePlayer(player, cost)) {
- String inFundMsg = Stargate.getString("ecoInFunds");
- inFundMsg = Stargate.replaceVars(inFundMsg, new String[] {"%cost%", "%portal%"}, new String[] {EconomyHandler.format(cost), name});
- Stargate.sendMessage(player, inFundMsg);
- Stargate.debug("createPortal", "Insufficient Funds");
- return null;
- }
- String deductMsg = Stargate.getString("ecoDeduct");
- deductMsg = Stargate.replaceVars(deductMsg, new String[] {"%cost%", "%portal%"}, new String[] {EconomyHandler.format(cost), name});
- Stargate.sendMessage(player, deductMsg, false);
- }
-
- // No button on an always-open gate.
- if (!alwaysOn) {
- button = topleft.modRelative(buttonVector.getRight(), buttonVector.getDepth(), buttonVector.getDistance() + 1, modX, 1, modZ);
- Directional buttondata = (Directional) Bukkit.createBlockData(gate.getButton());
- buttondata.setFacing(buttonfacing);
- button.getBlock().setBlockData(buttondata);
- portal.setButton(button);
- }
-
- portal.register();
- portal.drawSign();
- // Open always on gate
- if (portal.isRandom() || portal.isBungee()) {
- portal.open(true);
- } else if (portal.isAlwaysOn()) {
- Portal dest = Portal.getByName(destName, portal.getNetwork());
- if (dest != null) {
- portal.open(true);
- dest.drawSign();
- }
- // Set the inside of the gate to its closed material
- } else {
- for (Blox inside : portal.getEntrances()) {
- inside.setType(portal.getGate().getPortalBlockClosed());
- }
- }
-
- // Don't do network stuff for bungee gates
- if (!portal.isBungee()) {
- // Open any always on gate pointing at this gate
- for (String originName : allPortalsNet.get(portal.getNetwork().toLowerCase())) {
- Portal origin = Portal.getByName(originName, portal.getNetwork());
- if (origin == null) continue;
- if (!origin.getDestinationName().equalsIgnoreCase(portal.getName())) continue;
- if (!origin.isVerified()) continue;
- if (origin.isFixed()) origin.drawSign();
- if (origin.isAlwaysOn()) origin.open(true);
- }
- }
-
- saveAllGates(portal.getWorld());
-
- return portal;
- }
-
- public static Portal getByName(String name, String network) {
- if (!lookupNamesNet.containsKey(network.toLowerCase())) return null;
- return lookupNamesNet.get(network.toLowerCase()).get(name.toLowerCase());
-
- }
-
- public static Portal getByEntrance(Location location) {
- return lookupEntrances.get(new Blox(location));
- }
-
- public static Portal getByEntrance(Block block) {
- return lookupEntrances.get(new Blox(block));
- }
-
- public static Portal getByAdjacentEntrance(Location loc) {
- int centerX = loc.getBlockX();
- int centerY = loc.getBlockY();
- int centerZ = loc.getBlockZ();
- World world = loc.getWorld();
- Portal portal = lookupEntrances.get(new Blox(world, centerX, centerY, centerZ));
- if(portal != null) {
- return portal;
- }
- portal = lookupEntrances.get(new Blox(world, centerX + 1, centerY, centerZ));
- if(portal != null) {
- return portal;
- }
- portal = lookupEntrances.get(new Blox(world, centerX - 1, centerY, centerZ));
- if(portal != null) {
- return portal;
- }
- portal = lookupEntrances.get(new Blox(world, centerX, centerY, centerZ + 1));
- if(portal != null) {
- return portal;
- }
- portal = lookupEntrances.get(new Blox(world, centerX, centerY, centerZ - 1));
- if(portal != null) {
- return portal;
- }
- return null;
- }
-
- public static Portal getByControl(Block block) {
- return lookupControls.get(new Blox(block));
- }
-
- public static Portal getByBlock(Block block) {
- return lookupBlocks.get(new Blox(block));
- }
-
- public static Portal getBungeeGate(String name) {
- return bungeePortals.get(name.toLowerCase());
- }
-
- public static void saveAllGates(World world) {
- Stargate.managedWorlds.add(world.getName());
- String loc = Stargate.getSaveLocation() + "/" + world.getName() + ".db";
-
- try {
- BufferedWriter bw = new BufferedWriter(new FileWriter(loc, false));
-
- for (Portal portal : allPortals) {
- String wName = portal.world.getName();
- if (!wName.equalsIgnoreCase(world.getName())) continue;
- StringBuilder builder = new StringBuilder();
- Blox sign = portal.id;
- Blox button = portal.button;
-
- builder.append(portal.name);
- builder.append(':');
- builder.append(sign.toString());
- builder.append(':');
- builder.append((button != null) ? button.toString() : "");
- builder.append(':');
- builder.append(portal.modX);
- builder.append(':');
- builder.append(portal.modZ);
- builder.append(':');
- builder.append(portal.rotX);
- builder.append(':');
- builder.append(portal.topLeft.toString());
- builder.append(':');
- builder.append(portal.gate.getFilename());
- builder.append(':');
- builder.append(portal.isFixed() ? portal.getDestinationName() : "");
- builder.append(':');
- builder.append(portal.getNetwork());
- builder.append(':');
- UUID owner = portal.getOwnerUUID();
- if(owner != null) {
- builder.append(portal.getOwnerUUID().toString());
- } else {
- builder.append(portal.getOwnerName());
- }
- builder.append(':');
- builder.append(portal.isHidden());
- builder.append(':');
- builder.append(portal.isAlwaysOn());
- builder.append(':');
- builder.append(portal.isPrivate());
- builder.append(':');
- builder.append(portal.world.getName());
- builder.append(':');
- builder.append(portal.isFree());
- builder.append(':');
- builder.append(portal.isBackwards());
- builder.append(':');
- builder.append(portal.isShown());
- builder.append(':');
- builder.append(portal.isNoNetwork());
- builder.append(':');
- builder.append(portal.isRandom());
- builder.append(':');
- builder.append(portal.isBungee());
-
- bw.append(builder.toString());
- bw.newLine();
- }
-
- bw.close();
- } catch (Exception e) {
- Stargate.log.log(Level.SEVERE, "Exception while writing stargates to " + loc + ": " + e);
- }
- }
-
- public static void clearGates() {
- lookupBlocks.clear();
- lookupNamesNet.clear();
- lookupEntrances.clear();
- lookupControls.clear();
- allPortals.clear();
- allPortalsNet.clear();
- }
-
- public static boolean loadAllGates(World world) {
- String location = Stargate.getSaveLocation();
-
- File db = new File(location, world.getName() + ".db");
-
- if (db.exists()) {
- int l = 0;
- int portalCount = 0;
- try {
- Scanner scanner = new Scanner(db);
- while (scanner.hasNextLine()) {
- l++;
- String line = scanner.nextLine().trim();
- if (line.startsWith("#") || line.isEmpty()) {
- continue;
- }
- String[] split = line.split(":");
- if (split.length < 8) {
- Stargate.log.info("[Stargate] Invalid line - " + l);
- continue;
- }
- String name = split[0];
- Blox sign = new Blox(world, split[1]);
- Blox button = (split[2].length() > 0) ? new Blox(world, split[2]) : null;
- int modX = Integer.parseInt(split[3]);
- int modZ = Integer.parseInt(split[4]);
- float rotX = Float.parseFloat(split[5]);
- Blox topLeft = new Blox(world, split[6]);
- Gate gate = Gate.getGateByName(split[7]);
- if (gate == null) {
- Stargate.log.info("[Stargate] Gate layout on line " + l + " does not exist [" + split[7] + "]");
- continue;
- }
-
- String dest = (split.length > 8) ? split[8] : "";
- String network = (split.length > 9) ? split[9] : Stargate.getDefaultNetwork();
- if (network.isEmpty()) network = Stargate.getDefaultNetwork();
- String ownerString = (split.length > 10) ? split[10] : "";
- boolean hidden = (split.length > 11) && split[11].equalsIgnoreCase("true");
- boolean alwaysOn = (split.length > 12) && split[12].equalsIgnoreCase("true");
- boolean priv = (split.length > 13) && split[13].equalsIgnoreCase("true");
- boolean free = (split.length > 15) && split[15].equalsIgnoreCase("true");
- boolean backwards = (split.length > 16) && split[16].equalsIgnoreCase("true");
- boolean show = (split.length > 17) && split[17].equalsIgnoreCase("true");
- boolean noNetwork = (split.length > 18) && split[18].equalsIgnoreCase("true");
- boolean random = (split.length > 19) && split[19].equalsIgnoreCase("true");
- boolean bungee = (split.length > 20) && split[20].equalsIgnoreCase("true");
-
- // Attempt to get owner as UUID
- UUID ownerUUID = null;
- String ownerName;
- if(ownerString.length() > 16) {
- try {
- ownerUUID = UUID.fromString(ownerString);
- OfflinePlayer offlineOwner = Bukkit.getServer().getOfflinePlayer(ownerUUID);
- ownerName = offlineOwner.getName();
- } catch (IllegalArgumentException ex) {
- // neither name nor UUID, so keep it as-is
- ownerName = ownerString;
- Stargate.debug("loadAllGates", "Invalid Stargate owner string: " + ownerString);
- }
- } else {
- ownerName = ownerString;
- }
-
- Portal portal = new Portal(topLeft, modX, modZ, rotX, sign, button, dest, name, false, network, gate, ownerUUID, ownerName, hidden, alwaysOn, priv, free, backwards, show, noNetwork, random, bungee);
- portal.register();
- portal.close(true);
- }
- scanner.close();
-
- // Open any always-on gates. Do this here as it should be more efficient than in the loop.
- int OpenCount = 0;
- for (Iterator iter = allPortals.iterator(); iter.hasNext(); ) {
- Portal portal = iter.next();
- if (portal == null) continue;
-
- // Verify portal integrity/register portal
- if (!portal.wasVerified()) {
- if (!portal.isVerified() || !portal.checkIntegrity()) {
- // DEBUG
- for (RelativeBlockVector control : portal.getGate().getControls()) {
- if (!portal.getBlockAt(control).getBlock().getType().equals(portal.getGate().getControlBlock())) {
- Stargate.debug("loadAllGates", "Control Block Type == " + portal.getBlockAt(control).getBlock().getType().name());
- }
- }
- portal.unregister(false);
- iter.remove();
- Stargate.log.info("[Stargate] Destroying stargate at " + portal.toString());
- continue;
- }
- }
- portalCount++;
-
- if (portal.isFixed() && (Stargate.enableBungee && portal.isBungee()
- || portal.getDestination() != null && portal.isAlwaysOn())) {
- portal.open(true);
- OpenCount++;
- }
- }
- Stargate.log.info("[Stargate] {" + world.getName() + "} Loaded " + portalCount + " stargates with " + OpenCount + " set as always-on");
- return true;
- } catch (Exception e) {
- Stargate.log.log(Level.SEVERE, "Exception while reading stargates from " + db.getName() + ": " + l);
- e.printStackTrace();
- }
- } else {
- Stargate.log.info("[Stargate] {" + world.getName() + "} No stargates for world ");
- }
- return false;
- }
-
- public static void closeAllGates() {
- Stargate.log.info("Closing all stargates.");
- for (Portal p : allPortals) {
- if (p == null) continue;
- p.close(true);
- }
- }
-
- public static String filterName(String input) {
- if (input == null) {
- return "";
- }
- return input.replaceAll("[\\|:#]", "").trim();
- }
-
- @Override
- public String toString() {
- return String.format("Portal [id=%s, network=%s name=%s, type=%s]", id, network, name, gate.getFilename());
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- result = prime * result + ((network == null) ? 0 : network.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- Portal other = (Portal) obj;
- if (name == null) {
- if (other.name != null)
- return false;
- } else if (!name.equalsIgnoreCase(other.name))
- return false;
- if (network == null) {
- if (other.network != null)
- return false;
- } else if (!network.equalsIgnoreCase(other.network))
- return false;
- return true;
- }
-}
diff --git a/src/net/TheDgtl/Stargate/RelativeBlockVector.java b/src/net/TheDgtl/Stargate/RelativeBlockVector.java
deleted file mode 100644
index 60876a5..0000000
--- a/src/net/TheDgtl/Stargate/RelativeBlockVector.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package net.TheDgtl.Stargate;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011 Shaun (sturmeh)
- * Copyright (C) 2011 Dinnerbone
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class RelativeBlockVector {
- private int right = 0;
- private int depth = 0;
- private int distance = 0;
-
- public RelativeBlockVector(int right, int depth, int distance) {
- this.right = right;
- this.depth = depth;
- this.distance = distance;
- }
-
- public int getRight() {
- return right;
- }
-
- public int getDepth() {
- return depth;
- }
-
- public int getDistance() {
- return distance;
- }
-}
diff --git a/src/net/TheDgtl/Stargate/Stargate.java b/src/net/TheDgtl/Stargate/Stargate.java
deleted file mode 100644
index 2b995c5..0000000
--- a/src/net/TheDgtl/Stargate/Stargate.java
+++ /dev/null
@@ -1,1294 +0,0 @@
-package net.TheDgtl.Stargate;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import net.TheDgtl.Stargate.event.StargateAccessEvent;
-import net.TheDgtl.Stargate.event.StargateDestroyEvent;
-
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
-import org.bukkit.GameMode;
-import org.bukkit.Material;
-import org.bukkit.Server;
-import org.bukkit.Tag;
-import org.bukkit.World;
-import org.bukkit.block.Block;
-import org.bukkit.block.EndGateway;
-import org.bukkit.block.Sign;
-import org.bukkit.block.data.Orientable;
-import org.bukkit.block.data.type.WallSign;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandSender;
-import org.bukkit.configuration.file.FileConfiguration;
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.Player;
-import org.bukkit.entity.Vehicle;
-import org.bukkit.event.Event.Result;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.EventPriority;
-import org.bukkit.event.Listener;
-import org.bukkit.event.block.Action;
-import org.bukkit.event.block.BlockBreakEvent;
-import org.bukkit.event.block.BlockFromToEvent;
-import org.bukkit.event.block.BlockPhysicsEvent;
-import org.bukkit.event.block.BlockPistonExtendEvent;
-import org.bukkit.event.block.BlockPistonRetractEvent;
-import org.bukkit.event.block.SignChangeEvent;
-import org.bukkit.event.entity.EntityExplodeEvent;
-import org.bukkit.event.player.PlayerInteractEvent;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.event.player.PlayerMoveEvent;
-import org.bukkit.event.player.PlayerTeleportEvent;
-import org.bukkit.event.server.PluginDisableEvent;
-import org.bukkit.event.server.PluginEnableEvent;
-import org.bukkit.event.vehicle.VehicleMoveEvent;
-import org.bukkit.event.world.WorldLoadEvent;
-import org.bukkit.event.world.WorldUnloadEvent;
-import org.bukkit.plugin.Plugin;
-import org.bukkit.plugin.PluginDescriptionFile;
-import org.bukkit.plugin.PluginManager;
-import org.bukkit.plugin.java.JavaPlugin;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011 Shaun (sturmeh)
- * Copyright (C) 2011 Dinnerbone
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-@SuppressWarnings("unused")
-public class Stargate extends JavaPlugin {
- public static Logger log;
- private FileConfiguration newConfig;
- private PluginManager pm;
- public static Server server;
- public static Stargate stargate;
- private static LangLoader lang;
-
- private static String portalFolder;
- private static String gateFolder;
- private static String langFolder;
- private static String defNetwork = "central";
- private static boolean destroyExplosion = false;
- public static int maxGates = 0;
- private static String langName = "en";
- private static final int activeTime = 10;
- private static final int openTime = 10;
- public static boolean destMemory = false;
- public static boolean handleVehicles = true;
- public static boolean sortLists = false;
- public static boolean protectEntrance = false;
- public static boolean enableBungee = true;
- public static boolean verifyPortals = true;
- public static ChatColor signColor;
-
- // Temp workaround for snowmen, don't check gate entrance
- public static boolean ignoreEntrance = false;
-
- // Used for debug
- public static boolean debug = false;
- public static boolean permDebug = false;
-
- public static ConcurrentLinkedQueue openList = new ConcurrentLinkedQueue<>();
- public static ConcurrentLinkedQueue activeList = new ConcurrentLinkedQueue<>();
-
- // Used for populating gate open/closed material.
- public static Queue blockPopulatorQueue = new LinkedList<>();
-
- // HashMap of player names for Bungee support
- public static Map bungeeQueue = new HashMap<>();
-
- // World names that contain stargates
- public static HashSet managedWorlds = new HashSet<>();
-
- public void onDisable() {
- Portal.closeAllGates();
- Portal.clearGates();
- managedWorlds.clear();
- getServer().getScheduler().cancelTasks(this);
- }
-
- public void onEnable() {
- PluginDescriptionFile pdfFile = this.getDescription();
- pm = getServer().getPluginManager();
- newConfig = this.getConfig();
- log = Logger.getLogger("Minecraft");
- Stargate.server = getServer();
- Stargate.stargate = this;
-
- // Set portalFile and gateFolder to the plugin folder as defaults.
- portalFolder = getDataFolder().getPath().replaceAll("\\\\", "/") + "/portals/";
- gateFolder = getDataFolder().getPath().replaceAll("\\\\", "/") + "/gates/";
- langFolder = getDataFolder().getPath().replaceAll("\\\\", "/") + "/lang/";
-
- log.info(pdfFile.getName() + " v." + pdfFile.getVersion() + " is enabled.");
-
- // Register events before loading gates to stop weird things happening.
- pm.registerEvents(new pListener(), this);
- pm.registerEvents(new bListener(), this);
-
- pm.registerEvents(new vListener(), this);
- pm.registerEvents(new eListener(), this);
- pm.registerEvents(new wListener(), this);
- pm.registerEvents(new sListener(), this);
-
- this.loadConfig();
-
- // Enable the required channels for Bungee support
- if (enableBungee) {
- Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
- Bukkit.getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new pmListener());
- }
-
- // It is important to load languages here, as they are used during reloadGates()
- lang = new LangLoader(langFolder, Stargate.langName);
-
- this.migrate();
- this.loadGates();
- this.loadAllPortals();
-
- // Check to see if Economy is loaded yet.
- if (EconomyHandler.setupEconomy(pm)) {
- if (EconomyHandler.economy != null)
- log.info("[Stargate] Vault v" + EconomyHandler.vault.getDescription().getVersion() + " found");
- }
-
- getServer().getScheduler().scheduleSyncRepeatingTask(this, new SGThread(), 0L, 100L);
- getServer().getScheduler().scheduleSyncRepeatingTask(this, new BlockPopulatorThread(), 0L, 1L);
- }
-
- public void loadConfig() {
- reloadConfig();
- newConfig = this.getConfig();
- // Copy default values if required
- newConfig.options().copyDefaults(true);
-
- // Load values into variables
- portalFolder = newConfig.getString("portal-folder");
- gateFolder = newConfig.getString("gate-folder");
- defNetwork = newConfig.getString("default-gate-network").trim();
- destroyExplosion = newConfig.getBoolean("destroyexplosion");
- maxGates = newConfig.getInt("maxgates");
- langName = newConfig.getString("lang");
- destMemory = newConfig.getBoolean("destMemory");
- ignoreEntrance = newConfig.getBoolean("ignoreEntrance");
- handleVehicles = newConfig.getBoolean("handleVehicles");
- sortLists = newConfig.getBoolean("sortLists");
- protectEntrance = newConfig.getBoolean("protectEntrance");
- enableBungee = newConfig.getBoolean("enableBungee");
- verifyPortals = newConfig.getBoolean("verifyPortals");
- // Sign color
- String sc = newConfig.getString("signColor");
- try {
- signColor = ChatColor.valueOf(sc.toUpperCase());
- } catch (Exception ignore) {
- log.warning("[Stargate] You have specified an invalid color in your config.yml. Defaulting to BLACK");
- signColor = ChatColor.BLACK;
- }
- // Debug
- debug = newConfig.getBoolean("debug");
- permDebug = newConfig.getBoolean("permdebug");
- // Economy
- EconomyHandler.economyEnabled = newConfig.getBoolean("useeconomy");
- EconomyHandler.createCost = newConfig.getInt("createcost");
- EconomyHandler.destroyCost = newConfig.getInt("destroycost");
- EconomyHandler.useCost = newConfig.getInt("usecost");
- EconomyHandler.toOwner = newConfig.getBoolean("toowner");
- EconomyHandler.chargeFreeDestination = newConfig.getBoolean("chargefreedestination");
- EconomyHandler.freeGatesGreen = newConfig.getBoolean("freegatesgreen");
-
- this.saveConfig();
- }
-
- public void closeAllPortals() {
- // Close all gates prior to reloading
- for (Portal p : openList) {
- p.close(true);
- }
- }
-
- public void loadGates() {
- Gate.loadGates(gateFolder);
- log.info("[Stargate] Loaded " + Gate.getGateCount() + " gate layouts");
- }
-
- public void loadAllPortals() {
- for (World world : getServer().getWorlds()) {
- if(!managedWorlds.contains(world.getName())) {
- Portal.loadAllGates(world);
- managedWorlds.add(world.getName());
- }
- }
- }
-
- private void migrate() {
- // Only migrate if new file doesn't exist.
- File newPortalDir = new File(portalFolder);
- if (!newPortalDir.exists()) {
- newPortalDir.mkdirs();
- }
- File newFile = new File(portalFolder, getServer().getWorlds().get(0).getName() + ".db");
- if (!newFile.exists()) {
- newFile.getParentFile().mkdirs();
- }
- }
-
- public static void debug(String rout, String msg) {
- if (Stargate.debug) {
- log.info("[Stargate::" + rout + "] " + msg);
- } else {
- log.log(Level.FINEST, "[Stargate::" + rout + "] " + msg);
- }
- }
-
- public static void sendMessage(CommandSender player, String message) {
- sendMessage(player, message, true);
- }
-
- public static void sendMessage(CommandSender player, String message, boolean error) {
- if (message.isEmpty()) return;
- message = message.replaceAll("(&([a-f0-9]))", "\u00A7$2");
- if (error)
- player.sendMessage(ChatColor.RED + Stargate.getString("prefix") + ChatColor.WHITE + message);
- else
- player.sendMessage(ChatColor.GREEN + Stargate.getString("prefix") + ChatColor.WHITE + message);
- }
-
- public static void setLine(Sign sign, int index, String text) {
- sign.setLine(index, Stargate.signColor + text);
- }
-
- public static String getSaveLocation() {
- return portalFolder;
- }
-
- public static String getGateFolder() {
- return gateFolder;
- }
-
- public static String getDefaultNetwork() {
- return defNetwork;
- }
-
- public static String getString(String name) {
- return lang.getString(name);
- }
-
- public static void openPortal(Player player, Portal portal) {
- Portal destination = portal.getDestination();
-
- // Always-open gate -- Do nothing
- if (portal.isAlwaysOn()) {
- return;
- }
-
- // Random gate -- Do nothing
- if (portal.isRandom())
- return;
-
- // Invalid destination
- if ((destination == null) || (destination == portal)) {
- Stargate.sendMessage(player, Stargate.getString("invalidMsg"));
- return;
- }
-
- // Gate is already open
- if (portal.isOpen()) {
- // Close if this player opened the gate
- if (portal.getActivePlayer() == player) {
- portal.close(false);
- }
- return;
- }
-
- // Gate that someone else is using -- Deny access
- if ((!portal.isFixed()) && portal.isActive() && (portal.getActivePlayer() != player)) {
- Stargate.sendMessage(player, Stargate.getString("denyMsg"));
- return;
- }
-
- // Check if the player can use the private gate
- if (portal.isPrivate() && !Stargate.canPrivate(player, portal)) {
- Stargate.sendMessage(player, Stargate.getString("denyMsg"));
- return;
- }
-
- // Destination blocked
- if ((destination.isOpen()) && (!destination.isAlwaysOn())) {
- Stargate.sendMessage(player, Stargate.getString("blockMsg"));
- return;
- }
-
- // Open gate
- portal.open(player, false);
- }
-
- /*
- * Check whether the player has the given permissions.
- */
- public static boolean hasPerm(Player player, String perm) {
- if (permDebug)
- Stargate.debug("hasPerm::SuperPerm(" + player.getName() + ")", perm + " => " + player.hasPermission(perm));
- return player.hasPermission(perm);
- }
-
- /*
- * Check a deep permission, this will check to see if the permissions is defined for this use
- * If using Permissions it will return the same as hasPerm
- * If using SuperPerms will return true if the node isn't defined
- * Or the value of the node if it is
- */
- public static boolean hasPermDeep(Player player, String perm) {
- if (!player.isPermissionSet(perm)) {
- if (permDebug)
- Stargate.debug("hasPermDeep::SuperPerm", perm + " => true");
- return true;
- }
- if (permDebug)
- Stargate.debug("hasPermDeep::SuperPerms", perm + " => " + player.hasPermission(perm));
- return player.hasPermission(perm);
- }
-
- /*
- * Check whether player can teleport to dest world
- */
- public static boolean canAccessWorld(Player player, String world) {
- // Can use all Stargate player features or access all worlds
- if (hasPerm(player, "stargate.use") || hasPerm(player, "stargate.world")) {
- // Do a deep check to see if the player lacks this specific world node
- return hasPermDeep(player, "stargate.world." + world);
- }
- // Can access dest world
- return hasPerm(player, "stargate.world." + world);
- }
-
- /*
- * Check whether player can use network
- */
- public static boolean canAccessNetwork(Player player, String network) {
- // Can user all Stargate player features, or access all networks
- if (hasPerm(player, "stargate.use") || hasPerm(player, "stargate.network")) {
- // Do a deep check to see if the player lacks this specific network node
- return hasPermDeep(player, "stargate.network." + network);
- }
- // Can access this network
- if (hasPerm(player, "stargate.network." + network)) return true;
- // Is able to create personal gates (Assumption is made they can also access them)
- String playerName = player.getName();
- if (playerName.length() > 11) playerName = playerName.substring(0, 11);
- return network.equals(playerName) && hasPerm(player, "stargate.create.personal");
- }
-
- /*
- * Check whether the player can access this server
- */
- public static boolean canAccessServer(Player player, String server) {
- // Can user all Stargate player features, or access all servers
- if (hasPerm(player, "stargate.use") || hasPerm(player, "stargate.servers")) {
- // Do a deep check to see if the player lacks this specific server node
- return hasPermDeep(player, "stargate.server." + server);
- }
- // Can access this server
- return hasPerm(player, "stargate.server." + server);
- }
-
- /*
- * Call the StargateAccessPortal event, used for other plugins to bypass Permissions checks
- */
- public static boolean canAccessPortal(Player player, Portal portal, boolean deny) {
- StargateAccessEvent event = new StargateAccessEvent(player, portal, deny);
- Stargate.server.getPluginManager().callEvent(event);
- return !event.getDeny();
- }
-
- /*
- * Return true if the portal is free for the player
- */
- public static boolean isFree(Player player, Portal src, Portal dest) {
- // This gate is free
- if (src.isFree()) return true;
- // Player gets free use
- if (hasPerm(player, "stargate.free") || Stargate.hasPerm(player, "stargate.free.use")) return true;
- // Don't charge for free destination gates
- return dest != null && !EconomyHandler.chargeFreeDestination && dest.isFree();
- }
-
- /*
- * Check whether the player can see this gate (Hidden property check)
- */
- public static boolean canSee(Player player, Portal portal) {
- // The gate is not hidden
- if (!portal.isHidden()) return true;
- // The player is an admin with the ability to see hidden gates
- if (hasPerm(player, "stargate.admin") || hasPerm(player, "stargate.admin.hidden")) return true;
- // The player is the owner of the gate
- return portal.isOwner(player);
- }
-
- /*
- * Check if the player can use this private gate
- */
- public static boolean canPrivate(Player player, 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 hasPerm(player, "stargate.admin") || hasPerm(player, "stargate.admin.private");
- }
-
- /*
- * Check if the player has access to {option}
- */
- public static boolean canOption(Player player, String option) {
- // Check if the player can use all options
- if (hasPerm(player, "stargate.option")) return true;
- // Check if they can use this specific option
- return hasPerm(player, "stargate.option." + option);
- }
-
- /*
- * Check if the player can create gates on {network}
- */
- public static boolean canCreate(Player player, String network) {
- // Check for general create
- if (hasPerm(player, "stargate.create")) return true;
- // Check for all network create permission
- if (hasPerm(player, "stargate.create.network")) {
- // Do a deep check to see if the player lacks this specific network node
- return hasPermDeep(player, "stargate.create.network." + network);
- }
- // Check for this specific network
- return hasPerm(player, "stargate.create.network." + network);
-
- }
-
- /*
- * Check if the player can create a personal gate
- */
- public static boolean canCreatePersonal(Player player) {
- // Check for general create
- if (hasPerm(player, "stargate.create")) return true;
- // Check for personal
- return hasPerm(player, "stargate.create.personal");
- }
-
- /*
- * Check if the player can create this gate layout
- */
- public static boolean canCreateGate(Player player, String gate) {
- // Check for general create
- if (hasPerm(player, "stargate.create")) return true;
- // Check for all gate create permissions
- if (hasPerm(player, "stargate.create.gate")) {
- // Do a deep check to see if the player lacks this specific gate node
- return hasPermDeep(player, "stargate.create.gate." + gate);
- }
- // Check for this specific gate
- return hasPerm(player, "stargate.create.gate." + gate);
- }
-
- /*
- * Check if the player can destroy this gate
- */
- public static boolean canDestroy(Player player, Portal portal) {
- String network = portal.getNetwork();
- // Check for general destroy
- if (hasPerm(player, "stargate.destroy")) return true;
- // Check for all network destroy permission
- if (hasPerm(player, "stargate.destroy.network")) {
- // Do a deep check to see if the player lacks permission for this network node
- return hasPermDeep(player, "stargate.destroy.network." + network);
- }
- // Check for this specific network
- if (hasPerm(player, "stargate.destroy.network." + network)) return true;
- // Check for personal gate
- return portal.isOwner(player) && hasPerm(player, "stargate.destroy.personal");
- }
-
- /*
- * Charge player for {action} if required, true on success, false if can't afford
- */
- public static boolean chargePlayer(Player player, String target, int cost) {
- // If cost is 0
- if (cost == 0) return true;
- // Economy is disabled
- if (!EconomyHandler.useEconomy()) return true;
- // Charge player
- return EconomyHandler.chargePlayer(player, target, cost);
- }
-
- /*
- * Charge player for {action} if required, true on success, false if can't afford
- */
- public static boolean chargePlayer(Player player, UUID target, int cost) {
- // If cost is 0
- if (cost == 0) return true;
- // Economy is disabled
- if (!EconomyHandler.useEconomy()) return true;
- // Charge player
- return EconomyHandler.chargePlayer(player, target, cost);
- }
-
- /*
- * Charge player for {action} if required, true on success, false if can't afford
- */
- public static boolean chargePlayer(Player player, int cost) {
- // If cost is 0
- if (cost == 0) return true;
- // Economy is disabled
- if (!EconomyHandler.useEconomy()) return true;
- // Charge player
- return EconomyHandler.chargePlayer(player, cost);
- }
-
- /*
- * Determine the cost of a gate
- */
- public static int getUseCost(Player player, Portal src, Portal dest) {
- // Not using Economy
- if (!EconomyHandler.useEconomy()) return 0;
- // Portal is free
- if (src.isFree()) return 0;
- // Not charging for free destinations
- if (dest != null && !EconomyHandler.chargeFreeDestination && dest.isFree()) return 0;
- // Cost is 0 if the player owns this gate and funds go to the owner
- if (src.getGate().getToOwner() && src.isOwner(player)) return 0;
- // Player gets free gate use
- if (hasPerm(player, "stargate.free") || hasPerm(player, "stargate.free.use")) return 0;
-
- return src.getGate().getUseCost();
- }
-
- /*
- * Determine the cost to create the gate
- */
- public static int getCreateCost(Player player, Gate gate) {
- // Not using Economy
- if (!EconomyHandler.useEconomy()) return 0;
- // Player gets free gate destruction
- if (hasPerm(player, "stargate.free") || hasPerm(player, "stargate.free.create")) return 0;
-
- return gate.getCreateCost();
- }
-
- /*
- * Determine the cost to destroy the gate
- */
- public static int getDestroyCost(Player player, Gate gate) {
- // Not using Economy
- if (!EconomyHandler.useEconomy()) return 0;
- // Player gets free gate destruction
- if (hasPerm(player, "stargate.free") || hasPerm(player, "stargate.free.destroy")) return 0;
-
- return gate.getDestroyCost();
- }
-
- /*
- * Check if a plugin is loaded/enabled already. Returns the plugin if so, null otherwise
- */
- private Plugin checkPlugin(String p) {
- Plugin plugin = pm.getPlugin(p);
- return checkPlugin(plugin);
- }
-
- private Plugin checkPlugin(Plugin plugin) {
- if (plugin != null && plugin.isEnabled()) {
- log.info("[Stargate] Found " + plugin.getDescription().getName() + " (v" + plugin.getDescription().getVersion() + ")");
- return plugin;
- }
- return null;
- }
-
- /*
- * Parse a given text string and replace the variables
- */
- public static String replaceVars(String format, String[] search, String[] replace) {
- if (search.length != replace.length) return "";
- for (int i = 0; i < search.length; i++) {
- format = format.replace(search[i], replace[i]);
- }
- return format;
- }
-
- private class vListener implements Listener {
- @EventHandler
- public void onVehicleMove(VehicleMoveEvent event) {
- if (!handleVehicles) return;
- List passengers = event.getVehicle().getPassengers();
- Vehicle vehicle = event.getVehicle();
-
- Portal portal = Portal.getByEntrance(event.getTo());
- if (portal == null || !portal.isOpen()) return;
-
- // We don't support vehicles in Bungee portals
- if (portal.isBungee()) return;
-
- if (!passengers.isEmpty() && passengers.get(0) instanceof Player) {
- /*
- Player player = (Player) passengers.get(0);
- if (!portal.isOpenFor(player)) {
- Stargate.sendMessage(player, Stargate.getString("denyMsg"));
- return;
- }
-
- Portal dest = portal.getDestination(player);
- if (dest == null) return;
- boolean deny = false;
- // Check if player has access to this network
- if (!canAccessNetwork(player, portal.getNetwork())) {
- deny = true;
- }
-
- // Check if player has access to destination world
- if (!canAccessWorld(player, dest.getWorld().getName())) {
- deny = true;
- }
-
- if (!canAccessPortal(player, portal, deny)) {
- Stargate.sendMessage(player, Stargate.getString("denyMsg"));
- portal.close(false);
- return;
- }
-
- int cost = Stargate.getUseCost(player, portal, dest);
- if (cost > 0) {
- boolean success;
- if(portal.getGate().getToOwner()) {
- if(portal.getOwnerUUID() == null) {
- success = Stargate.chargePlayer(player, portal.getOwnerUUID(), cost);
- } else {
- success = Stargate.chargePlayer(player, portal.getOwnerName(), cost);
- }
- } else {
- success = Stargate.chargePlayer(player, cost);
- }
- if(!success) {
- // Insufficient Funds
- Stargate.sendMessage(player, Stargate.getString("inFunds"));
- portal.close(false);
- return;
- }
- String deductMsg = Stargate.getString("ecoDeduct");
- deductMsg = Stargate.replaceVars(deductMsg, new String[] {"%cost%", "%portal%"}, new String[] {EconomyHandler.format(cost), portal.getName()});
- sendMessage(player, deductMsg, false);
- if (portal.getGate().getToOwner()) {
- Player p;
- if(portal.getOwnerUUID() != null) {
- p = server.getPlayer(portal.getOwnerUUID());
- } else {
- p = server.getPlayer(portal.getOwnerName());
- }
- if (p != null) {
- String obtainedMsg = Stargate.getString("ecoObtain");
- obtainedMsg = Stargate.replaceVars(obtainedMsg, new String[] {"%cost%", "%portal%"}, new String[] {EconomyHandler.format(cost), portal.getName()});
- Stargate.sendMessage(p, obtainedMsg, false);
- }
- }
- }
-
- Stargate.sendMessage(player, Stargate.getString("teleportMsg"), false);
- dest.teleport(vehicle);
- portal.close(false);
- */
- } else {
- Portal dest = portal.getDestination();
- if (dest == null) return;
- dest.teleport(vehicle);
- }
- }
- }
-
- private class pListener implements Listener {
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent event) {
- if (!enableBungee) return;
-
- Player player = event.getPlayer();
- String destination = bungeeQueue.remove(player.getName().toLowerCase());
- if (destination == null) return;
-
- Portal portal = Portal.getBungeeGate(destination);
- if (portal == null) {
- Stargate.debug("PlayerJoin", "Error fetching destination portal: " + destination);
- return;
- }
- portal.teleport(player, portal, null);
- }
-
- @EventHandler
- public void onPlayerTeleport(PlayerTeleportEvent event) {
- // cancel portal and endgateway teleportation if it's from a Stargate entrance
- PlayerTeleportEvent.TeleportCause cause = event.getCause();
- if(!event.isCancelled()
- && (cause == PlayerTeleportEvent.TeleportCause.NETHER_PORTAL
- || cause == PlayerTeleportEvent.TeleportCause.END_GATEWAY && World.Environment.THE_END == event.getFrom().getWorld().getEnvironment())
- && Portal.getByAdjacentEntrance(event.getFrom()) != null) {
- event.setCancelled(true);
- }
- }
-
- @EventHandler
- public void onPlayerMove(PlayerMoveEvent event) {
- if (event.isCancelled()) return;
-
- // Check to see if the player actually moved
- if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockY() == event.getTo().getBlockY() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) {
- return;
- }
-
- Player player = event.getPlayer();
- Portal portal = Portal.getByEntrance(event.getTo());
- // No portal or not open
- if (portal == null || !portal.isOpen()) return;
-
- // Not open for this player
- if (!portal.isOpenFor(player)) {
- Stargate.sendMessage(player, Stargate.getString("denyMsg"));
- portal.teleport(player, portal, event);
- return;
- }
-
- Portal destination = portal.getDestination(player);
- if (!portal.isBungee() && destination == null) return;
-
- boolean deny = false;
- // Check if player has access to this server for Bungee gates
- if (portal.isBungee()) {
- if (!canAccessServer(player, portal.getNetwork())) {
- deny = true;
- }
- } else {
- // Check if player has access to this network
- if (!canAccessNetwork(player, portal.getNetwork())) {
- deny = true;
- }
-
- // Check if player has access to destination world
- if (!canAccessWorld(player, destination.getWorld().getName())) {
- deny = true;
- }
- }
-
- if (!canAccessPortal(player, portal, deny)) {
- Stargate.sendMessage(player, Stargate.getString("denyMsg"));
- portal.teleport(player, portal, event);
- portal.close(false);
- return;
- }
-
- int cost = Stargate.getUseCost(player, portal, destination);
- if (cost > 0) {
- boolean success;
- if(portal.getGate().getToOwner()) {
- if(portal.getOwnerUUID() == null) {
- success = Stargate.chargePlayer(player, portal.getOwnerUUID(), cost);
- } else {
- success = Stargate.chargePlayer(player, portal.getOwnerName(), cost);
- }
- } else {
- success = Stargate.chargePlayer(player, cost);
- }
- if(!success) {
- // Insufficient Funds
- Stargate.sendMessage(player, Stargate.getString("inFunds"));
- portal.close(false);
- return;
- }
- String deductMsg = Stargate.getString("ecoDeduct");
- deductMsg = Stargate.replaceVars(deductMsg, new String[] {"%cost%", "%portal%"}, new String[] {EconomyHandler.format(cost), portal.getName()});
- sendMessage(player, deductMsg, false);
- if (portal.getGate().getToOwner() && portal.getOwnerUUID() != null) {
- Player p;
- if(portal.getOwnerUUID() != null) {
- p = server.getPlayer(portal.getOwnerUUID());
- } else {
- p = server.getPlayer(portal.getOwnerName());
- }
- if (p != null) {
- String obtainedMsg = Stargate.getString("ecoObtain");
- obtainedMsg = Stargate.replaceVars(obtainedMsg, new String[] {"%cost%", "%portal%"}, new String[] {EconomyHandler.format(cost), portal.getName()});
- Stargate.sendMessage(p, obtainedMsg, false);
- }
- }
- }
-
- Stargate.sendMessage(player, Stargate.getString("teleportMsg"), false);
-
- // BungeeCord Support
- if (portal.isBungee()) {
- if (!enableBungee) {
- player.sendMessage(Stargate.getString("bungeeDisabled"));
- portal.close(false);
- return;
- }
-
- // Teleport the player back to this gate, for sanity's sake
- portal.teleport(player, portal, event);
-
- // Send the SGBungee packet first, it will be queued by BC if required
- try {
- // Build the message, format is #@#
- String msg = event.getPlayer().getName() + "#@#" + portal.getDestinationName();
- // Build the message data, sent over the SGBungee bungeecord channel
- ByteArrayOutputStream bao = new ByteArrayOutputStream();
- DataOutputStream msgData = new DataOutputStream(bao);
- msgData.writeUTF("Forward");
- msgData.writeUTF(portal.getNetwork()); // Server
- msgData.writeUTF("SGBungee"); // Channel
- msgData.writeShort(msg.length()); // Data Length
- msgData.writeBytes(msg); // Data
- player.sendPluginMessage(stargate, "BungeeCord", bao.toByteArray());
- } catch (IOException ex) {
- Stargate.log.severe("[Stargate] Error sending BungeeCord teleport packet");
- ex.printStackTrace();
- return;
- }
-
- // Connect player to new server
- try {
- ByteArrayOutputStream bao = new ByteArrayOutputStream();
- DataOutputStream msgData = new DataOutputStream(bao);
- msgData.writeUTF("Connect");
- msgData.writeUTF(portal.getNetwork());
-
- player.sendPluginMessage(stargate, "BungeeCord", bao.toByteArray());
- bao.reset();
- } catch(IOException ex) {
- Stargate.log.severe("[Stargate] Error sending BungeeCord connect packet");
- ex.printStackTrace();
- return;
- }
-
- // Close portal if required (Should never be)
- portal.close(false);
- return;
- }
-
- destination.teleport(player, portal, event);
- portal.close(false);
- }
-
- @EventHandler
- public void onPlayerInteract(PlayerInteractEvent event) {
- Player player = event.getPlayer();
- Block block = event.getClickedBlock();
-
- if(block == null) return;
-
- // Right click
- if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
- if (block.getBlockData() instanceof WallSign) {
- Portal portal = Portal.getByBlock(block);
- if (portal == null) return;
- // Cancel item use
- event.setUseItemInHand(Result.DENY);
- event.setUseInteractedBlock(Result.DENY);
-
- boolean deny = false;
- if (!Stargate.canAccessNetwork(player, portal.getNetwork())) {
- deny = true;
- }
-
- if (!Stargate.canAccessPortal(player, portal, deny)) {
- Stargate.sendMessage(player, Stargate.getString("denyMsg"));
- return;
- }
-
- if ((!portal.isOpen()) && (!portal.isFixed())) {
- portal.cycleDestination(player);
- }
- return;
- }
-
- // Implement right-click to toggle a stargate, gets around spawn protection problem.
- if (Tag.BUTTONS.isTagged(block.getType())) {
- Portal portal = Portal.getByBlock(block);
- if (portal == null) return;
-
- // Cancel item use
- event.setUseItemInHand(Result.DENY);
- event.setUseInteractedBlock(Result.DENY);
-
- boolean deny = false;
- if (!Stargate.canAccessNetwork(player, portal.getNetwork())) {
- deny = true;
- }
-
- if (!Stargate.canAccessPortal(player, portal, deny)) {
- Stargate.sendMessage(player, Stargate.getString("denyMsg"));
- return;
- }
-
- openPortal(player, portal);
- if (portal.isOpenFor(player)) {
- event.setUseInteractedBlock(Result.ALLOW);
- }
- }
- return;
- }
-
- // Left click
- if (event.getAction() == Action.LEFT_CLICK_BLOCK) {
- // Check if we're scrolling a sign
- if (block.getBlockData() instanceof WallSign) {
- Portal portal = Portal.getByBlock(block);
- if (portal == null) return;
-
- event.setUseInteractedBlock(Result.DENY);
- // Only cancel event in creative mode
- if (player.getGameMode().equals(GameMode.CREATIVE)) {
- event.setCancelled(true);
- }
-
- boolean deny = false;
- if (!Stargate.canAccessNetwork(player, portal.getNetwork())) {
- deny = true;
- }
-
- if (!Stargate.canAccessPortal(player, portal, deny)) {
- Stargate.sendMessage(player, Stargate.getString("denyMsg"));
- return;
- }
-
- if ((!portal.isOpen()) && (!portal.isFixed())) {
- portal.cycleDestination(player, -1);
- }
- }
- }
- }
- }
-
- private class bListener implements Listener {
- @EventHandler
- public void onSignChange(SignChangeEvent event) {
- if (event.isCancelled()) return;
- Player player = event.getPlayer();
- Block block = event.getBlock();
- if (!(block.getBlockData() instanceof WallSign)) return;
-
- final Portal portal = Portal.createPortal(event, player);
- // Not creating a gate, just placing a sign
- if (portal == null) return;
-
- Stargate.sendMessage(player, Stargate.getString("createMsg"), false);
- Stargate.debug("onSignChange", "Initialized stargate: " + portal.getName());
- Stargate.server.getScheduler().scheduleSyncDelayedTask(stargate, new Runnable() {
- public void run() {
- portal.drawSign();
- }
- }, 1);
- }
-
- // Switch to HIGHEST priority so as to come after block protection plugins (Hopefully)
- @EventHandler(priority = EventPriority.HIGHEST)
- public void onBlockBreak(BlockBreakEvent event) {
- if (event.isCancelled()) return;
- Block block = event.getBlock();
- Player player = event.getPlayer();
-
- Portal portal = Portal.getByBlock(block);
- if (portal == null && protectEntrance)
- portal = Portal.getByEntrance(block);
- if (portal == null) return;
-
- boolean deny = false;
- String denyMsg = "";
-
- if (!Stargate.canDestroy(player, portal)) {
- denyMsg = "Permission Denied"; // TODO: Change to Stargate.getString()
- deny = true;
- Stargate.log.info("[Stargate] " + player.getName() + " tried to destroy gate");
- }
-
- int cost = Stargate.getDestroyCost(player, portal.getGate());
-
- StargateDestroyEvent dEvent = new StargateDestroyEvent(portal, player, deny, denyMsg, cost);
- Stargate.server.getPluginManager().callEvent(dEvent);
- if (dEvent.isCancelled()) {
- event.setCancelled(true);
- return;
- }
- if (dEvent.getDeny()) {
- Stargate.sendMessage(player, dEvent.getDenyReason());
- event.setCancelled(true);
- return;
- }
-
- cost = dEvent.getCost();
-
- if (cost != 0) {
- if (!Stargate.chargePlayer(player, cost)) {
- Stargate.debug("onBlockBreak", "Insufficient Funds");
- Stargate.sendMessage(player, Stargate.getString("inFunds"));
- event.setCancelled(true);
- return;
- }
-
- if (cost > 0) {
- String deductMsg = Stargate.getString("ecoDeduct");
- deductMsg = Stargate.replaceVars(deductMsg, new String[] {"%cost%", "%portal%"}, new String[] {EconomyHandler.format(cost), portal.getName()});
- sendMessage(player, deductMsg, false);
- } else if (cost < 0) {
- String refundMsg = Stargate.getString("ecoRefund");
- refundMsg = Stargate.replaceVars(refundMsg, new String[] {"%cost%", "%portal%"}, new String[] {EconomyHandler.format(-cost), portal.getName()});
- sendMessage(player, refundMsg, false);
- }
- }
-
- portal.unregister(true);
- Stargate.sendMessage(player, Stargate.getString("destroyMsg"), false);
- }
-
- @EventHandler
- public void onBlockPhysics(BlockPhysicsEvent event) {
- Block block = event.getBlock();
- Portal portal = null;
-
- // Handle keeping portal material and buttons around
- if (block.getType() == Material.NETHER_PORTAL) {
- portal = Portal.getByEntrance(block);
- } else if (Tag.BUTTONS.isTagged(block.getType())) {
- portal = Portal.getByControl(block);
- }
- if (portal != null) event.setCancelled(true);
- }
-
- @EventHandler
- public void onBlockFromTo(BlockFromToEvent event) {
- Portal portal = Portal.getByEntrance(event.getBlock());
-
- if (portal != null) {
- event.setCancelled((event.getBlock().getY() == event.getToBlock().getY()));
- }
- }
-
- @EventHandler
- public void onPistonExtend(BlockPistonExtendEvent event) {
- for(Block block : event.getBlocks()) {
- Portal portal = Portal.getByBlock(block);
- if (portal != null) {
- event.setCancelled(true);
- return;
- }
- }
- }
-
- @EventHandler
- public void onPistonRetract(BlockPistonRetractEvent event) {
- if (!event.isSticky()) return;
- for(Block block : event.getBlocks()) {
- Portal portal = Portal.getByBlock(block);
- if (portal != null) {
- event.setCancelled(true);
- return;
- }
- }
- }
- }
-
- private class wListener implements Listener {
- @EventHandler
- public void onWorldLoad(WorldLoadEvent event) {
- if(!managedWorlds.contains(event.getWorld().getName())
- && Portal.loadAllGates(event.getWorld())) {
- managedWorlds.add(event.getWorld().getName());
- }
- }
-
- // We need to reload all gates on world unload, boo
- @EventHandler
- public void onWorldUnload(WorldUnloadEvent event) {
- Stargate.debug("onWorldUnload", "Reloading all Stargates");
- World w = event.getWorld();
- if(managedWorlds.contains(w.getName())) {
- managedWorlds.remove(w.getName());
- Portal.clearGates();
- for(World world : server.getWorlds()) {
- if(managedWorlds.contains(world.getName())) {
- Portal.loadAllGates(world);
- }
- }
- }
- }
- }
-
- private class eListener implements Listener {
- @EventHandler
- public void onEntityExplode(EntityExplodeEvent event) {
- if (event.isCancelled()) return;
- for (Block b : event.blockList()) {
- Portal portal = Portal.getByBlock(b);
- if (portal == null) continue;
- if (destroyExplosion) {
- portal.unregister(true);
- } else {
- event.setCancelled(true);
- break;
- }
- }
- }
- }
-
- private class sListener implements Listener {
- @EventHandler
- public void onPluginEnable(PluginEnableEvent event) {
- if (EconomyHandler.setupEconomy(getServer().getPluginManager())) {
- log.info("[Stargate] Vault v" + EconomyHandler.vault.getDescription().getVersion() + " found");
- }
- }
-
- @EventHandler
- public void onPluginDisable(PluginDisableEvent event) {
- if (event.getPlugin().equals(EconomyHandler.vault)) {
- log.info("[Stargate] Vault plugin lost.");
- }
- }
- }
-
- private class BlockPopulatorThread implements Runnable {
- public void run() {
- long sTime = System.nanoTime();
- while (System.nanoTime() - sTime < 25000000) {
- BloxPopulator b = Stargate.blockPopulatorQueue.poll();
- if (b == null) return;
- Block blk = b.getBlox().getBlock();
- blk.setType(b.getMat(), false);
- if(b.getMat() == Material.END_GATEWAY && blk.getWorld().getEnvironment() == World.Environment.THE_END) {
- // force a location to prevent exit gateway generation
- EndGateway gateway = (EndGateway) blk.getState();
- gateway.setExitLocation(blk.getWorld().getSpawnLocation());
- gateway.setExactTeleport(true);
- gateway.update(false, false);
- } else if(b.getAxis() != null) {
- Orientable orientable = (Orientable) blk.getBlockData();
- orientable.setAxis(b.getAxis());
- blk.setBlockData(orientable);
- }
- }
- }
- }
-
- private class SGThread implements Runnable {
- public void run() {
- long time = System.currentTimeMillis() / 1000;
- // Close open portals
- for (Iterator iter = Stargate.openList.iterator(); iter.hasNext();) {
- Portal p = iter.next();
- // Skip always open gates
- if (p.isAlwaysOn()) continue;
- if (!p.isOpen()) continue;
- if (time > p.getOpenTime() + Stargate.openTime) {
- p.close(false);
- iter.remove();
- }
- }
- // Deactivate active portals
- for (Iterator iter = Stargate.activeList.iterator(); iter.hasNext();) {
- Portal p = iter.next();
- if (!p.isActive()) continue;
- if (time > p.getOpenTime() + Stargate.activeTime) {
- p.deactivate();
- iter.remove();
- }
- }
- }
- }
-
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- String cmd = command.getName();
- if (cmd.equalsIgnoreCase("sg")) {
- if (args.length != 1) return false;
- if (args[0].equalsIgnoreCase("about")) {
- sender.sendMessage("Stargate Plugin created by Drakia");
- if (!lang.getString("author").isEmpty())
- sender.sendMessage("Language created by " + lang.getString("author"));
- return true;
- }
- if (sender instanceof Player) {
- Player p = (Player)sender;
- if (!hasPerm(p, "stargate.admin") && !hasPerm(p, "stargate.admin.reload")) {
- sendMessage(sender, "Permission Denied");
- return true;
- }
- }
- if (args[0].equalsIgnoreCase("reload")) {
- // Deactivate portals
- for (Portal p : activeList) {
- p.deactivate();
- }
- // Close portals
- closeAllPortals();
- // Clear all lists
- activeList.clear();
- openList.clear();
- managedWorlds.clear();
- Portal.clearGates();
- Gate.clearGates();
-
- // Store the old Bungee enabled value
- boolean oldEnableBungee = enableBungee;
- // Reload data
- loadConfig();
- loadGates();
- loadAllPortals();
- lang.setLang(langName);
- lang.reload();
-
- // Load Economy support if enabled/clear if disabled
- if (EconomyHandler.economyEnabled && EconomyHandler.economy == null) {
- if (EconomyHandler.setupEconomy(pm)) {
- if (EconomyHandler.economy != null)
- log.info("[Stargate] Vault v" + EconomyHandler.vault.getDescription().getVersion() + " found");
- }
- }
- if (!EconomyHandler.economyEnabled) {
- EconomyHandler.vault = null;
- EconomyHandler.economy = null;
- }
-
- // Enable the required channels for Bungee support
- if (oldEnableBungee != enableBungee) {
- if (enableBungee) {
- Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
- Bukkit.getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new pmListener());
- } else {
- Bukkit.getMessenger().unregisterIncomingPluginChannel(this, "BungeeCord");
- Bukkit.getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord");
- }
- }
-
- sendMessage(sender, "Stargate reloaded");
- return true;
- }
- return false;
- }
- return false;
- }
-}
diff --git a/src/net/TheDgtl/Stargate/event/StargateAccessEvent.java b/src/net/TheDgtl/Stargate/event/StargateAccessEvent.java
deleted file mode 100644
index 6dc1585..0000000
--- a/src/net/TheDgtl/Stargate/event/StargateAccessEvent.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package net.TheDgtl.Stargate.event;
-
-import org.bukkit.entity.Player;
-import org.bukkit.event.HandlerList;
-
-import net.TheDgtl.Stargate.Portal;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class StargateAccessEvent extends StargateEvent {
- private final Player player;
- private boolean deny;
-
- private static final HandlerList handlers = new HandlerList();
-
- public HandlerList getHandlers() {
- return handlers;
- }
-
- public static HandlerList getHandlerList() {
- return handlers;
- }
-
- public StargateAccessEvent(Player player, Portal portal, boolean deny) {
- super("StargateAccessEvent", portal);
-
- this.player = player;
- this.deny = deny;
- }
-
- public boolean getDeny() {
- return this.deny;
- }
-
- public void setDeny(boolean deny) {
- this.deny = deny;
- }
-
- public Player getPlayer() {
- return this.player;
- }
-
-}
diff --git a/src/net/TheDgtl/Stargate/event/StargateActivateEvent.java b/src/net/TheDgtl/Stargate/event/StargateActivateEvent.java
deleted file mode 100644
index e254c3b..0000000
--- a/src/net/TheDgtl/Stargate/event/StargateActivateEvent.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package net.TheDgtl.Stargate.event;
-
-import java.util.ArrayList;
-
-import org.bukkit.entity.Player;
-import org.bukkit.event.HandlerList;
-
-import net.TheDgtl.Stargate.Portal;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class StargateActivateEvent extends StargateEvent {
- private final Player player;
- private ArrayList destinations;
- private String destination;
-
- private static final HandlerList handlers = new HandlerList();
-
- public HandlerList getHandlers() {
- return handlers;
- }
-
- public static HandlerList getHandlerList() {
- return handlers;
- }
- public StargateActivateEvent(Portal portal, Player player, ArrayList destinations, String destination) {
- super("StargatActivateEvent", portal);
-
- this.player = player;
- this.destinations = destinations;
- this.destination = destination;
- }
-
- public Player getPlayer() {
- return player;
- }
-
- public ArrayList getDestinations() {
- return destinations;
- }
-
- public void setDestinations(ArrayList destinations) {
- this.destinations = destinations;
- }
-
- public String getDestination() {
- return destination;
- }
-
- public void setDestination(String destination) {
- this.destination = destination;
- }
-}
diff --git a/src/net/TheDgtl/Stargate/event/StargateCloseEvent.java b/src/net/TheDgtl/Stargate/event/StargateCloseEvent.java
deleted file mode 100644
index add60c0..0000000
--- a/src/net/TheDgtl/Stargate/event/StargateCloseEvent.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package net.TheDgtl.Stargate.event;
-
-import org.bukkit.event.HandlerList;
-
-import net.TheDgtl.Stargate.Portal;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class StargateCloseEvent extends StargateEvent {
- private boolean force;
-
- private static final HandlerList handlers = new HandlerList();
-
- public HandlerList getHandlers() {
- return handlers;
- }
-
- public static HandlerList getHandlerList() {
- return handlers;
- }
- public StargateCloseEvent(Portal portal, boolean force) {
- super("StargateCloseEvent", portal);
-
- this.force = force;
- }
-
- public boolean getForce() {
- return force;
- }
-
- public void setForce(boolean force) {
- this.force = force;
- }
-}
diff --git a/src/net/TheDgtl/Stargate/event/StargateCreateEvent.java b/src/net/TheDgtl/Stargate/event/StargateCreateEvent.java
deleted file mode 100644
index 05a46d0..0000000
--- a/src/net/TheDgtl/Stargate/event/StargateCreateEvent.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package net.TheDgtl.Stargate.event;
-
-import net.TheDgtl.Stargate.Portal;
-import org.bukkit.entity.Player;
-import org.bukkit.event.HandlerList;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class StargateCreateEvent extends StargateEvent {
- private final Player player;
- private boolean deny;
- private String denyReason;
- private final String[] lines;
- private int cost;
-
- private static final HandlerList handlers = new HandlerList();
-
- public HandlerList getHandlers() {
- return handlers;
- }
-
- public static HandlerList getHandlerList() {
- return handlers;
- }
-
- public StargateCreateEvent(Player player, Portal portal, String[] lines, boolean deny, String denyReason, int cost) {
- super("StargateCreateEvent", portal);
- this.player = player;
- this.lines = lines;
- this.deny = deny;
- this.denyReason = denyReason;
- this.cost = cost;
- }
-
- public Player getPlayer() {
- return player;
- }
-
- public String getLine(int index) throws IndexOutOfBoundsException {
- return lines[index];
- }
-
- public boolean getDeny() {
- return deny;
- }
-
- public void setDeny(boolean deny) {
- this.deny = deny;
- }
-
- public String getDenyReason() {
- return denyReason;
- }
-
- public void setDenyReason(String denyReason) {
- this.denyReason = denyReason;
- }
-
- public int getCost() {
- return cost;
- }
-
- public void setCost(int cost) {
- this.cost = cost;
- }
-
-}
diff --git a/src/net/TheDgtl/Stargate/event/StargateDeactivateEvent.java b/src/net/TheDgtl/Stargate/event/StargateDeactivateEvent.java
deleted file mode 100644
index b193139..0000000
--- a/src/net/TheDgtl/Stargate/event/StargateDeactivateEvent.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package net.TheDgtl.Stargate.event;
-
-import org.bukkit.event.HandlerList;
-
-import net.TheDgtl.Stargate.Portal;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class StargateDeactivateEvent extends StargateEvent {
- private static final HandlerList handlers = new HandlerList();
-
- public HandlerList getHandlers() {
- return handlers;
- }
-
- public static HandlerList getHandlerList() {
- return handlers;
- }
- public StargateDeactivateEvent(Portal portal) {
- super("StargatDeactivateEvent", portal);
-
- }
-}
diff --git a/src/net/TheDgtl/Stargate/event/StargateDestroyEvent.java b/src/net/TheDgtl/Stargate/event/StargateDestroyEvent.java
deleted file mode 100644
index 7ea06fe..0000000
--- a/src/net/TheDgtl/Stargate/event/StargateDestroyEvent.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package net.TheDgtl.Stargate.event;
-
-import net.TheDgtl.Stargate.Portal;
-import org.bukkit.entity.Player;
-import org.bukkit.event.HandlerList;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class StargateDestroyEvent extends StargateEvent {
- private final Player player;
- private boolean deny;
- private String denyReason;
- private int cost;
-
- private static final HandlerList handlers = new HandlerList();
-
- public HandlerList getHandlers() {
- return handlers;
- }
-
- public static HandlerList getHandlerList() {
- return handlers;
- }
-
- public StargateDestroyEvent(Portal portal, Player player, boolean deny, String denyMsg, int cost) {
- super("StargateDestroyEvent", portal);
- this.player = player;
- this.deny = deny;
- this.denyReason = denyMsg;
- this.cost = cost;
- }
-
- public Player getPlayer() {
- return player;
- }
-
- public boolean getDeny() {
- return deny;
- }
-
- public void setDeny(boolean deny) {
- this.deny = deny;
- }
-
- public String getDenyReason() {
- return denyReason;
- }
-
- public void setDenyReason(String denyReason) {
- this.denyReason = denyReason;
- }
-
- public int getCost() {
- return cost;
- }
-
- public void setCost(int cost) {
- this.cost = cost;
- }
-
-}
diff --git a/src/net/TheDgtl/Stargate/event/StargateEvent.java b/src/net/TheDgtl/Stargate/event/StargateEvent.java
deleted file mode 100644
index 7033f33..0000000
--- a/src/net/TheDgtl/Stargate/event/StargateEvent.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package net.TheDgtl.Stargate.event;
-
-import net.TheDgtl.Stargate.Portal;
-
-import org.bukkit.event.Cancellable;
-import org.bukkit.event.Event;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public abstract class StargateEvent extends Event implements Cancellable {
- protected Portal portal;
- protected boolean cancelled;
-
- public StargateEvent(String event, Portal portal) {
- this.portal = portal;
- this.cancelled = false;
- }
-
- public Portal getPortal() {
- return portal;
- }
-
- @Override
- public boolean isCancelled() {
- return this.cancelled;
- }
-
- @Override
- public void setCancelled(boolean cancelled) {
- this.cancelled = cancelled;
- }
-}
diff --git a/src/net/TheDgtl/Stargate/event/StargateOpenEvent.java b/src/net/TheDgtl/Stargate/event/StargateOpenEvent.java
deleted file mode 100644
index 26ddc08..0000000
--- a/src/net/TheDgtl/Stargate/event/StargateOpenEvent.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package net.TheDgtl.Stargate.event;
-
-import net.TheDgtl.Stargate.Portal;
-
-import org.bukkit.entity.Player;
-import org.bukkit.event.HandlerList;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class StargateOpenEvent extends StargateEvent {
- private final Player player;
- private boolean force;
-
- private static final HandlerList handlers = new HandlerList();
-
- public HandlerList getHandlers() {
- return handlers;
- }
-
- public static HandlerList getHandlerList() {
- return handlers;
- }
- public StargateOpenEvent(Player player, Portal portal, boolean force) {
- super ("StargateOpenEvent", portal);
-
- this.player = player;
- this.force = force;
- }
-
- /**
- * Return the player than opened the gate.
- * @return player than opened the gate
- */
- public Player getPlayer() {
- return player;
- }
-
- public boolean getForce() {
- return force;
- }
-
- public void setForce(boolean force) {
- this.force = force;
- }
-}
diff --git a/src/net/TheDgtl/Stargate/event/StargatePortalEvent.java b/src/net/TheDgtl/Stargate/event/StargatePortalEvent.java
deleted file mode 100644
index d02eb63..0000000
--- a/src/net/TheDgtl/Stargate/event/StargatePortalEvent.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package net.TheDgtl.Stargate.event;
-
-import net.TheDgtl.Stargate.Portal;
-
-import org.bukkit.Location;
-import org.bukkit.entity.Player;
-import org.bukkit.event.HandlerList;
-
-/**
- * Stargate - A portal plugin for Bukkit
- * Copyright (C) 2011, 2012 Steven "Drakia" Scott
- *
- * 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 .
- */
-
-public class StargatePortalEvent extends StargateEvent {
- private final Player player;
- private final Portal destination;
- private Location exit;
-
- private static final HandlerList handlers = new HandlerList();
-
- public HandlerList getHandlers() {
- return handlers;
- }
-
- public static HandlerList getHandlerList() {
- return handlers;
- }
-
- public StargatePortalEvent(Player player, Portal portal, Portal dest, Location exit) {
- super ("StargatePortalEvent", portal);
-
- this.player = player;
- this.destination = dest;
- this.exit = exit;
- }
-
- /**
- * Return the player that went through the gate.
- * @return player that went through the gate
- */
- public Player getPlayer() {
- return player;
- }
-
- /**
- * Return the destination gate
- * @return destination gate
- */
- public Portal getDestination() {
- return destination;
- }
-
- /**
- * Return the location of the players exit point
- * @return org.bukkit.Location Location of the exit point
- */
- public Location getExit() {
- return exit;
- }
-
- /**
- * Set the location of the players exit point
- */
- public void setExit(Location loc) {
- this.exit = loc;
- }
-}
diff --git a/src/net/TheDgtl/Stargate/pmListener.java b/src/net/TheDgtl/Stargate/pmListener.java
deleted file mode 100644
index ee83ba9..0000000
--- a/src/net/TheDgtl/Stargate/pmListener.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package net.TheDgtl.Stargate;
-
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import org.bukkit.entity.Player;
-import org.bukkit.plugin.messaging.PluginMessageListener;
-
-public class pmListener implements PluginMessageListener {
-
- @Override
- public void onPluginMessageReceived(String channel, Player unused, byte[] message) {
- if (!Stargate.enableBungee || !channel.equals("BungeeCord")) return;
-
- // Get data from message
- String inChannel;
- byte[] data;
- try {
- DataInputStream in = new DataInputStream(new ByteArrayInputStream(message));
- inChannel = in.readUTF();
- short len = in.readShort();
- data = new byte[len];
- in.readFully(data);
- } catch (IOException ex) {
- Stargate.log.severe("[Stargate] Error receiving BungeeCord message");
- ex.printStackTrace();
- return;
- }
-
- // Verify that it's an SGBungee packet
- if (!inChannel.equals("SGBungee")) {
- return;
- }
-
- // Data should be player name, and destination gate name
- String msg = new String(data);
- String[] parts = msg.split("#@#");
-
- String playerName = parts[0];
- String destination = parts[1];
-
- // Check if the player is online, if so, teleport, otherwise, queue
- Player player = Stargate.server.getPlayer(playerName);
- if (player == null) {
- Stargate.bungeeQueue.put(playerName.toLowerCase(), destination);
- } else {
- Portal dest = Portal.getBungeeGate(destination);
- // Specified an invalid gate. For now we'll just let them connect at their current location
- if (dest == null) {
- Stargate.log.info("[Stargate] Bungee gate " + destination + " does not exist");
- return;
- }
- dest.teleport(player, dest, null);
- }
- }
-}
diff --git a/src/plugin.yml b/src/plugin.yml
deleted file mode 100644
index 75b676c..0000000
--- a/src/plugin.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: Stargate
-main: net.TheDgtl.Stargate.Stargate
-version: 0.8.0.3
-description: Stargate mod for Bukkit
-author: Drakia
-website: http://www.thedgtl.net
-api-version: 1.13
-commands:
- sg:
- description: Used to reload the plugin. Console use only.
- usage: / reload - Used to reload the plugin. Console use only.
-permissions:
- stargate.use:
- description: Allow use of all gates linking to any world in any network
- default: true
- stargate.create:
- description: Allow creating gates on any network
- default: op
- stargate.destroy:
- description: Allow destruction gates on any network
- default: op
- stargate.free:
- description: Allow free use/creation/destruction of gates
- default: op
- stargate.option:
- description: Allow use of all options
- default: op
- stargate.admin:
- description: Allow all admin features (Hidden/Private only so far)
- default: op
\ No newline at end of file
diff --git a/src/test/java/net/knarcraft/stargate/BlockLocationTest.java b/src/test/java/net/knarcraft/stargate/BlockLocationTest.java
new file mode 100644
index 0000000..0935057
--- /dev/null
+++ b/src/test/java/net/knarcraft/stargate/BlockLocationTest.java
@@ -0,0 +1,85 @@
+package net.knarcraft.stargate;
+
+import be.seeseemelk.mockbukkit.MockBukkit;
+import be.seeseemelk.mockbukkit.WorldMock;
+import net.knarcraft.stargate.container.BlockLocation;
+import org.bukkit.Material;
+import org.junit.After;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+public class BlockLocationTest {
+ private WorldMock mockWorld;
+
+ @BeforeEach
+ public void setUp() {
+ mockWorld = new WorldMock(Material.DIRT, 5);
+ }
+
+ @After
+ public void tearDown() {
+ MockBukkit.unmock();
+ }
+
+ @Test
+ public void equalsTest() {
+ BlockLocation location1 = new BlockLocation(mockWorld, 1, 3, 4);
+ BlockLocation location2 = new BlockLocation(mockWorld, 1, 3, 4);
+ assertEquals(location1, location2);
+ }
+
+ @Test
+ public void notEqualsTest1() {
+ BlockLocation location1 = new BlockLocation(mockWorld, 1, 3, 4);
+ BlockLocation location2 = new BlockLocation(mockWorld, 2, 3, 4);
+ assertNotEquals(location1, location2);
+ }
+
+ @Test
+ public void notEqualsTest2() {
+ BlockLocation location1 = new BlockLocation(mockWorld, 1, 3, 4);
+ BlockLocation location2 = new BlockLocation(mockWorld, 1, 5, 4);
+ assertNotEquals(location1, location2);
+ }
+
+ @Test
+ public void notEqualsTest3() {
+ BlockLocation location1 = new BlockLocation(mockWorld, 1, 3, 4);
+ BlockLocation location2 = new BlockLocation(mockWorld, 1, 3, 7);
+ assertNotEquals(location1, location2);
+ }
+
+ @Test
+ public void notEqualsTest4() {
+ BlockLocation location1 = new BlockLocation(mockWorld, 1, 3, 4);
+ BlockLocation location2 = new BlockLocation(new WorldMock(Material.DIRT, 4), 1, 3, 4);
+ assertNotEquals(location1, location2);
+ }
+
+ @Test
+ public void makeRelativeTest() {
+ BlockLocation location = new BlockLocation(mockWorld, 3, 7, 19);
+ BlockLocation newLocation = location.makeRelativeBlockLocation(34, 65, 75);
+ assertEquals(37, newLocation.getBlockX());
+ assertEquals(72, newLocation.getBlockY());
+ assertEquals(94, newLocation.getBlockZ());
+ }
+
+ @Test
+ public void materialTest() {
+ BlockLocation location = new BlockLocation(mockWorld, 0, 0, 0);
+ assertNotEquals(Material.BOOKSHELF, location.getType());
+ location.setType(Material.BOOKSHELF);
+ assertEquals(Material.BOOKSHELF, location.getType());
+ }
+
+ @Test
+ public void toStringTest() {
+ BlockLocation location = new BlockLocation(mockWorld, 56, 87, 34);
+ assertEquals("56,87,34", location.toString());
+ }
+
+}
diff --git a/src/test/java/net/knarcraft/stargate/RelativeBlockVectorTest.java b/src/test/java/net/knarcraft/stargate/RelativeBlockVectorTest.java
new file mode 100644
index 0000000..f75aefe
--- /dev/null
+++ b/src/test/java/net/knarcraft/stargate/RelativeBlockVectorTest.java
@@ -0,0 +1,33 @@
+package net.knarcraft.stargate;
+
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+public class RelativeBlockVectorTest {
+
+ @Test
+ public void getTest() {
+ RelativeBlockVector relativeBlockVector = new RelativeBlockVector(56, 44, 23);
+ assertEquals(56, relativeBlockVector.getRight());
+ assertEquals(44, relativeBlockVector.getDown());
+ assertEquals(23, relativeBlockVector.getOut());
+ }
+
+ @Test
+ public void equalsTest() {
+ RelativeBlockVector vector1 = new RelativeBlockVector(56, 34, 76);
+ RelativeBlockVector vector2 = new RelativeBlockVector(56, 34, 76);
+ assertEquals(vector1, vector2);
+ }
+
+ @Test
+ public void notEqualsTest() {
+ RelativeBlockVector vector1 = new RelativeBlockVector(456, 78, 234);
+ RelativeBlockVector vector2 = new RelativeBlockVector(56, 34, 76);
+ assertNotEquals(vector1, vector2);
+ }
+
+}
diff --git a/src/test/java/net/knarcraft/stargate/container/BlockLocationTest.java b/src/test/java/net/knarcraft/stargate/container/BlockLocationTest.java
new file mode 100644
index 0000000..520627a
--- /dev/null
+++ b/src/test/java/net/knarcraft/stargate/container/BlockLocationTest.java
@@ -0,0 +1,50 @@
+package net.knarcraft.stargate.container;
+
+import be.seeseemelk.mockbukkit.WorldMock;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class BlockLocationTest {
+
+ @Test
+ public void makeRelativeBlockLocationTest() {
+ WorldMock world = new WorldMock();
+ BlockLocation startLocation = new BlockLocation(world, 5, 4, 3);
+
+ //Move to some other, different location
+ BlockLocation relativeLocation = startLocation.makeRelativeBlockLocation(4, 6, 8);
+ Assertions.assertNotEquals(startLocation, relativeLocation);
+
+ //Move back to make sure we can go back to where we started by going in the opposite direction
+ BlockLocation sameAsStartLocation = relativeLocation.makeRelativeBlockLocation(-4, -6, -8);
+ Assertions.assertEquals(startLocation, sameAsStartLocation);
+ }
+
+ @Test
+ public void getRelativeLocationTest() {
+ WorldMock world = new WorldMock();
+ BlockLocation startLocation = new BlockLocation(world, 7, 3, 6);
+
+ RelativeBlockVector relativeBlockVector = new RelativeBlockVector(2, 1, 3);
+ BlockLocation relativeLocation1 = startLocation.getRelativeLocation(relativeBlockVector, 0);
+ //With yaw = 0, going right goes in the x direction, and out goes in the z direction, while y is decremented
+ BlockLocation targetLocation1 = new BlockLocation(world, 9, 2, 9);
+ Assertions.assertEquals(targetLocation1, relativeLocation1);
+
+ BlockLocation relativeLocation2 = startLocation.getRelativeLocation(relativeBlockVector, 90);
+ //With yaw = 90, going right goes in the z direction, and out goes in the -x direction, while y is decremented
+ BlockLocation targetLocation2 = new BlockLocation(world, 4, 2, 8);
+ Assertions.assertEquals(targetLocation2, relativeLocation2);
+
+ BlockLocation relativeLocation3 = startLocation.getRelativeLocation(relativeBlockVector, 180);
+ //With yaw = 180, going right goes in the -x direction, and out goes in the -z direction, while y is decremented
+ BlockLocation targetLocation3 = new BlockLocation(world, 5, 2, 3);
+ Assertions.assertEquals(targetLocation3, relativeLocation3);
+
+ BlockLocation relativeLocation4 = startLocation.getRelativeLocation(relativeBlockVector, 270);
+ //With yaw = 270, going right goes in the -z direction, and out goes in the x direction, while y is decremented
+ BlockLocation targetLocation4 = new BlockLocation(world, 10, 2, 4);
+ Assertions.assertEquals(targetLocation4, relativeLocation4);
+ }
+
+}
diff --git a/src/test/java/net/knarcraft/stargate/container/RelativeBlockVectorTest.java b/src/test/java/net/knarcraft/stargate/container/RelativeBlockVectorTest.java
new file mode 100644
index 0000000..8259b37
--- /dev/null
+++ b/src/test/java/net/knarcraft/stargate/container/RelativeBlockVectorTest.java
@@ -0,0 +1,53 @@
+package net.knarcraft.stargate.container;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class RelativeBlockVectorTest {
+
+ @Test
+ public void addToVectorTest() {
+ int right = 5;
+ int down = 5;
+ int out = 3;
+
+ RelativeBlockVector relativeBlockVector = new RelativeBlockVector(right, down, out);
+
+ for (int i = 0; i < 1000; i++) {
+ int randomValue = getRandomNumber();
+ RelativeBlockVector newVector = relativeBlockVector.addToVector(RelativeBlockVector.Property.RIGHT, randomValue);
+ Assertions.assertEquals(new RelativeBlockVector(right + randomValue, down, out), newVector);
+
+ newVector = relativeBlockVector.addToVector(RelativeBlockVector.Property.OUT, randomValue);
+ Assertions.assertEquals(new RelativeBlockVector(right, down, out + randomValue), newVector);
+
+ newVector = relativeBlockVector.addToVector(RelativeBlockVector.Property.DOWN, randomValue);
+ Assertions.assertEquals(new RelativeBlockVector(right, down + randomValue, out), newVector);
+ }
+ }
+
+ @Test
+ public void invertTest() {
+ for (int i = 0; i < 1000; i++) {
+ int randomNumber1 = getRandomNumber();
+ int randomNumber2 = getRandomNumber();
+ int randomNumber3 = getRandomNumber();
+
+ RelativeBlockVector relativeBlockVector = new RelativeBlockVector(randomNumber1, randomNumber2,
+ randomNumber3);
+ RelativeBlockVector invertedBlockVector = new RelativeBlockVector(-randomNumber1, -randomNumber2,
+ -randomNumber3);
+ Assertions.assertEquals(invertedBlockVector, relativeBlockVector.invert());
+ }
+ }
+
+ /**
+ * Gets a random number between -500 and 500
+ *
+ * @return A random number between -500 and 500
+ */
+ private int getRandomNumber() {
+ return (int) ((Math.random() - 0.5) * 1000);
+ }
+
+}
diff --git a/src/test/java/net/knarcraft/stargate/portal/GateLayoutTest.java b/src/test/java/net/knarcraft/stargate/portal/GateLayoutTest.java
new file mode 100644
index 0000000..c8bf07c
--- /dev/null
+++ b/src/test/java/net/knarcraft/stargate/portal/GateLayoutTest.java
@@ -0,0 +1,102 @@
+package net.knarcraft.stargate.portal;
+
+import be.seeseemelk.mockbukkit.MockBukkit;
+import be.seeseemelk.mockbukkit.ServerMock;
+import be.seeseemelk.mockbukkit.WorldMock;
+import net.knarcraft.stargate.Stargate;
+import net.knarcraft.stargate.container.RelativeBlockVector;
+import net.knarcraft.stargate.portal.property.gate.GateHandler;
+import net.knarcraft.stargate.portal.property.gate.GateLayout;
+import org.bukkit.Material;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class GateLayoutTest {
+
+ private static GateLayout layout;
+
+ @BeforeAll
+ public static void setUp() {
+ ServerMock server = MockBukkit.mock();
+ server.addWorld(new WorldMock(Material.DIRT, 5));
+ MockBukkit.load(Stargate.class);
+ layout = GateHandler.getGateByName("nethergate.gate").getLayout();
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ MockBukkit.getMock().getPluginManager().disablePlugins();
+ MockBukkit.unmock();
+ }
+
+ @Test
+ public void gateLayoutExitTest() {
+ assertEquals(new RelativeBlockVector(1, 3, 0), layout.getExit());
+ }
+
+ @Test
+ public void gateLayoutExitsTest() {
+ List expected = new ArrayList<>();
+ expected.add(new RelativeBlockVector(1, 3, 0));
+ expected.add(new RelativeBlockVector(2, 3, 0));
+
+ List exits = layout.getExits();
+ exits.forEach((blockVector) -> assertTrue(expected.contains(blockVector)));
+ }
+
+ @Test
+ public void gateLayoutBorderTest() {
+ List expected = new ArrayList<>();
+ expected.add(new RelativeBlockVector(1, 0, 0));
+ expected.add(new RelativeBlockVector(2, 0, 0));
+ expected.add(new RelativeBlockVector(0, 1, 0));
+ expected.add(new RelativeBlockVector(0, 2, 0));
+ expected.add(new RelativeBlockVector(0, 3, 0));
+ expected.add(new RelativeBlockVector(1, 4, 0));
+ expected.add(new RelativeBlockVector(2, 4, 0));
+ expected.add(new RelativeBlockVector(3, 1, 0));
+ expected.add(new RelativeBlockVector(3, 2, 0));
+ expected.add(new RelativeBlockVector(3, 3, 0));
+
+ RelativeBlockVector[] borderBlocks = layout.getBorder();
+ for (RelativeBlockVector blockVector : borderBlocks) {
+ assertTrue(expected.contains(blockVector));
+ }
+ }
+
+ @Test
+ public void gateLayoutControlsTest() {
+ List expected = new ArrayList<>();
+ expected.add(new RelativeBlockVector(0, 2, 0));
+ expected.add(new RelativeBlockVector(3, 2, 0));
+
+ RelativeBlockVector[] controlBlocks = layout.getControls();
+ for (RelativeBlockVector blockVector : controlBlocks) {
+ assertTrue(expected.contains(blockVector));
+ }
+ }
+
+ @Test
+ public void gateLayoutEntrancesTest() {
+ List expected = new ArrayList<>();
+ expected.add(new RelativeBlockVector(1, 1, 0));
+ expected.add(new RelativeBlockVector(2, 1, 0));
+ expected.add(new RelativeBlockVector(1, 2, 0));
+ expected.add(new RelativeBlockVector(2, 2, 0));
+ expected.add(new RelativeBlockVector(1, 3, 0));
+ expected.add(new RelativeBlockVector(2, 3, 0));
+
+ RelativeBlockVector[] controlBlocks = layout.getEntrances();
+ for (RelativeBlockVector blockVector : controlBlocks) {
+ assertTrue(expected.contains(blockVector));
+ }
+ }
+
+}
diff --git a/src/test/java/net/knarcraft/stargate/utility/DirectionHelperTest.java b/src/test/java/net/knarcraft/stargate/utility/DirectionHelperTest.java
new file mode 100644
index 0000000..6de3716
--- /dev/null
+++ b/src/test/java/net/knarcraft/stargate/utility/DirectionHelperTest.java
@@ -0,0 +1,33 @@
+package net.knarcraft.stargate.utility;
+
+import be.seeseemelk.mockbukkit.WorldMock;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class DirectionHelperTest {
+
+ @Test
+ public void getYawFromLocationTest() {
+ World world = new WorldMock();
+ Location location1 = new Location(world, 100, 0, 100);
+ Location location2 = new Location(world, 100, 0, 101);
+
+ double yaw = DirectionHelper.getYawFromLocationDifference(location1, location2);
+ Assertions.assertEquals(0, yaw);
+
+ location2 = new Location(world, 100, 0, 99);
+ yaw = DirectionHelper.getYawFromLocationDifference(location1, location2);
+ Assertions.assertEquals(180, yaw);
+
+ location2 = new Location(world, 101, 0, 100);
+ yaw = DirectionHelper.getYawFromLocationDifference(location1, location2);
+ Assertions.assertEquals(270, yaw);
+
+ location2 = new Location(world, 99, 0, 100);
+ yaw = DirectionHelper.getYawFromLocationDifference(location1, location2);
+ Assertions.assertEquals(90, yaw);
+ }
+
+}
diff --git a/src/test/java/net/knarcraft/stargate/utility/MaterialHelperTest.java b/src/test/java/net/knarcraft/stargate/utility/MaterialHelperTest.java
new file mode 100644
index 0000000..f8481b2
--- /dev/null
+++ b/src/test/java/net/knarcraft/stargate/utility/MaterialHelperTest.java
@@ -0,0 +1,90 @@
+package net.knarcraft.stargate.utility;
+
+import be.seeseemelk.mockbukkit.MockBukkit;
+import org.bukkit.Material;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class MaterialHelperTest {
+
+ @BeforeAll
+ public static void setUp() {
+ MockBukkit.mock();
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ MockBukkit.unmock();
+ }
+
+ @Test
+ public void isWallCoralTest() {
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.DEAD_BRAIN_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.BRAIN_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.DEAD_BUBBLE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.BUBBLE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.DEAD_FIRE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.FIRE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.DEAD_HORN_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.HORN_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.DEAD_TUBE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isWallCoral(Material.TUBE_CORAL_WALL_FAN));
+
+ Assertions.assertFalse(MaterialHelper.isWallCoral(Material.DEAD_TUBE_CORAL));
+ Assertions.assertFalse(MaterialHelper.isWallCoral(Material.TUBE_CORAL));
+ Assertions.assertFalse(MaterialHelper.isWallCoral(Material.TUBE_CORAL_BLOCK));
+ }
+
+ @Test
+ public void isButtonCompatibleTest() {
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.DEAD_BRAIN_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.BRAIN_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.DEAD_BUBBLE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.BUBBLE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.DEAD_FIRE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.FIRE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.DEAD_HORN_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.HORN_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.DEAD_TUBE_CORAL_WALL_FAN));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.TUBE_CORAL_WALL_FAN));
+
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.STONE_BUTTON));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.BIRCH_BUTTON));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.ACACIA_BUTTON));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.CRIMSON_BUTTON));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.OAK_BUTTON));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.DARK_OAK_BUTTON));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.JUNGLE_BUTTON));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.POLISHED_BLACKSTONE_BUTTON));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.SPRUCE_BUTTON));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.WARPED_BUTTON));
+
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.BLACK_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.RED_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.GREEN_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.BLUE_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.YELLOW_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.CYAN_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.LIME_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.BROWN_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.GRAY_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.LIGHT_BLUE_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.LIGHT_GRAY_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.MAGENTA_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.ORANGE_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.PINK_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.PURPLE_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.WHITE_SHULKER_BOX));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.SHULKER_BOX));
+
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.CHEST));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.ENDER_CHEST));
+ Assertions.assertTrue(MaterialHelper.isButtonCompatible(Material.TRAPPED_CHEST));
+
+ //Chek something random to make sure isButtonCompatible is not just "return true;"
+ Assertions.assertFalse(MaterialHelper.isButtonCompatible(Material.OAK_LOG));
+ }
+
+}