From cc17c0da71e5ed99c0e591b626e4b835322d8c0e Mon Sep 17 00:00:00 2001 From: Marissa Staib Date: Wed, 10 Apr 2019 17:45:40 -0400 Subject: [PATCH] Data model for paired portals --- TODO | 2 +- build.gradle | 2 + .../minecraftportal/MinecraftPortalPlugin.kt | 42 +++++---- .../minecraftportal/extensions/Location.kt | 19 ++++ .../minecraftportal/extensions/Material.kt | 26 ++++++ .../model/DeserializationException.kt | 5 ++ .../minecraftportal/model/Portal.kt | 27 ++++++ .../minecraftportal/model/PortalDataStore.kt | 82 +++++++++++++++++ .../{ => model}/PortalFrame.kt | 68 ++++++++++---- .../minecraftportal/model/PortalType.kt | 89 +++++++++++++++++++ .../model/SerializationException.kt | 5 ++ src/main/proto/portal-save-data.proto | 47 ++++++++++ 12 files changed, 379 insertions(+), 35 deletions(-) create mode 100644 src/main/kotlin/net/deliciousreya/minecraftportal/extensions/Material.kt create mode 100644 src/main/kotlin/net/deliciousreya/minecraftportal/model/DeserializationException.kt create mode 100644 src/main/kotlin/net/deliciousreya/minecraftportal/model/Portal.kt create mode 100644 src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalDataStore.kt rename src/main/kotlin/net/deliciousreya/minecraftportal/{ => model}/PortalFrame.kt (66%) create mode 100644 src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalType.kt create mode 100644 src/main/kotlin/net/deliciousreya/minecraftportal/model/SerializationException.kt create mode 100644 src/main/proto/portal-save-data.proto diff --git a/TODO b/TODO index 685a789..c833230 100644 --- a/TODO +++ b/TODO @@ -7,4 +7,4 @@ * 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 -* add portal particles and ambient sounds to the teleporter \ No newline at end of file +* add portal particles and ambient sounds to the teleporter when portals are paired \ No newline at end of file diff --git a/build.gradle b/build.gradle index e9d770c..bd69551 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.jetbrains.kotlin.jvm' version '1.3.21' id "ru.endlesscode.bukkitgradle" version "0.8.1" + id 'com.google.protobuf' version '0.8.8' } group 'net.deliciousreya' @@ -26,6 +27,7 @@ dependencies { compileOnly bukkit() compileOnly group: 'com.destroystokyo.paper', name: 'paper-api', version: '1.13.2-R0.1-SNAPSHOT' shade "com.google.guava:guava:27.1-jre" + shade "com.google.protobuf:protobuf-java:3.7.1" shade "org.jetbrains.kotlin:kotlin-stdlib-jdk8" testCompile group: 'junit', name: 'junit', version: '4.12' } diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt index 00d8dcd..4c9379e 100644 --- a/src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt @@ -1,8 +1,10 @@ package net.deliciousreya.minecraftportal import com.destroystokyo.paper.event.block.BlockDestroyEvent -import org.bukkit.Effect -import org.bukkit.Material +import net.deliciousreya.minecraftportal.model.DOOR_TYPES +import net.deliciousreya.minecraftportal.model.PortalDataStore +import net.deliciousreya.minecraftportal.model.PortalFrame +import net.deliciousreya.minecraftportal.model.findPortalFrameConnectedTo import org.bukkit.Particle import org.bukkit.Sound import org.bukkit.block.Block @@ -10,10 +12,8 @@ import org.bukkit.block.data.type.Door import org.bukkit.event.EventHandler import org.bukkit.event.Listener 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 @@ -21,6 +21,8 @@ import org.bukkit.potion.PotionEffectType class MinecraftPortalPlugin() : JavaPlugin(), Listener { + val portals: PortalDataStore = PortalDataStore() + override fun onEnable() { super.onEnable() logger.info("Loaded the portal plugin!") @@ -30,12 +32,15 @@ class MinecraftPortalPlugin() : JavaPlugin(), Listener @EventHandler fun onBlockPlaced(e: BlockPlaceEvent) { if (e.block.type in PortalFrame.State.INACTIVE.allValidBlocks) { - val portalScanResults = findPortalFrameConnectedTo(e.block, PortalFrame.State.INACTIVE) - if (portalScanResults != null) { + val newPortal = findPortalFrameConnectedTo( + e.block, + PortalFrame.State.INACTIVE + ) + if (newPortal != null) { logger.info("found portal frame, creating portal") - portalScanResults.color() - e.block.world.playSound(portalScanResults.portalCenter, Sound.BLOCK_BEACON_ACTIVATE, 20f, 1f) - e.block.world.spawnParticle(Particle.SPELL, portalScanResults.portalCenter, 40) + val replacedPortal = portals.activateAndReplacePortal(newPortal) + newPortal.activate() + replacedPortal?.deactivate() } else { logger.info("no portal frame found matching placed block, ignoring") } @@ -87,7 +92,10 @@ class MinecraftPortalPlugin() : JavaPlugin(), Listener if(block.type !in DOOR_TYPES) { return } - val portalScanResults = findPortalFrameConnectedTo(block, PortalFrame.State.ACTIVE) ?: return + val portalScanResults = findPortalFrameConnectedTo( + block, + PortalFrame.State.ACTIVE + ) ?: return val door = block.blockData as Door if (!portalScanResults.isStandingInPortal(e.player.location)) { e.isCancelled = true @@ -112,14 +120,12 @@ class MinecraftPortalPlugin() : JavaPlugin(), Listener if (block.type !in PortalFrame.State.ACTIVE.allValidBlocks) { return } - val portalScanResults = findPortalFrameConnectedTo(block, PortalFrame.State.ACTIVE) - if (portalScanResults != null) { - logger.info("found portal frame matching destroyed block, deactivating") - portalScanResults.uncolor() - block.world.playSound(portalScanResults.portalCenter, Sound.BLOCK_BEACON_DEACTIVATE, 20f, 1f) - block.world.spawnParticle(Particle.SMOKE_NORMAL, portalScanResults.portalCenter, 40) - } else { - logger.info("no portal frame found matching destroyed block, ignoring") + val oldPortal = findPortalFrameConnectedTo( + block, + PortalFrame.State.ACTIVE + ) + if (oldPortal != null && portals.deactivatePortal(oldPortal)) { + oldPortal.deactivate() } } } \ No newline at end of file diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/extensions/Location.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/extensions/Location.kt index cb404c5..1ef25cf 100644 --- a/src/main/kotlin/net/deliciousreya/minecraftportal/extensions/Location.kt +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/extensions/Location.kt @@ -1,7 +1,15 @@ package net.deliciousreya.minecraftportal.extensions +import net.deliciousreya.minecraftportal.model.DeserializationException +import net.deliciousreya.minecraftportal.model.SerializationException +import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos import org.bukkit.Location +import org.bukkit.Server +import org.bukkit.util.BlockVector import org.bukkit.util.Vector +import java.lang.IllegalArgumentException +import java.lang.IllegalStateException +import java.util.* operator fun Location.plus(v: Vector):Location { return this.clone().add(v) @@ -29,4 +37,15 @@ fun Location.minus(x: Double, y: Double, z: Double):Location { operator fun Location.times(n:Double):Location { return this.clone().multiply(n) +} + +fun PortalSaveDataProtos.Location.toLocation(server: Server): Location { + val uuid = UUID.fromString(this.worldUuid) + val world = (server.getWorld(uuid) ?: throw DeserializationException("Couldn't find world with UUID $worldUuid")) + return BlockVector(this.x, this.y, this.z).toLocation(world) +} + +fun Location.toProto(): PortalSaveDataProtos.Location { + val world = this.world ?: throw SerializationException("Can't serialize location without world") + return PortalSaveDataProtos.Location.newBuilder().setWorldUuid(world.uid.toString()).setX(this.blockX).setY(this.blockY).setZ(this.blockZ).build() } \ No newline at end of file diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/extensions/Material.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/extensions/Material.kt new file mode 100644 index 0000000..8687744 --- /dev/null +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/extensions/Material.kt @@ -0,0 +1,26 @@ +package net.deliciousreya.minecraftportal.extensions + +import com.google.common.collect.ImmutableBiMap +import net.deliciousreya.minecraftportal.model.DeserializationException +import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos +import org.bukkit.Material +import java.lang.IllegalStateException + +val MINERAL_MAPPING: ImmutableBiMap = ImmutableBiMap.builder() + .put(Material.COAL_BLOCK, PortalSaveDataProtos.Portal.Mineral.COAL) + .put(Material.REDSTONE_BLOCK, PortalSaveDataProtos.Portal.Mineral.REDSTONE) + .put(Material.LAPIS_BLOCK, PortalSaveDataProtos.Portal.Mineral.LAPIS) + .put(Material.GOLD_BLOCK, PortalSaveDataProtos.Portal.Mineral.GOLD) + .put(Material.DIAMOND_BLOCK, PortalSaveDataProtos.Portal.Mineral.DIAMOND) + .put(Material.EMERALD_BLOCK, PortalSaveDataProtos.Portal.Mineral.EMERALD) + .put(Material.IRON_BLOCK, PortalSaveDataProtos.Portal.Mineral.IRON) + .put(Material.QUARTZ_BLOCK, PortalSaveDataProtos.Portal.Mineral.QUARTZ) + .build() + +fun Material.toMineralProto(): PortalSaveDataProtos.Portal.Mineral { + return MINERAL_MAPPING[this] ?: throw DeserializationException("Not a mineral: ${this}") +} + +fun PortalSaveDataProtos.Portal.Mineral.toMaterial(): Material { + return MINERAL_MAPPING.inverse()[this] ?: throw DeserializationException("Not a material: ${this}") +} \ No newline at end of file diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/model/DeserializationException.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/model/DeserializationException.kt new file mode 100644 index 0000000..faf0709 --- /dev/null +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/model/DeserializationException.kt @@ -0,0 +1,5 @@ +package net.deliciousreya.minecraftportal.model + +import java.lang.Exception + +class DeserializationException (message: String? = null, cause: Throwable? = null) : Exception(message, cause) \ No newline at end of file diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/model/Portal.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/model/Portal.kt new file mode 100644 index 0000000..d2cd53c --- /dev/null +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/model/Portal.kt @@ -0,0 +1,27 @@ +package net.deliciousreya.minecraftportal.model + +import net.deliciousreya.minecraftportal.extensions.toLocation +import net.deliciousreya.minecraftportal.extensions.toMaterial +import net.deliciousreya.minecraftportal.extensions.toMineralProto +import net.deliciousreya.minecraftportal.extensions.toProto +import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos +import org.bukkit.Material +import org.bukkit.Server +import java.lang.IllegalArgumentException + +fun PortalSaveDataProtos.Portal.toPortal(server: Server, portalTypes: Map): Portal { + val portalType = portalTypes[mineral.toMaterial()] ?: throw IllegalArgumentException("No portal type for $mineral") + val result = Portal(PortalFrame(lowerLeftFrontCorner.toLocation(server), entranceDirection.toDirection()), portalType) + // TODO: assert that the saved portal and mineral match the actual state of the world + return result +} + +class Portal(val frame: PortalFrame, val type: PortalType) { + fun toProto(): PortalSaveDataProtos.Portal { + return PortalSaveDataProtos.Portal.newBuilder() + .setLowerLeftFrontCorner(frame.lowerLeftFrontCorner.toProto()) + .setEntranceDirection(frame.direction.toProto()) + .setMineral(frame.mineral.toMineralProto()) + .build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalDataStore.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalDataStore.kt new file mode 100644 index 0000000..0bf60f2 --- /dev/null +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalDataStore.kt @@ -0,0 +1,82 @@ +package net.deliciousreya.minecraftportal.model + +import com.google.common.collect.ImmutableMap +import net.deliciousreya.minecraftportal.extensions.MINERAL_MAPPING +import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos +import org.bukkit.Material +import org.bukkit.Server +import java.io.File +import java.lang.IllegalArgumentException + +class PortalDataStore() { + val portalTypes: ImmutableMap + init { + val portalTypesBuilder = ImmutableMap.builder() + for (mineralType in MINERAL_MAPPING.keys) { + portalTypesBuilder.put(mineralType, PortalType(mineralType)) + } + portalTypes = portalTypesBuilder.build() + } + + fun onAfterChanged() { + // Does nothing for now. + // TODO: Save the data. + } + + /** Marks the new portal as active, and removes the one that needs to be deactivated, if any. */ + fun activateAndReplacePortal(frame: PortalFrame): PortalFrame? { + val type = portalTypes[frame.mineral] ?: throw IllegalArgumentException("Can't create a portal of type ${frame.mineral}") + val result = type.addOrReplacePortal(Portal(frame, type))?.frame + onAfterChanged() + return result + } + + /** Deactivates the given portal. */ + fun deactivatePortal(frame: PortalFrame): Boolean { + val type = portalTypes[frame.mineral] ?: throw IllegalArgumentException("There are no portals of type ${frame.mineral}") + val result = type.removePortalWithFrame(frame) + onAfterChanged() + return result + } + + /** Forgets all the data without actually affecting the world. */ + fun clear() { + for (portalType in this.portalTypes.values) { + portalType.clear() + } + } + + fun loadFromProto(server: Server, proto: PortalSaveDataProtos.PortalSaveData) { + clear() + for (portalProto in proto.unpairedPortalsList) { + val portal = portalProto.toPortal(server, portalTypes) + portal.type.loadUnpairedPortal(portal) + } + for (portalPair in proto.pairedPortalsList) { + val olderPortal = portalPair.older.toPortal(server, portalTypes) + val newerPortal = portalPair.newer.toPortal(server, portalTypes) + if (olderPortal.type != newerPortal.type) { + throw DeserializationException("Pair had non-matching types: ${olderPortal.type} and ${newerPortal.type}") + } + olderPortal.type.loadPairedPortals(olderPortal, newerPortal) + } + } + + fun toProto(): PortalSaveDataProtos.PortalSaveData { + val builder = PortalSaveDataProtos.PortalSaveData.newBuilder() + for (portalType in this.portalTypes.values) { + when { + portalType.isEmpty -> { + // Nothing here to save. Just skip it. + } + portalType.isPaired -> { + builder.addPairedPortals(portalType.toProto()) + } + else -> { + builder.addUnpairedPortals(portalType.getOnlyPortal.toProto()) + } + } + } + return builder.build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/PortalFrame.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalFrame.kt similarity index 66% rename from src/main/kotlin/net/deliciousreya/minecraftportal/PortalFrame.kt rename to src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalFrame.kt index 11b1c69..0a4e28d 100644 --- a/src/main/kotlin/net/deliciousreya/minecraftportal/PortalFrame.kt +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalFrame.kt @@ -1,4 +1,4 @@ -package net.deliciousreya.minecraftportal +package net.deliciousreya.minecraftportal.model import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap @@ -7,8 +7,11 @@ 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.material.Directional @@ -25,11 +28,15 @@ val MINERAL_TYPES: ImmutableMap = ImmutableMap.Builder = 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? { +fun findPortalFrameConnectedTo(block:Block, state: PortalFrame.State): PortalFrame? { 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, state) + val scanResult = checkPortalFrameAt( + block.location.subtract(vector), + direction, + state + ) if (scanResult != null) { return scanResult } @@ -37,14 +44,22 @@ fun findPortalFrameConnectedTo(block:Block, state:PortalFrame.State): PortalFram } block.type in state.doorBlocks -> for (direction in PortalFrame.EntranceDirection.values()) { for (vector in direction.doorOffsets) { - val scanResult = checkPortalFrameAt(block.location.subtract(vector), direction, state) + val scanResult = checkPortalFrameAt( + block.location.subtract(vector), + direction, + state + ) if (scanResult != null) { return scanResult } } } block.type in state.mineralBlocks -> for (direction in PortalFrame.EntranceDirection.values()) { - val scanResult = checkPortalFrameAt(block.location.subtract(direction.mineralOffset), direction, state) + val scanResult = checkPortalFrameAt( + block.location.subtract(direction.mineralOffset), + direction, + state + ) if (scanResult != null) { return scanResult } @@ -54,7 +69,7 @@ fun findPortalFrameConnectedTo(block:Block, state:PortalFrame.State): PortalFram } /** 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): PortalFrame? { +fun checkPortalFrameAt(location:Location, direction: PortalFrame.EntranceDirection, state: PortalFrame.State): PortalFrame? { val mineral = (location + direction.mineralOffset).block if (mineral.type !in state.mineralBlocks) { return null @@ -74,20 +89,31 @@ fun checkPortalFrameAt(location:Location, direction:PortalFrame.EntranceDirectio return PortalFrame(location, direction) } +fun PortalSaveDataProtos.Portal.Direction.toDirection(): PortalFrame.EntranceDirection { + for (direction in PortalFrame.EntranceDirection.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: EntranceDirection) { enum class State(val glassBlocks: ImmutableSet, val doorBlocks: 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)); + ACTIVE(ImmutableSet.copyOf(MINERAL_TYPES.values), + DOOR_TYPES, ImmutableSet.copyOf(MINERAL_TYPES.keys)), + INACTIVE(ImmutableSet.of(GLASS), + DOOR_TYPES, ImmutableSet.copyOf(MINERAL_TYPES.keys)); - val allValidBlocks = ImmutableSet.Builder().addAll(glassBlocks).addAll(doorBlocks).addAll(mineralBlocks).build() + val allValidBlocks:ImmutableSet = ImmutableSet.Builder().addAll(glassBlocks).addAll(doorBlocks).addAll(mineralBlocks).build() } /** The direction along which a portal frame extends. */ - enum class EntranceDirection(toRight: Vector, toBack: Vector, val 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); + enum class EntranceDirection(toRight: Vector, toBack: Vector, val doorDirection: BlockFace, val protoEnum: PortalSaveDataProtos.Portal.Direction) { + NORTH(Vector(1, 0, 0), Vector(0, 0, 1), BlockFace.NORTH, PortalSaveDataProtos.Portal.Direction.NORTH), + SOUTH(Vector(1, 0, 0), Vector(0, 0, -1), BlockFace.SOUTH, PortalSaveDataProtos.Portal.Direction.SOUTH), + EAST(Vector(0, 0, 1), Vector(-1, 0, 0), BlockFace.EAST, PortalSaveDataProtos.Portal.Direction.EAST), + WEST(Vector(0, 0, 1), Vector(1, 0, 0), BlockFace.WEST, PortalSaveDataProtos.Portal.Direction.WEST); val glassOffsets: ImmutableList = ImmutableList.of( // check corners first: @@ -138,6 +164,10 @@ data class PortalFrame(val lowerLeftFrontCorner: Location, val direction: Entran Vector(0, 1, 0) + toRight, Vector(0, 2, 0) + toRight ) + + fun toProto():PortalSaveDataProtos.Portal.Direction { + return this.protoEnum + } } val portalCenter = lowerLeftFrontCorner + direction.doorOffsets[0] @@ -146,16 +176,22 @@ data class PortalFrame(val lowerLeftFrontCorner: Location, val direction: Entran return direction.doorOffsets.any { offset -> location.block.location - offset == lowerLeftFrontCorner } } - fun color() { + val mineral = (lowerLeftFrontCorner + direction.mineralOffset).block.type + + fun activate() { val mineral = (lowerLeftFrontCorner + direction.mineralOffset).block for (offset in direction.glassOffsets) { (lowerLeftFrontCorner + offset).block.type = MINERAL_TYPES.getOrDefault(mineral.type, BROWN_STAINED_GLASS) } + portalCenter.world?.playSound(portalCenter, Sound.BLOCK_BEACON_ACTIVATE, 20f, 1f) + portalCenter.world?.spawnParticle(Particle.SPELL, portalCenter, 40) } - fun uncolor() { + fun deactivate() { for (offset in direction.glassOffsets) { (lowerLeftFrontCorner + offset).block.type = GLASS } + portalCenter.world?.playSound(portalCenter, Sound.BLOCK_BEACON_DEACTIVATE, 20f, 1f) + portalCenter.world?.spawnParticle(Particle.SMOKE_NORMAL, portalCenter, 40) } } \ No newline at end of file diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalType.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalType.kt new file mode 100644 index 0000000..f9f3da9 --- /dev/null +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalType.kt @@ -0,0 +1,89 @@ +package net.deliciousreya.minecraftportal.model + +import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos +import org.bukkit.Material +import java.lang.IllegalArgumentException +import java.lang.IllegalStateException + +class PortalType(val mineral: Material) { + var oldestActivePortal: Portal? = null + var newestActivePortal: Portal? = null + + val isEmpty: Boolean get() = oldestActivePortal == null && newestActivePortal == null + val isPaired: Boolean get() = oldestActivePortal != null && newestActivePortal != null + + val getOnlyPortal: Portal get() = if (!isEmpty && !isPaired) oldestActivePortal!! else throw IllegalStateException("Can't getOnlyPortal for empty or paired portal type $this") + + fun toProto(): PortalSaveDataProtos.PortalPair { + val builder = PortalSaveDataProtos.PortalPair.newBuilder() + val oldestPortal = oldestActivePortal + val newestPortal = newestActivePortal + if (oldestPortal != null) { + builder.older = oldestPortal.toProto() + } + if (newestPortal != null) { + builder.newer = newestPortal.toProto() + } + return builder.build() + } + + fun addOrReplacePortal(portal: Portal): Portal? { + if (portal.type != this) { + throw DeserializationException("Tried to add $portal which didn't belong to ${this}") + } + return when { + this.isEmpty -> { + this.oldestActivePortal = portal + null + } + else -> { + val replacedPortal = newestActivePortal + this.newestActivePortal = portal + replacedPortal + } + } + } + + fun loadUnpairedPortal(portal: Portal) { + if (!this.isEmpty) { + throw DeserializationException("Already have a value for ${this} when adding $portal") + } + if (portal.type != this) { + throw DeserializationException("Tried to add $portal which didn't belong to ${this}") + } + this.oldestActivePortal = portal + } + + fun loadPairedPortals(olderPortal: Portal, newerPortal: Portal) { + if (!this.isEmpty) { + throw DeserializationException("Already have a value for ${this} when adding $olderPortal and $newerPortal") + } + if (olderPortal.type != this) { + throw DeserializationException("Tried to add $olderPortal which didn't belong to ${this}") + } + if (newerPortal.type != this) { + throw DeserializationException("Tried to add $newerPortal which didn't belong to ${this}") + } + this.oldestActivePortal = olderPortal + this.newestActivePortal = newerPortal + } + + fun clear() { + oldestActivePortal = null + newestActivePortal = null + } + + override fun toString():String { + return "PortalType{mineral=$mineral, oldestActivePortal=$oldestActivePortal, newestActivePortal=$newestActivePortal}" + } + + fun removePortalWithFrame(frame: PortalFrame):Boolean { + if (oldestActivePortal?.frame == frame) { + oldestActivePortal = newestActivePortal + } else if (newestActivePortal?.frame != frame) { + return false + } + newestActivePortal = null + return true + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/deliciousreya/minecraftportal/model/SerializationException.kt b/src/main/kotlin/net/deliciousreya/minecraftportal/model/SerializationException.kt new file mode 100644 index 0000000..24b6303 --- /dev/null +++ b/src/main/kotlin/net/deliciousreya/minecraftportal/model/SerializationException.kt @@ -0,0 +1,5 @@ +package net.deliciousreya.minecraftportal.model + +import java.lang.Exception + +class SerializationException (message: String? = null, cause: Throwable? = null) : Exception(message, cause) \ No newline at end of file diff --git a/src/main/proto/portal-save-data.proto b/src/main/proto/portal-save-data.proto new file mode 100644 index 0000000..50512b6 --- /dev/null +++ b/src/main/proto/portal-save-data.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package net.deliciousreya.minecraftportal; + +option java_package = "net.deliciousreya.minecraftportal.proto"; +option java_outer_classname = "PortalSaveDataProtos"; + +message Location { + string world_uuid = 1; + int32 x = 2; + int32 y = 3; + int32 z = 4; +} + +message Portal { + enum Direction { + UNKNOWN_DIRECTION = 0; + NORTH = 1; + EAST = 2; + WEST = 3; + SOUTH = 4; + } + enum Mineral { + UNKNOWN_MINERAL = 0; + COAL = 1; + REDSTONE = 2; + LAPIS = 3; + GOLD = 4; + DIAMOND = 5; + EMERALD = 6; + IRON = 7; + QUARTZ = 8; + } + Location lower_left_front_corner = 1; + Direction entrance_direction = 2; + Mineral mineral = 3; +} + +message PortalPair { + Portal older = 1; + Portal newer = 2; +} + +message PortalSaveData { + repeated PortalPair paired_portals = 1; + repeated Portal unpaired_portals = 2; +} \ No newline at end of file