1
0
Fork 0

Nauseating teleportation and bigger portal frames

main
Marissa Staib 6 years ago
parent 6f5bee3485
commit 52b937f7d2
  1. 13
      TODO
  2. 2
      build.gradle
  3. 122
      src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt
  4. 126
      src/main/kotlin/net/deliciousreya/minecraftportal/PortalFrame.kt
  5. 1
      src/main/resources/plugin.yml

13
TODO

@ -1,4 +1,9 @@
* Detect a glass frame being placed * identify and pair portals by type/color
* Detect a glass frame with a mineral block * save portal pairs to disk and reload them on restarting the server
* Transform the glass frame to stained glass * deactivate most recently placed portal and replace with newly placed one when 3+ portals are created
* Test portal block teleportation interruption * 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

@ -31,7 +31,7 @@ dependencies {
} }
bukkit { bukkit {
version = "1.12.2" version = "1.13.2"
// Attributes for plugin.yml // Attributes for plugin.yml
meta { meta {

@ -1,10 +1,23 @@
package net.deliciousreya.minecraftportal package net.deliciousreya.minecraftportal
import com.destroystokyo.paper.event.block.BlockDestroyEvent
import org.bukkit.Effect
import org.bukkit.Material 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.EventHandler
import org.bukkit.event.Listener 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.plugin.java.JavaPlugin
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
class MinecraftPortalPlugin() : JavaPlugin(), Listener class MinecraftPortalPlugin() : JavaPlugin(), Listener
{ {
@ -16,14 +29,109 @@ class MinecraftPortalPlugin() : JavaPlugin(), Listener
@EventHandler @EventHandler
fun onBlockPlaced(e: BlockPlaceEvent) { fun onBlockPlaced(e: BlockPlaceEvent) {
logger.info("block placed of type " + e.block.type) if (e.block.type in PortalFrame.State.INACTIVE.allValidBlocks) {
if (e.block.type.equals(Material.GLASS)) { val portalScanResults = findPortalFrameConnectedTo(e.block, PortalFrame.State.INACTIVE)
logger.info("put down glass at " + e.block.location)
val portalScanResults = findPortalFrameConnectedTo(e.block)
if (portalScanResults.frame != null) { 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 { } 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")
} }
} }
} }

@ -1,31 +1,54 @@
package net.deliciousreya.minecraftportal package net.deliciousreya.minecraftportal
import com.google.common.collect.ImmutableList 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.Location
import org.bukkit.Material.AIR
import org.bukkit.Material.GLASS
import org.bukkit.block.Block import org.bukkit.block.Block
import org.bukkit.util.Vector import org.bukkit.util.Vector
import net.deliciousreya.minecraftportal.extensions.* 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<Material, Material> = ImmutableMap.Builder<Material, Material>()
.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<Material> = 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 var bestScan:PortalScanResults? = null
if (block.type == GLASS) { when {
for (direction in PortalFrame.Direction.values()) { block.type in state.glassBlocks -> for (direction in PortalFrame.EntranceDirection.values()) {
for (vector in direction.glassOffsets) { 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) { if (scanResult.frame != null) {
return scanResult 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 bestScan = scanResult
} }
} }
} }
} block.type in state.mineralBlocks -> for (direction in PortalFrame.EntranceDirection.values()) {
if (block.type == GLASS) { val scanResult = checkPortalFrameAt(block.location.subtract(direction.mineralOffset), direction, state)
for (direction in PortalFrame.Direction.values()) {
val scanResult = checkPortalFrameAt(block.location.subtract(direction.mineralOffset), direction)
if (scanResult.frame != null) { if (scanResult.frame != null) {
return scanResult return scanResult
} else if (bestScan == null || scanResult > bestScan) { } else if (bestScan == null || scanResult > bestScan) {
@ -33,27 +56,23 @@ fun findPortalFrameConnectedTo(block:Block): PortalScanResults {
} }
} }
} }
if (bestScan != null) { return bestScan!!
return bestScan
} else {
return PortalScanResults(null, ImmutableList.of(), ImmutableList.of())
}
} }
/** Detects whether there is a portal frame in the given location, extending in the given direction. */ /** 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.Direction): PortalScanResults { fun checkPortalFrameAt(location:Location, direction:PortalFrame.EntranceDirection, state:PortalFrame.State): PortalScanResults {
val matches = ImmutableList.Builder<Block>() val matches = ImmutableList.Builder<Block>()
val nonmatches = ImmutableList.Builder<Block>() val nonmatches = ImmutableList.Builder<Block>()
for (vector in direction.glassOffsets) { for (vector in direction.glassOffsets) {
val block = (location + vector).block 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) { for (vector in direction.airOffsets) {
val block = (location + vector).block 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 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() val nonmatchesBuilt = nonmatches.build()
return PortalScanResults(if (nonmatchesBuilt.isEmpty()) {PortalFrame(location, direction)} else {null}, matches.build(), nonmatchesBuilt) 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<Bl
} }
/** Information about a portal frame. */ /** Information about a portal frame. */
data class PortalFrame(val lowerLeftCorner: Location, val direction: Direction) { data class PortalFrame(val lowerLeftCorner: Location, val direction: EntranceDirection) {
val UP = Vector(0, 1, 0) val UP = Vector(0, 1, 0)
/** The direction along which a portal frame extends. */ enum class State(val glassBlocks: ImmutableSet<Material>, val airBlocks: ImmutableSet<Material>, val mineralBlocks: ImmutableSet<Material>) {
enum class Direction(vector: Vector) { ACTIVE(ImmutableSet.copyOf(MINERAL_TYPES.values), DOOR_TYPES, ImmutableSet.copyOf(MINERAL_TYPES.keys)),
X_AXIS(Vector(1, 0, 0)), INACTIVE(ImmutableSet.of(GLASS), DOOR_TYPES, ImmutableSet.copyOf(MINERAL_TYPES.keys));
Z_AXIS(Vector(0, 0, 1));
private val rightSideVector = vector * 2 val allValidBlocks = ImmutableSet.Builder<Material>().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<Vector> = ImmutableList.of( val glassOffsets: ImmutableList<Vector> = ImmutableList.of(
Vector(0, 0, 0), Vector(0, 0, 0),
Vector(0, 1, 0), Vector(0, 1, 0),
Vector(0, 2, 0), Vector(0, 2, 0),
Vector(0, 3, 0), Vector(0, 3, 0),
vector, Vector(0, 3, 0) + toRight,
rightSideVector, toRight * 2,
Vector(0, 1, 0) + rightSideVector, Vector(0, 1, 0) + toRight * 2,
Vector(0, 2, 0) + rightSideVector, Vector(0, 2, 0) + toRight * 2,
Vector(0, 3, 0) + rightSideVector 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<Vector> = ImmutableList.of( val airOffsets: ImmutableList<Vector> = ImmutableList.of(
Vector(0, 1, 0) + vector, Vector(0, 1, 0) + toRight,
Vector(0, 2, 0) + vector 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
}
}
} }

@ -0,0 +1 @@
api-version: '1.13'
Loading…
Cancel
Save