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: %s
Destination: " + + "%s
Owner: %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)); + } + +}