├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── art ├── hidden-entities.gif ├── meltpoint-banner2.png ├── nether-view-demo.gif └── view dist.png ├── pom.xml └── src └── main ├── java └── me │ └── gorgeousone │ └── netherview │ ├── ConfigSettings.java │ ├── NetherViewPlugin.java │ ├── blockcache │ ├── BlockCache.java │ ├── BlockCacheFactory.java │ ├── ProjectionCache.java │ ├── Transform.java │ └── TransformFactory.java │ ├── bstats │ └── Metrics.java │ ├── cmdframework │ ├── argument │ │ ├── ArgType.java │ │ ├── ArgValue.java │ │ └── Argument.java │ ├── command │ │ ├── ArgCommand.java │ │ ├── BasicCommand.java │ │ └── ParentCommand.java │ └── handlers │ │ ├── CommandCompleter.java │ │ └── CommandHandler.java │ ├── commmands │ ├── FlipPortalCommand.java │ ├── ListPortalsCommand.java │ ├── PortalInfoCommand.java │ ├── ReloadCommand.java │ ├── ToggleDebugCommand.java │ ├── TogglePortalViewCommand.java │ └── ToggleWarningsCommand.java │ ├── customportal │ ├── CustomPortal.java │ ├── CustomPortalCreator.java │ ├── CustomPortalHandler.java │ ├── CustomPortalSerializer.java │ ├── PlayerClickListener.java │ ├── PlayerCuboidSelection.java │ ├── PlayerSelectionHandler.java │ └── commands │ │ ├── CreatePortalCommand.java │ │ ├── DeletePortalCommand.java │ │ ├── GetWandCommand.java │ │ ├── LinkPortalCommand.java │ │ └── UnlinkPortalCommand.java │ ├── event │ ├── PortalLinkEvent.java │ ├── PortalUnlinkEvent.java │ └── UnlinkReason.java │ ├── geometry │ ├── AxisAlignedRect.java │ ├── BlockVec.java │ ├── Cuboid.java │ ├── Line.java │ ├── Plane.java │ └── viewfrustum │ │ ├── DefinedLine.java │ │ ├── ViewFrustum.java │ │ └── ViewFrustumFactory.java │ ├── handlers │ ├── EntityVisibilityHandler.java │ ├── PlayerViewSession.java │ ├── PortalHandler.java │ └── ViewHandler.java │ ├── listeners │ ├── BlockChangeListener.java │ ├── PlayerMoveListener.java │ ├── PlayerQuitListener.java │ └── PlayerTeleportListener.java │ ├── message │ ├── Message.java │ ├── MessageException.java │ └── MessageUtils.java │ ├── packet │ ├── EntitySpawnPacketFactory1_13.java │ └── PacketHandler.java │ ├── portal │ ├── Portal.java │ ├── PortalLocator.java │ ├── PortalSerializer.java │ ├── PortalSerializer2_1_0.java │ └── ProjectionEntity.java │ ├── updatechecks │ ├── UpdateCheck.java │ ├── UpdateInfo.java │ └── VersionResponse.java │ ├── utils │ ├── ConfigUtils.java │ ├── FacingUtils.java │ ├── NmsUtils.java │ ├── TeleportUtils.java │ ├── TimeUtils.java │ └── VersionUtils.java │ └── wrapper │ ├── Axis.java │ ├── WrappedBoundingBox.java │ ├── blocktype │ ├── AquaticBlockType.java │ ├── BlockType.java │ └── LegacyBlockType.java │ └── rotation │ ├── AquaticRailUtils.java │ └── RotationUtils.java └── resources ├── config.yml ├── config2.yml ├── language.yml └── plugin.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to world '...' 16 | 2. Create portal with size of '...' blocks 17 | 3. Click/Break nearby block 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Console logs** 24 | If relevant, add logs from your server console that might be related to your problem. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Server version and plugin version 30 | The version of Nether View you are using, and the spigot version your server is running on 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 8 | hs_err_pid* 9 | 10 | target/ 11 | .idea/ 12 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Nether View](art/meltpoint-banner2.png) 2 | 3 | # Nether View 4 | A plugin that makes it possible to see the nether through nether portals. 5 | This was inspired by SethBling's [Magic Nether Portal](https://www.youtube.com/watch?v=xewQL6CkMWI). 6 | 7 | ![yep, it's pretty cool](art/nether-view-demo.gif) 8 | 9 | ### Installation 10 | The latest releases can be downloaded here: [spigotmc.org](https://www.spigotmc.org/resources/nether-view.78885/) 11 | 12 | ### Compiling 13 | Nether View uses Maven 3 to manage dependencies. 14 | 15 | Required libraries: 16 | - Bukkit 17 | - ProtocolLib 18 | 19 | ### Contributing 20 | 21 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 22 | 23 | ### bStats 24 | [![bStats Graph Data](https://bstats.org/signatures/bukkit/NetherView.svg)](https://bstats.org/plugin/bukkit/NetherView/7571) 25 | -------------------------------------------------------------------------------- /art/hidden-entities.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GorgeousOne/NetherView/db9de649a5c6397bfba31704b279eaa9c0de8d0e/art/hidden-entities.gif -------------------------------------------------------------------------------- /art/meltpoint-banner2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GorgeousOne/NetherView/db9de649a5c6397bfba31704b279eaa9c0de8d0e/art/meltpoint-banner2.png -------------------------------------------------------------------------------- /art/nether-view-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GorgeousOne/NetherView/db9de649a5c6397bfba31704b279eaa9c0de8d0e/art/nether-view-demo.gif -------------------------------------------------------------------------------- /art/view dist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GorgeousOne/NetherView/db9de649a5c6397bfba31704b279eaa9c0de8d0e/art/view dist.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.gorgeousone 8 | netherview 9 | 3.0.0 10 | jar 11 | 12 | NetherView 13 | 14 | 15 | 1.8 16 | UTF-8 17 | 18 | 19 | 20 | clean package 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-compiler-plugin 25 | 3.7.0 26 | 27 | ${java.version} 28 | ${java.version} 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-shade-plugin 34 | 3.1.0 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | package 46 | 47 | shade 48 | 49 | 50 | false 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | src/main/resources 59 | true 60 | 61 | 62 | 63 | 64 | 65 | 66 | spigotmc-repo 67 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 68 | 69 | 70 | dmulloy2-repo 71 | https://repo.dmulloy2.net/nexus/repository/public/ 72 | 73 | 74 | CodeMC 75 | https://repo.codemc.org/repository/maven-public 76 | 77 | 78 | 79 | 80 | 81 | org.spigotmc 82 | spigot 83 | 1.13.2-R0.1-SNAPSHOT 84 | provided 85 | 86 | 87 | org.junit.jupiter 88 | junit-jupiter-api 89 | 5.6.1 90 | test 91 | 92 | 93 | com.comphenix.protocol 94 | ProtocolLib 95 | 4.6.0-SNAPSHOT 96 | provided 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/ConfigSettings.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview; 2 | 3 | import me.gorgeousone.netherview.message.MessageUtils; 4 | import me.gorgeousone.netherview.utils.VersionUtils; 5 | import me.gorgeousone.netherview.wrapper.blocktype.BlockType; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.World; 8 | import org.bukkit.configuration.ConfigurationSection; 9 | import org.bukkit.configuration.file.FileConfiguration; 10 | import org.bukkit.plugin.java.JavaPlugin; 11 | 12 | import java.util.Collection; 13 | import java.util.HashMap; 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Set; 17 | import java.util.UUID; 18 | import java.util.logging.Level; 19 | 20 | public class ConfigSettings { 21 | 22 | private final JavaPlugin plugin; 23 | 24 | private int portalProjectionDist; 25 | private int portalDisplayRangeSquared; 26 | private int maxPortalSize; 27 | private HashMap worldBorderBlockTypes; 28 | 29 | private boolean entityHidingEnabled; 30 | private boolean entityViewingEnabled; 31 | private int entityUpdateTicks; 32 | 33 | private boolean warningMessagesEnabled; 34 | private boolean debugMessagesEnabled; 35 | 36 | private boolean allWorldsCanCreateCustomPortals = false; 37 | private Set customPortalWhiteList; 38 | private Set customPortalBlackList; 39 | 40 | private boolean allWorldsCanCreatePortalViews = false; 41 | private Set portalViewWhiteList; 42 | private Set portalViewBlackList; 43 | 44 | private boolean netherPortalsAreFlippedByDefault; 45 | private boolean hidePortalBlocks; 46 | private boolean cancelTeleportWhenLinking; 47 | private boolean instantTeleportEnabled; 48 | 49 | public ConfigSettings(JavaPlugin plugin, FileConfiguration config) { 50 | this.plugin = plugin; 51 | } 52 | 53 | /** 54 | * Returns the approximate "radius" for the projections of the portals. The final side size of a projection will change 55 | * depending to the size of the portal. 56 | */ 57 | public int getPortalProjectionDist() { 58 | return portalProjectionDist; 59 | } 60 | 61 | /** 62 | * Returns the squared radius in which the view of a portal will be displayed to players. 63 | */ 64 | public int getPortalDisplayRangeSquared() { 65 | return portalDisplayRangeSquared; 66 | } 67 | 68 | public int getMaxPortalSize() { 69 | return maxPortalSize; 70 | } 71 | 72 | public boolean isEntityHidingEnabled() { 73 | return entityHidingEnabled; 74 | } 75 | 76 | public boolean isEntityViewingEnabled() { 77 | return entityViewingEnabled; 78 | } 79 | 80 | public int getEntityUpdateTicks() { 81 | return entityUpdateTicks; 82 | } 83 | 84 | public BlockType getWorldBorderBlockType(World.Environment environment) { 85 | return worldBorderBlockTypes.get(environment); 86 | } 87 | 88 | public boolean canCreateCustomPortals(World world) { 89 | 90 | UUID worldId = world.getUID(); 91 | return !customPortalBlackList.contains(worldId) && (allWorldsCanCreateCustomPortals || customPortalWhiteList.contains(worldId)); 92 | } 93 | 94 | public boolean canCreatePortalViews(World world) { 95 | 96 | UUID worldId = world.getUID(); 97 | return !portalViewBlackList.contains(worldId) && (allWorldsCanCreatePortalViews || portalViewWhiteList.contains(worldId)); 98 | } 99 | 100 | public boolean portalsAreFlippedByDefault() { 101 | return netherPortalsAreFlippedByDefault; 102 | } 103 | 104 | /** 105 | * Returns true if hiding the purple portal blocks when seeing a portal view is enabled in the config. 106 | */ 107 | public boolean hidePortalBlocksEnabled() { 108 | return hidePortalBlocks; 109 | } 110 | 111 | public boolean isInstantTeleportEnabled() { 112 | return instantTeleportEnabled; 113 | } 114 | 115 | public boolean cancelTeleportWhenLinkingPortalsEnabled() { 116 | return cancelTeleportWhenLinking; 117 | } 118 | 119 | public boolean areWarningMessagesEnabled() { 120 | return warningMessagesEnabled; 121 | } 122 | 123 | public boolean areDebugMessagesEnabled() { 124 | return debugMessagesEnabled; 125 | } 126 | 127 | public void setWarningMessagesEnabled(boolean warningMessagesEnabled) { 128 | this.warningMessagesEnabled = warningMessagesEnabled; 129 | } 130 | 131 | public void setDebugMessagesEnabled(boolean debugMessagesEnabled) { 132 | this.debugMessagesEnabled = debugMessagesEnabled; 133 | } 134 | 135 | public void addVersionSpecificDefaults(FileConfiguration config) { 136 | 137 | if (VersionUtils.IS_LEGACY_SERVER) { 138 | config.addDefault("overworld-border", "stained_clay"); 139 | config.addDefault("nether-border", "stained_clay:14"); 140 | config.addDefault("end-border", "wool:15"); 141 | 142 | } else { 143 | config.addDefault("overworld-border", "white_terracotta"); 144 | config.addDefault("nether-border", "red_concrete"); 145 | config.addDefault("end-border", "black_concrete"); 146 | } 147 | } 148 | 149 | public void loadGeneralSettings(FileConfiguration config) { 150 | 151 | int portalDisplayRange = clamp(config.getInt("portal-display-range"), 2, 128); 152 | portalDisplayRangeSquared = (int) Math.pow(portalDisplayRange, 2); 153 | portalProjectionDist = clamp(config.getInt("portal-projection-distance"), 1, 32); 154 | maxPortalSize = clamp(config.getInt("max-portal-size"), 3, 21); 155 | 156 | entityHidingEnabled = config.getBoolean("hide-entities-behind-portals"); 157 | entityViewingEnabled = config.getBoolean("show-entities-inside-portals"); 158 | entityUpdateTicks = clamp(config.getInt("entity-update-ticks"), 1, 3); 159 | 160 | warningMessagesEnabled = config.getBoolean("warning-messages"); 161 | debugMessagesEnabled = config.getBoolean("debug-messages"); 162 | 163 | worldBorderBlockTypes = new HashMap<>(); 164 | worldBorderBlockTypes.put(World.Environment.NORMAL, deserializeWorldBorderBlockType(config, "overworld-border")); 165 | worldBorderBlockTypes.put(World.Environment.NETHER, deserializeWorldBorderBlockType(config, "nether-border")); 166 | worldBorderBlockTypes.put(World.Environment.THE_END, deserializeWorldBorderBlockType(config, "end-border")); 167 | } 168 | 169 | public void loadNetherPortalSettings(FileConfiguration config) { 170 | 171 | ConfigurationSection configSection = config.getConfigurationSection("nether-portal-viewing"); 172 | 173 | netherPortalsAreFlippedByDefault = configSection.getBoolean("flip-portals-by-default"); 174 | hidePortalBlocks = configSection.getBoolean("hide-portal-blocks"); 175 | cancelTeleportWhenLinking = configSection.getBoolean("cancel-teleport-when-linking-portals"); 176 | instantTeleportEnabled = configSection.getBoolean("instant-teleport"); 177 | 178 | portalViewWhiteList = new HashSet<>(); 179 | portalViewBlackList = new HashSet<>(); 180 | 181 | allWorldsCanCreatePortalViews = deserializeWorldList(portalViewWhiteList, configSection, "whitelisted-worlds", "nether portal whitelist"); 182 | deserializeWorldList(portalViewBlackList, configSection, "blacklisted-worlds", "nether portal blacklist"); 183 | 184 | if (allWorldsCanCreatePortalViews) { 185 | MessageUtils.printDebug("Nether portal viewing enabled in all worlds except black listed ones."); 186 | } 187 | } 188 | 189 | public void loadCustomPortalSettings(FileConfiguration config) { 190 | 191 | ConfigurationSection configSection = config.getConfigurationSection("custom-portal-viewing"); 192 | 193 | customPortalWhiteList = new HashSet<>(); 194 | customPortalBlackList = new HashSet<>(); 195 | 196 | allWorldsCanCreateCustomPortals = deserializeWorldList(customPortalWhiteList, configSection, "whitelisted-worlds", "custom portal whitelist"); 197 | deserializeWorldList(customPortalBlackList, config, "blacklisted-worlds", "custom portal blacklist"); 198 | 199 | if (allWorldsCanCreateCustomPortals) { 200 | MessageUtils.printDebug("Custom portal viewing enabled in all worlds except black listed ones."); 201 | } 202 | } 203 | 204 | private BlockType deserializeWorldBorderBlockType(FileConfiguration config, String configPath) { 205 | 206 | String configValue = config.getString(configPath); 207 | String defaultValue = config.getDefaults().getString(configPath); 208 | BlockType worldBorder; 209 | 210 | try { 211 | worldBorder = BlockType.of(configValue); 212 | 213 | } catch (Exception e) { 214 | plugin.getLogger().log(Level.WARNING, "'" + configValue + "' could not be interpreted as a block type. Using '" + defaultValue + "' instead."); 215 | return BlockType.of(defaultValue); 216 | } 217 | 218 | if (!worldBorder.isOccluding()) { 219 | plugin.getLogger().log(Level.WARNING, "'" + configValue + "' is not an occluding block. Using '" + defaultValue + "' instead."); 220 | return BlockType.of(defaultValue); 221 | } 222 | 223 | return worldBorder; 224 | } 225 | 226 | private boolean deserializeWorldList(Set setToPopulate, 227 | ConfigurationSection config, 228 | String configPath, 229 | String listName) { 230 | 231 | List worldNames = config.getStringList(configPath); 232 | 233 | if (worldNames.contains("*")) { 234 | return true; 235 | } 236 | 237 | worldNames.forEach(worldName -> addWorld(worldName, setToPopulate, listName)); 238 | return false; 239 | } 240 | 241 | private void addWorld(String worldName, Collection worldCollection, String listName) { 242 | 243 | World world = Bukkit.getWorld(worldName); 244 | 245 | if (world == null) { 246 | plugin.getLogger().log(Level.WARNING, "Could not find world '" + worldName + "' from " + listName); 247 | 248 | } else { 249 | worldCollection.add(world.getUID()); 250 | MessageUtils.printDebug("Added '" + worldName + "' to " + listName); 251 | } 252 | } 253 | 254 | private int clamp(int value, int min, int max) { 255 | return Math.max(min, Math.min(value, max)); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/blockcache/BlockCache.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.blockcache; 2 | 3 | import me.gorgeousone.netherview.geometry.BlockVec; 4 | import me.gorgeousone.netherview.portal.Portal; 5 | import me.gorgeousone.netherview.utils.FacingUtils; 6 | import me.gorgeousone.netherview.wrapper.WrappedBoundingBox; 7 | import me.gorgeousone.netherview.wrapper.blocktype.BlockType; 8 | import org.bukkit.Chunk; 9 | import org.bukkit.World; 10 | import org.bukkit.block.Block; 11 | import org.bukkit.entity.Entity; 12 | import org.bukkit.util.Vector; 13 | 14 | import java.util.HashMap; 15 | import java.util.HashSet; 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | /** 20 | * One big array of BlockTypes used to store information about all blocks in a cuboid area around a portal. 21 | * For each of the 2 sides of a portal there is a separate BlockCache. 22 | */ 23 | public class BlockCache { 24 | 25 | private final Portal portal; 26 | private final Set chunks; 27 | 28 | private final BlockType[][][] blockCopies; 29 | private final BlockVec min; 30 | private final BlockVec max; 31 | 32 | private final BlockVec facing; 33 | private final BlockType borderType; 34 | 35 | public BlockCache(Portal portal, 36 | BlockVec offset, 37 | BlockVec size, 38 | BlockVec facing, 39 | BlockType borderType) { 40 | 41 | this.portal = portal; 42 | this.chunks = new HashSet<>(); 43 | 44 | this.blockCopies = new BlockType[size.getX()][size.getY()][size.getZ()]; 45 | this.min = offset.clone(); 46 | this.max = offset.clone().add(size()); 47 | 48 | this.facing = facing; 49 | this.borderType = borderType; 50 | 51 | for (int chunkX = min.getX() >> 4; chunkX <= max.getX() >> 4; chunkX++) { 52 | for (int chunkZ = min.getZ() >> 4; chunkZ <= max.getZ() >> 4; chunkZ++) { 53 | 54 | chunks.add(portal.getWorld().getChunkAt(chunkX, chunkZ)); 55 | } 56 | } 57 | } 58 | 59 | private BlockVec size() { 60 | return new BlockVec(blockCopies.length, blockCopies[0].length, blockCopies[0][0].length); 61 | } 62 | 63 | public Portal getPortal() { 64 | return portal; 65 | } 66 | 67 | public World getWorld() { 68 | return portal.getWorld(); 69 | } 70 | 71 | public Set getChunks() { 72 | return chunks; 73 | } 74 | 75 | public BlockVec getMin() { 76 | return min.clone(); 77 | } 78 | 79 | public BlockVec getMax() { 80 | return max.clone(); 81 | } 82 | 83 | public BlockVec getFacing() { 84 | return facing.clone(); 85 | } 86 | 87 | public boolean contains(BlockVec loc) { 88 | return contains(loc.getX(), loc.getY(), loc.getZ()); 89 | } 90 | 91 | public boolean contains(Vector loc) { 92 | return contains(loc.getX(), loc.getY(), loc.getZ()); 93 | } 94 | 95 | public boolean contains(double x, double y, double z) { 96 | return x >= min.getX() && x < max.getX() && 97 | y >= min.getY() && y < max.getY() && 98 | z >= min.getZ() && z < max.getZ(); 99 | } 100 | 101 | public boolean isBorder(BlockVec loc) { 102 | return isBorder(loc.getX(), loc.getY(), loc.getZ()); 103 | } 104 | 105 | public BlockType getBorderBlockType() { 106 | return borderType.clone(); 107 | } 108 | 109 | /** 110 | * Returns true if the block is at any position bordering the cuboid except the side facing the portal. 111 | */ 112 | public boolean isBorder(int x, int y, int z) { 113 | 114 | if (y == min.getY() || y == max.getY() - 1) { 115 | return true; 116 | } 117 | 118 | int minX = min.getX(); 119 | int minZ = min.getZ(); 120 | int maxX = max.getX() - 1; 121 | int maxZ = max.getZ() - 1; 122 | 123 | if (facing.getZ() != 0) { 124 | if (x == minX || x == maxX) { 125 | return true; 126 | } 127 | } else if (z == minZ || z == maxZ) { 128 | return true; 129 | } 130 | 131 | if (facing.getX() == 1) { 132 | return x == maxX; 133 | } 134 | if (facing.getX() == -1) { 135 | return x == minX; 136 | } 137 | if (facing.getZ() == 1) { 138 | return z == maxZ; 139 | } else { 140 | return z == minZ; 141 | } 142 | } 143 | 144 | public BlockType getBlockTypeAt(BlockVec blockPos) { 145 | return getBlockTypeAt(blockPos.getX(), 146 | blockPos.getY(), 147 | blockPos.getZ()); 148 | } 149 | 150 | public BlockType getBlockTypeAt(int x, int y, int z) { 151 | 152 | if (!contains(x, y, z)) { 153 | return null; 154 | } 155 | 156 | return blockCopies 157 | [x - min.getX()] 158 | [y - min.getY()] 159 | [z - min.getZ()]; 160 | } 161 | 162 | public void setBlockTypeAt(BlockVec blockPos, BlockType blockType) { 163 | setBlockTypeAt( 164 | blockPos.getX(), 165 | blockPos.getY(), 166 | blockPos.getZ(), 167 | blockType); 168 | } 169 | 170 | public void setBlockTypeAt(int x, int y, int z, BlockType blockType) { 171 | blockCopies 172 | [x - min.getX()] 173 | [y - min.getY()] 174 | [z - min.getZ()] = blockType; 175 | } 176 | 177 | public void removeBlockDataAt(BlockVec blockPos) { 178 | blockCopies 179 | [blockPos.getX() - min.getX()] 180 | [blockPos.getY() - min.getY()] 181 | [blockPos.getZ() - min.getZ()] = null; 182 | } 183 | 184 | /** 185 | * Returns true if the block copy at the given position is listed as visible (when it's not null) 186 | */ 187 | public boolean isBlockListedVisible(BlockVec blockPos) { 188 | return getBlockTypeAt(blockPos) != null; 189 | } 190 | 191 | /** 192 | * Returns true if the block copy at the given position is visible by checking the surrounding blocks in the world for transparent ones 193 | */ 194 | public boolean isBlockNowVisible(BlockVec blockPos) { 195 | 196 | for (BlockVec facing : FacingUtils.getAxesBlockVecs()) { 197 | 198 | BlockVec touchingBlockPos = blockPos.clone().add(facing); 199 | 200 | Block touchingBlock = getWorld().getBlockAt( 201 | touchingBlockPos.getX(), 202 | touchingBlockPos.getY(), 203 | touchingBlockPos.getZ()); 204 | 205 | 206 | if (!touchingBlock.getType().isOccluding()) { 207 | return true; 208 | } 209 | } 210 | 211 | //TODO check if block is directly in front of the portal. 212 | return false; 213 | } 214 | 215 | private final Map> unloadedChunks = new HashMap<>(); 216 | 217 | /** 218 | * Returns a set of all entities that are intersecting this BlockCache 219 | */ 220 | public Set getEntities() { 221 | 222 | Set containedEntities = new HashSet<>(); 223 | 224 | for (Chunk chunk : chunks) { 225 | 226 | if (!getWorld().isChunkLoaded(chunk)) { 227 | 228 | unloadedChunks.computeIfAbsent(chunk, set -> addContainedEntities(chunk, new HashSet<>())); 229 | containedEntities.addAll(unloadedChunks.get(chunk)); 230 | 231 | } else { 232 | 233 | addContainedEntities(chunk, containedEntities); 234 | unloadedChunks.remove(chunk); 235 | } 236 | } 237 | 238 | return containedEntities; 239 | } 240 | 241 | private Set addContainedEntities(Chunk chunk, Set setToAddTo) { 242 | 243 | boolean wasChunkLoaded = getWorld().isChunkLoaded(chunk); 244 | Entity[] chunkEntities = chunk.getEntities(); 245 | 246 | for (int i = 0; i < chunkEntities.length; i++) { 247 | 248 | Entity entity = chunkEntities[i]; 249 | 250 | if (WrappedBoundingBox.of(entity).intersectsBlockCache(this)) { 251 | setToAddTo.add(entity); 252 | } 253 | } 254 | 255 | if (!wasChunkLoaded) { 256 | getWorld().unloadChunk(chunk); 257 | } 258 | 259 | return setToAddTo; 260 | } 261 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/blockcache/ProjectionCache.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.blockcache; 2 | 3 | import me.gorgeousone.netherview.geometry.BlockVec; 4 | import me.gorgeousone.netherview.portal.Portal; 5 | import me.gorgeousone.netherview.wrapper.Axis; 6 | import me.gorgeousone.netherview.wrapper.blocktype.BlockType; 7 | 8 | /** 9 | * The equivalent to a BlockCache used to store information about all blocks that will be displayed in the animation of a portal. 10 | * For each of the 2 sides of a portal there will be a separate ProjectionCache. 11 | */ 12 | public class ProjectionCache extends BlockCache { 13 | 14 | private final BlockCache sourceCache; 15 | private final Transform linkTransform; 16 | private final int cacheLength; 17 | 18 | public ProjectionCache(Portal portal, 19 | BlockVec offset, 20 | BlockVec size, 21 | BlockVec facing, 22 | BlockType borderType, 23 | BlockCache sourceCache, 24 | Transform linkTransform) { 25 | 26 | super(portal, offset, size, facing, borderType); 27 | 28 | this.sourceCache = sourceCache; 29 | this.linkTransform = linkTransform; 30 | this.cacheLength = portal.getAxis() == Axis.X ? size.getZ() : size.getX(); 31 | } 32 | 33 | public BlockCache getSourceCache() { 34 | return sourceCache; 35 | } 36 | 37 | public Transform getLinkTransform() { 38 | return linkTransform.clone(); 39 | } 40 | 41 | /** 42 | * Returns the length of the projection cache measured from portal to back wall. 43 | * The value is important for the length of view frustums. 44 | */ 45 | public int getCacheLength() { 46 | return cacheLength; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/blockcache/Transform.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.blockcache; 2 | 3 | import me.gorgeousone.netherview.geometry.BlockVec; 4 | import org.bukkit.Location; 5 | import org.bukkit.util.Vector; 6 | 7 | /** 8 | * A class holding the relative translation and rotation between two portals and applying it to BlockVecs. 9 | */ 10 | public class Transform { 11 | 12 | private Vector translation; 13 | private Vector rotCenter; 14 | private final int[][] rotYMatrix; 15 | 16 | public Transform() { 17 | 18 | translation = new Vector(); 19 | rotCenter = new Vector(); 20 | rotYMatrix = new int[][]{{1, 0}, {0, 1}}; 21 | } 22 | 23 | protected Transform(BlockVec translation, BlockVec rotCenter, int[][] rotationY) { 24 | 25 | this.translation = translation.toVector(); 26 | this.rotCenter = rotCenter.toVector().add(new Vector(0.5, 0, 0.5)); 27 | this.rotYMatrix = rotationY; 28 | } 29 | 30 | protected Transform(Vector translation, Vector rotCenter, int[][] rotationY) { 31 | 32 | this.translation = translation.clone(); 33 | this.rotCenter = rotCenter.clone(); 34 | this.rotYMatrix = rotationY; 35 | } 36 | 37 | public Vector getTranslation() { 38 | return translation.clone(); 39 | } 40 | 41 | public Transform setTranslation(Vector pos) { 42 | this.translation = pos.clone(); 43 | return this; 44 | } 45 | 46 | public Transform setTranslation(BlockVec pos) { 47 | this.translation = pos.toVector(); 48 | return this; 49 | } 50 | 51 | public Transform translate(double dx, double dy, double dz) { 52 | this.translation.add(new Vector(dx, dy, dz)); 53 | return this; 54 | } 55 | 56 | public Transform setRotCenter(BlockVec rotCenter) { 57 | this.rotCenter = rotCenter.toVector().add(new Vector(0.5, 0, 0.5)); 58 | return this; 59 | } 60 | 61 | public Transform translateRotCenter(double dx, double dy, double dz) { 62 | this.rotCenter.add(new Vector(dx, dy, dz)); 63 | return this; 64 | } 65 | 66 | public boolean isRotY90DegRight() { 67 | return rotYMatrix[0][1] == -1; 68 | } 69 | 70 | public boolean isRotY90DegLeft() { 71 | return rotYMatrix[0][1] == 1; 72 | } 73 | 74 | public boolean isRotY180Deg() { 75 | return rotYMatrix[0][0] == -1; 76 | } 77 | 78 | public boolean isRotY0Deg() { 79 | return rotYMatrix[0][0] == 1; 80 | } 81 | 82 | public Transform setRotY90DegRight() { 83 | 84 | rotYMatrix[0][0] = 0; 85 | rotYMatrix[0][1] = -1; 86 | rotYMatrix[1][0] = 1; 87 | rotYMatrix[1][1] = 0; 88 | return this; 89 | } 90 | 91 | public Transform setRotY90DegLeft() { 92 | 93 | rotYMatrix[0][0] = 0; 94 | rotYMatrix[0][1] = 1; 95 | rotYMatrix[1][0] = -1; 96 | rotYMatrix[1][1] = 0; 97 | return this; 98 | } 99 | 100 | public Transform setRotY180Deg() { 101 | 102 | rotYMatrix[0][0] = -1; 103 | rotYMatrix[0][1] = 0; 104 | rotYMatrix[1][0] = 0; 105 | rotYMatrix[1][1] = -1; 106 | return this; 107 | } 108 | 109 | public BlockVec transformVec(BlockVec blockVec) { 110 | 111 | BlockVec blockRotCenter = new BlockVec(rotCenter); 112 | blockVec.subtract(blockRotCenter); 113 | rotateVec(blockVec); 114 | blockVec.add(blockRotCenter); 115 | 116 | return blockVec.add(new BlockVec(translation)); 117 | } 118 | 119 | public Vector transformVec(Vector vec) { 120 | 121 | Vector vecRotCenter = rotCenter; 122 | vec.subtract(vecRotCenter); 123 | rotateVec(vec); 124 | return vec.add(vecRotCenter).add(translation); 125 | } 126 | 127 | public Location transformLoc(Location loc) { 128 | 129 | if (!isRotY0Deg()) { 130 | 131 | loc.subtract(rotCenter); 132 | rotateLoc(loc); 133 | loc.add(rotCenter); 134 | } 135 | 136 | if (translation.lengthSquared() != 0) { 137 | loc.add(translation); 138 | } 139 | 140 | return loc; 141 | } 142 | 143 | private BlockVec rotateVec(BlockVec relativeVec) { 144 | 145 | int transX = relativeVec.getX(); 146 | int transZ = relativeVec.getZ(); 147 | 148 | relativeVec.setX(rotYMatrix[0][0] * transX + rotYMatrix[0][1] * transZ); 149 | relativeVec.setZ(rotYMatrix[1][0] * transX + rotYMatrix[1][1] * transZ); 150 | return relativeVec; 151 | } 152 | 153 | public Vector rotateVec(Vector relativeVec) { 154 | 155 | double transX = relativeVec.getX(); 156 | double transZ = relativeVec.getZ(); 157 | 158 | relativeVec.setX(rotYMatrix[0][0] * transX + rotYMatrix[0][1] * transZ); 159 | relativeVec.setZ(rotYMatrix[1][0] * transX + rotYMatrix[1][1] * transZ); 160 | return relativeVec; 161 | } 162 | 163 | public Location rotateLoc(Location relativeLoc) { 164 | 165 | double transX = relativeLoc.getX(); 166 | double transZ = relativeLoc.getZ(); 167 | 168 | relativeLoc.setX(rotYMatrix[0][0] * transX + rotYMatrix[0][1] * transZ); 169 | relativeLoc.setZ(rotYMatrix[1][0] * transX + rotYMatrix[1][1] * transZ); 170 | 171 | relativeLoc.setYaw(rotateYaw(relativeLoc.getYaw())); 172 | return relativeLoc; 173 | } 174 | 175 | public float rotateYaw(float yaw) { 176 | 177 | float rotatedYaw = yaw + getQuarterTurns() * 90; 178 | rotatedYaw %= 360; 179 | 180 | return rotatedYaw; 181 | } 182 | 183 | public int getQuarterTurns() { 184 | 185 | if (isRotY90DegLeft()) { 186 | return 3; 187 | } else if (isRotY180Deg()) { 188 | return 2; 189 | } else if (isRotY90DegRight()) { 190 | return 1; 191 | } 192 | 193 | return 0; 194 | } 195 | 196 | public Transform invert() { 197 | 198 | rotCenter.add(translation); 199 | translation.multiply(-1); 200 | invertRotation(); 201 | 202 | return this; 203 | } 204 | 205 | public Transform invertRotation() { 206 | 207 | rotYMatrix[0][1] *= -1; 208 | rotYMatrix[1][0] *= -1; 209 | return this; 210 | } 211 | 212 | @Override 213 | public Transform clone() { 214 | return new Transform(translation.clone(), rotCenter.clone(), cloneRotY()); 215 | } 216 | 217 | private int[][] cloneRotY() { 218 | 219 | return new int[][]{ 220 | {rotYMatrix[0][0], rotYMatrix[0][1]}, 221 | {rotYMatrix[1][0], rotYMatrix[1][1]} 222 | }; 223 | } 224 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/blockcache/TransformFactory.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.blockcache; 2 | 3 | import me.gorgeousone.netherview.geometry.BlockVec; 4 | import me.gorgeousone.netherview.portal.Portal; 5 | import me.gorgeousone.netherview.wrapper.Axis; 6 | 7 | public class TransformFactory { 8 | 9 | /** 10 | * Calculates a Transform that can translate and rotate the block cache blocks from one portal 11 | * to the related projection cache blocks of another portal. 12 | * 13 | * @param portal that has the projection cache 14 | * @param counterPortal that has the block cache related to it 15 | * @param isPortalFlipped flips the transform rotation by 180 degrees 16 | */ 17 | public static Transform calculateBlockLinkTransform(Portal portal, Portal counterPortal, boolean isPortalFlipped) { 18 | 19 | Transform linkTransform = new Transform(); 20 | Axis counterPortalAxis = counterPortal.getAxis(); 21 | 22 | BlockVec toLoc = portal.getFrame().getMin(); 23 | BlockVec fromLoc; 24 | 25 | if (portal.getAxis() == counterPortalAxis) { 26 | 27 | if (isPortalFlipped) { 28 | fromLoc = counterPortal.getMaxBlockAtFloor(); 29 | linkTransform.setRotY180Deg(); 30 | 31 | } else { 32 | fromLoc = counterPortal.getFrame().getMin(); 33 | } 34 | 35 | } else { 36 | 37 | if (counterPortalAxis == Axis.X ^ isPortalFlipped) { 38 | linkTransform.setRotY90DegLeft(); 39 | } else { 40 | linkTransform.setRotY90DegRight(); 41 | } 42 | 43 | fromLoc = isPortalFlipped ? counterPortal.getFrame().getMin() : counterPortal.getMaxBlockAtFloor(); 44 | } 45 | 46 | linkTransform.setRotCenter(fromLoc); 47 | linkTransform.setTranslation(toLoc.subtract(fromLoc)); 48 | return linkTransform; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/cmdframework/argument/ArgType.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.cmdframework.argument; 2 | 3 | public enum ArgType { 4 | 5 | INTEGER("integer"), 6 | DECIMAL("number"), 7 | STRING("string"), 8 | BOOLEAN("boolean"); 9 | 10 | private final String simpleName; 11 | 12 | ArgType(String simpleName) { 13 | this.simpleName = simpleName; 14 | } 15 | 16 | public String simpleName() { 17 | return simpleName; 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/cmdframework/argument/ArgValue.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.cmdframework.argument; 2 | 3 | import org.bukkit.ChatColor; 4 | 5 | public class ArgValue { 6 | 7 | private int intVal; 8 | private double decimalVal; 9 | private String stringVal; 10 | private boolean booleanVal; 11 | 12 | public ArgValue(String stringValue) { 13 | this(ArgType.STRING, stringValue); 14 | } 15 | 16 | public ArgValue(ArgType type, String value) { 17 | setValue(value, type); 18 | } 19 | 20 | public String getString() { 21 | return stringVal; 22 | } 23 | 24 | public int getInt() { 25 | return intVal; 26 | } 27 | 28 | public double getDouble() { 29 | return decimalVal; 30 | } 31 | 32 | public boolean getBoolean() { 33 | return booleanVal; 34 | } 35 | 36 | protected void setValue(String value, ArgType type) { 37 | 38 | try { 39 | switch (type) { 40 | 41 | case INTEGER: 42 | intVal = Integer.parseInt(value); 43 | 44 | case DECIMAL: 45 | decimalVal = Double.parseDouble(value); 46 | 47 | case STRING: 48 | stringVal = value; 49 | break; 50 | 51 | case BOOLEAN: 52 | booleanVal = Boolean.parseBoolean(value); 53 | break; 54 | } 55 | 56 | } catch (Exception e) { 57 | throw new IllegalArgumentException(ChatColor.RED + "'" + value + "' is not a " + type.simpleName() + "."); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/cmdframework/argument/Argument.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.cmdframework.argument; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | public class Argument { 8 | 9 | private final String name; 10 | 11 | private final ArgType type; 12 | private final List tabList; 13 | private ArgValue defValue; 14 | 15 | public Argument(String name, ArgType type) { 16 | this(name, type, new String[]{}); 17 | } 18 | 19 | public Argument(String name, ArgType type, String... tabList) { 20 | 21 | this.name = name; 22 | this.type = type; 23 | 24 | this.tabList = new ArrayList<>(); 25 | 26 | if (type == ArgType.BOOLEAN) { 27 | this.tabList.add("true"); 28 | this.tabList.add("false"); 29 | } else { 30 | this.tabList.addAll(Arrays.asList(tabList)); 31 | } 32 | } 33 | 34 | public boolean hasDefault() { 35 | return getDefault() != null; 36 | } 37 | 38 | public ArgValue getDefault() { 39 | return defValue; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public ArgType getType() { 47 | return type; 48 | } 49 | 50 | public List getTabList() { 51 | return tabList; 52 | } 53 | 54 | public Argument setDefaultTo(String value) { 55 | defValue = new ArgValue(getType(), value); 56 | return this; 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/cmdframework/command/ArgCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.cmdframework.command; 2 | 3 | import me.gorgeousone.netherview.cmdframework.argument.ArgType; 4 | import me.gorgeousone.netherview.cmdframework.argument.ArgValue; 5 | import me.gorgeousone.netherview.cmdframework.argument.Argument; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.entity.Player; 8 | 9 | import java.util.ArrayList; 10 | import java.util.LinkedList; 11 | import java.util.List; 12 | 13 | public abstract class ArgCommand extends BasicCommand { 14 | 15 | private final List arguments; 16 | 17 | protected ArgCommand(String name, String permission, boolean isPlayerRequired) { 18 | this(name, permission, isPlayerRequired, null); 19 | } 20 | 21 | protected ArgCommand(String name, String permission, boolean isPlayerRequired, ParentCommand parent) { 22 | 23 | super(name, permission, isPlayerRequired, parent); 24 | this.arguments = new ArrayList<>(); 25 | } 26 | 27 | public List getArgs() { 28 | return arguments; 29 | } 30 | 31 | protected void addArg(Argument arg) { 32 | arguments.add(arg); 33 | } 34 | 35 | @Override 36 | protected void onCommand(CommandSender sender, String[] stringArgs) { 37 | 38 | int argsSize = getArgs().size(); 39 | int stringArgsLength = stringArgs.length; 40 | 41 | ArgValue[] values = new ArgValue[Math.max(argsSize, stringArgsLength)]; 42 | 43 | try { 44 | if (stringArgsLength >= argsSize) { 45 | createMoreValuesThanOwnArgs(values, stringArgs); 46 | } else { 47 | createMoreValuesThanSenderInput(values, stringArgs); 48 | } 49 | 50 | } catch (ArrayIndexOutOfBoundsException e) { 51 | sendUsage(sender); 52 | return; 53 | 54 | } catch (IllegalArgumentException e) { 55 | sender.sendMessage(e.getMessage()); 56 | return; 57 | } 58 | 59 | onCommand(sender, values); 60 | } 61 | 62 | protected abstract void onCommand(CommandSender sender, ArgValue[] arguments); 63 | 64 | @Override 65 | public List getTabList(CommandSender sender, String[] arguments) { 66 | 67 | if (isPlayerRequired() && !(sender instanceof Player)) { 68 | return null; 69 | } 70 | 71 | if (this.arguments.size() < arguments.length) { 72 | return new LinkedList<>(); 73 | } 74 | 75 | return this.arguments.get(arguments.length - 1).getTabList(); 76 | } 77 | 78 | @Override 79 | public String getUsage() { 80 | 81 | StringBuilder usage = new StringBuilder(super.getUsage()); 82 | 83 | for (Argument arg : getArgs()) { 84 | usage.append(" <"); 85 | usage.append(arg.getName()); 86 | usage.append(">"); 87 | } 88 | 89 | return usage.toString(); 90 | } 91 | 92 | protected void createMoreValuesThanOwnArgs(ArgValue[] values, String[] stringArgs) { 93 | 94 | for (int i = 0; i < values.length; i++) { 95 | 96 | values[i] = i < getArgs().size() ? 97 | new ArgValue(getArgs().get(i).getType(), stringArgs[i]) : 98 | new ArgValue(ArgType.STRING, stringArgs[i]); 99 | } 100 | } 101 | 102 | protected void createMoreValuesThanSenderInput(ArgValue[] values, String[] stringArgs) { 103 | 104 | for (int i = 0; i < values.length; i++) { 105 | Argument arg = getArgs().get(i); 106 | 107 | if (i < stringArgs.length) { 108 | values[i] = new ArgValue(getArgs().get(i).getType(), stringArgs[i]); 109 | continue; 110 | } 111 | 112 | if (arg.hasDefault()) { 113 | values[i] = arg.getDefault(); 114 | } else { 115 | throw new ArrayIndexOutOfBoundsException(); 116 | } 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/cmdframework/command/BasicCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.cmdframework.command; 2 | 3 | import org.bukkit.ChatColor; 4 | import org.bukkit.command.CommandSender; 5 | import org.bukkit.entity.Player; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | /** 13 | * This is the beginning of a lot of unnecessary code. 14 | * I mean it is kind of useful because I can create child commands, aliases, number inputs and tab lists on the go but 15 | * yeah it feels somehow unnecessary that this "api" is like at least 10 pages extra code. 16 | * But it's more soft coded so it's also cool in a way. 17 | */ 18 | public abstract class BasicCommand { 19 | 20 | private final String name; 21 | private final String permission; 22 | private final boolean isPlayerRequired; 23 | 24 | private final Set aliases; 25 | private final ParentCommand parent; 26 | 27 | protected BasicCommand(String name, String permission, boolean isPlayerRequired) { 28 | this(name, permission, isPlayerRequired, null); 29 | } 30 | 31 | protected BasicCommand(String name, String permission, boolean isPlayerRequired, ParentCommand parent) { 32 | 33 | this.name = name; 34 | this.permission = permission; 35 | this.isPlayerRequired = isPlayerRequired; 36 | this.parent = parent; 37 | 38 | aliases = new HashSet<>(); 39 | aliases.add(name); 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public String getPermission() { 47 | return permission; 48 | } 49 | 50 | public boolean isPlayerRequired() { 51 | return isPlayerRequired; 52 | } 53 | 54 | public ParentCommand getParent() { 55 | return parent; 56 | } 57 | 58 | public boolean isChild() { 59 | return parent != null; 60 | } 61 | 62 | public boolean matches(String alias) { 63 | return aliases.contains(alias); 64 | } 65 | 66 | protected void addAlias(String alias) { 67 | aliases.add(alias); 68 | } 69 | 70 | public void execute(CommandSender sender, String[] arguments) { 71 | 72 | if (isPlayerRequired && !(sender instanceof Player)) { 73 | sender.sendMessage(ChatColor.RED + "Only players can execute this command."); 74 | return; 75 | } 76 | 77 | if (permission != null && !sender.hasPermission(getPermission())) { 78 | sender.sendMessage(ChatColor.RED + "You do not have the permission for this command."); 79 | return; 80 | } 81 | 82 | onCommand(sender, arguments); 83 | } 84 | 85 | protected abstract void onCommand(CommandSender sender, String[] arguments); 86 | 87 | public List getTabList(CommandSender sender, String[] arguments) { 88 | return new ArrayList<>(); 89 | } 90 | 91 | public String getUsage() { 92 | 93 | if (isChild()) { 94 | return getParent().getParentUsage() + " " + getName(); 95 | } else { 96 | return "/" + getName(); 97 | } 98 | } 99 | 100 | public void sendUsage(CommandSender sender) { 101 | sender.sendMessage(ChatColor.RED + "Usage: " + getUsage()); 102 | } 103 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/cmdframework/command/ParentCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.cmdframework.command; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.bukkit.entity.Player; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | public class ParentCommand extends BasicCommand { 11 | 12 | private final List children; 13 | private final String childrenType; 14 | 15 | public ParentCommand(String name, String permission, boolean isPlayerRequired, String childrenType) { 16 | this(name, permission, isPlayerRequired, childrenType, null); 17 | } 18 | 19 | public ParentCommand(String name, 20 | String permission, 21 | boolean isPlayerRequired, 22 | String childrenType, 23 | ParentCommand parent) { 24 | super(name, permission, isPlayerRequired, parent); 25 | 26 | this.childrenType = "<" + childrenType + ">"; 27 | this.children = new ArrayList<>(); 28 | } 29 | 30 | public void addChild(BasicCommand child) { 31 | children.add(child); 32 | } 33 | 34 | @Override 35 | public void onCommand(CommandSender sender, String[] arguments) { 36 | 37 | if (arguments.length == 0) { 38 | sendUsage(sender); 39 | return; 40 | } 41 | 42 | for (BasicCommand child : getChildren()) { 43 | 44 | if (child.matches(arguments[0])) { 45 | child.execute(sender, Arrays.copyOfRange(arguments, 1, arguments.length)); 46 | return; 47 | } 48 | } 49 | 50 | sendUsage(sender); 51 | } 52 | 53 | public List getChildren() { 54 | return children; 55 | } 56 | 57 | @Override 58 | public List getTabList(CommandSender sender, String[] arguments) { 59 | 60 | List tabList = new ArrayList<>(); 61 | 62 | //create a tab list of the children commands 63 | if (arguments.length == 1) { 64 | 65 | for (BasicCommand child : getChildren()) { 66 | 67 | if (child.isPlayerRequired() && !(sender instanceof Player)) { 68 | continue; 69 | } 70 | 71 | String subPermission = child.getPermission(); 72 | 73 | if (subPermission == null || sender.hasPermission(subPermission)) { 74 | tabList.add(child.getName()); 75 | } 76 | } 77 | 78 | return tabList; 79 | } 80 | 81 | //forwards the task of creating a tab list to a child commands 82 | for (BasicCommand child : getChildren()) { 83 | 84 | if (!child.matches(arguments[0])) { 85 | continue; 86 | } 87 | 88 | if (child.isPlayerRequired() && !(sender instanceof Player)) { 89 | continue; 90 | } 91 | 92 | String subPermission = getPermission(); 93 | 94 | if (subPermission == null || sender.hasPermission(subPermission)) { 95 | return child.getTabList(sender, Arrays.copyOfRange(arguments, 1, arguments.length)); 96 | } 97 | } 98 | 99 | return tabList; 100 | } 101 | 102 | @Override 103 | public String getUsage() { 104 | return super.getUsage() + " " + childrenType; 105 | } 106 | 107 | public String getParentUsage() { 108 | return super.getUsage(); 109 | } 110 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/cmdframework/handlers/CommandCompleter.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.cmdframework.handlers; 2 | 3 | import me.gorgeousone.netherview.cmdframework.command.BasicCommand; 4 | import org.bukkit.command.Command; 5 | import org.bukkit.command.CommandSender; 6 | import org.bukkit.command.TabCompleter; 7 | import org.bukkit.entity.Player; 8 | 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | 12 | public class CommandCompleter implements TabCompleter { 13 | 14 | private final CommandHandler cmdHandler; 15 | 16 | public CommandCompleter(CommandHandler cmdHandler) { 17 | this.cmdHandler = cmdHandler; 18 | } 19 | 20 | @Override 21 | public List onTabComplete(CommandSender sender, Command cmd, String label, String[] args) { 22 | 23 | for (BasicCommand command : cmdHandler.getCommands()) { 24 | 25 | if (command.matches(cmd.getName())) { 26 | 27 | List tabList = new LinkedList<>(); 28 | String permission = cmd.getPermission(); 29 | 30 | if (command.isPlayerRequired() && !(sender instanceof Player)) { 31 | return tabList; 32 | } 33 | 34 | if (permission != null && !sender.hasPermission(permission)) { 35 | return tabList; 36 | } 37 | 38 | for (String tab : command.getTabList(sender, args)) { 39 | 40 | if (tab.startsWith(args[args.length - 1])) { 41 | tabList.add(tab); 42 | } 43 | } 44 | 45 | return tabList; 46 | } 47 | } 48 | 49 | return null; 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/cmdframework/handlers/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.cmdframework.handlers; 2 | 3 | import me.gorgeousone.netherview.cmdframework.command.BasicCommand; 4 | import org.bukkit.command.Command; 5 | import org.bukkit.command.CommandExecutor; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.plugin.java.JavaPlugin; 8 | 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | public class CommandHandler implements CommandExecutor { 13 | 14 | private final JavaPlugin plugin; 15 | private final Set commands; 16 | private final CommandCompleter cmdCompleter; 17 | 18 | public CommandHandler(JavaPlugin plugin) { 19 | this.plugin = plugin; 20 | this.commands = new HashSet<>(); 21 | this.cmdCompleter = new CommandCompleter(this); 22 | } 23 | 24 | public void registerCommand(BasicCommand command) { 25 | commands.add(command); 26 | plugin.getCommand(command.getName()).setExecutor(this); 27 | plugin.getCommand(command.getName()).setTabCompleter(cmdCompleter); 28 | } 29 | 30 | public Set getCommands() { 31 | return commands; 32 | } 33 | 34 | @Override 35 | public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { 36 | 37 | String cmdName = cmd.getName(); 38 | 39 | for (BasicCommand command : commands) { 40 | 41 | if (command.matches(cmdName)) { 42 | command.execute(sender, args); 43 | return true; 44 | } 45 | } 46 | return false; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/commmands/FlipPortalCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.commmands; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.NetherViewPlugin; 5 | import me.gorgeousone.netherview.cmdframework.command.BasicCommand; 6 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 7 | import me.gorgeousone.netherview.handlers.PortalHandler; 8 | import me.gorgeousone.netherview.handlers.ViewHandler; 9 | import me.gorgeousone.netherview.message.Message; 10 | import me.gorgeousone.netherview.message.MessageUtils; 11 | import me.gorgeousone.netherview.portal.Portal; 12 | import org.bukkit.World; 13 | import org.bukkit.command.CommandSender; 14 | import org.bukkit.entity.Player; 15 | 16 | public class FlipPortalCommand extends BasicCommand { 17 | 18 | private final ConfigSettings configSettings; 19 | private final PortalHandler portalHandler; 20 | private final ViewHandler viewHandler; 21 | 22 | public FlipPortalCommand(ParentCommand parent, 23 | ConfigSettings configSettings, 24 | PortalHandler portalHandler, 25 | ViewHandler viewHandler) { 26 | 27 | super("flipportal", NetherViewPlugin.PORTAL_FLIP_PERM, true, parent); 28 | 29 | this.configSettings = configSettings; 30 | this.portalHandler = portalHandler; 31 | this.viewHandler = viewHandler; 32 | } 33 | 34 | @Override 35 | protected void onCommand(CommandSender sender, String[] arguments) { 36 | 37 | Player player = (Player) sender; 38 | World world = player.getWorld(); 39 | 40 | if (!configSettings.canCreatePortalViews(world)) { 41 | MessageUtils.sendInfo(player, Message.WORLD_NOT_WHITE_LISTED, player.getWorld().getName()); 42 | return; 43 | } 44 | 45 | if (!viewHandler.hasViewSession(player)) { 46 | MessageUtils.sendInfo(player, Message.NO_PORTAL_FOUND_NEARBY); 47 | return; 48 | } 49 | 50 | Portal viewedPortal = viewHandler.getViewSession(player).getViewedPortal(); 51 | 52 | viewHandler.removePortal(viewedPortal); 53 | viewedPortal.flipView(); 54 | portalHandler.loadProjectionCachesOf(viewedPortal); 55 | viewHandler.displayClosestPortalTo(player, player.getEyeLocation()); 56 | 57 | MessageUtils.sendInfo(player, Message.FLIPPED_PORTAL, viewedPortal.toString()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/commmands/ListPortalsCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.commmands; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.NetherViewPlugin; 5 | import me.gorgeousone.netherview.cmdframework.argument.ArgType; 6 | import me.gorgeousone.netherview.cmdframework.argument.ArgValue; 7 | import me.gorgeousone.netherview.cmdframework.argument.Argument; 8 | import me.gorgeousone.netherview.cmdframework.command.ArgCommand; 9 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 10 | import me.gorgeousone.netherview.handlers.PortalHandler; 11 | import me.gorgeousone.netherview.message.Message; 12 | import me.gorgeousone.netherview.message.MessageUtils; 13 | import me.gorgeousone.netherview.portal.Portal; 14 | import org.bukkit.Bukkit; 15 | import org.bukkit.ChatColor; 16 | import org.bukkit.World; 17 | import org.bukkit.command.CommandSender; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Set; 22 | 23 | public class ListPortalsCommand extends ArgCommand { 24 | 25 | private final ConfigSettings configSettings; 26 | private final PortalHandler portalHandler; 27 | 28 | public ListPortalsCommand(ParentCommand parent, ConfigSettings configSettings, PortalHandler portalHandler) { 29 | 30 | super("listportals", NetherViewPlugin.INFO_PERM, false, parent); 31 | addArg(new Argument("world", ArgType.STRING)); 32 | 33 | this.configSettings = configSettings; 34 | this.portalHandler = portalHandler; 35 | } 36 | 37 | @Override 38 | protected void onCommand(CommandSender sender, ArgValue[] arguments) { 39 | 40 | String worldName = arguments[0].getString(); 41 | World world = Bukkit.getWorld(worldName); 42 | 43 | if (world == null) { 44 | MessageUtils.sendInfo(sender, Message.NO_WORLD_FOUND, worldName); 45 | return; 46 | } 47 | 48 | if (!configSettings.canCreatePortalViews(world)) { 49 | MessageUtils.sendInfo(sender, Message.WORLD_NOT_WHITE_LISTED, worldName); 50 | return; 51 | } 52 | 53 | if (!portalHandler.hasPortals(world)) { 54 | MessageUtils.sendInfo(sender, Message.NO_PORTALS_FOUND, worldName); 55 | return; 56 | } 57 | 58 | Set portalSet = portalHandler.getPortals(world); 59 | StringBuilder portals = new StringBuilder(); 60 | 61 | for (Portal portal : portalSet) { 62 | portals.append("\\n").append(ChatColor.GRAY + "- ").append(ChatColor.RESET).append(portal.toString()); 63 | } 64 | 65 | MessageUtils.sendInfo(sender, Message.WORLD_INFO, String.valueOf(portalSet.size()), worldName, portals.toString()); 66 | } 67 | 68 | @Override 69 | public List getTabList(CommandSender sender, String[] arguments) { 70 | 71 | List worldNames = new ArrayList<>(); 72 | 73 | for (World world : Bukkit.getWorlds()) { 74 | 75 | if (world != null) { 76 | worldNames.add(world.getName()); 77 | } 78 | } 79 | 80 | return worldNames; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/commmands/PortalInfoCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.commmands; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.NetherViewPlugin; 5 | import me.gorgeousone.netherview.cmdframework.command.BasicCommand; 6 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 7 | import me.gorgeousone.netherview.customportal.CustomPortal; 8 | import me.gorgeousone.netherview.handlers.PortalHandler; 9 | import me.gorgeousone.netherview.message.Message; 10 | import me.gorgeousone.netherview.message.MessageUtils; 11 | import me.gorgeousone.netherview.portal.Portal; 12 | import org.bukkit.ChatColor; 13 | import org.bukkit.World; 14 | import org.bukkit.command.CommandSender; 15 | import org.bukkit.entity.Player; 16 | 17 | import java.util.Set; 18 | 19 | public class PortalInfoCommand extends BasicCommand { 20 | 21 | private final ConfigSettings configSettings; 22 | private final PortalHandler portalHandler; 23 | 24 | public PortalInfoCommand(ParentCommand parent, ConfigSettings configSettings, PortalHandler portalHandler) { 25 | 26 | super("portalinfo", NetherViewPlugin.INFO_PERM, true, parent); 27 | 28 | this.configSettings = configSettings; 29 | this.portalHandler = portalHandler; 30 | } 31 | 32 | @Override 33 | protected void onCommand(CommandSender sender, String[] arguments) { 34 | 35 | Player player = (Player) sender; 36 | World world = player.getWorld(); 37 | 38 | if (!configSettings.canCreatePortalViews(world)) { 39 | MessageUtils.sendInfo(player, Message.WORLD_NOT_WHITE_LISTED, player.getWorld().getName()); 40 | return; 41 | } 42 | 43 | Portal portal = portalHandler.getClosestPortal(player.getLocation(), false); 44 | 45 | if (portal == null) { 46 | MessageUtils.sendInfo(player, Message.NO_PORTALS_FOUND, player.getWorld().getName()); 47 | return; 48 | } 49 | 50 | StringBuilder infoText = new StringBuilder(); 51 | 52 | // &7 is flipped: &r%is-flipped% 53 | // &7 is linked to: &r%counter-portal% 54 | // &7 portals linked to portal: &r%linked-portals% 55 | 56 | if (portal instanceof CustomPortal) { 57 | infoText.append("\\n" + ChatColor.GRAY + "name: " + ChatColor.RESET + ((CustomPortal) portal).getName()); 58 | } 59 | 60 | infoText.append("\\n" + ChatColor.GRAY + "is flipped: " + ChatColor.RESET + portal.isViewFlipped()); 61 | infoText.append("\\n" + ChatColor.GRAY + "is linked: " + ChatColor.RESET + (portal.isLinked() ? portal.getCounterPortal().toString() : "-no portal-")); 62 | infoText.append("\\n" + ChatColor.GRAY + "is linked: " + ChatColor.RESET); 63 | 64 | Set connectedPortals = portalHandler.getPortalsLinkedTo(portal); 65 | 66 | if (connectedPortals.isEmpty()) { 67 | infoText.append("-no portal-"); 68 | 69 | } else { 70 | 71 | for (Portal connectedPortal : connectedPortals) { 72 | infoText.append("\\n" + ChatColor.GRAY + " - " + ChatColor.RESET + connectedPortal.toString()); 73 | } 74 | } 75 | 76 | MessageUtils.sendInfo(player, Message.PORTAL_INFO, portal.toString(), infoText.toString()); 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/commmands/ReloadCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.commmands; 2 | 3 | import me.gorgeousone.netherview.NetherViewPlugin; 4 | import me.gorgeousone.netherview.cmdframework.command.BasicCommand; 5 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 6 | import org.bukkit.command.CommandSender; 7 | 8 | public class ReloadCommand extends BasicCommand { 9 | 10 | private final NetherViewPlugin main; 11 | 12 | public ReloadCommand(ParentCommand parent, NetherViewPlugin main) { 13 | 14 | super("reload", NetherViewPlugin.CONFIG_PERM, false, parent); 15 | addAlias("rl"); 16 | 17 | this.main = main; 18 | } 19 | 20 | @Override 21 | protected void onCommand(CommandSender sender, String[] arguments) { 22 | 23 | main.reload(); 24 | sender.sendMessage(NetherViewPlugin.CHAT_PREFIX + " Reloaded config settings."); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/commmands/ToggleDebugCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.commmands; 2 | 3 | import me.gorgeousone.netherview.NetherViewPlugin; 4 | import me.gorgeousone.netherview.cmdframework.argument.ArgType; 5 | import me.gorgeousone.netherview.cmdframework.argument.ArgValue; 6 | import me.gorgeousone.netherview.cmdframework.argument.Argument; 7 | import me.gorgeousone.netherview.cmdframework.command.ArgCommand; 8 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 9 | import org.bukkit.command.CommandSender; 10 | 11 | public class ToggleDebugCommand extends ArgCommand { 12 | 13 | private final NetherViewPlugin main; 14 | 15 | public ToggleDebugCommand(ParentCommand parent, NetherViewPlugin main) { 16 | 17 | super("debugmessages", NetherViewPlugin.CONFIG_PERM, false, parent); 18 | addArg(new Argument("true/false", ArgType.BOOLEAN)); 19 | 20 | this.main = main; 21 | } 22 | 23 | @Override 24 | protected void onCommand(CommandSender sender, ArgValue[] arguments) { 25 | 26 | boolean newState = arguments[0].getBoolean(); 27 | boolean stateChanged = main.setDebugMessagesEnabled(newState); 28 | 29 | if (stateChanged) { 30 | sender.sendMessage(NetherViewPlugin.CHAT_PREFIX + (newState ? " Enabled" : " Disabled") + " debug messages."); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/commmands/TogglePortalViewCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.commmands; 2 | 3 | import me.gorgeousone.netherview.NetherViewPlugin; 4 | import me.gorgeousone.netherview.cmdframework.command.BasicCommand; 5 | import me.gorgeousone.netherview.handlers.ViewHandler; 6 | import me.gorgeousone.netherview.message.Message; 7 | import me.gorgeousone.netherview.message.MessageUtils; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.entity.Player; 10 | 11 | public class TogglePortalViewCommand extends BasicCommand { 12 | 13 | private final ViewHandler viewHandler; 14 | 15 | public TogglePortalViewCommand(ViewHandler viewHandler) { 16 | 17 | super("toggleportalview", NetherViewPlugin.VIEW_PERM, true); 18 | this.viewHandler = viewHandler; 19 | } 20 | 21 | @Override 22 | protected void onCommand(CommandSender sender, String[] arguments) { 23 | 24 | Player player = (Player) sender; 25 | 26 | boolean wantsToSeePortalViews = !viewHandler.hasPortalViewEnabled(player); 27 | viewHandler.setPortalViewEnabled(player, wantsToSeePortalViews); 28 | 29 | MessageUtils.sendInfo(player, wantsToSeePortalViews ? Message.PORTAL_VIEWING_ON : Message.PORTAL_VIEWING_OFF); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/commmands/ToggleWarningsCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.commmands; 2 | 3 | import me.gorgeousone.netherview.NetherViewPlugin; 4 | import me.gorgeousone.netherview.cmdframework.argument.ArgType; 5 | import me.gorgeousone.netherview.cmdframework.argument.ArgValue; 6 | import me.gorgeousone.netherview.cmdframework.argument.Argument; 7 | import me.gorgeousone.netherview.cmdframework.command.ArgCommand; 8 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 9 | import org.bukkit.command.CommandSender; 10 | 11 | public class ToggleWarningsCommand extends ArgCommand { 12 | 13 | private final NetherViewPlugin main; 14 | 15 | public ToggleWarningsCommand(ParentCommand parent, NetherViewPlugin main) { 16 | 17 | super("warningmessages", NetherViewPlugin.CONFIG_PERM, false, parent); 18 | addArg(new Argument("true/false", ArgType.BOOLEAN)); 19 | 20 | this.main = main; 21 | } 22 | 23 | @Override 24 | protected void onCommand(CommandSender sender, ArgValue[] arguments) { 25 | 26 | boolean newState = arguments[0].getBoolean(); 27 | boolean stateChanged = main.setWarningMessagesEnabled(newState); 28 | 29 | if (stateChanged) { 30 | sender.sendMessage(NetherViewPlugin.CHAT_PREFIX + (newState ? " Enabled" : " Disabled") + " warning messages."); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/CustomPortal.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal; 2 | 3 | import me.gorgeousone.netherview.geometry.AxisAlignedRect; 4 | import me.gorgeousone.netherview.geometry.Cuboid; 5 | import me.gorgeousone.netherview.portal.Portal; 6 | import org.bukkit.World; 7 | 8 | import java.util.HashSet; 9 | 10 | public class CustomPortal extends Portal { 11 | 12 | private String name; 13 | 14 | public CustomPortal(World world, 15 | AxisAlignedRect portalRect, 16 | Cuboid frameShape, 17 | Cuboid innerShape) { 18 | super(world, portalRect, frameShape, innerShape, new HashSet<>()); 19 | } 20 | 21 | public String getName() { 22 | return name; 23 | } 24 | 25 | public void setName(String name) { 26 | this.name = name; 27 | } 28 | 29 | @Override 30 | public void setLinkedTo(Portal counterPortal) { 31 | 32 | if (counterPortal instanceof CustomPortal) { 33 | super.setLinkedTo(counterPortal); 34 | } else { 35 | throw new IllegalArgumentException("Cannot link custom portal to not custom portal"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/CustomPortalCreator.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal; 2 | 3 | import me.gorgeousone.netherview.geometry.AxisAlignedRect; 4 | import me.gorgeousone.netherview.geometry.BlockVec; 5 | import me.gorgeousone.netherview.geometry.Cuboid; 6 | import me.gorgeousone.netherview.message.Message; 7 | import me.gorgeousone.netherview.message.MessageException; 8 | import me.gorgeousone.netherview.wrapper.Axis; 9 | import org.bukkit.World; 10 | import org.bukkit.util.Vector; 11 | 12 | public class CustomPortalCreator { 13 | 14 | public static CustomPortal createPortal(World world, Cuboid portalFrame) throws MessageException { 15 | 16 | int widthX = portalFrame.getWidthX(); 17 | int widthZ = portalFrame.getWidthZ(); 18 | 19 | if (widthX > 1 && widthZ > 1) { 20 | throw new MessageException(Message.SELECTION_NOT_FLAT); 21 | } 22 | 23 | Axis axis = widthX == 1 ? Axis.Z : Axis.X; 24 | BlockVec frameExtent = new BlockVec(axis.getCrossNormal()).setY(1); 25 | 26 | Cuboid portalInner = portalFrame.clone() 27 | .translateMin(frameExtent) 28 | .translateMax(frameExtent.multiply(-1)); 29 | 30 | Vector rectMin = portalInner.getMin().toVector(); 31 | Vector rectMax = portalInner.getMax().toVector().subtract(axis.getNormal()); 32 | AxisAlignedRect portalRect; 33 | 34 | try { 35 | portalRect = new AxisAlignedRect(axis, rectMin, rectMax); 36 | portalRect.translate(axis.getNormal().multiply(0.5)); 37 | 38 | } catch (IllegalArgumentException e) { 39 | throw new MessageException(Message.SELECTION_TOO_SMALL); 40 | } 41 | 42 | if (portalRect.width() < 1 || portalRect.height() < 1) { 43 | throw new MessageException(Message.SELECTION_TOO_SMALL); 44 | } else if (portalRect.width() > 20 || portalRect.height() > 20) { 45 | throw new MessageException(Message.PORTAL_TOO_BIG, "20"); 46 | } 47 | 48 | return new CustomPortal(world, portalRect, portalFrame, portalInner); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/CustomPortalHandler.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal; 2 | 3 | 4 | import me.gorgeousone.netherview.geometry.BlockVec; 5 | import org.bukkit.Location; 6 | import org.bukkit.World; 7 | 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.Map; 11 | import java.util.Set; 12 | import java.util.UUID; 13 | 14 | public class CustomPortalHandler { 15 | 16 | private final Map customPortals; 17 | private final Map> worldsWithCustomPortals; 18 | 19 | public CustomPortalHandler() { 20 | this.worldsWithCustomPortals = new HashMap<>(); 21 | this.customPortals = new HashMap<>(); 22 | } 23 | 24 | public void reload() { 25 | 26 | customPortals.clear(); 27 | worldsWithCustomPortals.clear(); 28 | } 29 | 30 | public CustomPortal getPortal(String portalName) { 31 | return customPortals.get(portalName); 32 | } 33 | 34 | public void addPortal(CustomPortal portal) { 35 | 36 | if (customPortals.containsKey(portal.getName())) { 37 | throw new IllegalArgumentException("Custom portal with this name already exists."); 38 | } 39 | 40 | customPortals.put(portal.getName(), portal); 41 | 42 | UUID worldId = portal.getWorld().getUID(); 43 | worldsWithCustomPortals.computeIfAbsent(worldId, set -> new HashSet<>()); 44 | worldsWithCustomPortals.get(worldId).add(portal); 45 | } 46 | 47 | public void removePortal(CustomPortal portal) { 48 | 49 | customPortals.remove(portal.getName()); 50 | worldsWithCustomPortals.get(portal.getWorld().getUID()).remove(portal); 51 | } 52 | 53 | public Set getPortalNames() { 54 | return customPortals.keySet(); 55 | } 56 | 57 | public Set getCustomPortals(World world) { 58 | return worldsWithCustomPortals.getOrDefault(world.getUID(), new HashSet<>()); 59 | } 60 | 61 | public CustomPortal getPortalAt(Location location) { 62 | 63 | for (CustomPortal portal : getCustomPortals(location.getWorld())) { 64 | 65 | if (portal.getInner().contains(new BlockVec(location))) { 66 | return portal; 67 | } 68 | } 69 | 70 | return null; 71 | } 72 | 73 | public boolean isValidName(String portalName) { 74 | return portalName.matches("^(?=.{1,32}$)[a-z0-9_-]+"); 75 | } 76 | 77 | public boolean isUniqueName(String portalName) { 78 | return !customPortals.containsKey(portalName); 79 | } 80 | 81 | public String createGenericPortalName() { 82 | 83 | for (int i = 1; i <= 10000; ++i) { 84 | 85 | String genericName = "portal" + i; 86 | 87 | if (isUniqueName(genericName)) { 88 | return genericName; 89 | } 90 | } 91 | 92 | return "there is no way you created over 10,000 custom portals"; 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/CustomPortalSerializer.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.geometry.BlockVec; 5 | import me.gorgeousone.netherview.geometry.Cuboid; 6 | import me.gorgeousone.netherview.handlers.PortalHandler; 7 | import me.gorgeousone.netherview.message.MessageException; 8 | import me.gorgeousone.netherview.portal.Portal; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.World; 11 | import org.bukkit.configuration.ConfigurationSection; 12 | import org.bukkit.configuration.file.FileConfiguration; 13 | import org.bukkit.plugin.java.JavaPlugin; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.Set; 18 | import java.util.UUID; 19 | 20 | public class CustomPortalSerializer { 21 | 22 | private final JavaPlugin plugin; 23 | private final ConfigSettings configSettings; 24 | private final PortalHandler portalHandler; 25 | private final CustomPortalHandler customPortalHandler; 26 | 27 | public CustomPortalSerializer(JavaPlugin plugin, 28 | ConfigSettings configSettings, 29 | PortalHandler portalHandler, 30 | CustomPortalHandler customPortalHandler) { 31 | 32 | this.configSettings = configSettings; 33 | this.portalHandler = portalHandler; 34 | this.plugin = plugin; 35 | this.customPortalHandler = customPortalHandler; 36 | } 37 | 38 | public void savePortals(FileConfiguration customPortalConfig) { 39 | 40 | customPortalConfig.set("plugin-version", plugin.getDescription().getVersion()); 41 | customPortalConfig.set("custom-portals", null); 42 | 43 | ConfigurationSection portalSection = customPortalConfig.createSection("custom-portals"); 44 | 45 | for (World world : Bukkit.getWorlds()) { 46 | 47 | if (!portalHandler.hasPortals(world)) { 48 | continue; 49 | } 50 | 51 | Set portalsInWorld = portalHandler.getPortals(world); 52 | String worldId = world.getUID().toString(); 53 | ConfigurationSection worldSection = null; 54 | 55 | for (Portal portal : portalsInWorld) { 56 | 57 | if (!(portal instanceof CustomPortal)) { 58 | continue; 59 | } 60 | 61 | if (worldSection == null) { 62 | worldSection = portalSection.createSection(worldId); 63 | } 64 | 65 | CustomPortal customPortal = (CustomPortal) portal; 66 | String portalName = customPortal.getName(); 67 | Cuboid portalFrame = customPortal.getFrame(); 68 | ConfigurationSection portalData = worldSection.createSection(portalName); 69 | 70 | portalData.set("min", portalFrame.getMin().serialize()); 71 | portalData.set("max", portalFrame.getMax().serialize()); 72 | portalData.set("is-flipped", portal.isViewFlipped()); 73 | 74 | if (customPortal.isLinked()) { 75 | portalData.set("link", ((CustomPortal) customPortal.getCounterPortal()).getName()); 76 | } 77 | } 78 | } 79 | } 80 | 81 | public void loadPortals(FileConfiguration customPortalConfig) { 82 | 83 | if (!customPortalConfig.contains("custom-portals")) { 84 | return; 85 | } 86 | 87 | ConfigurationSection portalsSection = customPortalConfig.getConfigurationSection("custom-portals"); 88 | Map portalLinks = new HashMap<>(); 89 | 90 | for (String worldId : portalsSection.getKeys(false)) { 91 | 92 | World world = Bukkit.getWorld(UUID.fromString(worldId)); 93 | 94 | if (world == null) { 95 | plugin.getLogger().warning("Could not find world with ID: '" + worldId + "'. Portals saved for this world will not be loaded."); 96 | continue; 97 | } 98 | 99 | if (configSettings.canCreateCustomPortals(world)) { 100 | 101 | ConfigurationSection worldSection = portalsSection.getConfigurationSection(worldId); 102 | 103 | for (String portalName : worldSection.getKeys(false)) { 104 | deserializeCustomPortal(world, portalName, worldSection.getConfigurationSection(portalName), portalLinks); 105 | } 106 | } 107 | } 108 | 109 | linkCustomPortals(portalLinks); 110 | } 111 | 112 | private void deserializeCustomPortal(World world, 113 | String portalName, 114 | ConfigurationSection portalData, 115 | Map portalLinks) { 116 | 117 | try { 118 | BlockVec portalMin = BlockVec.fromString(portalData.getString("min")); 119 | BlockVec portalMax = BlockVec.fromString(portalData.getString("max")); 120 | 121 | Cuboid portalFrame = new Cuboid(portalMin, portalMax); 122 | CustomPortal portal = CustomPortalCreator.createPortal(world, portalFrame); 123 | 124 | portal.setName(portalName); 125 | portal.setViewFlipped(portalData.getBoolean("is-flipped")); 126 | 127 | customPortalHandler.addPortal(portal); 128 | portalHandler.addPortal(portal); 129 | 130 | if (portalData.contains("link")) { 131 | portalLinks.put(portalName, portalData.getString("link")); 132 | } 133 | 134 | } catch (IllegalArgumentException | IllegalStateException | MessageException e) { 135 | plugin.getLogger().warning("Unable to load custom portal at [" + world.getName() + "," + portalName + "]: " + e.getMessage()); 136 | } 137 | } 138 | 139 | private void linkCustomPortals(Map portalLinks) { 140 | 141 | for (Map.Entry entry : portalLinks.entrySet()) { 142 | 143 | String fromName = entry.getKey(); 144 | String toName = entry.getValue(); 145 | 146 | CustomPortal fromPortal = customPortalHandler.getPortal(entry.getKey()); 147 | CustomPortal toPortal = customPortalHandler.getPortal(entry.getValue()); 148 | 149 | if (toPortal == null) { 150 | plugin.getLogger().warning("Could not find custom portal with name'" + toName + "' for linking with portal '" + fromName + "'."); 151 | continue; 152 | } 153 | 154 | try { 155 | portalHandler.linkPortalTo(fromPortal, toPortal, null); 156 | 157 | } catch (MessageException e) { 158 | plugin.getLogger().warning("Unable to link custom portal '" + fromName + "' to portal '" + toName + "': " + e.getMessage()); 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/PlayerClickListener.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.NetherViewPlugin; 5 | import me.gorgeousone.netherview.geometry.BlockVec; 6 | import me.gorgeousone.netherview.message.Message; 7 | import me.gorgeousone.netherview.message.MessageUtils; 8 | import org.bukkit.GameMode; 9 | import org.bukkit.Material; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.event.EventHandler; 12 | import org.bukkit.event.Listener; 13 | import org.bukkit.event.block.Action; 14 | import org.bukkit.event.player.PlayerInteractEvent; 15 | import org.bukkit.inventory.EquipmentSlot; 16 | import org.bukkit.inventory.ItemStack; 17 | 18 | public class PlayerClickListener implements Listener { 19 | 20 | private final PlayerSelectionHandler selectionHandler; 21 | private final ConfigSettings configSettings; 22 | 23 | public PlayerClickListener(PlayerSelectionHandler selectionHandler, 24 | ConfigSettings configSettings) { 25 | this.selectionHandler = selectionHandler; 26 | this.configSettings = configSettings; 27 | } 28 | 29 | @EventHandler 30 | public void onPlayerClick(PlayerInteractEvent event) { 31 | 32 | if (!configSettings.canCreateCustomPortals(event.getPlayer().getWorld())) { 33 | return; 34 | } 35 | 36 | Action action = event.getAction(); 37 | 38 | if (action != Action.LEFT_CLICK_BLOCK && action != Action.RIGHT_CLICK_BLOCK || isOffHandClick(event)) { 39 | return; 40 | } 41 | 42 | Player player = event.getPlayer(); 43 | 44 | if (player.getGameMode() != GameMode.CREATIVE || !player.hasPermission(NetherViewPlugin.CUSTOM_PORTAL_PERM)) { 45 | return; 46 | } 47 | 48 | ItemStack itemInHand = getHandItem(player); 49 | 50 | if (itemInHand == null || itemInHand.getType() != Material.BLAZE_ROD) { 51 | return; 52 | } 53 | 54 | event.setCancelled(true); 55 | BlockVec clickedPos = new BlockVec(event.getClickedBlock()); 56 | PlayerCuboidSelection selection = selectionHandler.getOrCreateCuboidSelection(player); 57 | 58 | if (action == Action.LEFT_CLICK_BLOCK) { 59 | 60 | if (clickedPos.equals(selection.getPos1())) { 61 | return; 62 | } 63 | 64 | selection.setPos1(clickedPos); 65 | MessageUtils.sendInfo(player, Message.SET_FIRST_CUBOID_POSITION, clickedPos.toString()); 66 | 67 | } else { 68 | 69 | if (clickedPos.equals(selection.getPos2())) { 70 | return; 71 | } 72 | 73 | selection.setPos2(clickedPos); 74 | MessageUtils.sendInfo(player, Message.SET_SECOND_CUBOID_POSITION, clickedPos.toString()); 75 | } 76 | } 77 | 78 | private boolean isOffHandClick(PlayerInteractEvent event) { 79 | 80 | try { 81 | return event.getHand() == EquipmentSlot.OFF_HAND; 82 | } catch (NoSuchMethodError e) { 83 | return false; 84 | } 85 | } 86 | 87 | private ItemStack getHandItem(Player player) { 88 | 89 | try { 90 | return player.getInventory().getItemInMainHand(); 91 | } catch (NoSuchMethodError e) { 92 | return player.getItemInHand(); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/PlayerCuboidSelection.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal; 2 | 3 | import me.gorgeousone.netherview.geometry.BlockVec; 4 | import me.gorgeousone.netherview.geometry.Cuboid; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.World; 7 | import org.bukkit.entity.Player; 8 | 9 | import java.util.UUID; 10 | 11 | public class PlayerCuboidSelection { 12 | 13 | private final UUID playerId; 14 | private final World world; 15 | 16 | private BlockVec pos1; 17 | private BlockVec pos2; 18 | 19 | public PlayerCuboidSelection(Player player) { 20 | 21 | this.playerId = player.getUniqueId(); 22 | this.world = player.getWorld(); 23 | } 24 | 25 | public Player getPlayer() { 26 | return Bukkit.getPlayer(playerId); 27 | } 28 | 29 | public World getWorld() { 30 | return world; 31 | } 32 | 33 | public BlockVec getPos1() { 34 | return pos1; 35 | } 36 | 37 | public void setPos1(BlockVec pos1) { 38 | this.pos1 = pos1; 39 | } 40 | 41 | public BlockVec getPos2() { 42 | return pos2; 43 | } 44 | 45 | public void setPos2(BlockVec pos2) { 46 | this.pos2 = pos2; 47 | } 48 | 49 | public boolean bothPositionsAreSet() { 50 | return pos1 != null && pos2 != null; 51 | } 52 | 53 | public Cuboid getCuboid() { 54 | return new Cuboid(pos1, pos2).translateMax(1, 1, 1); 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/PlayerSelectionHandler.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.UUID; 8 | 9 | public class PlayerSelectionHandler { 10 | 11 | private final Map cuboidSelections; 12 | 13 | public PlayerSelectionHandler() { 14 | this.cuboidSelections = new HashMap<>(); 15 | } 16 | 17 | public boolean hasCuboidSelection(Player player) { 18 | return cuboidSelections.containsKey(player.getUniqueId()); 19 | } 20 | 21 | public PlayerCuboidSelection getSelection(Player player) { 22 | return cuboidSelections.get(player.getUniqueId()); 23 | } 24 | 25 | public PlayerCuboidSelection getOrCreateCuboidSelection(Player player) { 26 | 27 | UUID playerId = player.getUniqueId(); 28 | PlayerCuboidSelection selection = cuboidSelections.get(playerId); 29 | 30 | if (selection == null || selection.getWorld() != player.getWorld()) { 31 | selection = new PlayerCuboidSelection(player); 32 | cuboidSelections.put(playerId, selection); 33 | } 34 | 35 | return selection; 36 | } 37 | 38 | public void removeSelection(Player player) { 39 | cuboidSelections.remove(player.getUniqueId()); 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/commands/CreatePortalCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal.commands; 2 | 3 | import me.gorgeousone.netherview.NetherViewPlugin; 4 | import me.gorgeousone.netherview.cmdframework.argument.ArgType; 5 | import me.gorgeousone.netherview.cmdframework.argument.ArgValue; 6 | import me.gorgeousone.netherview.cmdframework.argument.Argument; 7 | import me.gorgeousone.netherview.cmdframework.command.ArgCommand; 8 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 9 | import me.gorgeousone.netherview.customportal.CustomPortal; 10 | import me.gorgeousone.netherview.customportal.CustomPortalCreator; 11 | import me.gorgeousone.netherview.customportal.CustomPortalHandler; 12 | import me.gorgeousone.netherview.customportal.PlayerCuboidSelection; 13 | import me.gorgeousone.netherview.customportal.PlayerSelectionHandler; 14 | import me.gorgeousone.netherview.handlers.PortalHandler; 15 | import me.gorgeousone.netherview.message.Message; 16 | import me.gorgeousone.netherview.message.MessageException; 17 | import me.gorgeousone.netherview.message.MessageUtils; 18 | import org.bukkit.command.CommandSender; 19 | import org.bukkit.entity.Player; 20 | 21 | import java.util.Locale; 22 | 23 | public class CreatePortalCommand extends ArgCommand { 24 | 25 | private final static String genericNamePlaceHolder = "auto_inc"; 26 | 27 | private final PlayerSelectionHandler selectionHandler; 28 | private final PortalHandler portalHandler; 29 | private final CustomPortalHandler customPortalHandler; 30 | 31 | public CreatePortalCommand(ParentCommand parent, 32 | PlayerSelectionHandler selectionHandler, 33 | PortalHandler portalHandler, 34 | CustomPortalHandler customPortalHandler) { 35 | 36 | super("createportal", NetherViewPlugin.CUSTOM_PORTAL_PERM, true, parent); 37 | addArg(new Argument("portal name", ArgType.STRING).setDefaultTo(genericNamePlaceHolder)); 38 | 39 | this.selectionHandler = selectionHandler; 40 | this.portalHandler = portalHandler; 41 | this.customPortalHandler = customPortalHandler; 42 | } 43 | 44 | @Override 45 | protected void onCommand(CommandSender sender, ArgValue[] arguments) { 46 | 47 | Player player = (Player) sender; 48 | 49 | if (!selectionHandler.hasCuboidSelection(player)) { 50 | MessageUtils.sendInfo(player, Message.SELECTION_INCOMPLETE); 51 | return; 52 | } 53 | 54 | PlayerCuboidSelection selection = selectionHandler.getSelection(player); 55 | 56 | if (!selection.bothPositionsAreSet()) { 57 | MessageUtils.sendInfo(player, Message.SELECTION_INCOMPLETE); 58 | return; 59 | } 60 | 61 | CustomPortal portal; 62 | 63 | try { 64 | portal = CustomPortalCreator.createPortal(player.getWorld(), selection.getCuboid()); 65 | 66 | } catch (MessageException e) { 67 | MessageUtils.sendInfo(player, e.getPlayerMessage(), e.getPlaceholderValues()); 68 | return; 69 | } 70 | 71 | if (portalHandler.portalIntersectsOtherPortals(portal)) { 72 | MessageUtils.sendInfo(player, Message.PORTALS_INTERSECT); 73 | return; 74 | } 75 | 76 | String portalName = arguments[0].getString().toLowerCase(Locale.ENGLISH); 77 | 78 | if (portalName.equals(genericNamePlaceHolder)) { 79 | portalName = customPortalHandler.createGenericPortalName(); 80 | 81 | } else if (!customPortalHandler.isValidName(portalName)) { 82 | MessageUtils.sendInfo(player, Message.PORTAL_NAME_NOT_VALID); 83 | return; 84 | 85 | } else if (!customPortalHandler.isUniqueName(portalName)) { 86 | MessageUtils.sendInfo(player, Message.PORTAL_NAME_NOT_UNIQUE, portalName); 87 | return; 88 | } 89 | 90 | portal.setName(portalName); 91 | portalHandler.addPortal(portal); 92 | customPortalHandler.addPortal(portal); 93 | MessageUtils.sendInfo(player, Message.CREATED_PORTAL, portalName, portal.width() + "x" + portal.height()); 94 | } 95 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/commands/DeletePortalCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal.commands; 2 | 3 | import me.gorgeousone.netherview.NetherViewPlugin; 4 | import me.gorgeousone.netherview.cmdframework.argument.ArgType; 5 | import me.gorgeousone.netherview.cmdframework.argument.ArgValue; 6 | import me.gorgeousone.netherview.cmdframework.argument.Argument; 7 | import me.gorgeousone.netherview.cmdframework.command.ArgCommand; 8 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 9 | import me.gorgeousone.netherview.customportal.CustomPortal; 10 | import me.gorgeousone.netherview.customportal.CustomPortalHandler; 11 | import me.gorgeousone.netherview.handlers.PortalHandler; 12 | import me.gorgeousone.netherview.message.Message; 13 | import me.gorgeousone.netherview.message.MessageUtils; 14 | import org.bukkit.command.CommandSender; 15 | 16 | import java.util.List; 17 | import java.util.stream.Collectors; 18 | 19 | public class DeletePortalCommand extends ArgCommand { 20 | 21 | private final PortalHandler portalHandler; 22 | private final CustomPortalHandler customPortalHandler; 23 | 24 | public DeletePortalCommand(ParentCommand parent, 25 | PortalHandler portalHandler, 26 | CustomPortalHandler customPortalHandler) { 27 | 28 | super("deleteportal", NetherViewPlugin.CUSTOM_PORTAL_PERM, false, parent); 29 | addArg(new Argument("portal name", ArgType.STRING)); 30 | 31 | this.portalHandler = portalHandler; 32 | this.customPortalHandler = customPortalHandler; 33 | } 34 | 35 | @Override 36 | protected void onCommand(CommandSender sender, ArgValue[] arguments) { 37 | 38 | String portalName = arguments[0].getString(); 39 | CustomPortal portal = customPortalHandler.getPortal(portalName); 40 | 41 | if (portal == null) { 42 | MessageUtils.sendInfo(sender, Message.NO_PORTAL_FOUND_WITH_NAME, portalName); 43 | return; 44 | } 45 | 46 | customPortalHandler.removePortal(portal); 47 | portalHandler.removePortal(portal); 48 | MessageUtils.sendInfo(sender, Message.REMOVED_PORTAL, portalName); 49 | } 50 | 51 | @Override 52 | public List getTabList(CommandSender sender, String[] arguments) { 53 | 54 | if (arguments.length <= 1) { 55 | 56 | return customPortalHandler.getPortalNames().stream(). 57 | filter(name -> name.startsWith(arguments[arguments.length - 1])). 58 | collect(Collectors.toList()); 59 | } 60 | 61 | return super.getTabList(sender, arguments); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/commands/GetWandCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal.commands; 2 | 3 | import me.gorgeousone.netherview.NetherViewPlugin; 4 | import me.gorgeousone.netherview.cmdframework.command.BasicCommand; 5 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 6 | import me.gorgeousone.netherview.message.Message; 7 | import me.gorgeousone.netherview.message.MessageUtils; 8 | import org.bukkit.Material; 9 | import org.bukkit.command.CommandSender; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.inventory.ItemStack; 12 | 13 | public class GetWandCommand extends BasicCommand { 14 | 15 | public GetWandCommand(ParentCommand parent) { 16 | super("wand", NetherViewPlugin.PORTAL_WAND_PERM, true, parent); 17 | } 18 | 19 | @Override 20 | protected void onCommand(CommandSender sender, String[] arguments) { 21 | 22 | Player player = (Player) sender; 23 | player.getInventory().addItem(new ItemStack(Material.BLAZE_ROD)); 24 | MessageUtils.sendInfo(player, Message.WAND_INFO); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/commands/LinkPortalCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal.commands; 2 | 3 | import me.gorgeousone.netherview.NetherViewPlugin; 4 | import me.gorgeousone.netherview.cmdframework.argument.ArgType; 5 | import me.gorgeousone.netherview.cmdframework.argument.ArgValue; 6 | import me.gorgeousone.netherview.cmdframework.argument.Argument; 7 | import me.gorgeousone.netherview.cmdframework.command.ArgCommand; 8 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 9 | import me.gorgeousone.netherview.customportal.CustomPortalHandler; 10 | import me.gorgeousone.netherview.handlers.PortalHandler; 11 | import me.gorgeousone.netherview.handlers.ViewHandler; 12 | import me.gorgeousone.netherview.message.Message; 13 | import me.gorgeousone.netherview.message.MessageException; 14 | import me.gorgeousone.netherview.message.MessageUtils; 15 | import me.gorgeousone.netherview.portal.Portal; 16 | import org.bukkit.command.CommandSender; 17 | import org.bukkit.entity.Player; 18 | 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | public class LinkPortalCommand extends ArgCommand { 23 | 24 | private final ViewHandler viewHandler; 25 | private final PortalHandler portalHandler; 26 | private final CustomPortalHandler customPortalHandler; 27 | 28 | public LinkPortalCommand(ParentCommand parent, 29 | ViewHandler viewHandler, PortalHandler portalHandler, 30 | CustomPortalHandler customPortalHandler) { 31 | 32 | super("link", NetherViewPlugin.CUSTOM_PORTAL_PERM, true, parent); 33 | this.viewHandler = viewHandler; 34 | addArg(new Argument("from portal", ArgType.STRING)); 35 | addArg(new Argument("to portal", ArgType.STRING)); 36 | 37 | this.portalHandler = portalHandler; 38 | this.customPortalHandler = customPortalHandler; 39 | } 40 | 41 | @Override 42 | protected void onCommand(CommandSender sender, ArgValue[] arguments) { 43 | 44 | String portalName1 = arguments[0].getString(); 45 | String portalName2 = arguments[1].getString(); 46 | 47 | Portal portal1 = customPortalHandler.getPortal(portalName1); 48 | Portal portal2 = customPortalHandler.getPortal(portalName2); 49 | 50 | if (portal1 == null) { 51 | MessageUtils.sendInfo(sender, Message.NO_PORTAL_FOUND_WITH_NAME, portalName1); 52 | return; 53 | } 54 | 55 | if (portal2 == null) { 56 | MessageUtils.sendInfo(sender, Message.NO_PORTAL_FOUND_WITH_NAME, portalName2); 57 | return; 58 | } 59 | 60 | Player player = (Player) sender; 61 | 62 | try { 63 | viewHandler.removePortal(portal1); 64 | portalHandler.linkPortalTo(portal1, portal2, player); 65 | viewHandler.displayClosestPortalTo(player, player.getEyeLocation()); 66 | MessageUtils.sendInfo(sender, Message.LINKED_PORTAL, portalName1, portalName2); 67 | 68 | } catch (MessageException e) { 69 | MessageUtils.sendInfo(sender, e.getPlayerMessage(), e.getPlaceholderValues()); 70 | } 71 | } 72 | 73 | @Override 74 | public List getTabList(CommandSender sender, String[] arguments) { 75 | 76 | if (arguments.length <= 2) { 77 | 78 | return customPortalHandler.getPortalNames().stream(). 79 | filter(name -> name.startsWith(arguments[arguments.length - 1])). 80 | collect(Collectors.toList()); 81 | } 82 | 83 | return super.getTabList(sender, arguments); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/customportal/commands/UnlinkPortalCommand.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.customportal.commands; 2 | 3 | import me.gorgeousone.netherview.NetherViewPlugin; 4 | import me.gorgeousone.netherview.cmdframework.argument.ArgType; 5 | import me.gorgeousone.netherview.cmdframework.argument.ArgValue; 6 | import me.gorgeousone.netherview.cmdframework.argument.Argument; 7 | import me.gorgeousone.netherview.cmdframework.command.ArgCommand; 8 | import me.gorgeousone.netherview.cmdframework.command.ParentCommand; 9 | import me.gorgeousone.netherview.customportal.CustomPortalHandler; 10 | import me.gorgeousone.netherview.handlers.ViewHandler; 11 | import me.gorgeousone.netherview.message.Message; 12 | import me.gorgeousone.netherview.message.MessageUtils; 13 | import me.gorgeousone.netherview.portal.Portal; 14 | import org.bukkit.command.CommandSender; 15 | 16 | import java.util.List; 17 | import java.util.stream.Collectors; 18 | 19 | public class UnlinkPortalCommand extends ArgCommand { 20 | 21 | private final ViewHandler viewHandler; 22 | private final CustomPortalHandler customPortalHandler; 23 | 24 | public UnlinkPortalCommand(ParentCommand parent, 25 | ViewHandler viewHandler, 26 | CustomPortalHandler customPortalHandler) { 27 | 28 | super("unlink", NetherViewPlugin.CUSTOM_PORTAL_PERM, true, parent); 29 | this.viewHandler = viewHandler; 30 | addArg(new Argument("portal", ArgType.STRING)); 31 | 32 | this.customPortalHandler = customPortalHandler; 33 | } 34 | 35 | @Override 36 | protected void onCommand(CommandSender sender, ArgValue[] arguments) { 37 | 38 | String portalName = arguments[0].getString(); 39 | 40 | Portal portal = customPortalHandler.getPortal(portalName); 41 | 42 | if (portal == null) { 43 | MessageUtils.sendInfo(sender, Message.NO_PORTAL_FOUND_WITH_NAME, portalName); 44 | return; 45 | } 46 | 47 | if (!portal.isLinked()) { 48 | return; 49 | } 50 | 51 | viewHandler.removePortal(portal); 52 | portal.removeLink(); 53 | MessageUtils.sendInfo(sender, Message.UNLINKED_PORTAL, portalName); 54 | } 55 | 56 | @Override 57 | public List getTabList(CommandSender sender, String[] arguments) { 58 | 59 | if (arguments.length <= 1) { 60 | 61 | return customPortalHandler.getPortalNames().stream(). 62 | filter(name -> name.startsWith(arguments[arguments.length - 1])). 63 | collect(Collectors.toList()); 64 | } 65 | 66 | return super.getTabList(sender, arguments); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/event/PortalLinkEvent.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.event; 2 | 3 | import me.gorgeousone.netherview.portal.Portal; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.Cancellable; 6 | import org.bukkit.event.Event; 7 | import org.bukkit.event.HandlerList; 8 | 9 | public class PortalLinkEvent extends Event implements Cancellable { 10 | 11 | private static final HandlerList HANDLER_LIST = new HandlerList(); 12 | private boolean isCancelled; 13 | 14 | private final Player player; 15 | private final Portal fromPortal; 16 | private final Portal toPortal; 17 | 18 | public PortalLinkEvent(Portal fromPortal, Portal toPortal, Player player) { 19 | 20 | this.player = player; 21 | this.fromPortal = fromPortal; 22 | this.toPortal = toPortal; 23 | } 24 | 25 | public Player getPlayer() { 26 | return player; 27 | } 28 | 29 | public Portal getFromPortal() { 30 | return fromPortal; 31 | } 32 | 33 | public Portal getToPortal() { 34 | return toPortal; 35 | } 36 | 37 | @Override 38 | public boolean isCancelled() { 39 | return isCancelled; 40 | } 41 | 42 | @Override 43 | public void setCancelled(boolean isCancelled) { 44 | this.isCancelled = isCancelled; 45 | } 46 | 47 | @Override 48 | public HandlerList getHandlers() { 49 | return HANDLER_LIST; 50 | } 51 | 52 | public static HandlerList getHandlerList() { 53 | return HANDLER_LIST; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/event/PortalUnlinkEvent.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.event; 2 | 3 | import me.gorgeousone.netherview.portal.Portal; 4 | import org.bukkit.event.Event; 5 | import org.bukkit.event.HandlerList; 6 | 7 | public class PortalUnlinkEvent extends Event { 8 | 9 | private static final HandlerList HANDLER_LIST = new HandlerList(); 10 | 11 | private final Portal fromPortal; 12 | private final Portal toPortal; 13 | private final UnlinkReason reason; 14 | 15 | public PortalUnlinkEvent(Portal portal, Portal linkedPortal, UnlinkReason reason) { 16 | 17 | this.fromPortal = portal; 18 | this.toPortal = linkedPortal; 19 | this.reason = reason; 20 | } 21 | 22 | public Portal getFromPortal() { 23 | return fromPortal; 24 | } 25 | 26 | public Portal getToPortal() { 27 | return toPortal; 28 | } 29 | 30 | public UnlinkReason getReason() { 31 | return reason; 32 | } 33 | 34 | @Override 35 | public HandlerList getHandlers() { 36 | return HANDLER_LIST; 37 | } 38 | 39 | public static HandlerList getHandlerList() { 40 | return HANDLER_LIST; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/event/UnlinkReason.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.event; 2 | 3 | public enum UnlinkReason { 4 | PORTAL_DESTROYED, LINKED_PORTAL_DESTROYED, SWITCHED_TARGET_PORTAL 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/geometry/AxisAlignedRect.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.geometry; 2 | 3 | import me.gorgeousone.netherview.wrapper.Axis; 4 | import org.bukkit.util.Vector; 5 | 6 | /** 7 | * A rectangle used to describe the size of a nether portal (the part without the frame) or used as near plane in a view frustum. 8 | */ 9 | public class AxisAlignedRect { 10 | 11 | private final Axis axis; 12 | private final Plane plane; 13 | 14 | private final Vector min; 15 | private final Vector max; 16 | 17 | public AxisAlignedRect(Axis axis, Vector min, Vector max) { 18 | 19 | this.axis = axis; 20 | this.min = min.clone(); 21 | this.max = max.clone(); 22 | 23 | if (axis == Axis.X) { 24 | plane = new Plane(min, new Vector(0, 0, 1)); 25 | 26 | if (min.getZ() != max.getZ()) { 27 | throw new IllegalArgumentException("Z coordinates of x aligned portal must be equal"); 28 | } 29 | } else { 30 | plane = new Plane(min, new Vector(1, 0, 0)); 31 | 32 | if (min.getX() != max.getX()) { 33 | throw new IllegalArgumentException("Z coordinates of x aligned portal must be equal"); 34 | } 35 | } 36 | 37 | if (height() < 0 || width() < 0) { 38 | throw new IllegalArgumentException("Rectangle maximum coordinates must be greater than minimum coordinates"); 39 | } 40 | } 41 | 42 | public Axis getAxis() { 43 | return axis; 44 | } 45 | 46 | public Vector getMin() { 47 | return min.clone(); 48 | } 49 | 50 | public Vector getMax() { 51 | return max.clone(); 52 | } 53 | 54 | public double width() { 55 | return axis == Axis.X ? 56 | max.getX() - min.getX() : 57 | max.getZ() - min.getZ(); 58 | } 59 | 60 | public double height() { 61 | return max.getY() - min.getY(); 62 | } 63 | 64 | public AxisAlignedRect translate(Vector delta) { 65 | min.add(delta); 66 | max.add(delta); 67 | plane.translate(delta); 68 | return this; 69 | } 70 | 71 | public Plane getPlane() { 72 | return plane; 73 | } 74 | 75 | public boolean contains(Vector pointInPlane) { 76 | 77 | double pointY = pointInPlane.getY(); 78 | 79 | if (pointY < min.getY() || pointY > max.getY()) { 80 | return false; 81 | } 82 | 83 | if (axis == Axis.X) { 84 | double pointX = pointInPlane.getX(); 85 | return pointX >= min.getX() && pointX <= max.getX(); 86 | 87 | } else { 88 | double pointZ = pointInPlane.getZ(); 89 | return pointZ >= min.getZ() && pointZ <= max.getZ(); 90 | } 91 | } 92 | 93 | @Override 94 | public AxisAlignedRect clone() { 95 | return new AxisAlignedRect(getAxis(), getMin(), getMax()); 96 | } 97 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/geometry/BlockVec.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.geometry; 2 | 3 | import com.comphenix.protocol.wrappers.BlockPosition; 4 | import com.comphenix.protocol.wrappers.ChunkCoordIntPair; 5 | import org.bukkit.Location; 6 | import org.bukkit.World; 7 | import org.bukkit.block.Block; 8 | import org.bukkit.util.Vector; 9 | 10 | import java.util.Objects; 11 | 12 | /** 13 | * A simple 3D vector class with int coordinates only designed for storing block locations. 14 | */ 15 | public class BlockVec { 16 | 17 | private int x; 18 | private int y; 19 | private int z; 20 | 21 | public BlockVec() {} 22 | 23 | public BlockVec(Block block) { 24 | this(block.getX(), block.getY(), block.getZ()); 25 | } 26 | 27 | public BlockVec(Location location) { 28 | this(location.getBlockX(), location.getBlockY(), location.getBlockZ()); 29 | } 30 | 31 | public BlockVec(Vector vector) { 32 | this(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); 33 | } 34 | 35 | public BlockVec(BlockPosition blockPosition) { 36 | this(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); 37 | } 38 | 39 | public BlockVec(ChunkCoordIntPair chunkLocation) { 40 | this(chunkLocation.getChunkX() << 4, 0, chunkLocation.getChunkZ() << 4); 41 | } 42 | 43 | public BlockVec(short posInChunk) { 44 | x = posInChunk >>> 8 & 0xF; 45 | y = posInChunk & 0xF; 46 | z = posInChunk >>> 4 & 0xF; 47 | } 48 | 49 | public BlockVec(int x, int y, int z) { 50 | this.x = x; 51 | this.y = y; 52 | this.z = z; 53 | } 54 | 55 | public int getX() { 56 | return x; 57 | } 58 | 59 | public BlockVec setX(int x) { 60 | this.x = x; 61 | return this; 62 | } 63 | 64 | public int getZ() { 65 | return z; 66 | } 67 | 68 | public BlockVec setZ(int z) { 69 | this.z = z; 70 | return this; 71 | } 72 | 73 | public int getY() { 74 | return y; 75 | } 76 | 77 | public BlockVec setY(int y) { 78 | this.y = y; 79 | return this; 80 | } 81 | 82 | public BlockVec add(BlockVec other) { 83 | return add(other.x, other.y, other.z); 84 | } 85 | 86 | public BlockVec add(int dx, int dy, int dz) { 87 | x += dx; 88 | y += dy; 89 | z += dz; 90 | return this; 91 | } 92 | 93 | public BlockVec subtract(BlockVec other) { 94 | x -= other.getX(); 95 | y -= other.getY(); 96 | z -= other.getZ(); 97 | return this; 98 | } 99 | 100 | public BlockVec multiply(int multiplier) { 101 | x *= multiplier; 102 | y *= multiplier; 103 | z *= multiplier; 104 | return this; 105 | } 106 | 107 | public static BlockVec getMinimum(BlockVec v1, BlockVec v2) { 108 | return new BlockVec( 109 | Math.min(v1.x, v2.x), 110 | Math.min(v1.y, v2.y), 111 | Math.min(v1.z, v2.z)); 112 | } 113 | 114 | public static BlockVec getMaximum(BlockVec v1, BlockVec v2) { 115 | return new BlockVec( 116 | Math.max(v1.x, v2.x), 117 | Math.max(v1.y, v2.y), 118 | Math.max(v1.z, v2.z)); 119 | } 120 | 121 | public Vector toVector() { 122 | return new Vector(x, y, z); 123 | } 124 | 125 | public Location toLocation(World world) { 126 | return new Location(world, x, y, z); 127 | } 128 | 129 | public Block toBlock(World world) { 130 | return world.getBlockAt(x, y, z); 131 | } 132 | 133 | public BlockPosition toBlockPos() { 134 | return new BlockPosition(x, y, z); 135 | } 136 | 137 | @Override 138 | public BlockVec clone() { 139 | return new BlockVec(x, y, z); 140 | } 141 | 142 | @Override 143 | public boolean equals(Object o) { 144 | if (this == o) { 145 | return true; 146 | } 147 | if (!(o instanceof BlockVec)) { 148 | return false; 149 | } 150 | BlockVec otherVec = (BlockVec) o; 151 | return x == otherVec.x && 152 | y == otherVec.y && 153 | z == otherVec.z; 154 | } 155 | 156 | @Override 157 | public int hashCode() { 158 | return Objects.hash(x, y, z); 159 | } 160 | 161 | public static String toSimpleString(Location loc) { 162 | return "[" + loc.getWorld().getName() + ", " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + "]"; 163 | } 164 | 165 | @Override 166 | public String toString() { 167 | return "[" + x + ", " + y + ", " + z + "]"; 168 | } 169 | 170 | public String serialize() { 171 | return "x=" + x + ",y=" + y + ",z=" + z; 172 | } 173 | 174 | /** 175 | * Converts the vector into a short that represents it's position relative to the chunk it's in. 176 | * Used by the MultiBlockChangePacket introduced in 1.16.2 177 | */ 178 | public short toChunkShort() { 179 | 180 | return (short) ((x & 0xF) << 8 | 181 | (z & 0xF) << 4 | 182 | (y & 0xF)); 183 | } 184 | 185 | public static BlockVec fromString(String serialized) { 186 | 187 | if (serialized.length() < 11) { 188 | throw new IllegalArgumentException("Cannot deserialize BlockVec from string " + serialized + ": String is too short."); 189 | } 190 | 191 | String coordinateString = serialized; 192 | 193 | //So I decided to remove the square brackets from the BlockVec string in v1.2.1 194 | //but for migrating from an earlier plugin version I will have to leave this check in 195 | if (coordinateString.startsWith("[")) { 196 | coordinateString = coordinateString.substring(1, coordinateString.length() - 1); 197 | } 198 | 199 | String[] coordinates = coordinateString.split(","); 200 | 201 | if (coordinates.length != 3) { 202 | throw new IllegalArgumentException("Cannot deserialize BlockVec from string " + serialized + ": String contains too few coordinates."); 203 | } 204 | 205 | try { 206 | int x = Integer.parseInt(coordinates[0].substring(2)); 207 | int y = Integer.parseInt(coordinates[1].substring(2)); 208 | int z = Integer.parseInt(coordinates[2].substring(2)); 209 | return new BlockVec(x, y, z); 210 | 211 | } catch (NumberFormatException e) { 212 | throw new IllegalArgumentException("Cannot deserialize BlockVec from string " + serialized + ": " + e.getMessage()); 213 | } 214 | } 215 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/geometry/Cuboid.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.geometry; 2 | 3 | import org.bukkit.block.Block; 4 | 5 | public class Cuboid { 6 | 7 | private final BlockVec min; 8 | private final BlockVec max; 9 | 10 | public Cuboid(BlockVec pos1, BlockVec pos2) { 11 | 12 | this.min = BlockVec.getMinimum(pos1, pos2); 13 | this.max = BlockVec.getMaximum(pos1, pos2); 14 | } 15 | 16 | public BlockVec getMin() { 17 | return min.clone(); 18 | } 19 | 20 | public BlockVec getMax() { 21 | return max.clone(); 22 | } 23 | 24 | public int getWidthX() { 25 | return max.getX() - min.getX(); 26 | } 27 | 28 | public int getHeight() { 29 | return max.getY() - min.getY(); 30 | } 31 | 32 | public int getWidthZ() { 33 | return max.getZ() - min.getZ(); 34 | } 35 | 36 | public boolean contains(BlockVec vec) { 37 | return vec.getX() >= min.getX() && vec.getX() < max.getX() && 38 | vec.getY() >= min.getY() && vec.getY() < max.getY() && 39 | vec.getZ() >= min.getZ() && vec.getZ() < max.getZ(); 40 | } 41 | 42 | public boolean contains(Block block) { 43 | return block.getX() >= min.getX() && block.getX() < max.getX() && 44 | block.getY() >= min.getY() && block.getY() < max.getY() && 45 | block.getZ() >= min.getZ() && block.getZ() < max.getZ(); 46 | } 47 | 48 | public Cuboid translateMin(int dx, int dy, int dz) { 49 | min.add(dx, dy, dz); 50 | return this; 51 | } 52 | 53 | public Cuboid translateMin(BlockVec vec) { 54 | min.add(vec); 55 | return this; 56 | } 57 | 58 | public Cuboid translateMax(int dx, int dy, int dz) { 59 | max.add(dx, dy, dz); 60 | return this; 61 | } 62 | 63 | public Cuboid translateMax(BlockVec vec) { 64 | max.add(vec); 65 | return this; 66 | } 67 | 68 | public boolean intersects(Cuboid otherBox) { 69 | return intersectsX(otherBox) && intersectsY(otherBox) && intersectsZ(otherBox) || 70 | otherBox.intersectsX(this) && otherBox.intersectsY(this) && otherBox.intersectsZ(this); 71 | } 72 | 73 | public boolean intersectsX(Cuboid otherBox) { 74 | return containsX(otherBox.min.getX()) || containsX(otherBox.max.getX() - 1) || otherBox.min.getX() < min.getX() && otherBox.max.getX() > max.getX(); 75 | } 76 | 77 | public boolean intersectsY(Cuboid otherBox) { 78 | return containsY(otherBox.min.getY()) || containsY(otherBox.max.getY() - 1) || otherBox.min.getY() < min.getY() && otherBox.max.getY() > max.getY(); 79 | } 80 | 81 | public boolean intersectsZ(Cuboid otherBox) { 82 | return containsZ(otherBox.min.getZ()) || containsZ(otherBox.max.getZ() - 1) || otherBox.min.getZ() < min.getZ() && otherBox.max.getZ() > max.getZ(); 83 | } 84 | 85 | public boolean containsX(double x) { 86 | return x >= min.getX() && x < max.getX(); 87 | } 88 | 89 | public boolean containsY(double y) { 90 | return y >= min.getY() && y < max.getY(); 91 | } 92 | 93 | public boolean containsZ(double z) { 94 | return z >= min.getZ() && z <= max.getZ(); 95 | } 96 | 97 | @Override 98 | public Cuboid clone() { 99 | return new Cuboid(min, max); 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/geometry/Line.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.geometry; 2 | 3 | import org.bukkit.util.Vector; 4 | 5 | public class Line { 6 | 7 | private final Vector origin; 8 | private final Vector direction; 9 | 10 | public Line(Vector point1, Vector point2) { 11 | 12 | this.origin = point1.clone(); 13 | this.direction = point2.clone().subtract(point1); 14 | } 15 | 16 | public Vector getOrigin() { 17 | return origin.clone(); 18 | } 19 | 20 | public Vector getDirection() { 21 | return direction.clone(); 22 | } 23 | 24 | public Vector getPoint(double d) { 25 | return getOrigin().add(getDirection().multiply(d)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/geometry/Plane.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.geometry; 2 | 3 | import org.bukkit.util.Vector; 4 | 5 | public class Plane { 6 | 7 | private final Vector origin; 8 | private final Vector normal; 9 | 10 | public Plane(Vector origin, Vector normal) { 11 | 12 | this.origin = origin.clone(); 13 | this.normal = normal.clone().normalize(); 14 | 15 | if (normal.lengthSquared() == 0) { 16 | throw new IllegalArgumentException("normal cannot be 0"); 17 | } 18 | } 19 | 20 | public Vector getOrigin() { 21 | return origin.clone(); 22 | } 23 | 24 | public Vector getNormal() { 25 | return normal.clone(); 26 | } 27 | 28 | public boolean contains(Vector point) { 29 | 30 | if (point == null) { 31 | return false; 32 | } 33 | 34 | Vector relPoint = getOrigin().subtract(point); 35 | return Math.abs(getNormal().dot(relPoint)) < 0.0001; 36 | } 37 | 38 | public Vector getIntersection(Line line) { 39 | 40 | Vector normal = getNormal(); 41 | 42 | double d = getOrigin().subtract(line.getOrigin()).dot(normal) / line.getDirection().dot(normal); 43 | Vector intersection = line.getPoint(d); 44 | 45 | return contains(intersection) ? intersection : null; 46 | } 47 | 48 | public void translate(Vector delta) { 49 | origin.add(delta); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/geometry/viewfrustum/DefinedLine.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.geometry.viewfrustum; 2 | 3 | import me.gorgeousone.netherview.geometry.Line; 4 | import org.bukkit.util.Vector; 5 | 6 | /** 7 | * An euclidean line where only points between the given start and end can be accessed. 8 | */ 9 | public class DefinedLine extends Line { 10 | 11 | public DefinedLine(Vector start, Vector end) { 12 | super(start, end); 13 | } 14 | 15 | @Override 16 | public Vector getPoint(double d) { 17 | return d < 0 || d > 1 ? null : super.getPoint(d); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/geometry/viewfrustum/ViewFrustumFactory.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.geometry.viewfrustum; 2 | 3 | import me.gorgeousone.netherview.geometry.AxisAlignedRect; 4 | import me.gorgeousone.netherview.geometry.Line; 5 | import me.gorgeousone.netherview.geometry.Plane; 6 | import me.gorgeousone.netherview.portal.Portal; 7 | import me.gorgeousone.netherview.wrapper.Axis; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.util.Vector; 10 | 11 | public final class ViewFrustumFactory { 12 | 13 | private ViewFrustumFactory() {} 14 | 15 | /** 16 | * Returns a viewing frustum with a near plane precisely representing the area the player can see through the portal. 17 | */ 18 | public static ViewFrustum createFrustum(Vector viewPoint, AxisAlignedRect portalRect, int frustumLength) { 19 | 20 | boolean isPlayerBehindPortal = isPlayerBehindPortal(viewPoint, portalRect); 21 | 22 | Vector portalNormal = portalRect.getPlane().getNormal(); 23 | Vector playerFacingToPortal = portalNormal.multiply(isPlayerBehindPortal ? 1 : -1); 24 | 25 | //this will become near plane of the viewing frustum. It will be cropped to fit the actual player view through the portal 26 | AxisAlignedRect totalViewingRect = portalRect.clone().translate(playerFacingToPortal.clone().multiply(0.5)); 27 | Plane portalPlane = totalViewingRect.getPlane(); 28 | 29 | Vector viewingRectMin = totalViewingRect.getMin(); 30 | Vector viewingRectMax = totalViewingRect.getMax(); 31 | 32 | addTolerance(viewingRectMin, viewingRectMax, portalRect, 0.15); 33 | 34 | //depending on which portal frame blocks will block the view, the viewing rect bounds are contracted by casting rays along the block edges 35 | //here for the height of the rect... 36 | if (viewPoint.getY() < viewingRectMin.getY()) { 37 | 38 | Vector closeRectMin = viewingRectMin.clone().subtract(playerFacingToPortal); 39 | Vector newRectMin = portalPlane.getIntersection(new Line(viewPoint, closeRectMin)); 40 | viewingRectMin.setY(newRectMin.getY()); 41 | 42 | } else if (viewPoint.getY() > viewingRectMax.getY()) { 43 | 44 | Vector closeRectMax = viewingRectMax.clone().subtract(playerFacingToPortal); 45 | Vector newRectMax = portalPlane.getIntersection(new Line(viewPoint, closeRectMax)); 46 | viewingRectMax.setY(newRectMax.getY()); 47 | } 48 | 49 | Axis portalAxis = portalRect.getAxis(); 50 | 51 | //... also for the width 52 | if (portalAxis == Axis.X) { 53 | 54 | if (viewPoint.getX() < viewingRectMin.getX()) { 55 | 56 | Vector closeRectMin = viewingRectMin.clone().subtract(playerFacingToPortal); 57 | Vector newRectMin = portalPlane.getIntersection(new Line(viewPoint, closeRectMin)); 58 | viewingRectMin.setX(newRectMin.getX()); 59 | 60 | } else if (viewPoint.getX() > viewingRectMax.getX()) { 61 | 62 | Vector closeRectMax = viewingRectMax.clone().subtract(playerFacingToPortal); 63 | Vector newRectMax = portalPlane.getIntersection(new Line(viewPoint, closeRectMax)); 64 | viewingRectMax.setX(newRectMax.getX()); 65 | } 66 | 67 | } else { 68 | 69 | if (viewPoint.getZ() < viewingRectMin.getZ()) { 70 | 71 | Vector closeRectMin = viewingRectMin.clone().subtract(playerFacingToPortal); 72 | Vector newRectMin = portalPlane.getIntersection(new Line(viewPoint, closeRectMin)); 73 | viewingRectMin.setZ(newRectMin.getZ()); 74 | 75 | } else if (viewPoint.getZ() > viewingRectMax.getZ()) { 76 | 77 | Vector closeRectMax = viewingRectMax.clone().subtract(playerFacingToPortal); 78 | Vector newRectMax = portalPlane.getIntersection(new Line(viewPoint, closeRectMax)); 79 | viewingRectMax.setZ(newRectMax.getZ()); 80 | } 81 | } 82 | 83 | try { 84 | AxisAlignedRect actualViewingRect = new AxisAlignedRect(totalViewingRect.getAxis(), viewingRectMin, viewingRectMax); 85 | return new ViewFrustum(viewPoint, actualViewingRect, frustumLength); 86 | 87 | } catch (IllegalArgumentException e) { 88 | return null; 89 | } 90 | } 91 | 92 | public static boolean isPlayerBehindPortal(Player player, Portal portal) { 93 | return isPlayerBehindPortal(player.getEyeLocation().toVector(), portal.getPortalRect()); 94 | } 95 | 96 | public static boolean isPlayerBehindPortal(Vector viewPoint, AxisAlignedRect portalRect) { 97 | 98 | Vector portalPos = portalRect.getMin(); 99 | 100 | return portalRect.getAxis() == Axis.X ? 101 | viewPoint.getZ() < portalPos.getZ() : 102 | viewPoint.getX() < portalPos.getX(); 103 | } 104 | 105 | //widen the rectangle bounds a bit so the projection becomes smoother/more consistent when moving quickly 106 | //side effects are blocks slightly sticking out at the sides when standing further away 107 | private static void addTolerance(Vector rectMin, Vector rectMax, AxisAlignedRect portalRect, double tolerance) { 108 | 109 | Vector toleranceVec = portalRect.getAxis().getCrossNormal(); 110 | toleranceVec.setY(1); 111 | toleranceVec.multiply(tolerance); 112 | 113 | rectMin.subtract(toleranceVec); 114 | rectMax.add(toleranceVec); 115 | } 116 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/handlers/EntityVisibilityHandler.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.handlers; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.blockcache.BlockCache; 5 | import me.gorgeousone.netherview.blockcache.ProjectionCache; 6 | import me.gorgeousone.netherview.blockcache.Transform; 7 | import me.gorgeousone.netherview.message.MessageUtils; 8 | import me.gorgeousone.netherview.packet.PacketHandler; 9 | import me.gorgeousone.netherview.portal.ProjectionEntity; 10 | import org.bukkit.Location; 11 | import org.bukkit.entity.Entity; 12 | import org.bukkit.entity.Player; 13 | import org.bukkit.plugin.java.JavaPlugin; 14 | import org.bukkit.scheduler.BukkitRunnable; 15 | 16 | import java.util.Collection; 17 | import java.util.HashMap; 18 | import java.util.HashSet; 19 | import java.util.Map; 20 | import java.util.Set; 21 | 22 | /** 23 | * A class that runs a BukkitRunnable to check on entities nearby portals and their movements. 24 | * It will show/hide entities that walk in/out of areas viewed by players from portals. 25 | * It also creates movement animations for projected entities for players. 26 | */ 27 | public class EntityVisibilityHandler { 28 | 29 | private final JavaPlugin plugin; 30 | private final ConfigSettings configSettings; 31 | 32 | private final ViewHandler viewHandler; 33 | private final PacketHandler packetHandler; 34 | 35 | private BukkitRunnable entityMotionChecker; 36 | private final Map projectionEntities; 37 | 38 | public EntityVisibilityHandler(JavaPlugin plugin, 39 | ConfigSettings configSettings, 40 | ViewHandler viewHandler, 41 | PacketHandler packetHandler) { 42 | 43 | this.plugin = plugin; 44 | this.configSettings = configSettings; 45 | this.viewHandler = viewHandler; 46 | this.packetHandler = packetHandler; 47 | 48 | projectionEntities = new HashMap<>(); 49 | 50 | if (configSettings.isEntityHidingEnabled()) { 51 | startEntityCheckerChecker(); 52 | } 53 | } 54 | 55 | public void reload() { 56 | 57 | disable(); 58 | 59 | if (configSettings.isEntityHidingEnabled()) { 60 | startEntityCheckerChecker(); 61 | } 62 | } 63 | 64 | public void disable() { 65 | 66 | entityMotionChecker.cancel(); 67 | projectionEntities.clear(); 68 | } 69 | 70 | private void startEntityCheckerChecker() { 71 | 72 | MessageUtils.printDebug("Starting entity visibility timer"); 73 | 74 | entityMotionChecker = new BukkitRunnable() { 75 | @Override 76 | public void run() { 77 | 78 | Map> portalSideViewers = viewHandler.getSessionsSortedByPortalSides(); 79 | handleRealEntitiesVisibility(portalSideViewers); 80 | 81 | if (!configSettings.isEntityViewingEnabled()) { 82 | return; 83 | } 84 | 85 | Map> watchedBlockCaches = getProjectionsSortedByBlockCaches(portalSideViewers.keySet()); 86 | handleProjectionEntitiesVisibility(portalSideViewers, watchedBlockCaches); 87 | 88 | projectionEntities.entrySet().removeIf(entry -> entry.getKey().isDead()); 89 | projectionEntities.values().forEach(ProjectionEntity::updateLastLoc); 90 | } 91 | }; 92 | 93 | entityMotionChecker.runTaskTimer(plugin, 0, configSettings.getEntityUpdateTicks()); 94 | } 95 | 96 | private void handleRealEntitiesVisibility(Map> portalSideViewers) { 97 | 98 | for (ProjectionCache projection : portalSideViewers.keySet()) { 99 | 100 | Set currentEntities = projection.getEntities(); 101 | 102 | for (PlayerViewSession session : portalSideViewers.get(projection)) { 103 | 104 | showEntitiesNextToFrustum(session); 105 | hideEntitiesInFrustum(session, currentEntities); 106 | } 107 | } 108 | } 109 | 110 | private Map> getProjectionsSortedByBlockCaches(Set projections) { 111 | 112 | Map> sortedSources = new HashMap<>(); 113 | 114 | for (ProjectionCache projection : projections) { 115 | 116 | BlockCache source = projection.getSourceCache(); 117 | sortedSources.computeIfAbsent(source, set -> new HashSet<>()); 118 | sortedSources.get(source).add(projection); 119 | } 120 | 121 | return sortedSources; 122 | } 123 | 124 | private void handleProjectionEntitiesVisibility(Map> portalSideViewers, 125 | Map> watchedBlockCaches) { 126 | 127 | for (BlockCache blockCache : watchedBlockCaches.keySet()) { 128 | 129 | Set currentEntities = blockCache.getEntities(); 130 | Set newEntities = getProjectionEntities(currentEntities); 131 | 132 | currentEntities.forEach(entity -> projectionEntities.computeIfAbsent(entity, ProjectionEntity::new)); 133 | 134 | Map movingEntities = getMovingEntities(currentEntities); 135 | 136 | for (ProjectionCache projection : watchedBlockCaches.get(blockCache)) { 137 | for (PlayerViewSession session : portalSideViewers.get(projection)) { 138 | 139 | hideEntitiesOutsideProjection(session, newEntities); 140 | 141 | if (session.getLastViewFrustum() == null) { 142 | continue; 143 | } 144 | 145 | projectNewEntitiesInsideProjection(session, newEntities); 146 | displayEntityMovements(session, movingEntities); 147 | } 148 | } 149 | } 150 | } 151 | 152 | private Set getProjectionEntities(Set entities) { 153 | 154 | Set projectionEntities = new HashSet<>(); 155 | 156 | entities.forEach(entity -> { 157 | this.projectionEntities.computeIfAbsent(entity, ProjectionEntity::new); 158 | projectionEntities.add(this.projectionEntities.get(entity)); 159 | }); 160 | 161 | return projectionEntities; 162 | } 163 | 164 | private void hideEntitiesOutsideProjection(PlayerViewSession session, Set newEntities) { 165 | 166 | for (ProjectionEntity entityProjection : new HashSet<>(session.getProjectedEntities())) { 167 | 168 | if (!newEntities.contains(entityProjection) || !session.isVisibleInProjection(entityProjection.getEntity())) { 169 | viewHandler.destroyProjectedEntity(session.getPlayer(), entityProjection); 170 | } 171 | } 172 | } 173 | 174 | private void projectNewEntitiesInsideProjection(PlayerViewSession session, Set newEntities) { 175 | 176 | for (ProjectionEntity entityProjection : newEntities) { 177 | 178 | if (!session.getProjectedEntities().contains(entityProjection) && session.isVisibleInProjection(entityProjection.getEntity())) { 179 | viewHandler.projectEntity(session.getPlayer(), entityProjection, session.getViewedPortalSide().getLinkTransform()); 180 | } 181 | } 182 | } 183 | 184 | private Map getMovingEntities(Collection entities) { 185 | 186 | Map movingEntities = new HashMap<>(); 187 | 188 | for (Entity entity : entities) { 189 | 190 | ProjectionEntity projectionEntity = projectionEntities.get(entity); 191 | Location lastLoc = projectionEntity.getLastLoc(); 192 | 193 | if (lastLoc == null) { 194 | continue; 195 | } 196 | 197 | Location newLoc = entity.getLocation(); 198 | 199 | if (newLoc.getWorld() == lastLoc.getWorld() && !newLoc.equals(lastLoc)) { 200 | movingEntities.put(projectionEntity, newLoc.clone().subtract(lastLoc)); 201 | } 202 | } 203 | 204 | return movingEntities; 205 | } 206 | 207 | private void displayEntityMovements(PlayerViewSession session, Map movedEntities) { 208 | 209 | Player player = session.getPlayer(); 210 | ProjectionCache projection = session.getViewedPortalSide(); 211 | Transform linkTransform = projection.getLinkTransform(); 212 | Set projectedEntities = session.getProjectedEntities(); 213 | 214 | for (ProjectionEntity entity : movedEntities.keySet()) { 215 | 216 | boolean entityIsVisibleInProjection = session.isVisibleInProjection(entity.getEntity()); 217 | 218 | if (!projectedEntities.contains(entity)) { 219 | 220 | if (entityIsVisibleInProjection) { 221 | viewHandler.projectEntity(session.getPlayer(), entity, linkTransform); 222 | } 223 | continue; 224 | } 225 | 226 | if (entityIsVisibleInProjection) { 227 | 228 | Location newEntityLoc = entity.getEntity().getLocation(); 229 | Location lastEntityLoc = entity.getLastLoc(); 230 | 231 | Location projectedMovement = linkTransform.rotateLoc(newEntityLoc.clone().subtract(lastEntityLoc)); 232 | 233 | packetHandler.sendEntityMoveLook( 234 | session.getPlayer(), 235 | entity, 236 | projectedMovement.toVector(), 237 | projectedMovement.getYaw(), 238 | projectedMovement.getPitch(), 239 | entity.getEntity().isOnGround()); 240 | 241 | } else { 242 | viewHandler.destroyProjectedEntity(player, entity); 243 | } 244 | } 245 | } 246 | 247 | private void showEntitiesNextToFrustum(PlayerViewSession session) { 248 | 249 | for (Entity entity : new HashSet<>(session.getHiddenEntities())) { 250 | 251 | if (!session.isHiddenBehindProjection(entity)) { 252 | viewHandler.showEntity(session.getPlayer(), entity); 253 | } 254 | } 255 | } 256 | 257 | private void hideEntitiesInFrustum(PlayerViewSession session, Set newEntities) { 258 | 259 | for (Entity entity : newEntities) { 260 | 261 | if (!session.getHiddenEntities().contains(entity) && session.isHiddenBehindProjection(entity)) { 262 | viewHandler.hideEntity(session.getPlayer(), entity); 263 | } 264 | } 265 | } 266 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/handlers/PlayerViewSession.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.handlers; 2 | 3 | import me.gorgeousone.netherview.blockcache.ProjectionCache; 4 | import me.gorgeousone.netherview.geometry.BlockVec; 5 | import me.gorgeousone.netherview.geometry.viewfrustum.ViewFrustum; 6 | import me.gorgeousone.netherview.portal.Portal; 7 | import me.gorgeousone.netherview.portal.ProjectionEntity; 8 | import me.gorgeousone.netherview.wrapper.WrappedBoundingBox; 9 | import me.gorgeousone.netherview.wrapper.blocktype.BlockType; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.Location; 12 | import org.bukkit.entity.Entity; 13 | import org.bukkit.entity.Player; 14 | 15 | import java.util.HashMap; 16 | import java.util.HashSet; 17 | import java.util.Map; 18 | import java.util.Set; 19 | import java.util.UUID; 20 | 21 | public class PlayerViewSession { 22 | 23 | private final UUID playerId; 24 | private final Portal viewedPortal; 25 | 26 | private ProjectionCache viewedPortalSide; 27 | private ViewFrustum lastViewFrustum; 28 | 29 | private final Map projectedBlocks; 30 | private final Set hiddenEntities; 31 | private final Set projectedEntities; 32 | 33 | public PlayerViewSession(Player player, Portal viewedPortal) { 34 | 35 | this.playerId = player.getUniqueId(); 36 | this.viewedPortal = viewedPortal; 37 | 38 | this.projectedBlocks = new HashMap<>(); 39 | this.hiddenEntities = new HashSet<>(); 40 | this.projectedEntities = new HashSet<>(); 41 | } 42 | 43 | public UUID getPlayerId() { 44 | return playerId; 45 | } 46 | 47 | public Player getPlayer() { 48 | return Bukkit.getPlayer(playerId); 49 | } 50 | 51 | public Portal getViewedPortal() { 52 | return viewedPortal; 53 | } 54 | 55 | /** 56 | * Returns the one of the two projection caches of a portal that is being displayed to the player in the portal view. 57 | */ 58 | public ProjectionCache getViewedPortalSide() { 59 | return viewedPortalSide; 60 | } 61 | 62 | public void setViewedPortalSide(ProjectionCache viewedPortalSide) { 63 | this.viewedPortalSide = viewedPortalSide; 64 | } 65 | 66 | public ViewFrustum getLastViewFrustum() { 67 | return lastViewFrustum; 68 | } 69 | 70 | /** 71 | * Returns the latest view frustum that was used to calculate the projection blocks for the player's projection. 72 | * Returns null if player is not nearby any portal or no blocks were displayed (due to steep view angles) 73 | */ 74 | public void setLastViewFrustum(ViewFrustum lastViewFrustum) { 75 | this.lastViewFrustum = lastViewFrustum; 76 | } 77 | 78 | /** 79 | * Returns a Map of BlockTypes linked to their location that are currently displayed with fake blocks to the player. 80 | * The method is being used very frequently so it does not check for the player's permission to view portal projections. 81 | * Returns (and internally adds) an empty Map if no projected blocks found. 82 | */ 83 | public Map getProjectedBlocks() { 84 | return projectedBlocks; 85 | } 86 | 87 | public Set getHiddenEntities() { 88 | return hiddenEntities; 89 | } 90 | 91 | public Set getProjectedEntities() { 92 | return projectedEntities; 93 | } 94 | 95 | public boolean isHiddenBehindProjection(Entity entity) { 96 | 97 | if (lastViewFrustum == null) { 98 | return false; 99 | } 100 | 101 | WrappedBoundingBox boundingBox = WrappedBoundingBox.of(entity, entity.getLocation()); 102 | return boundingBox.intersectsBlockCache(viewedPortalSide) && boundingBox.intersectsFrustum(lastViewFrustum); 103 | } 104 | 105 | public boolean isVisibleInProjection(Entity entity) { 106 | 107 | if (lastViewFrustum == null) { 108 | return false; 109 | } 110 | 111 | Location projectionLoc = viewedPortalSide.getLinkTransform().transformLoc(entity.getLocation()); 112 | WrappedBoundingBox boundingBox = WrappedBoundingBox.of(entity, projectionLoc); 113 | 114 | return boundingBox.intersectsBlockCache(viewedPortalSide) && boundingBox.intersectsFrustum(lastViewFrustum); 115 | } 116 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/listeners/PlayerMoveListener.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.listeners; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.NetherViewPlugin; 5 | import me.gorgeousone.netherview.blockcache.Transform; 6 | import me.gorgeousone.netherview.customportal.CustomPortal; 7 | import me.gorgeousone.netherview.customportal.CustomPortalHandler; 8 | import me.gorgeousone.netherview.handlers.ViewHandler; 9 | import me.gorgeousone.netherview.utils.TeleportUtils; 10 | import org.bukkit.GameMode; 11 | import org.bukkit.Location; 12 | import org.bukkit.Material; 13 | import org.bukkit.World; 14 | import org.bukkit.entity.Player; 15 | import org.bukkit.event.EventHandler; 16 | import org.bukkit.event.Listener; 17 | import org.bukkit.event.player.PlayerGameModeChangeEvent; 18 | import org.bukkit.event.player.PlayerMoveEvent; 19 | import org.bukkit.event.player.PlayerToggleSneakEvent; 20 | import org.bukkit.plugin.java.JavaPlugin; 21 | import org.bukkit.scheduler.BukkitRunnable; 22 | import org.bukkit.util.Vector; 23 | 24 | import java.util.HashSet; 25 | import java.util.Set; 26 | import java.util.UUID; 27 | 28 | /** 29 | * A listener class that informs the view handler to update the portal view for players when they move. 30 | * It also takes care of the instant teleportation feature. 31 | */ 32 | public class PlayerMoveListener implements Listener { 33 | 34 | private final JavaPlugin plugin; 35 | private final ConfigSettings configSettings; 36 | private final ViewHandler viewHandler; 37 | private final CustomPortalHandler customPortalHandler; 38 | private final Material portalMaterial; 39 | 40 | private final Set teleportedPlayers; 41 | 42 | public PlayerMoveListener(NetherViewPlugin plugin, 43 | ConfigSettings configSettings, ViewHandler viewHandler, 44 | CustomPortalHandler customPortalHandler, 45 | Material portalMaterial) { 46 | 47 | this.plugin = plugin; 48 | this.configSettings = configSettings; 49 | this.viewHandler = viewHandler; 50 | this.customPortalHandler = customPortalHandler; 51 | this.portalMaterial = portalMaterial; 52 | 53 | teleportedPlayers = new HashSet<>(); 54 | } 55 | 56 | @EventHandler 57 | public void onPlayerMove(PlayerMoveEvent event) { 58 | 59 | Location from = event.getFrom(); 60 | Location to = event.getTo(); 61 | 62 | Vector fromVec = from.toVector(); 63 | Vector toVec = to.toVector(); 64 | 65 | if (fromVec.equals(toVec)) { 66 | return; 67 | } 68 | 69 | Player player = event.getPlayer(); 70 | World world = player.getWorld(); 71 | boolean enteredNewBlock = playerEnteredNewBlock(from, to); 72 | 73 | if (enteredNewBlock && configSettings.canCreatePortalViews(world)) { 74 | 75 | if (handleCustomPortalTp(player, from, to)) { 76 | return; 77 | } 78 | } 79 | 80 | if (configSettings.canCreatePortalViews(world)) { 81 | 82 | //sets player temporarily invulnerable so the game will instantly teleport them on entering a portal 83 | if (enteredNewBlock && configSettings.isInstantTeleportEnabled() && mortalEnteredPortal(player, from, to)) { 84 | TeleportUtils.setTemporarilyInvulnerable(player, plugin, 2); 85 | } 86 | 87 | handlePortalViewingOnMove(player, fromVec, toVec); 88 | } 89 | } 90 | 91 | /** 92 | * Teleports player if they entered a block of custom portal and it's not already their destination portal 93 | */ 94 | private boolean handleCustomPortalTp(Player player, Location from, Location to) { 95 | 96 | CustomPortal portal = customPortalHandler.getPortalAt(to); 97 | UUID playerId = player.getUniqueId(); 98 | 99 | if (portal == null) { 100 | teleportedPlayers.remove(playerId); 101 | return false; 102 | } 103 | 104 | if (teleportedPlayers.contains(playerId) || !portal.isLinked()) { 105 | return false; 106 | } 107 | 108 | viewHandler.hidePortalProjection(player); 109 | Transform tpTransform = portal.getTpTransform(); 110 | 111 | Location destination = tpTransform.transformLoc(to.clone()); 112 | destination.setWorld(portal.getCounterPortal().getWorld()); 113 | player.teleport(destination); 114 | teleportedPlayers.add(playerId); 115 | 116 | Vector vel = to.toVector().subtract(from.toVector()); 117 | Vector transformedVel = tpTransform.rotateVec(vel); 118 | 119 | new BukkitRunnable() { 120 | @Override 121 | public void run() { 122 | player.setVelocity(transformedVel); 123 | } 124 | }.runTask(plugin); 125 | return true; 126 | } 127 | 128 | /** 129 | * Checks if the player in a vulnerable game mode (so they would usually not be teleported instantly by a nether portal) and if they are entering a nether portal. 130 | */ 131 | private boolean mortalEnteredPortal(Player player, Location from, Location to) { 132 | 133 | GameMode gameMode = player.getGameMode(); 134 | 135 | return (gameMode == GameMode.SURVIVAL || gameMode == GameMode.ADVENTURE) && 136 | to.getBlock().getType() == portalMaterial && 137 | from.getBlock().getType() != portalMaterial; 138 | } 139 | 140 | private void handlePortalViewingOnMove(Player player, Vector fromVec, Vector toVec) { 141 | 142 | if (!teleportedPlayers.contains(player.getUniqueId()) && 143 | player.getGameMode() != GameMode.SPECTATOR && 144 | viewHandler.hasPortalViewEnabled(player) && 145 | player.hasPermission(NetherViewPlugin.VIEW_PERM)) { 146 | 147 | Vector playerMovement = fromVec.clone().subtract(toVec); 148 | viewHandler.displayClosestPortalTo(player, player.getEyeLocation().add(playerMovement)); 149 | } 150 | } 151 | 152 | private boolean playerEnteredNewBlock(Location from, Location to) { 153 | 154 | return (int) from.getX() != (int) to.getX() || 155 | (int) from.getY() != (int) to.getY() || 156 | (int) from.getZ() != (int) to.getZ(); 157 | } 158 | 159 | @EventHandler 160 | public void onPlayerSneak(PlayerToggleSneakEvent event) { 161 | 162 | Player player = event.getPlayer(); 163 | 164 | if (!viewHandler.hasViewSession(player)) { 165 | return; 166 | } 167 | 168 | new BukkitRunnable() { 169 | @Override 170 | public void run() { 171 | viewHandler.displayClosestPortalTo(player, player.getEyeLocation()); 172 | } 173 | }.runTaskLater(plugin, 2); 174 | } 175 | 176 | @EventHandler 177 | public void onGameModeChange(PlayerGameModeChangeEvent event) { 178 | 179 | if (event.getNewGameMode() == GameMode.SPECTATOR) { 180 | viewHandler.hidePortalProjection(event.getPlayer()); 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/listeners/PlayerQuitListener.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.listeners; 2 | 3 | import me.gorgeousone.netherview.customportal.PlayerSelectionHandler; 4 | import me.gorgeousone.netherview.handlers.ViewHandler; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.player.PlayerQuitEvent; 9 | 10 | public class PlayerQuitListener implements Listener { 11 | 12 | private final ViewHandler viewHandler; 13 | private final PlayerSelectionHandler selectionHandler; 14 | 15 | public PlayerQuitListener(ViewHandler viewHandler, 16 | PlayerSelectionHandler selectionHandler) { 17 | this.viewHandler = viewHandler; 18 | this.selectionHandler = selectionHandler; 19 | } 20 | 21 | @EventHandler 22 | public void onQuit(PlayerQuitEvent event) { 23 | 24 | Player player = event.getPlayer(); 25 | viewHandler.unregisterPlayer(player); 26 | selectionHandler.removeSelection(player); 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/listeners/PlayerTeleportListener.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.listeners; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.NetherViewPlugin; 5 | import me.gorgeousone.netherview.event.PortalUnlinkEvent; 6 | import me.gorgeousone.netherview.event.UnlinkReason; 7 | import me.gorgeousone.netherview.geometry.BlockVec; 8 | import me.gorgeousone.netherview.handlers.PortalHandler; 9 | import me.gorgeousone.netherview.handlers.ViewHandler; 10 | import me.gorgeousone.netherview.message.Message; 11 | import me.gorgeousone.netherview.message.MessageException; 12 | import me.gorgeousone.netherview.message.MessageUtils; 13 | import me.gorgeousone.netherview.portal.Portal; 14 | import me.gorgeousone.netherview.portal.PortalLocator; 15 | import org.bukkit.Bukkit; 16 | import org.bukkit.GameMode; 17 | import org.bukkit.Location; 18 | import org.bukkit.block.Block; 19 | import org.bukkit.entity.Player; 20 | import org.bukkit.event.EventHandler; 21 | import org.bukkit.event.EventPriority; 22 | import org.bukkit.event.Listener; 23 | import org.bukkit.event.player.PlayerTeleportEvent; 24 | 25 | /** 26 | * Takes care of registering and linking nether portals when players use them. 27 | */ 28 | public class PlayerTeleportListener implements Listener { 29 | 30 | private final ConfigSettings configSettings; 31 | private final PortalHandler portalHandler; 32 | private final ViewHandler viewHandler; 33 | 34 | public PlayerTeleportListener( 35 | ConfigSettings configSettings, PortalHandler portalHandler, 36 | ViewHandler viewHandler) { 37 | 38 | this.configSettings = configSettings; 39 | this.portalHandler = portalHandler; 40 | this.viewHandler = viewHandler; 41 | } 42 | 43 | //I did not use the PlayerPortalEvent because it only give information about where the player should theoretically perfectly teleport to 44 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 45 | public void onTeleport(PlayerTeleportEvent event) { 46 | 47 | Player player = event.getPlayer(); 48 | Location from = event.getFrom(); 49 | Location to = event.getTo(); 50 | 51 | if (to == null) { 52 | return; 53 | } 54 | 55 | //updates portal animation for the player if they teleport with e.g. an ender pearl 56 | if (from.getWorld() == to.getWorld()) { 57 | viewHandler.hidePortalProjection(player); 58 | return; 59 | } 60 | 61 | if (event.getCause() != PlayerTeleportEvent.TeleportCause.NETHER_PORTAL || 62 | !player.hasPermission(NetherViewPlugin.LINK_PERM)) { 63 | return; 64 | } 65 | 66 | viewHandler.unregisterPortalProjection(player); 67 | boolean createdNewPortalLink = createPortalLink(event); 68 | 69 | if (createdNewPortalLink && viewHandler.hasPortalViewEnabled(player) && 70 | (player.getGameMode() == GameMode.CREATIVE || configSettings.cancelTeleportWhenLinkingPortalsEnabled())) { 71 | event.setCancelled(true); 72 | } 73 | } 74 | 75 | /** 76 | * Tries to locates or register portals at the given locations and to link them if they aren't already. 77 | */ 78 | private boolean createPortalLink(PlayerTeleportEvent event) { 79 | 80 | Player player = event.getPlayer(); 81 | Location from = event.getFrom(); 82 | Location to = event.getTo(); 83 | 84 | if (!configSettings.canCreatePortalViews(from.getWorld())) { 85 | MessageUtils.printDebug("World '" + from.getWorld().getName() + "' not listed in config for portal viewing"); 86 | return false; 87 | } 88 | 89 | Block portalBlock = PortalLocator.getNearbyPortalBlock(from); 90 | 91 | //might happen if the player mysteriously moved more than a block away from the portal in split seconds 92 | if (portalBlock == null) { 93 | MessageUtils.printDebug("No portal found to teleport from at location [" + from.getWorld().getName() + "," + new BlockVec(from).toString() + "]"); 94 | return false; 95 | } 96 | 97 | Block counterPortalBlock = PortalLocator.getNearbyPortalBlock(to); 98 | 99 | if (counterPortalBlock == null) { 100 | MessageUtils.printDebug("No portal found to teleport to at location [" + to.getWorld().getName() + "," + new BlockVec(to).toString() + "]"); 101 | return false; 102 | } 103 | 104 | try { 105 | Portal portal = portalHandler.getPortalAt(portalBlock); 106 | Portal counterPortal = portalHandler.getPortalAt(counterPortalBlock); 107 | 108 | if (counterPortal.equals(portal.getCounterPortal())) { 109 | return false; 110 | } 111 | 112 | if (portal.isLinked()) { 113 | 114 | Bukkit.getPluginManager().callEvent(new PortalUnlinkEvent(portal, portal.getCounterPortal(), UnlinkReason.SWITCHED_TARGET_PORTAL)); 115 | portal.removeLink(); 116 | 117 | portalHandler.linkPortalTo(portal, counterPortal, player); 118 | return false; 119 | } 120 | 121 | portalHandler.linkPortalTo(portal, counterPortal, player); 122 | MessageUtils.sendInfo(player, Message.SUCCESSFUL_PORTAL_LINKING); 123 | return true; 124 | 125 | } catch (MessageException e) { 126 | 127 | MessageUtils.sendWarning(player, e.getPlayerMessage(), e.getPlaceholderValues()); 128 | return false; 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/message/Message.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.message; 2 | 3 | import org.bukkit.ChatColor; 4 | import org.bukkit.configuration.file.FileConfiguration; 5 | 6 | public enum Message { 7 | 8 | SUCCESSFUL_PORTAL_LINKING("successful-portal-linking"), 9 | UNEQUAL_PORTALS("unequal-portal-sizes"), 10 | PORTAL_FRAME_INCOMPLETE("portal-frame-incomplete", "world-type"), 11 | PORTAL_CORNERS_INCOMPLETE("portal-corners-incomplete", "world-type"), 12 | PORTAL_TOO_BIG("portal-too-big", "size"), 13 | PORTAL_NOT_INTACT("portal-not-intact", "world-type"), 14 | WORLD_NOT_WHITE_LISTED("world-not-white-listed", "world"), 15 | NO_WORLD_FOUND("no-world-found", "world"), 16 | NO_PORTALS_FOUND("no-portals-found", "world"), 17 | NO_PORTAL_FOUND_NEARBY("no-portal-found-nearby"), 18 | PORTAL_INFO("portal-info", "location", "info"), 19 | WORLD_INFO("world-info", "count", "world", "portals"), 20 | FLIPPED_PORTAL("flipped-portal", "portal"), 21 | PORTAL_VIEWING_ON("portal-viewing-on"), 22 | PORTAL_VIEWING_OFF("portal-viewing-off"), 23 | 24 | WAND_INFO("wand-info"), 25 | SELECTION_INCOMPLETE("selection-incomplete"), 26 | SET_FIRST_CUBOID_POSITION("set-first-position", "position"), 27 | SET_SECOND_CUBOID_POSITION("set-second-position", "position"), 28 | SELECTION_TOO_SMALL("selection-too-small"), 29 | SELECTION_NOT_FLAT("selection-not-flat"), 30 | NO_PORTAL_FOUND_WITH_NAME("no-portal-found-with-name", "name"), 31 | PORTALS_INTERSECT("portals-intersect"), 32 | PORTAL_NAME_NOT_VALID("portal-name-not-valid"), 33 | PORTAL_NAME_NOT_UNIQUE("portal-name-not-unique", "name"), 34 | CREATED_PORTAL("created-portal", "name", "size"), 35 | REMOVED_PORTAL("deleted-portal", "name"), 36 | LINKED_PORTAL("linked-portal", "from-portal", "to-portal"), 37 | UNLINKED_PORTAL("unlinked-portal", "portal"); 38 | 39 | private final String configKey; 40 | private String configValue; 41 | private final String[] placeholdersTokens; 42 | 43 | Message(String configKey, String... placeholdersTokens) { 44 | this.configKey = configKey; 45 | this.placeholdersTokens = placeholdersTokens; 46 | } 47 | 48 | private String getConfigKey() { 49 | return configKey; 50 | } 51 | 52 | public String[] getPlaceholdersTokens() { 53 | return placeholdersTokens; 54 | } 55 | 56 | public String[] getMessage(String... placeholderValues) { 57 | 58 | if (placeholderValues.length != placeholdersTokens.length) { 59 | throw new IllegalArgumentException("Expected " + placeholdersTokens.length + " placeholder values, found " + placeholderValues.length); 60 | } 61 | 62 | String formattedMessage = configValue; 63 | 64 | for (int i = 0; i < placeholdersTokens.length; ++i) { 65 | formattedMessage = formattedMessage.replace("%" + placeholdersTokens[i] + "%", placeholderValues[i]); 66 | } 67 | 68 | return ChatColor.translateAlternateColorCodes('&', formattedMessage).split("\\\\n"); 69 | } 70 | 71 | public static void loadLangConfigValues(FileConfiguration langConfig) { 72 | 73 | for (Message message : Message.values()) { 74 | 75 | String configKey = message.getConfigKey(); 76 | 77 | if (!langConfig.contains(configKey)) { 78 | throw new IllegalArgumentException("Language config does not contain key '" + configKey + "' for message " + message + "."); 79 | } 80 | 81 | message.configValue = langConfig.getString(configKey); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/message/MessageException.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.message; 2 | 3 | import java.util.Arrays; 4 | 5 | public class MessageException extends Exception { 6 | 7 | private final Message message; 8 | private final String[] placeholderValues; 9 | 10 | public MessageException(Message message, String... placeholderValues) { 11 | super(Arrays.toString(message.getMessage(placeholderValues))); 12 | this.message = message; 13 | this.placeholderValues = placeholderValues; 14 | } 15 | 16 | public Message getPlayerMessage() { 17 | return message; 18 | } 19 | 20 | public String[] getPlaceholderValues() { 21 | return placeholderValues; 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/message/MessageUtils.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.message; 2 | 3 | import net.md_5.bungee.api.chat.BaseComponent; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.ChatColor; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.entity.Player; 8 | 9 | public final class MessageUtils { 10 | 11 | private MessageUtils() {} 12 | 13 | public static boolean debugMessagesEnabled; 14 | public static boolean warningMessagesEnabled; 15 | 16 | public static void setWarningMessagesEnabled(boolean warningMessagesEnabled) { 17 | MessageUtils.warningMessagesEnabled = warningMessagesEnabled; 18 | } 19 | 20 | public static void setDebugMessagesEnabled(boolean debugMessagesEnabled) { 21 | MessageUtils.debugMessagesEnabled = debugMessagesEnabled; 22 | } 23 | 24 | public static void sendInfo(CommandSender sender, Message message, String... placeholderValues) { 25 | sender.sendMessage(message.getMessage(placeholderValues)); 26 | } 27 | 28 | public static void sendWarning(CommandSender sender, Message message, String... placeholderValues) { 29 | 30 | if (warningMessagesEnabled) { 31 | sender.sendMessage(message.getMessage(placeholderValues)); 32 | } 33 | } 34 | 35 | public static void printDebug(String message) { 36 | 37 | if (debugMessagesEnabled) { 38 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "[Debug] " + message); 39 | } 40 | } 41 | 42 | public static void sendStaffInfo(String message) { 43 | 44 | for (Player player : Bukkit.getOnlinePlayers()) { 45 | 46 | if (player.isOp()) { 47 | player.sendMessage(message); 48 | } 49 | } 50 | 51 | Bukkit.getConsoleSender().sendMessage(message); 52 | } 53 | 54 | public static void sendStaffInfo(BaseComponent[] message) { 55 | 56 | for (Player player : Bukkit.getOnlinePlayers()) { 57 | 58 | if (player.isOp()) { 59 | player.spigot().sendMessage(message); 60 | } 61 | } 62 | 63 | Bukkit.getConsoleSender().spigot().sendMessage(message); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/packet/EntitySpawnPacketFactory1_13.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.packet; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.ProtocolLibrary; 5 | import com.comphenix.protocol.events.PacketContainer; 6 | import com.comphenix.protocol.injector.PacketConstructor; 7 | import me.gorgeousone.netherview.utils.NmsUtils; 8 | import me.gorgeousone.netherview.utils.VersionUtils; 9 | import org.bukkit.entity.Entity; 10 | 11 | import java.lang.reflect.Constructor; 12 | import java.lang.reflect.Field; 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.lang.reflect.Method; 15 | 16 | public final class EntitySpawnPacketFactory1_13 { 17 | 18 | private EntitySpawnPacketFactory1_13() {} 19 | 20 | private static Constructor CONSTRUCTOR_TRACKER_ENTRY; 21 | private static Method TRACKER_ENTRY_CREATE_PACKET; 22 | 23 | private static Field NMS_SPAWN_PACKET_ENTITY_ID; 24 | private static Field NMS_SPAWN_PACKET_ENTITY_DATA; 25 | 26 | private static PacketConstructor CONSTRUCTOR_SPAWN_PACKET; 27 | 28 | static { 29 | 30 | if (!VersionUtils.serverIsAtOrAbove("1.14")) { 31 | 32 | try { 33 | 34 | Class trackerEntryClass = NmsUtils.getNmsClass("EntityTrackerEntry"); 35 | CONSTRUCTOR_TRACKER_ENTRY = trackerEntryClass.getConstructor(NmsUtils.getNmsClass("Entity"), int.class, int.class, int.class, boolean.class); 36 | TRACKER_ENTRY_CREATE_PACKET = trackerEntryClass.getDeclaredMethod("e"); 37 | 38 | boolean serverIs1_8 = !VersionUtils.serverIsAtOrAbove("1.9"); 39 | 40 | Class entitySpawnPacket = NmsUtils.getNmsClass("PacketPlayOutSpawnEntity"); 41 | NMS_SPAWN_PACKET_ENTITY_ID = entitySpawnPacket.getDeclaredField(serverIs1_8 ? "j" : "k"); 42 | NMS_SPAWN_PACKET_ENTITY_DATA = entitySpawnPacket.getDeclaredField(serverIs1_8 ? "k" : "l"); 43 | 44 | CONSTRUCTOR_SPAWN_PACKET = ProtocolLibrary.getProtocolManager().createPacketConstructor(PacketType.Play.Server.SPAWN_ENTITY, NmsUtils.getNmsClass("Entity"), int.class, int.class); 45 | 46 | } catch (ClassNotFoundException | NoSuchFieldException | NoSuchMethodException e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Creates a spawn packet container for <1.14 non living entities. 54 | * It first creates a nms spawn packet with a method from EntityTrackerEntry in order to 55 | * read entity object data values from it (which are difficult to replicate by hand). 56 | */ 57 | public static PacketContainer createPacket(Entity entity) { 58 | 59 | try { 60 | TRACKER_ENTRY_CREATE_PACKET.setAccessible(true); 61 | NMS_SPAWN_PACKET_ENTITY_ID.setAccessible(true); 62 | NMS_SPAWN_PACKET_ENTITY_DATA.setAccessible(true); 63 | 64 | Object trackerEntry = CONSTRUCTOR_TRACKER_ENTRY.newInstance(NmsUtils.getHandle(entity), 0, 0, 0, false); 65 | Object nmsSpawnPacket = TRACKER_ENTRY_CREATE_PACKET.invoke(trackerEntry); 66 | 67 | int entityId = NMS_SPAWN_PACKET_ENTITY_ID.getInt(nmsSpawnPacket); 68 | int entityData = NMS_SPAWN_PACKET_ENTITY_DATA.getInt(nmsSpawnPacket); 69 | 70 | TRACKER_ENTRY_CREATE_PACKET.setAccessible(false); 71 | NMS_SPAWN_PACKET_ENTITY_ID.setAccessible(false); 72 | NMS_SPAWN_PACKET_ENTITY_DATA.setAccessible(false); 73 | 74 | return CONSTRUCTOR_SPAWN_PACKET.createPacket(NmsUtils.getHandle(entity), entityId, entityData); 75 | 76 | } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { 77 | 78 | e.printStackTrace(); 79 | return null; 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/portal/Portal.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.portal; 2 | 3 | import me.gorgeousone.netherview.blockcache.BlockCache; 4 | import me.gorgeousone.netherview.blockcache.ProjectionCache; 5 | import me.gorgeousone.netherview.blockcache.Transform; 6 | import me.gorgeousone.netherview.geometry.AxisAlignedRect; 7 | import me.gorgeousone.netherview.geometry.BlockVec; 8 | import me.gorgeousone.netherview.geometry.Cuboid; 9 | import me.gorgeousone.netherview.wrapper.Axis; 10 | import org.bukkit.Location; 11 | import org.bukkit.World; 12 | import org.bukkit.block.Block; 13 | 14 | import java.util.HashSet; 15 | import java.util.Map; 16 | import java.util.Objects; 17 | import java.util.Set; 18 | 19 | /** 20 | * A class containing information about a located portal structure in a world. 21 | */ 22 | public class Portal { 23 | 24 | private final World world; 25 | private final AxisAlignedRect portalRect; 26 | 27 | private final Set portalBlocks; 28 | 29 | private Portal counterPortal; 30 | private Transform tpTransform; 31 | 32 | private Map.Entry blockCaches; 33 | private Map.Entry projectionCaches; 34 | 35 | private final Cuboid frameShape; 36 | private final Cuboid innerShape; 37 | 38 | private boolean isViewFlipped; 39 | 40 | public Portal(World world, 41 | AxisAlignedRect portalRect, 42 | Cuboid frameShape, 43 | Cuboid innerShape, 44 | Set portalBlocks) { 45 | 46 | this.world = world; 47 | this.portalRect = portalRect.clone(); 48 | this.frameShape = frameShape.clone(); 49 | this.innerShape = innerShape.clone(); 50 | this.portalBlocks = portalBlocks; 51 | } 52 | 53 | public World getWorld() { 54 | return world; 55 | } 56 | 57 | public Location getLocation() { 58 | return portalRect.getMin().toLocation(world); 59 | } 60 | 61 | public BlockVec getMaxBlockAtFloor() { 62 | 63 | BlockVec maxBlock = frameShape.getMax().clone().add(-1, 0, -1); 64 | maxBlock.setY(frameShape.getMin().getY()); 65 | return maxBlock; 66 | } 67 | 68 | public Cuboid getFrame() { 69 | return frameShape; 70 | } 71 | 72 | public Cuboid getInner() { 73 | return innerShape; 74 | } 75 | 76 | public int width() { 77 | return getAxis() == Axis.X ? frameShape.getWidthX() : frameShape.getWidthZ(); 78 | } 79 | 80 | public int height() { 81 | return frameShape.getHeight(); 82 | } 83 | 84 | public AxisAlignedRect getPortalRect() { 85 | return portalRect.clone(); 86 | } 87 | 88 | public Axis getAxis() { 89 | return portalRect.getAxis(); 90 | } 91 | 92 | public Set getPortalBlocks() { 93 | return new HashSet<>(portalBlocks); 94 | } 95 | 96 | public boolean equalsInSize(Portal other) { 97 | 98 | AxisAlignedRect otherRect = other.getPortalRect(); 99 | 100 | return portalRect.width() == otherRect.width() && 101 | portalRect.height() == otherRect.height(); 102 | } 103 | 104 | public Portal getCounterPortal() { 105 | return counterPortal; 106 | } 107 | 108 | public void setTpTransform(Transform tpTransform) { 109 | this.tpTransform = tpTransform; 110 | } 111 | 112 | public Transform getTpTransform() { 113 | return tpTransform; 114 | } 115 | 116 | public void setLinkedTo(Portal counterPortal) { 117 | this.counterPortal = counterPortal; 118 | } 119 | 120 | public void removeLink() { 121 | 122 | this.counterPortal = null; 123 | this.tpTransform = null; 124 | removeProjectionCaches(); 125 | } 126 | 127 | public boolean isLinked() { 128 | return counterPortal != null; 129 | } 130 | 131 | public void setBlockCaches(Map.Entry blockCaches) { 132 | this.blockCaches = blockCaches; 133 | } 134 | 135 | public void removeBlockCaches() { 136 | blockCaches = null; 137 | } 138 | 139 | public boolean blockCachesAreLoaded() { 140 | return blockCaches != null; 141 | } 142 | 143 | public BlockCache getFrontCache() { 144 | return blockCaches.getKey(); 145 | } 146 | 147 | public BlockCache getBackCache() { 148 | return blockCaches.getValue(); 149 | } 150 | 151 | /** 152 | * Sets the front and back projection caches for this portal. 153 | * 154 | * @param projectionCaches where the key is referred to as front projection and value as back projection 155 | */ 156 | public void setProjectionCaches(Map.Entry projectionCaches) { 157 | this.projectionCaches = projectionCaches; 158 | } 159 | 160 | public void removeProjectionCaches() { 161 | this.projectionCaches = null; 162 | } 163 | 164 | public boolean projectionsAreLoaded() { 165 | return projectionCaches != null; 166 | } 167 | 168 | public ProjectionCache getFrontProjection() { 169 | return projectionCaches.getKey(); 170 | } 171 | 172 | public ProjectionCache getBackProjection() { 173 | return projectionCaches.getValue(); 174 | } 175 | 176 | /** 177 | * Returns true if the the 2 projections of the portal have been switched with each other for aesthetic reasons. 178 | */ 179 | public boolean isViewFlipped() { 180 | return isViewFlipped; 181 | } 182 | 183 | /** 184 | * Sets whether the 2 projections of the portal are switched with each other or not. 185 | * The {@link ProjectionCache}s have to be set again to realize this change. 186 | */ 187 | public void setViewFlipped(boolean isViewFlipped) { 188 | this.isViewFlipped = isViewFlipped; 189 | } 190 | 191 | public void flipView() { 192 | isViewFlipped = !isViewFlipped; 193 | } 194 | 195 | @Override 196 | public String toString() { 197 | return BlockVec.toSimpleString(getLocation()); 198 | } 199 | 200 | @Override 201 | public int hashCode() { 202 | return Objects.hash(getLocation()); 203 | } 204 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/portal/PortalLocator.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.portal; 2 | 3 | import me.gorgeousone.netherview.geometry.AxisAlignedRect; 4 | import me.gorgeousone.netherview.geometry.BlockVec; 5 | import me.gorgeousone.netherview.geometry.Cuboid; 6 | import me.gorgeousone.netherview.message.Message; 7 | import me.gorgeousone.netherview.message.MessageException; 8 | import me.gorgeousone.netherview.message.MessageUtils; 9 | import me.gorgeousone.netherview.utils.FacingUtils; 10 | import me.gorgeousone.netherview.wrapper.Axis; 11 | import org.bukkit.Location; 12 | import org.bukkit.Material; 13 | import org.bukkit.World; 14 | import org.bukkit.block.Block; 15 | import org.bukkit.block.BlockFace; 16 | import org.bukkit.util.Vector; 17 | 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | 21 | public class PortalLocator { 22 | 23 | private static Material PORTAL_MATERIAL; 24 | private static int MAX_PORTAL_SIZE; 25 | 26 | public static void configureVersion(Material portalMaterial) { 27 | PortalLocator.PORTAL_MATERIAL = portalMaterial; 28 | } 29 | 30 | public static void setMaxPortalSize(int portalSize) { 31 | MAX_PORTAL_SIZE = portalSize; 32 | } 33 | 34 | /** 35 | * Finds the portal block a player might have touched at the location or the blocks next to it 36 | * (players in creative mode often teleport to the nether before their location appears to be inside a portal). 37 | */ 38 | public static Block getNearbyPortalBlock(Location location) { 39 | 40 | Block block = location.getBlock(); 41 | 42 | if (block.getType() == PORTAL_MATERIAL) { 43 | return block; 44 | } 45 | 46 | for (BlockFace face : FacingUtils.getAxesFaces()) { 47 | Block neighbor = block.getRelative(face); 48 | 49 | if (neighbor.getType() == PORTAL_MATERIAL) { 50 | return neighbor; 51 | } 52 | } 53 | 54 | return null; 55 | } 56 | 57 | public static Portal locatePortalStructure(Block portalBlock) throws MessageException { 58 | 59 | //this only happens when some data read from the portal config is wrong 60 | //that's why it is not a gray message 61 | if (portalBlock.getType() != PORTAL_MATERIAL) { 62 | throw new IllegalStateException("No portal found at " + BlockVec.toSimpleString(portalBlock.getLocation())); 63 | } 64 | 65 | World world = portalBlock.getWorld(); 66 | AxisAlignedRect portalRect = getPortalRect(portalBlock); 67 | Axis portalAxis = portalRect.getAxis(); 68 | 69 | BlockVec portalMin = new BlockVec(portalRect.getMin()); 70 | BlockVec portalMax = new BlockVec(portalRect.getMax()); 71 | portalMax.add(new BlockVec(portalAxis.getNormal())); 72 | 73 | Cuboid innerShape = new Cuboid(portalMin, portalMax); 74 | Set innerBlocks = getInnerPortalBlocks(world, innerShape); 75 | checkInnerBlocksConsistency(innerBlocks); 76 | 77 | BlockVec frameExtent = new BlockVec(portalAxis.getCrossNormal()).setY(1); 78 | Cuboid frameShape = new Cuboid(portalMin.subtract(frameExtent), portalMax.add(frameExtent)); 79 | checkFrameBlocksOcclusion(world, frameShape, portalRect.getAxis()); 80 | 81 | return new Portal(world, portalRect, frameShape, innerShape, innerBlocks); 82 | } 83 | 84 | /** 85 | * Returns a rectangle with the size and location of the rectangle the inner portal blocks form. 86 | */ 87 | private static AxisAlignedRect getPortalRect(Block portalBlock) throws MessageException { 88 | 89 | Axis portalAxis = FacingUtils.getAxis(portalBlock); 90 | 91 | Vector rectMin = new Vector( 92 | portalBlock.getX(), 93 | getPortalExtent(portalBlock, BlockFace.DOWN).getY(), 94 | portalBlock.getZ()); 95 | 96 | Vector rectMax = rectMin.clone(); 97 | rectMax.setY(getPortalExtent(portalBlock, BlockFace.UP).getY() + 1); 98 | 99 | if (portalAxis == Axis.X) { 100 | rectMin.setX(getPortalExtent(portalBlock, BlockFace.WEST).getX()); 101 | rectMax.setX(getPortalExtent(portalBlock, BlockFace.EAST).getX() + 1); 102 | 103 | } else { 104 | rectMin.setZ(getPortalExtent(portalBlock, BlockFace.NORTH).getZ()); 105 | rectMax.setZ(getPortalExtent(portalBlock, BlockFace.SOUTH).getZ() + 1); 106 | } 107 | 108 | //translate the portalRect towards the middle of the block; 109 | AxisAlignedRect portalRect = new AxisAlignedRect(portalAxis, rectMin, rectMax); 110 | 111 | if (portalRect.width() > MAX_PORTAL_SIZE || portalRect.height() > MAX_PORTAL_SIZE) { 112 | throw new MessageException(Message.PORTAL_TOO_BIG, String.valueOf(MAX_PORTAL_SIZE)); 113 | } 114 | 115 | portalRect.translate(portalRect.getPlane().getNormal().multiply(0.5)); 116 | return portalRect; 117 | } 118 | 119 | /** 120 | * Returns the last portal block of a portal's extent into a certain direction. 121 | */ 122 | private static Block getPortalExtent(Block sourceBlock, BlockFace facing) throws MessageException { 123 | 124 | Block blockIterator = sourceBlock; 125 | 126 | for (int i = 0; i <= MAX_PORTAL_SIZE; i++) { 127 | 128 | Block nextBlock = blockIterator.getRelative(facing); 129 | 130 | if (nextBlock.getType() != PORTAL_MATERIAL) { 131 | return blockIterator; 132 | } 133 | 134 | blockIterator = nextBlock; 135 | } 136 | 137 | MessageUtils.printDebug("Detection stopped after exceeding " + MAX_PORTAL_SIZE + " portal blocks towards " + facing.name() + " at " + BlockVec.toSimpleString(blockIterator.getLocation())); 138 | throw new MessageException(Message.PORTAL_TOO_BIG, String.valueOf(MAX_PORTAL_SIZE)); 139 | } 140 | 141 | /** 142 | * Returns a set of blocks of all portal blocks of a portal according to the passed rectangle. 143 | */ 144 | private static Set getInnerPortalBlocks(World world, 145 | Cuboid portalInner) throws MessageException { 146 | 147 | BlockVec portalMin = portalInner.getMin(); 148 | BlockVec portalMax = portalInner.getMax(); 149 | Set portalBlocks = new HashSet<>(); 150 | 151 | for (int x = portalMin.getX(); x < portalMax.getX(); x++) { 152 | for (int y = portalMin.getY(); y < portalMax.getY(); y++) { 153 | for (int z = portalMin.getZ(); z < portalMax.getZ(); z++) { 154 | 155 | Block portalBlock = world.getBlockAt(x, y, z); 156 | 157 | if (portalBlock.getType() == PORTAL_MATERIAL) { 158 | portalBlocks.add(portalBlock); 159 | 160 | } else { 161 | 162 | MessageUtils.printDebug("Expected portal block at " + BlockVec.toSimpleString(portalBlock.getLocation())); 163 | String worldType = world.getEnvironment().name().toLowerCase().replaceAll("_", " "); 164 | throw new MessageException(Message.PORTAL_NOT_INTACT, worldType); 165 | } 166 | } 167 | } 168 | } 169 | 170 | return portalBlocks; 171 | } 172 | 173 | private static void checkInnerBlocksConsistency(Set portalBlocks) throws MessageException { 174 | 175 | for (Block portalBlock : portalBlocks) { 176 | 177 | if (portalBlock.getType() != PORTAL_MATERIAL) { 178 | 179 | MessageUtils.printDebug("Expected portal block at " + BlockVec.toSimpleString(portalBlock.getLocation())); 180 | String worldType = portalBlock.getWorld().getEnvironment().name().toLowerCase().replaceAll("_", " "); 181 | throw new MessageException(Message.PORTAL_NOT_INTACT, worldType); 182 | } 183 | } 184 | } 185 | 186 | /** 187 | * Returns a set of blocks where obsidian needs to be placed for a portal frame according to the given portal bounds. 188 | */ 189 | private static void checkFrameBlocksOcclusion(World world, 190 | Cuboid portalFrame, 191 | Axis portalAxis) throws MessageException { 192 | 193 | BlockVec portalMin = portalFrame.getMin(); 194 | BlockVec portalMax = portalFrame.getMax(); 195 | 196 | for (int x = portalMin.getX(); x < portalMax.getX(); x++) { 197 | for (int y = portalMin.getY(); y < portalMax.getY(); y++) { 198 | for (int z = portalMin.getZ(); z < portalMax.getZ(); z++) { 199 | 200 | //only check for obsidian frame blocks that are part of the portal frame 201 | if (y > portalMin.getY() && y < portalMax.getY() - 1 && 202 | (portalAxis == Axis.X ? 203 | x > portalMin.getX() && x < portalMax.getX() - 1 : 204 | z > portalMin.getZ() && z < portalMax.getZ() - 1)) { 205 | continue; 206 | } 207 | 208 | Block portalBlock = world.getBlockAt(x, y, z); 209 | Material portalBlockType = portalBlock.getType(); 210 | 211 | if (portalBlockType.isOccluding()) { 212 | continue; 213 | } 214 | 215 | MessageUtils.printDebug("Expected obsidian/occluding block at portal corner block " + BlockVec.toSimpleString(portalBlock.getLocation())); 216 | 217 | String worldType = world.getEnvironment().name().toLowerCase().replaceAll("_", " "); 218 | 219 | if (isPortalCorner(x, y, z, portalMin, portalMax, portalAxis)) { 220 | throw new MessageException(Message.PORTAL_CORNERS_INCOMPLETE, worldType); 221 | 222 | } else { 223 | throw new MessageException(Message.PORTAL_FRAME_INCOMPLETE, worldType); 224 | } 225 | } 226 | } 227 | } 228 | } 229 | 230 | private static boolean isPortalCorner(int x, 231 | int y, 232 | int z, 233 | BlockVec portalMin, 234 | BlockVec portalMax, 235 | Axis portalAxis) { 236 | 237 | if (y != portalMin.getY() && y != portalMax.getY() - 1) { 238 | return false; 239 | } 240 | 241 | if (portalAxis == Axis.X) { 242 | return (x == portalMin.getX() || x == portalMax.getX() - 1); 243 | } else { 244 | return (z == portalMin.getZ() || z == portalMax.getZ() - 1); 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/portal/PortalSerializer.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.portal; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.customportal.CustomPortal; 5 | import me.gorgeousone.netherview.geometry.BlockVec; 6 | import me.gorgeousone.netherview.handlers.PortalHandler; 7 | import me.gorgeousone.netherview.message.MessageException; 8 | import me.gorgeousone.netherview.utils.VersionUtils; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.World; 11 | import org.bukkit.configuration.ConfigurationSection; 12 | import org.bukkit.configuration.file.FileConfiguration; 13 | import org.bukkit.plugin.java.JavaPlugin; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.Set; 18 | import java.util.UUID; 19 | 20 | public class PortalSerializer { 21 | 22 | private final JavaPlugin plugin; 23 | private final ConfigSettings configSettings; 24 | private final PortalHandler portalHandler; 25 | 26 | public PortalSerializer(JavaPlugin plugin, 27 | ConfigSettings configSettings, 28 | PortalHandler portalHandler) { 29 | 30 | this.configSettings = configSettings; 31 | this.portalHandler = portalHandler; 32 | this.plugin = plugin; 33 | } 34 | 35 | public void savePortals(FileConfiguration portalConfig) { 36 | 37 | portalConfig.set("plugin-version", plugin.getDescription().getVersion()); 38 | portalConfig.set("portals", null); 39 | 40 | ConfigurationSection portalSection = portalConfig.createSection("portals"); 41 | 42 | for (World world : Bukkit.getWorlds()) { 43 | 44 | if (!portalHandler.hasPortals(world)) { 45 | continue; 46 | } 47 | 48 | Set portalsInWorld = portalHandler.getPortals(world); 49 | String worldId = world.getUID().toString(); 50 | ConfigurationSection worldSection = null; 51 | 52 | for (Portal portal : portalsInWorld) { 53 | 54 | if (portal instanceof CustomPortal) { 55 | continue; 56 | } 57 | 58 | if (worldSection == null) { 59 | worldSection = portalSection.createSection(worldId); 60 | } 61 | 62 | int portalHash = portal.hashCode(); 63 | ConfigurationSection portalData = worldSection.createSection(Integer.toString(portalHash)); 64 | 65 | portalData.set("location", new BlockVec(portal.getLocation()).serialize()); 66 | portalData.set("is-flipped", portal.isViewFlipped()); 67 | 68 | if (portal.isLinked()) { 69 | portalData.set("link", portal.getCounterPortal().hashCode()); 70 | } 71 | } 72 | } 73 | } 74 | 75 | public void loadPortals(FileConfiguration portalConfig) { 76 | 77 | if (!portalConfig.contains("plugin-version") || VersionUtils.isVersionLowerThan(portalConfig.getString("plugin-version"), "3")) { 78 | new PortalSerializer2_1_0(plugin, configSettings, portalHandler).loadPortals(portalConfig); 79 | return; 80 | } 81 | 82 | if (!portalConfig.contains("portals")) { 83 | return; 84 | } 85 | 86 | ConfigurationSection portalsSection = portalConfig.getConfigurationSection("portals"); 87 | Map portalLinks = new HashMap<>(); 88 | 89 | for (String worldId : portalsSection.getKeys(false)) { 90 | 91 | World world = Bukkit.getWorld(UUID.fromString(worldId)); 92 | 93 | if (world == null) { 94 | plugin.getLogger().warning("Could not find world with ID: '" + worldId + "'. Portals saved for this world will not be loaded."); 95 | continue; 96 | } 97 | 98 | if (configSettings.canCreatePortalViews(world)) { 99 | 100 | ConfigurationSection worldSection = portalsSection.getConfigurationSection(worldId); 101 | 102 | for (String portalHashString : worldSection.getKeys(false)) { 103 | deserializePortal(world, portalHashString, worldSection.getConfigurationSection(portalHashString), portalLinks); 104 | } 105 | } 106 | } 107 | 108 | linkPortals(portalLinks); 109 | } 110 | 111 | private void deserializePortal(World world, 112 | String portalHashString, 113 | ConfigurationSection portalData, 114 | Map portalLinks) { 115 | 116 | try { 117 | 118 | int portalHash = Integer.parseInt(portalHashString); 119 | 120 | BlockVec portalLoc = BlockVec.fromString(portalData.getString("location")); 121 | Portal portal = PortalLocator.locatePortalStructure(portalLoc.toBlock(world)); 122 | portalHandler.addPortal(portal); 123 | portal.setViewFlipped(portalData.getBoolean("is-flipped")); 124 | 125 | if (portalData.contains("link")) { 126 | portalLinks.put(portalHash, portalData.getInt("link")); 127 | } 128 | 129 | } catch (IllegalArgumentException | IllegalStateException | MessageException e) { 130 | plugin.getLogger().warning("Unable to load portal '" + portalHashString + "' in world " + world.getName() + "': " + e.getMessage()); 131 | } 132 | } 133 | 134 | private void linkPortals(Map portalLinks) { 135 | 136 | for (Map.Entry entry : portalLinks.entrySet()) { 137 | 138 | Integer fromPortalHash = entry.getKey(); 139 | Integer toPortalHash = entry.getValue(); 140 | 141 | Portal fromPortal = portalHandler.getPortalByHash(fromPortalHash); 142 | Portal toPortal = portalHandler.getPortalByHash(toPortalHash); 143 | 144 | if (toPortal == null) { 145 | plugin.getLogger().warning("Could not find custom portal with name'" + toPortalHash + "' for linking with portal '" + fromPortalHash + "'."); 146 | continue; 147 | } 148 | 149 | try { 150 | portalHandler.linkPortalTo(fromPortal, toPortal, null); 151 | 152 | } catch (MessageException e) { 153 | plugin.getLogger().warning("Unable to link custom portal '" + fromPortalHash + "' to portal '" + toPortalHash + "': " + e.getMessage()); 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/portal/PortalSerializer2_1_0.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.portal; 2 | 3 | import me.gorgeousone.netherview.ConfigSettings; 4 | import me.gorgeousone.netherview.geometry.BlockVec; 5 | import me.gorgeousone.netherview.handlers.PortalHandler; 6 | import me.gorgeousone.netherview.message.MessageException; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.World; 9 | import org.bukkit.configuration.ConfigurationSection; 10 | import org.bukkit.configuration.file.FileConfiguration; 11 | import org.bukkit.plugin.java.JavaPlugin; 12 | 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | public class PortalSerializer2_1_0 { 17 | 18 | private final JavaPlugin plugin; 19 | private final ConfigSettings configSettings; 20 | private final PortalHandler portalHandler; 21 | 22 | public PortalSerializer2_1_0(JavaPlugin plugin, 23 | ConfigSettings configSettings, 24 | PortalHandler portalHandler) { 25 | this.configSettings = configSettings; 26 | this.portalHandler = portalHandler; 27 | this.plugin = plugin; 28 | } 29 | 30 | public void loadPortals(FileConfiguration portalConfig) { 31 | 32 | loadPortalLocations(portalConfig); 33 | loadPortalData(portalConfig); 34 | } 35 | 36 | private void loadPortalLocations(FileConfiguration portalConfig) { 37 | 38 | if (!portalConfig.contains("portal-locations")) { 39 | return; 40 | } 41 | 42 | ConfigurationSection portalLocations = portalConfig.getConfigurationSection("portal-locations"); 43 | 44 | for (String worldID : portalLocations.getKeys(false)) { 45 | 46 | World worldWithPortals = Bukkit.getWorld(UUID.fromString(worldID)); 47 | 48 | if (worldWithPortals == null) { 49 | plugin.getLogger().warning("Could not find world with ID: '" + worldID + "'. Portals saved for this world will not be loaded."); 50 | continue; 51 | } 52 | 53 | if (configSettings.canCreatePortalViews(worldWithPortals)) { 54 | deserializePortals(worldWithPortals, portalLocations.getStringList(worldID)); 55 | } 56 | } 57 | } 58 | 59 | private void deserializePortals(World world, List portalLocs) { 60 | 61 | for (String serializedBlockVec : portalLocs) { 62 | 63 | try { 64 | BlockVec portalLoc = BlockVec.fromString(serializedBlockVec); 65 | Portal portal = PortalLocator.locatePortalStructure(portalLoc.toBlock(world)); 66 | portalHandler.addPortal(portal); 67 | 68 | } catch (IllegalArgumentException | IllegalStateException | MessageException e) { 69 | plugin.getLogger().warning("Unable to load portal at [" + world.getName() + "," + serializedBlockVec + "]: " + e.getMessage()); 70 | } 71 | } 72 | } 73 | 74 | private void loadPortalData(FileConfiguration portalConfig) { 75 | 76 | if (!portalConfig.contains("portal-data")) { 77 | return; 78 | } 79 | 80 | ConfigurationSection portalData = portalConfig.getConfigurationSection("portal-data"); 81 | 82 | for (String portalHashString : portalData.getKeys(false)) { 83 | 84 | Portal portal = portalHandler.getPortalByHash(Integer.parseInt(portalHashString)); 85 | 86 | if (portal == null) { 87 | continue; 88 | } 89 | 90 | portal.setViewFlipped(portalData.getBoolean(portalHashString + ".is-flipped")); 91 | 92 | if (!portalData.contains(portalHashString + ".link")) { 93 | continue; 94 | } 95 | 96 | int linkedPortalHash = portalData.getInt(portalHashString + ".link"); 97 | Portal counterPortal = portalHandler.getPortalByHash(linkedPortalHash); 98 | 99 | if (counterPortal != null) { 100 | 101 | try { 102 | portalHandler.linkPortalTo(portal, counterPortal, null); 103 | } catch (MessageException e) { 104 | plugin.getLogger().warning("Could not link portal '" + portal.toString() + "' to portal '" + counterPortal.toString() + "': " + e.getMessage()); 105 | } 106 | } 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/portal/ProjectionEntity.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.portal; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.entity.Entity; 5 | 6 | import java.util.Random; 7 | 8 | public class ProjectionEntity { 9 | 10 | private final static Random RANDOM = new Random(); 11 | 12 | private final Entity entity; 13 | private final int fakeId; 14 | private Location lastLoc; 15 | 16 | public ProjectionEntity(Entity entity) { 17 | 18 | this.entity = entity; 19 | //chances to match an existing id are like 10,000/2,000,000,000 which is 0,0005%. hope that's enough 20 | this.fakeId = RANDOM.nextInt(); 21 | } 22 | 23 | public Entity getEntity() { 24 | return entity; 25 | } 26 | 27 | public int getFakeId() { 28 | return fakeId; 29 | } 30 | 31 | public Location getLastLoc() { 32 | return lastLoc; 33 | } 34 | 35 | public void updateLastLoc() { 36 | lastLoc = entity.getLocation(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/updatechecks/UpdateCheck.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.updatechecks; 2 | 3 | import me.gorgeousone.netherview.message.MessageUtils; 4 | import me.gorgeousone.netherview.utils.VersionUtils; 5 | import net.md_5.bungee.api.ChatColor; 6 | import net.md_5.bungee.api.chat.ClickEvent; 7 | import net.md_5.bungee.api.chat.ComponentBuilder; 8 | import net.md_5.bungee.api.chat.HoverEvent; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.plugin.java.JavaPlugin; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.net.URL; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Scanner; 18 | 19 | public class UpdateCheck { 20 | 21 | private final JavaPlugin plugin; 22 | 23 | private final String currentVersion; 24 | private final int resourceId; 25 | private final String resourceName; 26 | private final String updateInfoPasteUrl; 27 | 28 | public UpdateCheck(JavaPlugin plugin, int resourceId, String resourceName, String updateInfoPasteUrl) { 29 | 30 | this.plugin = plugin; 31 | this.currentVersion = plugin.getDescription().getVersion(); 32 | this.resourceId = resourceId; 33 | this.resourceName = resourceName; 34 | this.updateInfoPasteUrl = updateInfoPasteUrl; 35 | } 36 | 37 | public void run(int maxDisplayedMessages) { 38 | 39 | Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { 40 | 41 | try { 42 | List newUpdates = readNewUpdates(); 43 | 44 | if (newUpdates.isEmpty()) { 45 | plugin.getLogger().info("Plugin is up to date :)"); 46 | return; 47 | } 48 | 49 | if (newUpdates.size() < 2) { 50 | MessageUtils.sendStaffInfo("A new version of Nether View is available!"); 51 | }else { 52 | MessageUtils.sendStaffInfo("New updates for Nether View are available!"); 53 | } 54 | 55 | for (int i = 0; i < newUpdates.size(); ++i) { 56 | 57 | if (i > maxDisplayedMessages - 1) { 58 | ComponentBuilder message = new ComponentBuilder((newUpdates.size() - maxDisplayedMessages) + " more...").color(ChatColor.LIGHT_PURPLE); 59 | message.event(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.spigotmc.org/resources/" + resourceName + "." + resourceId + "/updates")); 60 | message.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("see all updates").create())); 61 | MessageUtils.sendStaffInfo(message.create()); 62 | break; 63 | } 64 | 65 | MessageUtils.sendStaffInfo(newUpdates.get(i).getChatMessage()); 66 | } 67 | 68 | ComponentBuilder builder = new ComponentBuilder("download").color(ChatColor.YELLOW).underlined(true); 69 | builder.event(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.spigotmc.org/resources/" + resourceName + "." + resourceId)); 70 | builder.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("visit ").append("download page").color(ChatColor.LIGHT_PURPLE).create())); 71 | MessageUtils.sendStaffInfo(builder.create()); 72 | 73 | } catch (IOException exception) { 74 | plugin.getLogger().info("Unable to check for updates..."); 75 | } 76 | }); 77 | } 78 | 79 | private List readNewUpdates() throws IOException { 80 | 81 | InputStream inputStream = new URL(updateInfoPasteUrl).openStream(); 82 | Scanner scanner = new Scanner(inputStream); 83 | List updates = new ArrayList<>(); 84 | 85 | while (scanner.hasNext()) { 86 | 87 | UpdateInfo updateInfo = new UpdateInfo(scanner.nextLine(), resourceName, resourceId); 88 | 89 | if (VersionUtils.isVersionLowerThan(currentVersion, updateInfo.getVersion())) { 90 | System.out.println(currentVersion + " is lower than " + updateInfo.getVersion()); 91 | updates.add(updateInfo); 92 | }else { 93 | break; 94 | } 95 | } 96 | return updates; 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/updatechecks/UpdateInfo.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.updatechecks; 2 | 3 | import net.md_5.bungee.api.ChatColor; 4 | import net.md_5.bungee.api.chat.BaseComponent; 5 | import net.md_5.bungee.api.chat.ClickEvent; 6 | import net.md_5.bungee.api.chat.ComponentBuilder; 7 | import net.md_5.bungee.api.chat.HoverEvent; 8 | 9 | public class UpdateInfo { 10 | 11 | private final String version; 12 | private final String description; 13 | private final String updateId; 14 | 15 | private final int resourceId; 16 | private final String resourceName; 17 | 18 | public UpdateInfo(String updateInfo, String resourceName, int resourceId) { 19 | 20 | String[] split = updateInfo.split(";"); 21 | version = split[0]; 22 | description = split[1]; 23 | updateId = split[2]; 24 | 25 | this.resourceName = resourceName; 26 | this.resourceId = resourceId; 27 | } 28 | 29 | public String getVersion() { 30 | return version; 31 | } 32 | 33 | public BaseComponent[] getChatMessage() { 34 | 35 | ComponentBuilder builder = new ComponentBuilder(version).color(ChatColor.LIGHT_PURPLE); 36 | builder.append(" " + description).color(ChatColor.YELLOW); 37 | builder.event(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.spigotmc.org/resources/" + resourceName + "." + resourceId + "/update?update=" + updateId)); 38 | builder.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("read more about ").append(version).color(ChatColor.LIGHT_PURPLE).create())); 39 | 40 | return builder.create(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/updatechecks/VersionResponse.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.updatechecks; 2 | 3 | public enum VersionResponse { 4 | LATEST, FOUND_NEW, UNAVAILABLE 5 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/utils/ConfigUtils.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.utils; 2 | 3 | import org.bukkit.configuration.file.YamlConfiguration; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | 10 | public final class ConfigUtils { 11 | 12 | private ConfigUtils() {} 13 | 14 | public static YamlConfiguration loadConfig(String configName, JavaPlugin plugin) { 15 | 16 | File configFile = new File(plugin.getDataFolder() + File.separator + configName + ".yml"); 17 | YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(plugin.getResource(configName + ".yml"))); 18 | 19 | if (!configFile.exists()) { 20 | plugin.saveResource(configName + ".yml", true); 21 | } 22 | 23 | YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); 24 | config.setDefaults(defConfig); 25 | config.options().copyDefaults(true); 26 | 27 | try { 28 | config.save(configFile); 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } 32 | 33 | return config; 34 | } 35 | 36 | // public static YamlConfiguration loadDefaultConfig(String configName, JavaPlugin plugin) { 37 | // 38 | // InputStream defConfigStream = plugin.getResource(configName + ".yml"); 39 | // return YamlConfiguration.loadConfiguration(new InputStreamReader(defConfigStream)); 40 | // } 41 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/utils/FacingUtils.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.utils; 2 | 3 | import com.comphenix.protocol.wrappers.EnumWrappers; 4 | import me.gorgeousone.netherview.geometry.BlockVec; 5 | import me.gorgeousone.netherview.wrapper.Axis; 6 | import org.bukkit.block.Block; 7 | import org.bukkit.block.BlockFace; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | public final class FacingUtils { 14 | 15 | private FacingUtils() {} 16 | 17 | private final static List ROTATION_FACES = new ArrayList<>(Arrays.asList( 18 | BlockFace.NORTH_WEST, 19 | BlockFace.NORTH_NORTH_WEST, 20 | BlockFace.NORTH, 21 | BlockFace.NORTH_NORTH_EAST, 22 | BlockFace.NORTH_EAST, 23 | 24 | BlockFace.EAST_NORTH_EAST, 25 | BlockFace.EAST, 26 | BlockFace.EAST_SOUTH_EAST, 27 | 28 | BlockFace.SOUTH_EAST, 29 | BlockFace.SOUTH_SOUTH_EAST, 30 | BlockFace.SOUTH, 31 | BlockFace.SOUTH_SOUTH_WEST, 32 | BlockFace.SOUTH_WEST, 33 | 34 | BlockFace.WEST_SOUTH_WEST, 35 | BlockFace.WEST, 36 | BlockFace.WEST_NORTH_WEST 37 | )); 38 | 39 | /** 40 | * Returns the rotated version of a block face 41 | * 42 | * @param face face to be rotated 43 | * @param quarterTurns count of 90 degree turns that should be performed (0 - 3) 44 | */ 45 | public static BlockFace getRotatedFace(BlockFace face, int quarterTurns) { 46 | 47 | if (!ROTATION_FACES.contains(face)) { 48 | return face; 49 | } 50 | 51 | int rotatedFaceIndex = (ROTATION_FACES.indexOf(face) + quarterTurns * 4) % ROTATION_FACES.size(); 52 | return ROTATION_FACES.get(rotatedFaceIndex); 53 | } 54 | 55 | public static BlockFace[] getAxesFaces() { 56 | return new BlockFace[]{ 57 | BlockFace.UP, 58 | BlockFace.DOWN, 59 | BlockFace.WEST, 60 | BlockFace.EAST, 61 | BlockFace.SOUTH, 62 | BlockFace.NORTH}; 63 | } 64 | 65 | public static BlockVec[] getAxesBlockVecs() { 66 | return new BlockVec[]{ 67 | new BlockVec(1, 0, 0), 68 | new BlockVec(0, 1, 0), 69 | new BlockVec(0, 0, 1), 70 | new BlockVec(-1, 0, 0), 71 | new BlockVec(0, -1, 0), 72 | new BlockVec(0, 0, -1)}; 73 | } 74 | 75 | public static Axis getAxis(Block portalBlock) { 76 | return portalBlock.getData() == 2 ? Axis.Z : Axis.X; 77 | } 78 | 79 | public static EnumWrappers.Direction getBlockFaceToDirection(BlockFace face) { 80 | return EnumWrappers.Direction.valueOf(face.name()); 81 | } 82 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/utils/NmsUtils.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.utils; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.Entity; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | 9 | public class NmsUtils { 10 | 11 | private static final String VERSION = Bukkit.getServer().getClass().getName().split("\\.")[3]; 12 | private static Method ENTITY_GET_HANDLE; 13 | private static Method ENTITY_GET_DATA_WATCHER; 14 | 15 | static { 16 | try { 17 | ENTITY_GET_HANDLE = NmsUtils.getCraftBukkitClass("entity.CraftEntity").getMethod("getHandle"); 18 | ENTITY_GET_DATA_WATCHER = NmsUtils.getNmsClass("Entity").getMethod("getDataWatcher"); 19 | } catch (NoSuchMethodException | ClassNotFoundException e) { 20 | e.printStackTrace(); 21 | } 22 | } 23 | 24 | public static Object getHandle(Entity entity) throws InvocationTargetException, IllegalAccessException { 25 | return ENTITY_GET_HANDLE.invoke(entity); 26 | } 27 | 28 | public static Object getDataWatcher(Entity entity) throws InvocationTargetException, IllegalAccessException { 29 | return ENTITY_GET_DATA_WATCHER.invoke(getHandle(entity)); 30 | } 31 | 32 | public static Class getNmsClass(String nmsClassString) throws ClassNotFoundException { 33 | return Class.forName("net.minecraft.server." + VERSION + "." + nmsClassString); 34 | } 35 | 36 | public static Class getCraftBukkitClass(String cbClassString) throws ClassNotFoundException { 37 | return Class.forName("org.bukkit.craftbukkit." + VERSION + "." + cbClassString); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/utils/TeleportUtils.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.utils; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | import org.bukkit.scheduler.BukkitRunnable; 6 | 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.InvocationTargetException; 9 | 10 | public final class TeleportUtils { 11 | 12 | private TeleportUtils() {} 13 | 14 | private static Field FIELD_PLAYER_ABILITIES; 15 | private static Field FIELD_IS_INVULNERABLE; 16 | 17 | static { 18 | 19 | try { 20 | FIELD_PLAYER_ABILITIES = NmsUtils.getNmsClass("EntityPlayer").getField("abilities"); 21 | FIELD_IS_INVULNERABLE = NmsUtils.getNmsClass("PlayerAbilities").getField("isInvulnerable"); 22 | 23 | } catch (ClassNotFoundException | NoSuchFieldException e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | 28 | public static void setTemporarilyInvulnerable(Player player, JavaPlugin plugin, long duration) { 29 | 30 | try { 31 | Object nmsPlayer = NmsUtils.getHandle(player); 32 | Object playerAbilities = FIELD_PLAYER_ABILITIES.get(nmsPlayer); 33 | FIELD_IS_INVULNERABLE.setBoolean(playerAbilities, true); 34 | 35 | new BukkitRunnable() { 36 | @Override 37 | public void run() { 38 | 39 | try { 40 | FIELD_IS_INVULNERABLE.setBoolean(playerAbilities, false); 41 | } catch (IllegalAccessException e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | }.runTaskLater(plugin, duration); 46 | 47 | } catch (SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { 48 | e.printStackTrace(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/utils/TimeUtils.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.utils; 2 | 3 | import java.time.LocalDate; 4 | import java.time.LocalTime; 5 | import java.time.Month; 6 | import java.time.temporal.ChronoUnit; 7 | 8 | public class TimeUtils { 9 | 10 | public static long getTicksTillNextMinute() { 11 | 12 | LocalTime now = LocalTime.now(); 13 | LocalTime nextMinute = now.truncatedTo(ChronoUnit.MINUTES).plusMinutes(1); 14 | return now.until(nextMinute, ChronoUnit.MILLIS) / 50; 15 | } 16 | 17 | public static boolean isSpooktober() { 18 | return LocalDate.now().getMonth() == Month.OCTOBER; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/utils/VersionUtils.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.utils; 2 | 3 | import org.bukkit.Bukkit; 4 | 5 | public final class VersionUtils { 6 | 7 | private VersionUtils() {} 8 | 9 | public static final String VERSION_STRING = Bukkit.getServer().getClass().getName().split("\\.")[3]; 10 | private static final int[] CURRENT_VERSION_INTS = new int[3]; 11 | 12 | static { 13 | String versionStringNumbersOnly = VERSION_STRING.replaceAll("[a-zA-Z]", ""); 14 | System.arraycopy(getVersionAsIntArray(versionStringNumbersOnly, "_"), 0, CURRENT_VERSION_INTS, 0, 3); 15 | } 16 | 17 | public static final boolean IS_LEGACY_SERVER = !serverIsAtOrAbove("1.13.0"); 18 | 19 | public static boolean isVersionLowerThan(String currentVersion, String requestedVersion) { 20 | 21 | int[] currentVersionInts = getVersionAsIntArray(currentVersion, "\\."); 22 | int[] requestedVersionInts = getVersionAsIntArray(requestedVersion, "\\."); 23 | 24 | for (int i = 0; i < Math.min(currentVersionInts.length, requestedVersionInts.length); i++) { 25 | 26 | int versionDiff = currentVersionInts[i] - requestedVersionInts[i]; 27 | 28 | if (versionDiff > 0) { 29 | return false; 30 | }else if (versionDiff < 0) { 31 | return true; 32 | } 33 | } 34 | 35 | return requestedVersionInts.length > currentVersionInts.length; 36 | } 37 | 38 | public static boolean serverIsAtOrAbove(String requestedVersion) { 39 | 40 | int[] requestedVersionInts = getVersionAsIntArray(requestedVersion, "\\."); 41 | 42 | for (int i = 0; i < requestedVersionInts.length; i++) { 43 | 44 | int versionDiff = requestedVersionInts[i] - CURRENT_VERSION_INTS[i]; 45 | 46 | if (versionDiff > 0) { 47 | return false; 48 | }else if (versionDiff < 0){ 49 | return true; 50 | } 51 | } 52 | 53 | return true; 54 | } 55 | 56 | private static int[] getVersionAsIntArray(String version, String delimiter) { 57 | 58 | String[] split = version.split(delimiter); 59 | 60 | if (split.length > 3) { 61 | throw new IllegalArgumentException("Cannot process awfully long version string \"" + version + "\"."); 62 | } 63 | 64 | int[] versionInts = new int[split.length]; 65 | 66 | for (int i = 0; i < versionInts.length; i++) { 67 | versionInts[i] = Integer.parseInt(split[i]); 68 | } 69 | 70 | return versionInts; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/wrapper/Axis.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.wrapper; 2 | 3 | import org.bukkit.util.Vector; 4 | 5 | /** 6 | * A replacement for the Axis enum of bukkit which did not exist before 1.13. 7 | * It is used to describe the orientation of portals which can be along the x-axis or the z-axis. 8 | */ 9 | public enum Axis { 10 | 11 | X(new Vector(0, 0, 1), new Vector(1, 0, 0)), 12 | Z(new Vector(1, 0, 0), new Vector(0, 0, 1)); 13 | 14 | private final Vector normal; 15 | private final Vector crossNormal; 16 | 17 | Axis(Vector normal, Vector crossNormal) { 18 | this.normal = normal; 19 | this.crossNormal = crossNormal; 20 | } 21 | 22 | /** 23 | * Returns the normal vector for the plane aligned to this axis. 24 | */ 25 | public Vector getNormal() { 26 | return normal.clone(); 27 | } 28 | 29 | /** 30 | * Returns a vector that is inside the plane aligned to this axis. 31 | * It like the cross product of the plane normal with the vector (0|1|0) but it's never negative. 32 | */ 33 | public Vector getCrossNormal() { 34 | return crossNormal.clone(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/wrapper/WrappedBoundingBox.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.wrapper; 2 | 3 | import me.gorgeousone.netherview.blockcache.BlockCache; 4 | import me.gorgeousone.netherview.geometry.viewfrustum.ViewFrustum; 5 | import me.gorgeousone.netherview.utils.NmsUtils; 6 | import me.gorgeousone.netherview.utils.VersionUtils; 7 | import org.bukkit.Location; 8 | import org.bukkit.entity.Entity; 9 | import org.bukkit.entity.EntityType; 10 | import org.bukkit.util.BoundingBox; 11 | import org.bukkit.util.Vector; 12 | 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.InvocationTargetException; 15 | import java.lang.reflect.Method; 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.List; 19 | 20 | /** 21 | * A wrapper for the extent of a bounding boxes of an entity throughout different Minecraft versions. 22 | */ 23 | public class WrappedBoundingBox { 24 | 25 | private static Method ENTITY_GET_AABB; 26 | private static Field AABB_MIN_X; 27 | private static Field AABB_MIN_Y; 28 | private static Field AABB_MIN_Z; 29 | 30 | private static Field AABB_MAX_X; 31 | private static Field AABB_MAX_Y; 32 | private static Field AABB_MAX_Z; 33 | 34 | static { 35 | 36 | if (VersionUtils.IS_LEGACY_SERVER) { 37 | 38 | try { 39 | ENTITY_GET_AABB = NmsUtils.getNmsClass("Entity").getDeclaredMethod("getBoundingBox"); 40 | Class aabbClass = NmsUtils.getNmsClass("AxisAlignedBB"); 41 | 42 | AABB_MIN_X = aabbClass.getDeclaredField("a"); 43 | AABB_MIN_Y = aabbClass.getDeclaredField("b"); 44 | AABB_MIN_Z = aabbClass.getDeclaredField("c"); 45 | AABB_MAX_X = aabbClass.getDeclaredField("d"); 46 | AABB_MAX_Y = aabbClass.getDeclaredField("e"); 47 | AABB_MAX_Z = aabbClass.getDeclaredField("f"); 48 | 49 | } catch (NoSuchMethodException | ClassNotFoundException | NoSuchFieldException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | } 54 | 55 | private final double widthX; 56 | private final double widthZ; 57 | private final double height; 58 | private final List vertices; 59 | 60 | public WrappedBoundingBox(Entity entity, Location entityLoc, double widthX, double height, double widthZ) { 61 | 62 | this.widthX = widthX; 63 | this.widthZ = widthZ; 64 | this.height = height; 65 | 66 | //dunno, paintings are a bit off 67 | if (entity.getType() == EntityType.PAINTING && VersionUtils.IS_LEGACY_SERVER) { 68 | entityLoc.subtract(0, height / 2, 0); 69 | } 70 | 71 | Vector min = entityLoc.clone().subtract( 72 | widthX / 2, 73 | 0, 74 | widthZ / 2).toVector(); 75 | 76 | Vector max = entityLoc.clone().add( 77 | widthX / 2, 78 | height, 79 | widthZ / 2).toVector(); 80 | 81 | vertices = new ArrayList<>(Arrays.asList( 82 | min, 83 | new Vector(max.getX(), min.getY(), min.getZ()), 84 | new Vector(min.getX(), min.getY(), max.getZ()), 85 | new Vector(max.getX(), min.getY(), max.getZ()), 86 | new Vector(min.getX(), max.getY(), min.getZ()), 87 | new Vector(max.getX(), max.getY(), min.getZ()), 88 | new Vector(min.getX(), max.getY(), max.getZ()), 89 | max 90 | )); 91 | } 92 | 93 | public double getWidthX() { 94 | return widthX; 95 | } 96 | 97 | public double getWidthZ() { 98 | return widthZ; 99 | } 100 | 101 | public double getHeight() { 102 | return height; 103 | } 104 | 105 | /** 106 | * Returns the 8 vertices of the entity's bounding box 107 | */ 108 | public List getVertices() { 109 | return vertices; 110 | } 111 | 112 | public static WrappedBoundingBox of(Entity entity) { 113 | return of(entity, entity.getLocation()); 114 | } 115 | 116 | public static WrappedBoundingBox of(Entity entity, Location entityLoc) { 117 | 118 | if (VersionUtils.IS_LEGACY_SERVER) { 119 | 120 | try { 121 | Object entityAabb = ENTITY_GET_AABB.invoke(NmsUtils.getHandle(entity)); 122 | 123 | return new WrappedBoundingBox( 124 | entity, 125 | entityLoc, 126 | getBoxWidthX(entityAabb), 127 | getBoxHeight(entityAabb), 128 | getBoxWidthZ(entityAabb)); 129 | 130 | } catch (IllegalAccessException | InvocationTargetException e) { 131 | 132 | e.printStackTrace(); 133 | return null; 134 | } 135 | 136 | } else { 137 | 138 | BoundingBox box = entity.getBoundingBox(); 139 | 140 | return new WrappedBoundingBox( 141 | entity, 142 | entityLoc, 143 | box.getWidthX(), 144 | box.getHeight(), 145 | box.getWidthZ()); 146 | } 147 | } 148 | 149 | private static double getBoxWidthX(Object aabb) throws IllegalAccessException { 150 | return AABB_MAX_X.getDouble(aabb) - AABB_MIN_X.getDouble(aabb); 151 | } 152 | 153 | private static double getBoxWidthZ(Object aabb) throws IllegalAccessException { 154 | return AABB_MAX_Z.getDouble(aabb) - AABB_MIN_Z.getDouble(aabb); 155 | } 156 | 157 | private static double getBoxHeight(Object aabb) throws IllegalAccessException { 158 | return AABB_MAX_Y.getDouble(aabb) - AABB_MIN_Y.getDouble(aabb); 159 | } 160 | 161 | /** 162 | * Returns true if any of the 8 vertices of the bounding box are inside of the block cache. 163 | */ 164 | public boolean intersectsBlockCache(BlockCache cache) { 165 | 166 | for (Vector vertex : getVertices()) { 167 | 168 | if (cache.contains(vertex)) { 169 | return true; 170 | } 171 | } 172 | return false; 173 | } 174 | 175 | /** 176 | * Returns true if any of the 8 vertices of the bounding box are inside of the view frustum. 177 | */ 178 | public boolean intersectsFrustum(ViewFrustum viewFrustum) { 179 | 180 | for (Vector vertex : getVertices()) { 181 | 182 | if (viewFrustum.contains(vertex)) { 183 | return true; 184 | } 185 | } 186 | return false; 187 | } 188 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/wrapper/blocktype/AquaticBlockType.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.wrapper.blocktype; 2 | 3 | import com.comphenix.protocol.wrappers.WrappedBlockData; 4 | import me.gorgeousone.netherview.utils.FacingUtils; 5 | import me.gorgeousone.netherview.wrapper.rotation.AquaticRailUtils; 6 | import org.bukkit.Axis; 7 | import org.bukkit.Material; 8 | import org.bukkit.block.Block; 9 | import org.bukkit.block.BlockFace; 10 | import org.bukkit.block.BlockState; 11 | import org.bukkit.block.data.BlockData; 12 | import org.bukkit.block.data.Directional; 13 | import org.bukkit.block.data.MultipleFacing; 14 | import org.bukkit.block.data.Orientable; 15 | import org.bukkit.block.data.Rail; 16 | import org.bukkit.block.data.Rotatable; 17 | import org.bukkit.block.data.type.RedstoneWire; 18 | 19 | import java.util.HashMap; 20 | import java.util.HashSet; 21 | import java.util.Locale; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | import java.util.Set; 25 | 26 | /** 27 | * Wrapper for the block data of blocks after the aquatic update (1.13) 28 | */ 29 | public class AquaticBlockType extends BlockType { 30 | 31 | private final BlockData blockData; 32 | 33 | public AquaticBlockType(Material material) { 34 | blockData = material.createBlockData(); 35 | } 36 | 37 | public AquaticBlockType(Block block) { 38 | blockData = block.getBlockData().clone(); 39 | } 40 | 41 | public AquaticBlockType(BlockState state) { 42 | blockData = state.getBlockData().clone(); 43 | } 44 | 45 | public AquaticBlockType(BlockData data) { 46 | blockData = data.clone(); 47 | } 48 | 49 | public AquaticBlockType(String serialized) { 50 | blockData = Material.valueOf(serialized.toUpperCase(Locale.ENGLISH)).createBlockData(); 51 | } 52 | 53 | @Override 54 | public BlockType rotate(int quarterTurns) { 55 | 56 | if (quarterTurns == 0) { 57 | return this; 58 | } 59 | 60 | //e.g. logs 61 | if (blockData instanceof Orientable) { 62 | 63 | if (quarterTurns % 2 == 0) { 64 | return this; 65 | } 66 | 67 | Orientable orientable = (Orientable) blockData; 68 | 69 | if (orientable.getAxis() != Axis.Y) { 70 | orientable.setAxis(orientable.getAxis() == Axis.X ? Axis.Z : Axis.X); 71 | } 72 | 73 | //e.g. furnaces, hoppers 74 | } else if (blockData instanceof Directional) { 75 | 76 | Directional directional = (Directional) blockData; 77 | directional.setFacing(FacingUtils.getRotatedFace(directional.getFacing(), quarterTurns)); 78 | 79 | //e.g. signs 80 | } else if (blockData instanceof Rotatable) { 81 | 82 | Rotatable rotatable = (Rotatable) blockData; 83 | rotatable.setRotation(FacingUtils.getRotatedFace(rotatable.getRotation(), quarterTurns)); 84 | 85 | //e.g. fences 86 | } else if (blockData instanceof MultipleFacing) { 87 | 88 | MultipleFacing multiFacing = (MultipleFacing) blockData; 89 | Set facings = new HashSet<>(multiFacing.getFaces()); 90 | 91 | for (BlockFace face : multiFacing.getAllowedFaces()) { 92 | multiFacing.setFace(face, false); 93 | } 94 | 95 | for (BlockFace face : facings) { 96 | multiFacing.setFace(FacingUtils.getRotatedFace(face, quarterTurns), true); 97 | } 98 | 99 | } else if (blockData instanceof RedstoneWire) { 100 | 101 | RedstoneWire wire = (RedstoneWire) blockData; 102 | Map connections = new HashMap<>(); 103 | 104 | for (BlockFace face : wire.getAllowedFaces()) { 105 | connections.put(face, wire.getFace(face)); 106 | } 107 | 108 | for (BlockFace face : connections.keySet()) 109 | wire.setFace(FacingUtils.getRotatedFace(face, quarterTurns), connections.get(face)); 110 | 111 | } else if (blockData instanceof Rail) { 112 | 113 | Rail rail = (Rail) blockData; 114 | rail.setShape(AquaticRailUtils.getRotatedRail(rail.getShape(), quarterTurns)); 115 | } 116 | 117 | return this; 118 | } 119 | 120 | @Override 121 | public WrappedBlockData getWrapped() { 122 | return WrappedBlockData.createData(blockData); 123 | } 124 | 125 | @Override 126 | public boolean isOccluding() { 127 | return blockData.getMaterial().isOccluding(); 128 | } 129 | 130 | @Override 131 | public int hashCode() { 132 | return Objects.hash(blockData); 133 | } 134 | 135 | @Override 136 | public AquaticBlockType clone() { 137 | return new AquaticBlockType(blockData); 138 | } 139 | 140 | @Override 141 | public boolean equals(Object o) { 142 | if (this == o) { 143 | return true; 144 | } 145 | if (!(o instanceof AquaticBlockType)) { 146 | return false; 147 | } 148 | AquaticBlockType blockType = (AquaticBlockType) o; 149 | return blockData.equals(blockType.blockData); 150 | } 151 | 152 | @Override 153 | public String toString() { 154 | return "AquaBlock{" + blockData.getAsString() + '}'; 155 | } 156 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/wrapper/blocktype/BlockType.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.wrapper.blocktype; 2 | 3 | import com.comphenix.protocol.wrappers.WrappedBlockData; 4 | import org.bukkit.Material; 5 | import org.bukkit.block.Block; 6 | import org.bukkit.block.BlockState; 7 | 8 | /** 9 | * A wrapper for block materials and their (block-) data to support copying blocks across all Minecraft versions. 10 | */ 11 | public abstract class BlockType { 12 | 13 | private static boolean isLegacyServer; 14 | 15 | /** 16 | * Sets whether BlockType.of() will create a LegacyBlockType or an AquaticBlockType 17 | */ 18 | public static void configureVersion(boolean isLegacyServer) { 19 | BlockType.isLegacyServer = isLegacyServer; 20 | } 21 | 22 | public static BlockType of(Block block) { 23 | return isLegacyServer ? new LegacyBlockType(block) : new AquaticBlockType(block); 24 | } 25 | 26 | public static BlockType of(Material material) { 27 | return isLegacyServer ? new LegacyBlockType(material) : new AquaticBlockType(material); 28 | } 29 | 30 | public static BlockType of(BlockState state) { 31 | return isLegacyServer ? new LegacyBlockType(state) : new AquaticBlockType(state); 32 | } 33 | 34 | public static BlockType of(String serialized) { 35 | return isLegacyServer ? new LegacyBlockType(serialized) : new AquaticBlockType(serialized); 36 | } 37 | 38 | /** 39 | * Rotates the BlockType if it is rotatable in the xz plane in any way 40 | * 41 | * @param quarterTurns count of 90° turns performed (between 0 and 3) 42 | */ 43 | public abstract BlockType rotate(int quarterTurns); 44 | 45 | public abstract WrappedBlockData getWrapped(); 46 | 47 | public abstract boolean isOccluding(); 48 | 49 | public abstract BlockType clone(); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/wrapper/blocktype/LegacyBlockType.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.wrapper.blocktype; 2 | 3 | import com.comphenix.protocol.wrappers.WrappedBlockData; 4 | import me.gorgeousone.netherview.utils.FacingUtils; 5 | import org.bukkit.Material; 6 | import org.bukkit.block.Block; 7 | import org.bukkit.block.BlockFace; 8 | import org.bukkit.block.BlockState; 9 | import org.bukkit.material.Directional; 10 | import org.bukkit.material.MaterialData; 11 | import org.bukkit.material.Mushroom; 12 | import org.bukkit.material.Rails; 13 | 14 | import java.util.HashSet; 15 | import java.util.Locale; 16 | import java.util.Objects; 17 | import java.util.Set; 18 | 19 | /** 20 | * A wrapper for material data used before the aquatic update (1.12 and before) 21 | */ 22 | @SuppressWarnings("deprecation") 23 | public class LegacyBlockType extends BlockType { 24 | 25 | private final MaterialData materialData; 26 | 27 | public LegacyBlockType(Material material) { 28 | materialData = new MaterialData(material); 29 | } 30 | 31 | public LegacyBlockType(Block block) { 32 | materialData = block.getState().getData().clone(); 33 | } 34 | 35 | public LegacyBlockType(BlockState state) { 36 | materialData = state.getData().clone(); 37 | } 38 | 39 | public LegacyBlockType(MaterialData data) { 40 | materialData = data.clone(); 41 | } 42 | 43 | public LegacyBlockType(String serialized) { 44 | 45 | Material material; 46 | byte data = 0; 47 | 48 | if (serialized.contains(":")) { 49 | 50 | String[] fullData = serialized.split(":"); 51 | 52 | material = Material.valueOf(fullData[0].toUpperCase(Locale.ENGLISH)); 53 | data = Byte.parseByte(fullData[1]); 54 | 55 | } else { 56 | material = Material.valueOf(serialized.toUpperCase(Locale.ENGLISH)); 57 | } 58 | 59 | materialData = new MaterialData(material, data); 60 | } 61 | 62 | @Override 63 | public BlockType rotate(int quarterTurns) { 64 | 65 | if (quarterTurns == 0) { 66 | return this; 67 | } 68 | 69 | if (materialData instanceof Directional) { 70 | 71 | Directional directional = (Directional) materialData; 72 | BlockFace facing = directional.getFacing(); 73 | 74 | //somehow the facing of stairs is always reversed, nothing else, just stairs 75 | if (materialData.getItemType().name().contains("STAIRS")) { 76 | facing = facing.getOppositeFace(); 77 | } 78 | 79 | directional.setFacingDirection(FacingUtils.getRotatedFace(facing, quarterTurns)); 80 | 81 | } else if (materialData instanceof Rails) { 82 | 83 | Rails rails = (Rails) materialData; 84 | BlockFace facing = rails.getDirection(); 85 | rails.setDirection(FacingUtils.getRotatedFace(facing, quarterTurns), rails.isOnSlope()); 86 | 87 | } else if (materialData instanceof Mushroom) { 88 | 89 | Mushroom mushroom = (Mushroom) materialData; 90 | Set paintedFaces = new HashSet<>(mushroom.getPaintedFaces()); 91 | 92 | for (BlockFace paintedFace : paintedFaces) { 93 | mushroom.setFacePainted(paintedFace, false); 94 | } 95 | 96 | for (BlockFace paintedFace : paintedFaces) { 97 | mushroom.setFacePainted(FacingUtils.getRotatedFace(paintedFace, quarterTurns), true); 98 | } 99 | } 100 | return this; 101 | } 102 | 103 | @Override 104 | public WrappedBlockData getWrapped() { 105 | return WrappedBlockData.createData(materialData.getItemType(), materialData.getData()); 106 | } 107 | 108 | @Override 109 | public boolean isOccluding() { 110 | return materialData.getItemType().isOccluding(); 111 | } 112 | 113 | @Override 114 | public LegacyBlockType clone() { 115 | return new LegacyBlockType(materialData.clone()); 116 | } 117 | 118 | @Override 119 | public boolean equals(Object o) { 120 | if (this == o) { 121 | return true; 122 | } 123 | if (!(o instanceof LegacyBlockType)) { 124 | return false; 125 | } 126 | LegacyBlockType blockType = (LegacyBlockType) o; 127 | return materialData.equals(blockType.materialData); 128 | } 129 | 130 | @Override 131 | public int hashCode() { 132 | return Objects.hash(materialData); 133 | } 134 | 135 | @Override 136 | public String toString() { 137 | return "LegacyBlock{" + materialData.toString() + '}'; 138 | } 139 | } -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/wrapper/rotation/AquaticRailUtils.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.wrapper.rotation; 2 | 3 | import org.bukkit.block.data.Rail; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class AquaticRailUtils { 10 | 11 | private final static List STRAIGHT_RAILS = new ArrayList<>(Arrays.asList( 12 | Rail.Shape.NORTH_SOUTH, 13 | Rail.Shape.EAST_WEST 14 | )); 15 | 16 | private final static List CURVED_RAILS = new ArrayList<>(Arrays.asList( 17 | Rail.Shape.NORTH_WEST, 18 | Rail.Shape.NORTH_EAST, 19 | Rail.Shape.SOUTH_EAST, 20 | Rail.Shape.SOUTH_WEST 21 | )); 22 | 23 | private final static List ASCENDING_RAILS = new ArrayList<>(Arrays.asList( 24 | Rail.Shape.ASCENDING_NORTH, 25 | Rail.Shape.ASCENDING_EAST, 26 | Rail.Shape.ASCENDING_SOUTH, 27 | Rail.Shape.ASCENDING_WEST 28 | )); 29 | 30 | public static Rail.Shape getRotatedRail(Rail.Shape railShape, int quarterTurns) { 31 | 32 | if (STRAIGHT_RAILS.contains(railShape)) { 33 | 34 | if (quarterTurns % 2 == 0) { 35 | return railShape; 36 | } 37 | 38 | return railShape == Rail.Shape.NORTH_SOUTH ? Rail.Shape.EAST_WEST : Rail.Shape.NORTH_SOUTH; 39 | 40 | } else if (CURVED_RAILS.contains(railShape)) { 41 | 42 | int rotatedShapeIndex = (CURVED_RAILS.indexOf(railShape) + quarterTurns) % CURVED_RAILS.size(); 43 | return CURVED_RAILS.get(rotatedShapeIndex); 44 | 45 | } else { 46 | 47 | int rotatedShapeIndex = (ASCENDING_RAILS.indexOf(railShape) + quarterTurns) % ASCENDING_RAILS.size(); 48 | return ASCENDING_RAILS.get(rotatedShapeIndex); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/me/gorgeousone/netherview/wrapper/rotation/RotationUtils.java: -------------------------------------------------------------------------------- 1 | package me.gorgeousone.netherview.wrapper.rotation; 2 | 3 | public class RotationUtils { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | portal-display-range: 16 2 | portal-projection-distance: 4 3 | max-portal-size: 5 4 | 5 | hide-entities-behind-portals: true 6 | show-entities-inside-portals: true 7 | entity-update-ticks: 3 8 | 9 | warning-messages: true 10 | debug-messages: false 11 | 12 | nether-portal-viewing: 13 | flip-portals-by-default: true 14 | hide-portal-blocks: true 15 | cancel-teleport-when-linking-portals: true 16 | instant-teleport: true 17 | whitelisted-worlds: 18 | - world 19 | - world_nether 20 | blacklisted-worlds: 21 | - null 22 | 23 | custom-portal-viewing: 24 | whitelisted-worlds: 25 | - "*" 26 | blacklisted-worlds: 27 | - null -------------------------------------------------------------------------------- /src/main/resources/config2.yml: -------------------------------------------------------------------------------- 1 | max-portal-size: 5 2 | portal-view-distance: 4 3 | portal-display-range: 32 4 | flip-portals-by-default: false 5 | hide-portal-blocks: true 6 | cancel-teleport-when-linking-portals: true 7 | instant-teleport: true 8 | hide-entities-behind-portals: true 9 | show-entities-inside-portals: true 10 | warning-messages: true 11 | debug-messages: false 12 | worlds-with-portal-viewing: 13 | - world 14 | - world_nether 15 | world-black-list: 16 | - null 17 | -------------------------------------------------------------------------------- /src/main/resources/language.yml: -------------------------------------------------------------------------------- 1 | successful-portal-linking: '&7&oThe veil between the two worlds has lifted a little bit!' 2 | unequal-portal-sizes: '&7These two portals are not the same size.' 3 | portal-frame-incomplete: '&7All frame blocks of this %world-type%-world portal need to be occluding blocks.' 4 | portal-corners-incomplete: '&7All four corners corners of this %world-type%-world portal need to be occluding blocks.' 5 | portal-too-big: '&7Cannot make portal views bigger than %size% blocks!' 6 | portal-not-intact: '&7Some portal blocks of this %world-type%-world portal seem to be broken.' 7 | world-not-white-listed: |- 8 | &7NetherView is not enabled in world &r%world%&7. 9 | &7You can enable it by adding the world's name to 'worlds-with-portal-viewing' in the config. 10 | no-world-found: '&7No world found with name &r%world%&7.' 11 | no-portals-found: '&7There are no nether portals listed for world &r%world%&7.' 12 | no-portal-found-nearby: '&7You need to look at a nether portal with NetherView enabled for this command to work.' 13 | portal-info: '&7Info about portal at &r%location%: %info%' 14 | 15 | world-info: '%count% &7portal(s) listed for world &r%world%&7: %portals%' 16 | flipped-portal: '&7Flipped view of portal &r%portal%&7.' 17 | portal-viewing-on: '&7Enabled portal viewing for you.' 18 | portal-viewing-off: '&7Disabled portal viewing for you.' 19 | 20 | wand-info: '&7Left click to set first portal corner. Right click to set the second one. Use &r/nv createportal&7 to create the portal.' 21 | selection-incomplete: '&7Please select two blocks with a portal wand first.' 22 | set-first-position: '&7First position set %position%.' 23 | set-second-position: '&7Second position set %position%.' 24 | selection-too-small: '&7Your selection is too small to create a portal.' 25 | selection-not-flat: '&7A selection must be 1 block wide in either X or Z direction.' 26 | portals-intersect: '&7This portal would overlap with another portal that is already here.' 27 | portal-name-not-valid: '&7A portal name can be max. 32 characters long and may only contain alphanumeric letters and dashes/underscores.' 28 | portal-name-not-unique: '&7There is already a portal named &r%name%&7.' 29 | created-portal: '&7Created portal &r%name%&7 (%size% blocks).' 30 | no-portal-found-with-name: '&7No portal found with name &r%name%&7.' 31 | deleted-portal: '&7Removed portal &r%name%&7.' 32 | linked-portal: '&7Linked portal &r%from-portal%&7 to portal &r%to-portal%&7.' 33 | unlinked-portal: '&7Removed link of portal &r%portal%&7.' 34 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: NetherView 2 | version: ${project.version} 3 | api-version: 1.13 4 | 5 | main: me.gorgeousone.netherview.NetherViewPlugin 6 | 7 | softdepend: [ProtocolLib] 8 | 9 | commands: 10 | netherview: 11 | aliases: [nv] 12 | toggleportalview: 13 | description: Toggles whether you can see potal projections or not for yourself. 14 | 15 | permissions: 16 | netherview.viewportals: 17 | default: true 18 | netherview.linkportals: 19 | default: true 20 | netherview.config: 21 | default: op 22 | netherview.info: 23 | default: op 24 | netherview.flipportal: 25 | default: op 26 | netherview.portalwand: 27 | default: op 28 | netherview.customportals: 29 | defult: op --------------------------------------------------------------------------------