1
0
Fork 0

Portals are super pretty and functional!

main
Marissa Staib 5 years ago
parent 2ae528fc98
commit 8dbd61c1ef
  1. 6
      TODO
  2. 42
      src/main/kotlin/net/deliciousreya/minecraftportal/MinecraftPortalPlugin.kt
  3. 4
      src/main/kotlin/net/deliciousreya/minecraftportal/extensions/Vector.kt
  4. 21
      src/main/kotlin/net/deliciousreya/minecraftportal/model/Portal.kt
  5. 68
      src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalDataStore.kt
  6. 56
      src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalFrame.kt
  7. 35
      src/main/kotlin/net/deliciousreya/minecraftportal/model/PortalType.kt
  8. 118
      src/main/kotlin/net/deliciousreya/minecraftportal/model/Teleportation.kt

@ -1,7 +1,3 @@
* 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
* teleport everyone inside the portal, not just the person who closed the door
* interrupt teleportation if portal is destroyed mid-teleportation, or if a new portal is constructed during teleportation, changing the destination
* cancel teleportation if someone moves out of the teleporter during teleportation (maybe prevent them from starting?)
* 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 when portals are paired
* fix facing and location when teleporting in different directions

@ -7,18 +7,19 @@ 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
import org.bukkit.block.data.type.Door
import org.bukkit.entity.Entity
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.block.*
import org.bukkit.event.entity.EntityChangeBlockEvent
import org.bukkit.event.entity.EntityExplodeEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerMoveEvent
import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
class MinecraftPortalPlugin() : JavaPlugin(), Listener
@ -28,6 +29,8 @@ class MinecraftPortalPlugin() : JavaPlugin(), Listener
override fun onEnable() {
super.onEnable()
portals.loadFromAndAutoSaveTo(server, dataFolder.resolve("portal_data.binproto"), dataFolder.resolve("portal_data.binproto.bak"))
portals.validateWorldOnStartup(logger)
portals.launchEffectsOnStartup(this)
server.pluginManager.registerEvents(this, this)
}
@ -51,6 +54,7 @@ class MinecraftPortalPlugin() : JavaPlugin(), Listener
if (otherPortal != null) {
newPortal.open()
otherPortal.open()
portals.startEffectsFor(newPortal, this)
} else {
newPortal.ejectEntities()
newPortal.close()
@ -113,28 +117,26 @@ class MinecraftPortalPlugin() : JavaPlugin(), Listener
return
}
if (!door.isOpen) {
// door is about to be opened, so undrug the player
e.player.stopSound(Sound.BLOCK_PORTAL_TRIGGER)
val otherPortal = portals.getOtherPortal(frame = portal) ?: return
otherPortal.open()
/* e.player.removePotionEffect(PotionEffectType.BLINDNESS)
e.player.removePotionEffect(PotionEffectType.CONFUSION)
e.player.removePotionEffect(PotionEffectType.INVISIBILITY) */
portals.stopTeleporting(portal)
} else {
// door is about to be closed, so drug the player
val otherPortal = portals.getOtherPortal(frame = portal) ?: return
otherPortal.close()
val relativeLocation = portal.getRelativeLocationFromAbsoluteLocation(location)
val destination = otherPortal.getAbsoluteLocationFromRelativeLocation(relativeLocation)
relativeLocation.world?.playSound(e.player.location, Sound.BLOCK_PORTAL_TRIGGER, 20f, 1f)
destination.world?.playSound(destination, Sound.BLOCK_PORTAL_TRAVEL, 20f, 1f)
e.player.teleport(destination)
/* e.player.addPotionEffect(PotionEffect(PotionEffectType.CONFUSION, 200, 1, 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)) */
portals.startTeleporting(portal, this)
}
}
fun onPlayerMove(e: PlayerMoveEvent) {
val player = e.player
val oldLocation = e.from
val portal = portals.isLocationInPortalChamber(oldLocation) ?: return
val newLocation = e.to ?: return
if (oldLocation.world != newLocation.world || oldLocation.toVector() !in portal.portalInsideBoundingBox) {
cancelTeleportFor(player)
}
}
private fun cancelTeleportFor(entity: Entity) {
}
fun onDestroyedBlock(block: Block) {
if (block.type !in PortalFrame.State.ACTIVE.allValidBlocks) {
return

@ -33,4 +33,8 @@ operator fun Vector.times(v:Vector):Vector {
operator fun Vector.div(v:Vector):Vector {
return this.clone().divide(v)
}
operator fun Vector.unaryMinus(): Vector {
return this * -1
}

@ -7,16 +7,35 @@ import net.deliciousreya.minecraftportal.extensions.toProto
import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos
import org.bukkit.Material
import org.bukkit.Server
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitRunnable
import java.lang.IllegalArgumentException
fun PortalSaveDataProtos.Portal.toPortal(server: Server, portalTypes: Map<Material, PortalType>): Portal {
val portalType = portalTypes[mineral.toMaterial()] ?: throw IllegalArgumentException("No portal type for $mineral")
val result = Portal(PortalFrame(lowerLeftFrontCorner.toLocation(server), exitDirection.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) {
var portalSounds: PortalFrame.MakePortalSound? = null
var portalSparkles: PortalFrame.MakePortalSparkles? = null
fun startEffects(plugin: Plugin) {
stopEffects()
portalSounds = frame.MakePortalSound()
portalSparkles = frame.MakePortalSparkles()
portalSounds?.runTaskTimer(plugin, 0, 120)
portalSparkles?.runTaskTimer(plugin, 0, 20)
}
fun stopEffects() {
portalSounds?.cancel()
portalSparkles?.cancel()
portalSounds = null
portalSparkles = null
}
fun toProto(): PortalSaveDataProtos.Portal {
return PortalSaveDataProtos.Portal.newBuilder()
.setLowerLeftFrontCorner(frame.lowerLeftFrontCorner.toProto())

@ -7,11 +7,13 @@ import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.Server
import org.bukkit.plugin.Plugin
import java.io.File
import java.io.IOException
import java.lang.IllegalArgumentException
import java.util.logging.Logger
class PortalDataStore {
class PortalDataStore (){
var saveDataTo: File? = null
var useBackup: File? = null
val portalTypes: ImmutableMap<Material, PortalType>
@ -87,6 +89,52 @@ class PortalDataStore {
this.useBackup = backupFile
}
fun validateWorldOnStartup(logger: Logger) {
for (portalType in this.portalTypes.values) {
val oldPortal = portalType.oldestActivePortal
val newPortal = portalType.newestActivePortal
if (newPortal != null && !validatePortal(newPortal)) {
logger.warning("Portal $newPortal was no longer valid!")
portalType.newestActivePortal = null
}
if (oldPortal != null && !validatePortal(oldPortal)) {
logger.warning("Portal $oldPortal was no longer valid!")
portalType.oldestActivePortal = portalType.newestActivePortal
portalType.newestActivePortal = null
}
}
}
fun validatePortal(portal: Portal): Boolean {
return portal.frame.mineral == portal.type.mineral && portal.frame.isActivePortal()
}
fun launchEffectsOnStartup(plugin: Plugin) {
for (portalType in this.portalTypes.values) {
startEffectsFor(portalType, plugin)
}
}
fun startEffectsFor(portal: PortalFrame, plugin: Plugin) {
startEffectsFor(getPortalType(portal), plugin)
}
fun startEffectsFor(portalType: PortalType, plugin: Plugin) {
val oldPortal = portalType.oldestActivePortal
val newPortal = portalType.newestActivePortal
if (portalType.isPaired) {
oldPortal?.startEffects(plugin)
oldPortal?.frame?.open()
newPortal?.startEffects(plugin)
newPortal?.frame?.open()
} else if (!portalType.isEmpty) {
oldPortal?.stopEffects()
oldPortal?.frame?.close()
newPortal?.stopEffects()
newPortal?.frame?.close()
}
}
fun unload() {
this.clear()
this.saveDataTo = null
@ -112,17 +160,17 @@ class PortalDataStore {
/** Marks the new portal as active, and removes the one that needs to be deactivated, if any. */
fun activateAndReplacePortal(frame: PortalFrame): PortalFrame? {
val type = getPortalType(frame)
val result = type.addOrReplacePortal(Portal(frame, type))?.frame
val result = type.addOrReplacePortal(Portal(frame, type))
onAfterChanged()
return result
return result?.frame
}
/** Deactivates the given portal. Returns the other portal if it needs to be closed. */
fun deactivatePortal(frame: PortalFrame): PortalFrame? {
val type = getPortalType(frame)
val result = type.removePortalWithFrame(frame)?.frame
val result = type.removePortalWithFrame(frame)
onAfterChanged()
return result
return result?.frame
}
fun getPortalType(frame: PortalFrame): PortalType {
@ -138,6 +186,8 @@ class PortalDataStore {
/** Forgets all the data without actually affecting the world. */
fun clear() {
for (portalType in this.portalTypes.values) {
portalType.oldestActivePortal?.stopEffects()
portalType.newestActivePortal?.stopEffects()
portalType.clear()
}
}
@ -175,4 +225,12 @@ class PortalDataStore {
}
return builder.build()
}
fun startTeleporting(portal: PortalFrame, plugin: Plugin) {
getPortalType(portal).beginTeleportation(plugin)
}
fun stopTeleporting(portal: PortalFrame) {
getPortalType(portal).cancelTeleportation()
}
}

@ -15,6 +15,8 @@ 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>()
@ -174,6 +176,7 @@ data class PortalFrame(val lowerLeftFrontCorner: Location, val direction: ExitDi
}
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)
@ -238,8 +241,7 @@ data class PortalFrame(val lowerLeftFrontCorner: Location, val direction: ExitDi
for (offset in direction.glassOffsets) {
(lowerLeftFrontCorner + offset).block.type = MINERAL_TYPES.getOrDefault(mineral.type, BROWN_STAINED_GLASS)
}
val midCenter = portalCenter + MID_BLOCK
portalCenter.world?.playSound(midCenter, Sound.BLOCK_BEACON_ACTIVATE, 20f, 1f)
portalCenter.world?.playSound(midCenter, Sound.ENTITY_EVOKER_CAST_SPELL, 20f, 1f)
portalCenter.world?.spawnParticle(Particle.SPELL, midCenter, 75)
open()
}
@ -248,8 +250,54 @@ data class PortalFrame(val lowerLeftFrontCorner: Location, val direction: ExitDi
for (offset in direction.glassOffsets) {
(lowerLeftFrontCorner + offset).block.type = GLASS
}
val midCenter = portalCenter + MID_BLOCK
portalCenter.world?.playSound(midCenter, Sound.BLOCK_BEACON_DEACTIVATE, 20f, 1f)
portalCenter.world?.playSound(midCenter, Sound.ENTITY_ILLUSIONER_CAST_SPELL, 20f, 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, 20f, 1f)
portalCenter.world?.spawnParticle(Particle.SPELL, midCenter, 30)
}
fun playPortalsUnlinkedEffect() {
portalCenter.world?.playSound(midCenter, Sound.BLOCK_BEACON_DEACTIVATE, 20f, 1f)
portalCenter.world?.spawnParticle(Particle.SMOKE_NORMAL, midCenter, 30)
}
fun playTeleporterActivatedEffect() {
portalCenter.world?.playSound(midCenter, Sound.BLOCK_PORTAL_TRIGGER, 20f, 1f)
}
fun playTeleportTravelEffect() {
portalCenter.world?.playSound(midCenter, Sound.BLOCK_PORTAL_TRAVEL, 20f, 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, 20f, 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, 3f, 0f)
}
}
inner class MakePortalSparkles : BukkitRunnable() {
override fun run() {
portalCenter.world?.spawnParticle(Particle.PORTAL, portalCenter + MID_BLOCK, 20)
}
}
}

@ -3,6 +3,7 @@ package net.deliciousreya.minecraftportal.model
import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.plugin.Plugin
import java.lang.IllegalArgumentException
import java.lang.IllegalStateException
@ -39,6 +40,12 @@ class PortalType(val mineral: Material) {
}
else -> {
val replacedPortal = newestActivePortal
replacedPortal?.stopEffects()
replacedPortal?.frame?.playPortalsUnlinkedEffect()
if (replacedPortal != null) {
cancelTeleportation()
}
portal.frame.playPortalsLinkedEffect()
this.newestActivePortal = portal
replacedPortal
}
@ -69,6 +76,19 @@ class PortalType(val mineral: Material) {
this.newestActivePortal = newerPortal
}
private var teleportation: Teleportation? = null
fun beginTeleportation(plugin: Plugin) {
val teleportation = Teleportation(this, plugin)
teleportation.start()
this.teleportation = teleportation
}
fun cancelTeleportation() {
teleportation?.cancelTeleportation()
teleportation = null
}
fun clear() {
oldestActivePortal = null
newestActivePortal = null
@ -83,11 +103,26 @@ class PortalType(val mineral: Material) {
* returns it. Otherwise (if that frame wasn't for either portal or if there are none left) returns null.
*/
fun removePortalWithFrame(frame: PortalFrame): Portal? {
val isPaired = this.isPaired
if (oldestActivePortal?.frame == frame) {
oldestActivePortal?.stopEffects()
newestActivePortal?.stopEffects()
if (isPaired) {
oldestActivePortal?.frame?.playPortalsUnlinkedEffect()
newestActivePortal?.frame?.playPortalsUnlinkedEffect()
}
cancelTeleportation()
oldestActivePortal = newestActivePortal
} else if (newestActivePortal?.frame != frame) {
return null
}
oldestActivePortal?.stopEffects()
newestActivePortal?.stopEffects()
cancelTeleportation()
if (isPaired) {
oldestActivePortal?.frame?.playPortalsUnlinkedEffect()
newestActivePortal?.frame?.playPortalsUnlinkedEffect()
}
newestActivePortal = null
return oldestActivePortal
}

@ -0,0 +1,118 @@
package net.deliciousreya.minecraftportal.model
import net.deliciousreya.minecraftportal.proto.PortalSaveDataProtos
import org.bukkit.Location
import org.bukkit.entity.Entity
import org.bukkit.entity.LivingEntity
import org.bukkit.plugin.Plugin
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
import org.bukkit.scheduler.BukkitRunnable
class Teleportation(val portalType: PortalType, val plugin: Plugin) {
val leftEntities = LinkedHashSet<Entity>()
val rightEntities = LinkedHashSet<Entity>()
val allEntities = LinkedHashSet<Entity>()
var currentTask: BukkitRunnable? = null
var sparklesTask: DoSparkles? = null
val leftPortal = portalType.newestActivePortal!!
val rightPortal = portalType.oldestActivePortal!!
fun cancelFor(entity: Entity) {
leftEntities.remove(entity)
rightEntities.remove(entity)
allEntities.remove(entity)
if (allEntities.isEmpty()) {
cancelTeleportation()
}
}
fun cancelEffectsOn(entity: Entity) {
if (entity is LivingEntity) {
entity.removePotionEffect(PotionEffectType.BLINDNESS)
entity.removePotionEffect(PotionEffectType.CONFUSION)
}
}
fun cancelTeleportation() {
leftPortal.frame.open()
rightPortal.frame.open()
leftPortal.frame.playTeleportCanceledEffect()
rightPortal.frame.playTeleportCanceledEffect()
cancelEffectsOnAll()
leftEntities.clear()
rightEntities.clear()
allEntities.clear()
currentTask?.cancel()
currentTask = null
sparklesTask?.cancel()
sparklesTask = null
}
fun cancelEffectsOnAll() {
for (entity in allEntities) {
cancelEffectsOn(entity)
}
}
fun start() {
leftEntities.addAll(leftPortal.frame.getEntities())
rightEntities.addAll(rightPortal.frame.getEntities())
allEntities.addAll(leftEntities)
allEntities.addAll(rightEntities)
for (entity in allEntities) {
if (entity is LivingEntity) {
entity.addPotionEffect(PotionEffect(PotionEffectType.CONFUSION, 200, 1, false, false, false))
entity.addPotionEffect(PotionEffect(PotionEffectType.BLINDNESS, 200, 1, false, false, false))
}
}
leftPortal.frame.close()
rightPortal.frame.close()
leftPortal.frame.playTeleporterActivatedEffect()
rightPortal.frame.playTeleporterActivatedEffect()
val task = DoTeleport()
task.runTaskLater(plugin, 100)
currentTask = task
val sparkles = DoSparkles()
sparkles.runTaskTimer(plugin, 10, 15)
sparklesTask = sparkles
}
inner class DoSparkles : BukkitRunnable() {
override fun run() {
leftPortal.frame.playTeleporterActiveEffect()
rightPortal.frame.playTeleporterActiveEffect()
}
}
inner class DoTeleport : BukkitRunnable() {
override fun run() {
for(leftEntity in leftEntities) {
val relativeLocation = leftPortal.frame.getRelativeLocationFromAbsoluteLocation(leftEntity.location)
val destination = rightPortal.frame.getAbsoluteLocationFromRelativeLocation(relativeLocation)
leftEntity.teleport(destination)
}
for(rightEntity in rightEntities) {
val relativeLocation = rightPortal.frame.getRelativeLocationFromAbsoluteLocation(rightEntity.location)
val destination = leftPortal.frame.getAbsoluteLocationFromRelativeLocation(relativeLocation)
rightEntity.teleport(destination)
}
leftPortal.frame.playTeleportTravelEffect()
rightPortal.frame.playTeleportTravelEffect()
val task = OpenDoors()
task.runTaskLater(plugin, 100)
currentTask = task
}
}
inner class OpenDoors: BukkitRunnable() {
override fun run() {
leftPortal.frame.open()
rightPortal.frame.open()
cancelEffectsOnAll()
currentTask = null
sparklesTask?.cancel()
sparklesTask = null
}
}
}
Loading…
Cancel
Save