You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
13 KiB
305 lines
13 KiB
package net.deliciousreya.minecraftportal.model
|
|
|
|
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.block.Block
|
|
import org.bukkit.util.Vector
|
|
import net.deliciousreya.minecraftportal.extensions.*
|
|
import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos
|
|
import org.bukkit.Material
|
|
import org.bukkit.Material.*
|
|
import org.bukkit.Particle
|
|
import org.bukkit.Sound
|
|
import org.bukkit.block.BlockFace
|
|
import org.bukkit.block.data.Directional
|
|
import org.bukkit.block.data.Openable
|
|
import org.bukkit.entity.Entity
|
|
import org.bukkit.scheduler.BukkitRunnable
|
|
import org.bukkit.util.BoundingBox
|
|
|
|
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)
|
|
.put(TERRACOTTA, ORANGE_STAINED_GLASS)
|
|
.put(SMOOTH_RED_SANDSTONE, PINK_STAINED_GLASS)
|
|
.put(END_STONE_BRICKS, LIME_STAINED_GLASS)
|
|
.put(PRISMARINE, LIGHT_BLUE_STAINED_GLASS)
|
|
.put(BRICKS, MAGENTA_STAINED_GLASS)
|
|
.put(PURPUR_BLOCK, PURPLE_STAINED_GLASS)
|
|
.put(POLISHED_GRANITE, BROWN_STAINED_GLASS)
|
|
.put(POLISHED_ANDESITE, LIGHT_GRAY_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): PortalFrame? {
|
|
for (blockSet in state.blockSets) {
|
|
for (direction in PortalFrame.ExitDirection.values()) {
|
|
for (offset in blockSet.offsets(direction)) {
|
|
val scanResult = checkPortalFrameAt(
|
|
block.location.subtract(offset),
|
|
direction,
|
|
state
|
|
)
|
|
if (scanResult != null) {
|
|
return scanResult
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
/** 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.ExitDirection, state: PortalFrame.State): PortalFrame? {
|
|
val mineral = (location + direction.mineralOffset).block
|
|
if (mineral.type !in state.mineralBlocks) {
|
|
return null
|
|
}
|
|
val color = (location + direction.colorOffset).block
|
|
if (color.type !in state.colorBlocks) {
|
|
return null
|
|
}
|
|
for (vector in direction.glassOffsets) {
|
|
val block = (location + vector).block
|
|
if (block.type !in state.glassBlocks) {
|
|
return null
|
|
}
|
|
}
|
|
for (vector in direction.doorOffsets) {
|
|
val block = (location + vector).block
|
|
val door = block.blockData
|
|
if (block.type !in state.doorBlocks || (door is Directional && door.facing != direction.doorDirection)) {
|
|
return null
|
|
}
|
|
}
|
|
return PortalFrame(location, direction)
|
|
}
|
|
|
|
fun PortalSaveDataProtos.Portal.Direction.toDirection(): PortalFrame.ExitDirection {
|
|
for (direction in PortalFrame.ExitDirection.values()) {
|
|
if (direction.protoEnum == this) {
|
|
return direction
|
|
}
|
|
}
|
|
throw IllegalStateException("Bad enum value: $this")
|
|
}
|
|
|
|
/** Information about a portal frame. */
|
|
data class PortalFrame(val lowerLeftFrontCorner: Location, val direction: ExitDirection) {
|
|
enum class State(val glassBlocks: ImmutableSet<Material>, val colorBlocks: ImmutableSet<Material>, val doorBlocks: ImmutableSet<Material>, val mineralBlocks: ImmutableSet<Material>) {
|
|
ACTIVE(ImmutableSet.copyOf(MINERAL_TYPES.values), ImmutableSet.copyOf(COLOR_MAPPING.keys),
|
|
DOOR_TYPES, ImmutableSet.copyOf(MINERAL_TYPES.keys)),
|
|
INACTIVE(ImmutableSet.of(GLASS), ImmutableSet.builder<Material>().addAll(COLOR_MAPPING.keys).add(GLASS).build(),
|
|
DOOR_TYPES, ImmutableSet.copyOf(MINERAL_TYPES.keys));
|
|
|
|
val allValidBlocks:ImmutableSet<Material> = ImmutableSet.Builder<Material>().addAll(glassBlocks).addAll(doorBlocks).addAll(mineralBlocks).addAll(colorBlocks).build()
|
|
data class BlockSet(val blockTypes: Set<Material>, val offsets: (direction: ExitDirection) -> List<Vector>)
|
|
val blockSets = listOf(
|
|
BlockSet(glassBlocks, offsets = { it.glassOffsets }),
|
|
BlockSet(doorBlocks, offsets = { it.doorOffsets }),
|
|
BlockSet(colorBlocks, offsets = { listOf(it.colorOffset) }),
|
|
BlockSet(mineralBlocks, offsets = { listOf(it.mineralOffset) })
|
|
)
|
|
}
|
|
/** The direction a player faces when exiting the portal. */
|
|
enum class ExitDirection(val toRight: Vector, val toBack: Vector, val doorDirection: BlockFace, val protoEnum: PortalSaveDataProtos.Portal.Direction) {
|
|
NORTH(Vector(1, 0, 0), Vector(0, 0, 1), BlockFace.SOUTH, PortalSaveDataProtos.Portal.Direction.NORTH),
|
|
SOUTH(Vector(-1, 0, 0), Vector(0, 0, -1), BlockFace.NORTH, PortalSaveDataProtos.Portal.Direction.SOUTH),
|
|
EAST(Vector(0, 0, 1), Vector(-1, 0, 0), BlockFace.WEST, PortalSaveDataProtos.Portal.Direction.EAST),
|
|
WEST(Vector(0, 0, -1), Vector(1, 0, 0), BlockFace.EAST, PortalSaveDataProtos.Portal.Direction.WEST);
|
|
|
|
val glassOffsets: ImmutableList<Vector> = ImmutableList.of(
|
|
// check corners first:
|
|
// bottom left, front
|
|
ZERO,
|
|
// top right, back
|
|
UP * 3 + toBack + toRight * 2,
|
|
// bottom right, front
|
|
toRight * 2,
|
|
// top right, front
|
|
UP * 3 + toRight * 2,
|
|
// top left, front
|
|
UP * 3,
|
|
// bottom left, back
|
|
toBack,
|
|
// top left, back
|
|
UP * 3 + toBack,
|
|
// bottom right, back
|
|
toBack + toRight * 2,
|
|
|
|
// then do front walls
|
|
// left front wall
|
|
UP,
|
|
UP * 2,
|
|
// right front wall
|
|
UP + toRight * 2,
|
|
UP * 2 + toRight * 2,
|
|
|
|
// now do the rest of the back
|
|
// left back wall
|
|
UP + toBack,
|
|
UP * 2 + toBack,
|
|
// center back wall
|
|
toBack + toRight,
|
|
UP + toBack + toRight,
|
|
UP * 2 + toBack + toRight,
|
|
UP * 3 + toBack + toRight,
|
|
// right back wall
|
|
UP + toBack + toRight * 2,
|
|
UP * 2 + toBack + toRight * 2
|
|
)
|
|
|
|
val mineralOffset: Vector = toRight
|
|
val colorOffset: Vector = UP * 3 + toRight
|
|
|
|
val doorOffsets: ImmutableList<Vector> = ImmutableList.of(
|
|
UP + toRight,
|
|
UP * 2 + toRight
|
|
)
|
|
|
|
fun toProto():PortalSaveDataProtos.Portal.Direction {
|
|
return this.protoEnum
|
|
}
|
|
}
|
|
|
|
|
|
val portalCenter = lowerLeftFrontCorner + direction.doorOffsets[0]
|
|
val midCenter = portalCenter + MID_BLOCK
|
|
val fullPortalBoundingBox = BoundingBox.of(lowerLeftFrontCorner.block, (lowerLeftFrontCorner + UP * 3 + direction.toRight * 2 + direction.toBack).block)
|
|
val portalInsideBoundingBox = BoundingBox.of(portalCenter.block, (portalCenter + UP).block)
|
|
|
|
/** Gets the worldless location (direction and position vectors) relative to the center of the chamber, rotating so that the exit is north. */
|
|
fun getRelativeLocationFromAbsoluteLocation(location: Location): Location {
|
|
if (portalCenter.world != location.world) {
|
|
throw IllegalArgumentException("can't get relative position from ${portalCenter.world} to ${location.world}")
|
|
}
|
|
if (portalCenter.world == null) {
|
|
throw IllegalArgumentException("How did we end up with a relative portal center??")
|
|
}
|
|
val oldDirection = location.direction
|
|
val translatedPosition = location.toVector() - (portalCenter + MID_BLOCK_BOTTOM).toVector()
|
|
val newLocation = Location(null, translatedPosition.dot(direction.toRight), translatedPosition.dot(UP), translatedPosition.dot(direction.toBack), 0f, 0f)
|
|
newLocation.direction = Vector(oldDirection.dot(direction.toRight), oldDirection.dot(UP), oldDirection.dot(direction.toBack))
|
|
return newLocation
|
|
}
|
|
|
|
fun getAbsoluteLocationFromRelativeLocation(location: Location): Location {
|
|
if (location.world != null) {
|
|
throw IllegalArgumentException("can't get absolute position from already absolute position")
|
|
}
|
|
val world = portalCenter.world ?: throw IllegalArgumentException("How did we end up with a relative portal center??")
|
|
val oldDirection = location.direction
|
|
val oldPosition = location.toVector()
|
|
val rotatedPosition = Vector(oldPosition.dot(direction.toRight), oldPosition.dot(UP), oldPosition.dot(direction.toBack))
|
|
val newLocation = (rotatedPosition + portalCenter.toVector() + MID_BLOCK_BOTTOM).toLocation(world)
|
|
newLocation.direction = Vector(oldDirection.dot(direction.toRight), oldDirection.dot(UP), oldDirection.dot(direction.toBack))
|
|
return newLocation
|
|
}
|
|
|
|
val mineral = (lowerLeftFrontCorner + direction.mineralOffset).block.type
|
|
var color: Material
|
|
get() = (lowerLeftFrontCorner + direction.colorOffset).block.type
|
|
set(newColor) {
|
|
(lowerLeftFrontCorner + direction.colorOffset).block.type = newColor
|
|
}
|
|
|
|
fun open() {
|
|
val block = portalCenter.block
|
|
val data = block.blockData
|
|
if (data is Openable) {
|
|
data.isOpen = true
|
|
block.blockData = data
|
|
}
|
|
}
|
|
|
|
fun close() {
|
|
val block = portalCenter.block
|
|
val data = block.blockData
|
|
if (data is Openable) {
|
|
data.isOpen = false
|
|
block.blockData = data
|
|
}
|
|
}
|
|
|
|
fun ejectEntities() {
|
|
val world = portalCenter.world ?: return
|
|
for (entity in world.getNearbyEntities(portalInsideBoundingBox)) {
|
|
// kick 'em out the front!
|
|
entity.teleport(entity.location - direction.toBack)
|
|
}
|
|
}
|
|
|
|
fun activate() {
|
|
val mineral = (lowerLeftFrontCorner + direction.mineralOffset).block
|
|
for (offset in direction.glassOffsets) {
|
|
(lowerLeftFrontCorner + offset).block.type = MINERAL_TYPES.getOrDefault(mineral.type, GLASS)
|
|
}
|
|
portalCenter.world?.playSound(midCenter, Sound.ENTITY_EVOKER_CAST_SPELL, 1f, 1f)
|
|
portalCenter.world?.spawnParticle(Particle.SPELL, midCenter, 75)
|
|
open()
|
|
}
|
|
|
|
fun deactivate() {
|
|
for (offset in direction.glassOffsets) {
|
|
(lowerLeftFrontCorner + offset).block.type = GLASS
|
|
}
|
|
portalCenter.world?.playSound(midCenter, Sound.ENTITY_ILLUSIONER_CAST_SPELL, 1f, 1f)
|
|
portalCenter.world?.spawnParticle(Particle.SMOKE_NORMAL, midCenter, 75)
|
|
}
|
|
|
|
fun isActivePortal(): Boolean {
|
|
return checkPortalFrameAt(lowerLeftFrontCorner, direction, State.ACTIVE) == this
|
|
}
|
|
|
|
fun playPortalsLinkedEffect() {
|
|
portalCenter.world?.playSound(midCenter, Sound.BLOCK_BEACON_ACTIVATE, 1f, 1f)
|
|
portalCenter.world?.spawnParticle(Particle.SPELL, midCenter, 30)
|
|
}
|
|
|
|
fun playPortalsUnlinkedEffect() {
|
|
portalCenter.world?.playSound(midCenter, Sound.BLOCK_BEACON_DEACTIVATE, 1f, 1f)
|
|
portalCenter.world?.spawnParticle(Particle.SMOKE_NORMAL, midCenter, 30)
|
|
}
|
|
|
|
fun playTeleporterActivatedEffect() {
|
|
portalCenter.world?.playSound(midCenter, Sound.BLOCK_PORTAL_TRIGGER, 1f, 1f)
|
|
}
|
|
|
|
fun playTeleportTravelEffect() {
|
|
portalCenter.world?.playSound(midCenter, Sound.BLOCK_PORTAL_TRAVEL, 1f, 1f)
|
|
portalCenter.world?.spawnParticle(Particle.FIREWORKS_SPARK, midCenter, 75)
|
|
}
|
|
|
|
fun getEntities(): Collection<Entity> {
|
|
return portalCenter.world?.getNearbyEntities(portalInsideBoundingBox) ?: ImmutableList.of()
|
|
}
|
|
|
|
fun playTeleporterActiveEffect() {
|
|
portalCenter.world?.spawnParticle(Particle.SPELL_MOB_AMBIENT, midCenter, 10)
|
|
}
|
|
|
|
fun playTeleportCanceledEffect() {
|
|
portalCenter.world?.playSound(midCenter, Sound.BLOCK_LAVA_EXTINGUISH, 1f, 1f)
|
|
portalCenter.world?.spawnParticle(Particle.SMOKE_NORMAL, midCenter, 30)
|
|
}
|
|
|
|
inner class MakePortalSound : BukkitRunnable() {
|
|
override fun run() {
|
|
portalCenter.world?.playSound(portalCenter + MID_BLOCK, Sound.BLOCK_PORTAL_AMBIENT, 0.1f, 0f)
|
|
}
|
|
}
|
|
inner class MakePortalSparkles : BukkitRunnable() {
|
|
override fun run() {
|
|
portalCenter.world?.spawnParticle(Particle.PORTAL, portalCenter + MID_BLOCK, 20)
|
|
}
|
|
}
|
|
} |