From 52b937f7d2792d872f13136a3f1eea0c869ed0f2 Mon Sep 17 00:00:00 2001 From: Marissa Staib Date: Wed, 10 Apr 2019 14:50:26 -0400 Subject: [PATCH] Nauseating teleportation and bigger portal frames --- TODO | 13 +- build.gradle | 2 +- .../minecraftportal/MinecraftPortalPlugin.kt | 122 ++++++++++++++++- .../minecraftportal/PortalFrame.kt | 126 +++++++++++++----- src/main/resources/plugin.yml | 1 + 5 files changed, 217 insertions(+), 47 deletions(-) create mode 100644 src/main/resources/plugin.yml diff --git a/TODO b/TODO index 98405f2..74475a5 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,9 @@ -* Detect a glass frame being placed -* Detect a glass frame with a mineral block -* Transform the glass frame to stained glass -* Test portal block teleportation interruption \ No newline at end of file +* identify and pair portals by type/color +* save portal pairs to disk and reload them on restarting the server +* deactivate most recently placed portal and replace with newly placed one when 3+ portals are created +* teleport from one to the other a set time after entering a teleportation chamber and closing the door +* automatically open the door after effects wear off +* close the door on the other side when someone shuts the door +* teleport everyone inside the portal, not just the person who closed the door +* interrupt teleportation if portal is destroyed mid-teleportation +* save any effects that would be overwritten by teleportation (including to disk!), and put them back when the player opens the door or arrives \ No newline at end of file diff --git a/build.gradle b/build.gradle index 85b38ff..e9d770c 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ dependencies { } bukkit { - version = "1.12.2" + version = "1.13.2" // Attributes for plugin.yml meta { diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt index f27bb5e..0a11fe2 100644 --- a/src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt @@ -1,10 +1,23 @@ package net.deliciousreya.minecraftportal +import com.destroystokyo.paper.event.block.BlockDestroyEvent +import org.bukkit.Effect import org.bukkit.Material +import org.bukkit.Particle +import org.bukkit.Sound +import org.bukkit.block.Block +import org.bukkit.block.data.type.Door import org.bukkit.event.EventHandler import org.bukkit.event.Listener -import org.bukkit.event.block.BlockPlaceEvent +import org.bukkit.event.block.* +import org.bukkit.event.entity.CreatureSpawnEvent +import org.bukkit.event.entity.EntityChangeBlockEvent +import org.bukkit.event.entity.EntityExplodeEvent +import org.bukkit.event.player.PlayerBucketFillEvent +import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType class MinecraftPortalPlugin() : JavaPlugin(), Listener { @@ -16,14 +29,109 @@ class MinecraftPortalPlugin() : JavaPlugin(), Listener @EventHandler fun onBlockPlaced(e: BlockPlaceEvent) { - logger.info("block placed of type " + e.block.type) - if (e.block.type.equals(Material.GLASS)) { - logger.info("put down glass at " + e.block.location) - val portalScanResults = findPortalFrameConnectedTo(e.block) + if (e.block.type in PortalFrame.State.INACTIVE.allValidBlocks) { + val portalScanResults = findPortalFrameConnectedTo(e.block, PortalFrame.State.INACTIVE) if (portalScanResults.frame != null) { - logger.info("found portal frame $portalScanResults") + logger.info("found portal frame, creating portal") + portalScanResults.frame.color() + e.block.world.playSound(portalScanResults.frame.portalCenter, Sound.BLOCK_BEACON_ACTIVATE, 20f, 1f) + e.block.world.spawnParticle(Particle.SPELL, portalScanResults.frame.portalCenter, 40) } else { - logger.info("no portal frame found, best scan was $portalScanResults") + logger.info("no portal frame found matching placed block, ignoring") + } + } + } + + @EventHandler + fun onBlockDestroy(e:BlockDestroyEvent) { + onDestroyedBlock(e.block) + } + + @EventHandler + fun onEntityExplosion(e:EntityExplodeEvent) { + e.blockList().forEach(::onDestroyedBlock) + } + + @EventHandler + fun onBlockExplosion(e:BlockExplodeEvent) { + onDestroyedBlock(e.block) + e.blockList().forEach(::onDestroyedBlock) + } + + @EventHandler + fun onBlockBurn(e: BlockBurnEvent) { + onDestroyedBlock(e.block) + } + + @EventHandler + fun onBlockFade(e: BlockFadeEvent) { + onDestroyedBlock(e.block) + } + + @EventHandler + fun onSpongeAbsorb(e: SpongeAbsorbEvent) { + e.blocks.forEach { blockstate -> onDestroyedBlock(blockstate.location.block) } + } + + @EventHandler + fun onBlockBreak(e:BlockBreakEvent) { + onDestroyedBlock(e.block) + } + + @EventHandler + fun onEntityChangeBlock(e: EntityChangeBlockEvent) { + onDestroyedBlock(e.block) + } + + @EventHandler + fun onPlayerFillBucket(e: PlayerBucketFillEvent) { + onDestroyedBlock(e.blockClicked) + } + + @EventHandler + fun onPlayerInteract(e: PlayerInteractEvent) { + if (e.clickedBlock == null || e.action != Action.RIGHT_CLICK_BLOCK) { + return + } + val block = e.clickedBlock!! + if(block.type !in DOOR_TYPES) { + return + } + val portalScanResults = findPortalFrameConnectedTo(block, PortalFrame.State.ACTIVE) + if (portalScanResults.frame == null) { + return + } + val door = block.blockData as Door + + if (!portalScanResults.frame.isStandingInPortal(e.player.location)) { + e.isCancelled = true + return + } + if (door.isOpen) { + // door is about to be closed, so drug the player + e.player.playSound(e.player.location, Sound.BLOCK_PORTAL_TRIGGER, 20f, 1f) + e.player.addPotionEffect(PotionEffect(PotionEffectType.CONFUSION, 200, 10, false, false, false)) + e.player.addPotionEffect(PotionEffect(PotionEffectType.BLINDNESS, 200, 1, false, false, false)) + e.player.addPotionEffect(PotionEffect(PotionEffectType.INVISIBILITY, 200, 1, false, false, false)) + } else { + // door is about to be opened, so undrug the player + e.player.stopSound(Sound.BLOCK_PORTAL_TRIGGER) + e.player.removePotionEffect(PotionEffectType.BLINDNESS) + e.player.removePotionEffect(PotionEffectType.CONFUSION) + e.player.removePotionEffect(PotionEffectType.INVISIBILITY) + } + } + + fun onDestroyedBlock(block: Block) { + if (block.type in PortalFrame.State.ACTIVE.allValidBlocks) { + val portalScanResults = findPortalFrameConnectedTo(block, PortalFrame.State.ACTIVE) + if (portalScanResults.frame != null) { + logger.info("found portal frame matching destroyed block, deactivating") + portalScanResults.frame.uncolor() + block.world.playSound(portalScanResults.frame.portalCenter, Sound.BLOCK_BEACON_DEACTIVATE, 20f, 1f) + block.world.spawnParticle(Particle.SMOKE_NORMAL, portalScanResults.frame.portalCenter, 40) + } else { + logger.info("no portal frame found matching destroyed block, ignoring") } } } diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/PortalFrame.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/PortalFrame.kt index a86f886..b46a305 100644 --- a/src/main/kotlin/net/deliciousreya/minecraftportal/PortalFrame.kt +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/PortalFrame.kt @@ -1,31 +1,54 @@ package net.deliciousreya.minecraftportal import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.ImmutableSet import org.bukkit.Location -import org.bukkit.Material.AIR -import org.bukkit.Material.GLASS import org.bukkit.block.Block import org.bukkit.util.Vector import net.deliciousreya.minecraftportal.extensions.* +import org.bukkit.Material +import org.bukkit.Material.* +import org.bukkit.block.BlockFace -fun findPortalFrameConnectedTo(block:Block): PortalScanResults { +val MINERAL_TYPES: ImmutableMap = ImmutableMap.Builder() + .put(COAL_BLOCK, BLACK_STAINED_GLASS) + .put(REDSTONE_BLOCK, RED_STAINED_GLASS) + .put(LAPIS_BLOCK, BLUE_STAINED_GLASS) + .put(GOLD_BLOCK, YELLOW_STAINED_GLASS) + .put(DIAMOND_BLOCK, CYAN_STAINED_GLASS) + .put(EMERALD_BLOCK, GREEN_STAINED_GLASS) + .put(IRON_BLOCK, GRAY_STAINED_GLASS) + .put(QUARTZ_BLOCK, WHITE_STAINED_GLASS) + .build() + +val DOOR_TYPES: ImmutableSet = ImmutableSet.of(ACACIA_DOOR, BIRCH_DOOR, DARK_OAK_DOOR, IRON_DOOR, JUNGLE_DOOR, OAK_DOOR, SPRUCE_DOOR) + +fun findPortalFrameConnectedTo(block:Block, state:PortalFrame.State): PortalScanResults { var bestScan:PortalScanResults? = null - if (block.type == GLASS) { - for (direction in PortalFrame.Direction.values()) { + when { + block.type in state.glassBlocks -> for (direction in PortalFrame.EntranceDirection.values()) { for (vector in direction.glassOffsets) { - val scanResult = checkPortalFrameAt(block.location.subtract(vector), direction) + val scanResult = checkPortalFrameAt(block.location.subtract(vector), direction, state) if (scanResult.frame != null) { return scanResult + } else if (bestScan == null || scanResult > bestScan) { + bestScan = scanResult } - if (bestScan == null || scanResult > bestScan) { + } + } + block.type in state.airBlocks -> for (direction in PortalFrame.EntranceDirection.values()) { + for (vector in direction.airOffsets) { + val scanResult = checkPortalFrameAt(block.location.subtract(vector), direction, state) + if (scanResult.frame != null) { + return scanResult + } else if (bestScan == null || scanResult > bestScan) { bestScan = scanResult } } } - } - if (block.type == GLASS) { - for (direction in PortalFrame.Direction.values()) { - val scanResult = checkPortalFrameAt(block.location.subtract(direction.mineralOffset), direction) + block.type in state.mineralBlocks -> for (direction in PortalFrame.EntranceDirection.values()) { + val scanResult = checkPortalFrameAt(block.location.subtract(direction.mineralOffset), direction, state) if (scanResult.frame != null) { return scanResult } else if (bestScan == null || scanResult > bestScan) { @@ -33,27 +56,23 @@ fun findPortalFrameConnectedTo(block:Block): PortalScanResults { } } } - if (bestScan != null) { - return bestScan - } else { - return PortalScanResults(null, ImmutableList.of(), ImmutableList.of()) - } + return bestScan!! } -/** Detects whether there is a portal frame in the given location, extending in the given direction. */ -fun checkPortalFrameAt(location:Location, direction:PortalFrame.Direction): PortalScanResults { +/** Detects whether there is a portal frame in the given state at the given location, extending in the given direction. */ +fun checkPortalFrameAt(location:Location, direction:PortalFrame.EntranceDirection, state:PortalFrame.State): PortalScanResults { val matches = ImmutableList.Builder() val nonmatches = ImmutableList.Builder() for (vector in direction.glassOffsets) { val block = (location + vector).block - (if (block.type == GLASS) matches else nonmatches).add(block) + (if (block.type in state.glassBlocks) matches else nonmatches).add(block) } for (vector in direction.airOffsets) { val block = (location + vector).block - (if (block.type == AIR) matches else nonmatches).add(block) + (if (block.type in state.airBlocks) matches else nonmatches).add(block) } val block = (location + direction.mineralOffset).block - (if (block.type == GLASS) matches else nonmatches).add(block) + (if (block.type in state.mineralBlocks) matches else nonmatches).add(block) val nonmatchesBuilt = nonmatches.build() return PortalScanResults(if (nonmatchesBuilt.isEmpty()) {PortalFrame(location, direction)} else {null}, matches.build(), nonmatchesBuilt) } @@ -65,32 +84,69 @@ data class PortalScanResults(val frame: PortalFrame?, val matchingBlocks:List, val airBlocks: ImmutableSet, val mineralBlocks: ImmutableSet) { + ACTIVE(ImmutableSet.copyOf(MINERAL_TYPES.values), DOOR_TYPES, ImmutableSet.copyOf(MINERAL_TYPES.keys)), + INACTIVE(ImmutableSet.of(GLASS), DOOR_TYPES, ImmutableSet.copyOf(MINERAL_TYPES.keys)); - private val rightSideVector = vector * 2 + val allValidBlocks = ImmutableSet.Builder().addAll(glassBlocks).addAll(airBlocks).addAll(mineralBlocks).build() + } + /** The direction along which a portal frame extends. */ + enum class EntranceDirection(toRight: Vector, toBack: Vector, doorDirection: BlockFace) { + NORTH(Vector(1, 0, 0), Vector(0, 0, 1), BlockFace.NORTH), + SOUTH(Vector(1, 0, 0), Vector(0, 0, -1), BlockFace.SOUTH), + EAST(Vector(0, 0, 1), Vector(-1, 0, 0), BlockFace.EAST), + WEST(Vector(0, 0, 1), Vector(1, 0, 0), BlockFace.WEST); val glassOffsets: ImmutableList = ImmutableList.of( Vector(0, 0, 0), Vector(0, 1, 0), Vector(0, 2, 0), Vector(0, 3, 0), - vector, - rightSideVector, - Vector(0, 1, 0) + rightSideVector, - Vector(0, 2, 0) + rightSideVector, - Vector(0, 3, 0) + rightSideVector + Vector(0, 3, 0) + toRight, + toRight * 2, + Vector(0, 1, 0) + toRight * 2, + Vector(0, 2, 0) + toRight * 2, + Vector(0, 3, 0) + toRight * 2, + Vector(0, 0, 0) + toBack, + Vector(0, 1, 0) + toBack, + Vector(0, 2, 0) + toBack, + Vector(0, 3, 0) + toBack, + Vector(0, 0, 0) + toBack + toRight, + Vector(0, 1, 0) + toBack + toRight, + Vector(0, 2, 0) + toBack + toRight, + Vector(0, 3, 0) + toBack + toRight, + Vector(0, 0, 0) + toBack + toRight * 2, + Vector(0, 1, 0) + toBack + toRight * 2, + Vector(0, 2, 0) + toBack + toRight * 2, + Vector(0, 3, 0) + toBack + toRight * 2 ) - val mineralOffset: Vector = Vector(0, 3, 0) + vector + val mineralOffset: Vector = toRight val airOffsets: ImmutableList = ImmutableList.of( - Vector(0, 1, 0) + vector, - Vector(0, 2, 0) + vector + Vector(0, 1, 0) + toRight, + Vector(0, 2, 0) + toRight ) } + + val portalCenter = lowerLeftCorner + direction.airOffsets[0] + + fun isStandingInPortal(location: Location): Boolean { + return direction.airOffsets.any { offset -> location.block.location - offset == lowerLeftCorner } + } + + fun color() { + val mineral = (lowerLeftCorner + direction.mineralOffset).block + for (offset in direction.glassOffsets) { + (lowerLeftCorner + offset).block.type = MINERAL_TYPES.getOrDefault(mineral.type, BROWN_STAINED_GLASS) + } + } + + fun uncolor() { + for (offset in direction.glassOffsets) { + (lowerLeftCorner + offset).block.type = GLASS + } + } } \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..d08f7fc --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1 @@ +api-version: '1.13'