1
0
Fork 0
The portal creating plugin for Minecraft.
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.

273 lines
10 KiB

package net.deliciousreya.minecraftportal.model
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.protobuf.InvalidProtocolBufferException
import net.deliciousreya.minecraftportal.extensions.COLOR_MAPPING
import net.deliciousreya.minecraftportal.extensions.MINERAL_MAPPING
import net.deliciousreya.minecraftportal.extensions.toColorProto
import net.deliciousreya.minecraftportal.extensions.toMaterial
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 (val logger: Logger) {
var saveDataTo: File? = null
var useBackup: File? = null
private val userColors: MutableMap<String, Material> = mutableMapOf()
private val portalTypes: ImmutableMap<Material, ImmutableMap<Material, PortalType>>
private val allPortals: ImmutableList<PortalType>
init {
val allPortalsBuilder = ImmutableList.builder<PortalType>()
val portalTypesBuilder = ImmutableMap.builder<Material, ImmutableMap<Material, PortalType>>()
for (mineralType in MINERAL_MAPPING.keys) {
val colors = ImmutableMap.Builder<Material, PortalType>()
for (color in COLOR_MAPPING.keys) {
val portalType = PortalType(mineralType, color)
colors.put(color, portalType)
allPortalsBuilder.add(portalType)
}
portalTypesBuilder.put(mineralType, colors.build())
}
portalTypes = portalTypesBuilder.build()
allPortals = allPortalsBuilder.build()
}
fun isLocationInPortalOrChamber(location: Location): PortalFrame? {
for (type in this.allPortals) {
if (type.isEmpty) {
continue
}
val result = type.isLocationInPortalOrChamber(location)
if (result != null) {
return result.frame
}
}
return null
}
fun isLocationInPortalChamber(location: Location): PortalFrame? {
for (type in this.allPortals) {
if (type.isEmpty) {
continue
}
val result = type.isLocationInPortalChamber(location)
if (result != null) {
return result.frame
}
}
return null
}
fun loadFromAndAutoSaveTo(server: Server, mainFile: File, backupFile: File? = null) {
var firstException:Throwable? = null
val loadingMainFile = mainFile.exists()
if (loadingMainFile) {
try {
val protoData = mainFile.readBytes()
val proto = PortalSaveDataProtos.PortalSaveData.parseFrom(protoData)
this.loadFromProto(server, proto)
} catch (e: IOException) {
if (backupFile == null || !backupFile.exists()) {
throw DeserializationException(cause = e)
} else {
firstException = e
}
} catch (e: InvalidProtocolBufferException) {
if (backupFile == null || !backupFile.exists()) {
throw DeserializationException(cause = e)
} else {
firstException = e
}
}
}
if ((!loadingMainFile || firstException != null) && backupFile != null && backupFile.exists()) {
try {
val protoData = backupFile.readBytes()
backupFile.renameTo(mainFile)
val proto = PortalSaveDataProtos.PortalSaveData.parseFrom(protoData)
this.loadFromProto(server, proto)
} catch (e: IOException) {
throw DeserializationException(cause = firstException ?: e)
} catch (e: InvalidProtocolBufferException) {
throw DeserializationException(cause = firstException ?: e)
}
}
this.saveDataTo = mainFile
this.useBackup = backupFile
logger.info("Saving data to ${saveDataTo?.absolutePath} and using ${useBackup?.absolutePath} as backup")
}
fun validateWorldOnStartup(logger: Logger) {
for (portalType in this.allPortals) {
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.color == portal.type.color && portal.frame.isActivePortal()
}
fun launchEffectsOnStartup(plugin: Plugin) {
for (portalType in this.allPortals) {
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
this.useBackup = null
}
fun onAfterChanged() {
val destination = saveDataTo ?: return
logger.info("Saving updated data to $destination...")
val tempDestination = File.createTempFile(destination.nameWithoutExtension, ".tmp.binproto", destination.parentFile)
val backupDestination = useBackup
if (backupDestination != null && destination.exists()) {
backupDestination.delete()
destination.copyTo(backupDestination)
}
val proto = toProto()
val bytes = proto.toByteArray()
tempDestination.writeBytes(bytes)
destination.delete()
tempDestination.renameTo(destination)
backupDestination?.delete()
}
/** 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))
onAfterChanged()
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)
onAfterChanged()
return result?.frame
}
fun getPortalType(frame: PortalFrame): PortalType {
return portalTypes[frame.mineral]?.get(frame.color) ?: throw IllegalArgumentException("There are no portals of type ${frame.mineral}/${frame.color}")
}
/** Gets the other portal, if the given frame is one of the portals in a pair. */
fun getOtherPortal(frame: PortalFrame): PortalFrame? {
val type = getPortalType(frame)
return type.getOtherPortal(frame)?.frame
}
/** Forgets all the data without actually affecting the world. */
fun clear() {
for (portalType in this.allPortals) {
portalType.oldestActivePortal?.stopEffects()
portalType.newestActivePortal?.stopEffects()
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)
}
for (userData in proto.userDataList) {
userColors[userData.name] = userData.color.toMaterial()
}
}
fun toProto(): PortalSaveDataProtos.PortalSaveData {
val builder = PortalSaveDataProtos.PortalSaveData.newBuilder()
for (portalType in this.allPortals) {
when {
portalType.isEmpty -> {
// Nothing here to save. Just skip it.
}
portalType.isPaired -> {
builder.addPairedPortals(portalType.toProto())
}
else -> {
builder.addUnpairedPortals(portalType.getOnlyPortal.toProto())
}
}
}
for (user in this.userColors.keys) {
builder.addUserData(PortalSaveDataProtos.UserData.newBuilder().setName(user).setColor(this.userColors[user]?.toColorProto()))
}
return builder.build()
}
fun startTeleporting(portal: PortalFrame, plugin: Plugin) {
getPortalType(portal).beginTeleportation(plugin)
}
fun stopTeleporting(portal: PortalFrame) {
getPortalType(portal).cancelTeleportation()
}
private val defaultColor = Material.GRAY_STAINED_GLASS
fun getColorFor(name: String): Material {
return userColors.getOrDefault(name, defaultColor)
}
fun setColorFor(name: String, color: Material) {
if (color == defaultColor) {
userColors.remove(name)
} else {
userColors[name] = color
}
onAfterChanged()
}
}