diff --git a/src/main/kotlin/com/coderjoe/atlas/Atlas.kt b/src/main/kotlin/com/coderjoe/atlas/Atlas.kt index 41cae52..5cbaa7a 100644 --- a/src/main/kotlin/com/coderjoe/atlas/Atlas.kt +++ b/src/main/kotlin/com/coderjoe/atlas/Atlas.kt @@ -1,13 +1,13 @@ package com.coderjoe.atlas +import com.coderjoe.atlas.core.AtlasBlockListener +import com.coderjoe.atlas.core.BlockSystem import com.coderjoe.atlas.fluid.FluidBlockDialog -import com.coderjoe.atlas.fluid.FluidBlockInitializer -import com.coderjoe.atlas.fluid.FluidBlockListener +import com.coderjoe.atlas.fluid.FluidBlockFactory import com.coderjoe.atlas.fluid.FluidBlockPersistence import com.coderjoe.atlas.fluid.FluidBlockRegistry import com.coderjoe.atlas.power.PowerBlockDialog -import com.coderjoe.atlas.power.PowerBlockInitializer -import com.coderjoe.atlas.power.PowerBlockListener +import com.coderjoe.atlas.power.PowerBlockFactory import com.coderjoe.atlas.power.PowerBlockPersistence import com.coderjoe.atlas.power.PowerBlockRegistry import org.bukkit.plugin.java.JavaPlugin @@ -44,6 +44,32 @@ class Atlas : JavaPlugin() { initPowerSystem() initFluidSystem() + // Register unified listener + val powerSystem = BlockSystem( + name = "power", + registry = powerBlockRegistry, + factory = PowerBlockFactory, + descriptors = powerDescriptors(), + showDialog = { player, block -> + PowerBlockDialog.showPowerDialog(player, block as com.coderjoe.atlas.power.PowerBlock, powerBlockRegistry) + } + ) + + val fluidSystem = BlockSystem( + name = "fluid", + registry = fluidBlockRegistry, + factory = FluidBlockFactory, + descriptors = fluidDescriptors(), + showDialog = { player, block -> + FluidBlockDialog.showFluidDialog(player, block as com.coderjoe.atlas.fluid.FluidBlock, fluidBlockRegistry) + } + ) + + server.pluginManager.registerEvents( + AtlasBlockListener(this, listOf(powerSystem, fluidSystem)), + this + ) + // Auto-save every 5 minutes (6000 ticks) autoSaveTask = server.scheduler.runTaskTimer(this, Runnable { powerBlockPersistence.save(powerBlockRegistry) @@ -79,24 +105,37 @@ class Atlas : JavaPlugin() { } fun initPowerSystem() { - PowerBlockInitializer.initialize(this) + PowerBlockFactory.registerFromDescriptors(powerDescriptors().values) powerBlockRegistry = PowerBlockRegistry(this) powerBlockPersistence = PowerBlockPersistence(this) powerBlockPersistence.load(powerBlockRegistry) - server.pluginManager.registerEvents(PowerBlockListener(this, powerBlockRegistry), this) - - logger.info("Power system initialized") + logger.info("Power system initialized with ${PowerBlockFactory.getRegisteredBlockIds().size} block types") } fun initFluidSystem() { - FluidBlockInitializer.initialize(this) + FluidBlockFactory.registerFromDescriptors(fluidDescriptors().values) fluidBlockRegistry = FluidBlockRegistry(this) fluidBlockPersistence = FluidBlockPersistence(this) fluidBlockPersistence.load(fluidBlockRegistry) - server.pluginManager.registerEvents(FluidBlockListener(this, fluidBlockRegistry), this) + logger.info("Fluid system initialized with ${FluidBlockFactory.getRegisteredBlockIds().size} block types") + } + + private fun powerDescriptors(): Map { + return listOf( + com.coderjoe.atlas.power.block.SmallSolarPanel.descriptor, + com.coderjoe.atlas.power.block.SmallDrill.descriptor, + com.coderjoe.atlas.power.block.SmallBattery.descriptor, + com.coderjoe.atlas.power.block.PowerCable.descriptor + ).associateBy { it.baseBlockId } + } - logger.info("Fluid system initialized") + private fun fluidDescriptors(): Map { + return listOf( + com.coderjoe.atlas.fluid.block.FluidPump.descriptor, + com.coderjoe.atlas.fluid.block.FluidPipe.descriptor, + com.coderjoe.atlas.fluid.block.FluidContainer.descriptor + ).associateBy { it.baseBlockId } } } diff --git a/src/main/kotlin/com/coderjoe/atlas/NexoIntegration.kt b/src/main/kotlin/com/coderjoe/atlas/NexoIntegration.kt index 77cab81..0cbede7 100644 --- a/src/main/kotlin/com/coderjoe/atlas/NexoIntegration.kt +++ b/src/main/kotlin/com/coderjoe/atlas/NexoIntegration.kt @@ -1,11 +1,9 @@ package com.coderjoe.atlas -import com.nexomc.nexo.api.NexoBlocks -import com.nexomc.nexo.api.NexoItems -import org.bukkit.Location -import org.bukkit.block.Block import org.bukkit.plugin.java.JavaPlugin import java.io.File +import java.net.URI +import java.util.jar.JarFile class NexoIntegration(private val plugin: JavaPlugin) { private val nexoFolder: File @@ -41,99 +39,42 @@ class NexoIntegration(private val plugin: JavaPlugin) { texturesFolder.mkdirs() } - // Copy block textures - val textures = listOf( - "small_solar_panel", - "small_solar_panel_full", - "small_solar_panel_side", - "small_solar_panel_bottom", - "power_cable_front_powered", - "power_cable_back_powered", - "power_cable_side_up_powered", - "power_cable_side_down_powered", - "power_cable_side_left_powered", - "power_cable_side_right_powered", - "power_cable_cap_up_powered", - "power_cable_cap_down_powered", - "power_cable_cap_left_powered", - "power_cable_cap_right_powered", - "small_drill", - "small_drill_front", - "small_drill_arrow_up", - "small_drill_arrow_down", - "small_drill_arrow_left", - "small_drill_arrow_right", - "small_battery", - "small_battery_low", - "small_battery_medium", - "small_battery_full", - "small_battery_side", - "small_battery_bottom", - "fluid_pump_top", - "fluid_pump_side", - "fluid_pump_bottom", - "fluid_pump_top_active", - "fluid_pump_side_active", - "fluid_pump_bottom_active", - "fluid_pipe_front", - "fluid_pipe_back", - "fluid_pipe_side_up", - "fluid_pipe_side_down", - "fluid_pipe_side_left", - "fluid_pipe_side_right", - "fluid_pipe_front_filled", - "fluid_pipe_back_filled", - "fluid_pipe_side_filled_up", - "fluid_pipe_side_filled_down", - "fluid_pipe_side_filled_left", - "fluid_pipe_side_filled_right", - "fluid_pump_top_active_lava", - "fluid_pump_side_active_lava", - "fluid_pump_bottom_active_lava", - "fluid_pipe_front_filled_lava", - "fluid_pipe_back_filled_lava", - "fluid_pipe_side_filled_lava_up", - "fluid_pipe_side_filled_lava_down", - "fluid_pipe_side_filled_lava_left", - "fluid_pipe_side_filled_lava_right", - "fluid_container_front", - "fluid_container_back", - "fluid_container_side", - "fluid_container_top", - "fluid_container_front_water_low", - "fluid_container_back_water_low", - "fluid_container_side_water_low", - "fluid_container_top_water_low", - "fluid_container_front_water_medium", - "fluid_container_back_water_medium", - "fluid_container_side_water_medium", - "fluid_container_top_water_medium", - "fluid_container_front_water_full", - "fluid_container_back_water_full", - "fluid_container_side_water_full", - "fluid_container_top_water_full", - "fluid_container_front_lava_low", - "fluid_container_back_lava_low", - "fluid_container_side_lava_low", - "fluid_container_top_lava_low", - "fluid_container_front_lava_medium", - "fluid_container_back_lava_medium", - "fluid_container_side_lava_medium", - "fluid_container_top_lava_medium", - "fluid_container_front_lava_full", - "fluid_container_back_lava_full", - "fluid_container_side_lava_full", - "fluid_container_top_lava_full" - ) - for (textureName in textures) { - val textureFile = File(texturesFolder, "$textureName.png") - plugin.saveResource("nexo/pack/assets/atlas/textures/block/$textureName.png", true) - val sourceFile = File(plugin.dataFolder, "nexo/pack/assets/atlas/textures/block/$textureName.png") + val prefix = "nexo/pack/assets/atlas/textures/block/" + val texturePaths = discoverResources(prefix, ".png") + + for (resourcePath in texturePaths) { + val fileName = resourcePath.substringAfterLast("/") + val textureFile = File(texturesFolder, fileName) + plugin.saveResource(resourcePath, true) + val sourceFile = File(plugin.dataFolder, resourcePath) if (sourceFile.exists()) { sourceFile.copyTo(textureFile, overwrite = true) sourceFile.delete() - plugin.logger.info("Copied $textureName texture to Nexo") + plugin.logger.info("Copied ${fileName.removeSuffix(".png")} texture to Nexo") + } + } + } + + private fun discoverResources(prefix: String, suffix: String): List { + val url = javaClass.classLoader.getResource(prefix) ?: return emptyList() + + return when (url.protocol) { + "jar" -> { + val jarPath = url.toURI().schemeSpecificPart.substringBefore("!") + JarFile(File(URI(jarPath))).use { jar -> + jar.entries().asSequence() + .filter { it.name.startsWith(prefix) && it.name.endsWith(suffix) && !it.isDirectory } + .map { it.name } + .toList() + } + } + "file" -> { + File(url.toURI()).listFiles() + ?.filter { it.name.endsWith(suffix) } + ?.map { prefix + it.name } + ?: emptyList() } + else -> emptyList() } } diff --git a/src/main/kotlin/com/coderjoe/atlas/core/AtlasBlock.kt b/src/main/kotlin/com/coderjoe/atlas/core/AtlasBlock.kt new file mode 100644 index 0000000..ce22002 --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/core/AtlasBlock.kt @@ -0,0 +1,71 @@ +package com.coderjoe.atlas.core + +import com.coderjoe.atlas.Atlas +import com.nexomc.nexo.api.NexoBlocks +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.block.BlockFace +import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.scheduler.BukkitTask + +abstract class AtlasBlock( + val location: Location +) { + private var updateTask: BukkitTask? = null + protected val plugin: JavaPlugin get() = testPlugin ?: JavaPlugin.getPlugin(Atlas::class.java) + protected open val updateIntervalTicks: Long = 20L + private var currentVisualState: String? = null + + companion object { + @JvmStatic + internal var testPlugin: JavaPlugin? = null + } + + protected abstract fun blockUpdate() + abstract fun getVisualStateBlockId(): String + abstract fun getRegistry(): BlockRegistry<*> + + open val facing: BlockFace get() = BlockFace.SELF + open val baseBlockId: String get() = "" + + protected fun updateVisualState() { + val newState = getVisualStateBlockId() + if (newState != currentVisualState) { + val registry = getRegistry() + val key = BlockRegistry.locationKey(location) + registry.updatingLocations.add(key) + try { + location.block.setType(Material.AIR, false) + NexoBlocks.place(newState, location) + currentVisualState = newState + } finally { + registry.updatingLocations.remove(key) + } + } + } + + fun start() { + currentVisualState = NexoBlocks.customBlockMechanic(location.block)?.itemID + + plugin.server.scheduler.runTask(plugin, Runnable { + updateVisualState() + }) + + updateTask = plugin.server.scheduler.runTaskTimer(plugin, Runnable { + try { + blockUpdate() + updateVisualState() + } catch (e: Exception) { + plugin.logger.warning("Error in block tick at ${location.blockX},${location.blockY},${location.blockZ}: ${e.message}") + } + }, updateIntervalTicks, updateIntervalTicks) + + plugin.logger.info("${this::class.simpleName} at ${location.blockX},${location.blockY},${location.blockZ} started") + } + + fun stop() { + updateTask?.cancel() + updateTask = null + plugin.logger.info("${this::class.simpleName} at ${location.blockX},${location.blockY},${location.blockZ} stopped") + } +} diff --git a/src/main/kotlin/com/coderjoe/atlas/core/AtlasBlockDialog.kt b/src/main/kotlin/com/coderjoe/atlas/core/AtlasBlockDialog.kt new file mode 100644 index 0000000..c4d3ece --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/core/AtlasBlockDialog.kt @@ -0,0 +1,52 @@ +package com.coderjoe.atlas.core + +import org.bukkit.entity.Player +import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.scheduler.BukkitTask +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +object AtlasBlockDialog { + private lateinit var plugin: JavaPlugin + private val activeDialogs = ConcurrentHashMap() + + fun init(plugin: JavaPlugin) { + this.plugin = plugin + } + + fun cleanup() { + activeDialogs.values.forEach { it.cancel() } + activeDialogs.clear() + } + + fun showDialog( + player: Player, + block: AtlasBlock, + registry: BlockRegistry<*>, + renderDialog: (Player, AtlasBlock, onClose: (Player) -> Unit) -> Unit + ) { + activeDialogs.remove(player.uniqueId)?.cancel() + + val onClose: (Player) -> Unit = { p -> activeDialogs.remove(p.uniqueId)?.cancel() } + + renderDialog(player, block, onClose) + + val task = plugin.server.scheduler.runTaskTimer(plugin, Runnable { + if (!player.isOnline) { + activeDialogs.remove(player.uniqueId)?.cancel() + return@Runnable + } + if (player.location.distance(block.location) > 10) { + activeDialogs.remove(player.uniqueId)?.cancel() + return@Runnable + } + if (registry.getBlock(block.location) == null) { + activeDialogs.remove(player.uniqueId)?.cancel() + return@Runnable + } + renderDialog(player, block, onClose) + }, 10L, 10L) + + activeDialogs[player.uniqueId] = task + } +} diff --git a/src/main/kotlin/com/coderjoe/atlas/core/AtlasBlockListener.kt b/src/main/kotlin/com/coderjoe/atlas/core/AtlasBlockListener.kt new file mode 100644 index 0000000..9eabb5f --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/core/AtlasBlockListener.kt @@ -0,0 +1,167 @@ +package com.coderjoe.atlas.core + +import com.nexomc.nexo.api.NexoBlocks +import com.nexomc.nexo.api.NexoItems +import org.bukkit.Material +import org.bukkit.block.BlockFace +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.block.Action +import org.bukkit.event.block.BlockBreakEvent +import org.bukkit.event.block.BlockPlaceEvent +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.plugin.java.JavaPlugin + +class AtlasBlockListener( + private val plugin: JavaPlugin, + private val systems: List> +) : Listener { + + @EventHandler + fun onBlockPlace(event: BlockPlaceEvent) { + val location = event.block.location + val key = BlockRegistry.locationKey(location) + + if (systems.any { it.registry.updatingLocations.contains(key) }) return + + val mechanic = NexoBlocks.customBlockMechanic(event.block) ?: return + val blockId = mechanic.itemID + + for (system in systems) { + val baseDescriptor = system.findDescriptorByBaseId(blockId) + if (baseDescriptor != null) { + handlePlacement(event, system, baseDescriptor, blockId) + return + } + + val descriptor = system.findDescriptorForBlockId(blockId) + if (descriptor != null) { + val facing = resolveFacingFromVariant(descriptor, blockId) + createAndRegister(system, blockId, event.block.location, facing) + return + } + } + } + + private fun handlePlacement( + event: BlockPlaceEvent, + system: BlockSystem<*>, + descriptor: BlockDescriptor, + blockId: String + ) { + when (descriptor.placementType) { + PlacementType.SIMPLE -> { + val location = event.block.location.clone() + val facing = if (descriptor.directionalVariants.isEmpty()) { + getPlayerFacing(event) + } else { + BlockFace.SELF + } + createAndRegister(system, blockId, location, facing) + } + PlacementType.DIRECTIONAL -> { + val facing = getPlayerFacing(event) + val variantId = descriptor.directionalVariants[facing] ?: return + val location = event.block.location.clone() + plugin.server.scheduler.runTask(plugin, Runnable { + location.block.setType(Material.AIR, false) + NexoBlocks.place(variantId, location) + createAndRegister(system, variantId, location, facing) + }) + } + PlacementType.DIRECTIONAL_OPPOSITE -> { + val facing = getPlayerFacing(event).oppositeFace + val variantId = descriptor.directionalVariants[facing] ?: blockId + val location = event.block.location.clone() + plugin.server.scheduler.runTask(plugin, Runnable { + location.block.setType(Material.AIR, false) + NexoBlocks.place(variantId, location) + createAndRegister(system, variantId, location, facing) + }) + } + } + } + + @Suppress("UNCHECKED_CAST") + private fun createAndRegister( + system: BlockSystem<*>, + blockId: String, + location: org.bukkit.Location, + facing: BlockFace + ) { + val factory = system.factory as BlockFactory + val registry = system.registry as BlockRegistry + val block = factory.create(blockId, location, facing) + if (block != null) { + registry.register(block, blockId) + } + } + + @EventHandler + fun onBlockBreak(event: BlockBreakEvent) { + val location = event.block.location + val key = BlockRegistry.locationKey(location) + + if (systems.any { it.registry.updatingLocations.contains(key) }) return + + for (system in systems) { + val block = system.registry.unregister(location) + if (block != null) { + val baseItemId = block.baseBlockId.ifEmpty { null } + if (baseItemId != null) { + val itemBuilder = NexoItems.itemFromId(baseItemId) + if (itemBuilder != null) { + val dropLocation = event.block.location.add(0.5, 0.5, 0.5) + event.block.world.dropItemNaturally(dropLocation, itemBuilder.build()) + event.isDropItems = false + } + } + return + } + } + } + + @EventHandler + fun onPlayerInteract(event: PlayerInteractEvent) { + if (event.action != Action.RIGHT_CLICK_BLOCK) return + if (event.player.isSneaking) return + val clickedBlock = event.clickedBlock ?: return + val location = clickedBlock.location + + for (system in systems) { + val block = system.registry.getBlock(location) + if (block != null) { + system.showDialog(event.player, block) + event.isCancelled = true + return + } + } + } + + private fun resolveFacingFromVariant(descriptor: BlockDescriptor, blockId: String): BlockFace { + for ((face, id) in descriptor.directionalVariants) { + if (id == blockId) return face + } + return BlockFace.SELF + } + + companion object { + fun getPlayerFacing(event: BlockPlaceEvent): BlockFace { + val against = event.blockAgainst.location + val placed = event.block.location + val dx = placed.blockX - against.blockX + val dy = placed.blockY - against.blockY + val dz = placed.blockZ - against.blockZ + + return when { + dy > 0 -> BlockFace.UP + dy < 0 -> BlockFace.DOWN + dx > 0 -> BlockFace.EAST + dx < 0 -> BlockFace.WEST + dz > 0 -> BlockFace.SOUTH + dz < 0 -> BlockFace.NORTH + else -> event.player.facing + } + } + } +} diff --git a/src/main/kotlin/com/coderjoe/atlas/core/BlockDescriptor.kt b/src/main/kotlin/com/coderjoe/atlas/core/BlockDescriptor.kt new file mode 100644 index 0000000..11c4921 --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/core/BlockDescriptor.kt @@ -0,0 +1,20 @@ +package com.coderjoe.atlas.core + +import org.bukkit.Location +import org.bukkit.block.BlockFace + +enum class PlacementType { + SIMPLE, + DIRECTIONAL, + DIRECTIONAL_OPPOSITE +} + +data class BlockDescriptor( + val baseBlockId: String, + val displayName: String, + val description: String, + val placementType: PlacementType, + val directionalVariants: Map, + val allRegistrableIds: List, + val constructor: (Location, BlockFace) -> AtlasBlock +) diff --git a/src/main/kotlin/com/coderjoe/atlas/core/BlockFactory.kt b/src/main/kotlin/com/coderjoe/atlas/core/BlockFactory.kt new file mode 100644 index 0000000..a53e351 --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/core/BlockFactory.kt @@ -0,0 +1,37 @@ +package com.coderjoe.atlas.core + +import org.bukkit.Location +import org.bukkit.block.BlockFace + +open class BlockFactory { + private val blockConstructors = mutableMapOf T>() + + fun register(blockId: String, constructor: (Location, BlockFace) -> T) { + blockConstructors[blockId] = constructor + } + + fun create(blockId: String, location: Location, facing: BlockFace = BlockFace.SELF): T? { + return blockConstructors[blockId]?.invoke(location, facing) + } + + fun isRegistered(blockId: String): Boolean { + return blockConstructors.containsKey(blockId) + } + + fun getRegisteredBlockIds(): Set { + return blockConstructors.keys + } + + @Suppress("UNCHECKED_CAST") + fun registerFromDescriptors(descriptors: Collection) { + for (desc in descriptors) { + for (id in desc.allRegistrableIds) { + register(id, desc.constructor as (Location, BlockFace) -> T) + } + } + } + + fun clear() { + blockConstructors.clear() + } +} diff --git a/src/main/kotlin/com/coderjoe/atlas/core/BlockPersistence.kt b/src/main/kotlin/com/coderjoe/atlas/core/BlockPersistence.kt new file mode 100644 index 0000000..48d23e5 --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/core/BlockPersistence.kt @@ -0,0 +1,109 @@ +package com.coderjoe.atlas.core + +import org.bukkit.Location +import org.bukkit.block.BlockFace +import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.plugin.java.JavaPlugin +import java.io.File + +class BlockPersistence( + private val plugin: JavaPlugin, + private val fileName: String, + private val yamlKey: String, + private val factory: BlockFactory, + private val serialize: (T, String) -> Map, + private val restore: (T, Map) -> Unit +) { + private val dataFile = File(plugin.dataFolder, fileName) + + fun save(registry: BlockRegistry) { + val config = YamlConfiguration() + val blocksWithIds = registry.getAllBlocksWithIds() + + plugin.logger.info("Saving ${blocksWithIds.size} blocks to $fileName...") + + val blockDataList = mutableListOf>() + + for ((block, blockId) in blocksWithIds) { + val map = mutableMapOf( + "blockId" to blockId, + "world" to (block.location.world?.name ?: "world"), + "x" to block.location.blockX, + "y" to block.location.blockY, + "z" to block.location.blockZ + ) + val facing = block.facing + if (facing != BlockFace.SELF) { + map["facing"] = facing.name + } + map.putAll(serialize(block, blockId)) + blockDataList.add(map) + } + + config.set(yamlKey, blockDataList) + + try { + config.save(dataFile) + plugin.logger.info("Successfully saved ${blockDataList.size} blocks to $fileName") + } catch (e: Exception) { + plugin.logger.severe("Failed to save blocks to $fileName: ${e.message}") + e.printStackTrace() + } + } + + fun load(registry: BlockRegistry) { + if (!dataFile.exists()) { + plugin.logger.info("No $fileName data file found, starting fresh") + return + } + + val config = YamlConfiguration.loadConfiguration(dataFile) + val blockDataList = config.getMapList(yamlKey) + + plugin.logger.info("Loading ${blockDataList.size} blocks from $fileName...") + + var loadedCount = 0 + var failedCount = 0 + + for (blockDataMap in blockDataList) { + try { + val blockId = blockDataMap["blockId"] as? String ?: continue + val worldName = blockDataMap["world"] as? String ?: continue + val x = (blockDataMap["x"] as? Number)?.toInt() ?: continue + val y = (blockDataMap["y"] as? Number)?.toInt() ?: continue + val z = (blockDataMap["z"] as? Number)?.toInt() ?: continue + val facingStr = blockDataMap["facing"] as? String + + val world = plugin.server.getWorld(worldName) + if (world == null) { + plugin.logger.warning("Failed to load block at $worldName $x,$y,$z - world not found") + failedCount++ + continue + } + + val location = Location(world, x.toDouble(), y.toDouble(), z.toDouble()) + val facing = if (facingStr != null) { + try { BlockFace.valueOf(facingStr) } catch (_: Exception) { BlockFace.SELF } + } else { + BlockFace.SELF + } + + val block = factory.create(blockId, location, facing) + if (block != null) { + @Suppress("UNCHECKED_CAST") + restore(block, blockDataMap as Map) + registry.register(block, blockId) + loadedCount++ + } else { + plugin.logger.warning("Failed to create block for ID: $blockId at $x,$y,$z") + failedCount++ + } + } catch (e: Exception) { + plugin.logger.warning("Failed to load block: ${e.message}") + failedCount++ + } + } + + plugin.logger.info("Loaded $loadedCount blocks from $fileName, $failedCount failed") + } +} diff --git a/src/main/kotlin/com/coderjoe/atlas/core/BlockRegistry.kt b/src/main/kotlin/com/coderjoe/atlas/core/BlockRegistry.kt new file mode 100644 index 0000000..9e9d836 --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/core/BlockRegistry.kt @@ -0,0 +1,78 @@ +package com.coderjoe.atlas.core + +import org.bukkit.Location +import org.bukkit.block.BlockFace +import org.bukkit.plugin.java.JavaPlugin +import java.util.concurrent.ConcurrentHashMap + +open class BlockRegistry(protected val plugin: JavaPlugin) { + protected val blocks = ConcurrentHashMap() + protected val blockIds = ConcurrentHashMap() + val updatingLocations: MutableSet = ConcurrentHashMap.newKeySet() + + companion object { + fun locationKey(location: Location): String { + return "${location.world?.name}:${location.blockX},${location.blockY},${location.blockZ}" + } + } + + fun register(block: T, blockId: String) { + val key = locationKey(block.location) + blocks[key] = block + blockIds[key] = blockId + block.start() + plugin.logger.info("Registered ${block::class.simpleName} at ${block.location.blockX},${block.location.blockY},${block.location.blockZ}") + } + + fun unregister(location: Location): T? { + val key = locationKey(location) + val block = blocks.remove(key) + blockIds.remove(key) + block?.stop() + if (block != null) { + plugin.logger.info("Unregistered ${block::class.simpleName} at ${location.blockX},${location.blockY},${location.blockZ}") + } + return block + } + + fun getBlock(location: Location): T? { + return blocks[locationKey(location)] + } + + fun getAdjacentBlock(location: Location, face: BlockFace): T? { + val offset = face.direction + return getBlock(Location(location.world, + (location.blockX + offset.blockX).toDouble(), + (location.blockY + offset.blockY).toDouble(), + (location.blockZ + offset.blockZ).toDouble())) + } + + fun getAdjacentBlocks(location: Location): List { + val offsets = listOf( + intArrayOf(1, 0, 0), intArrayOf(-1, 0, 0), + intArrayOf(0, 1, 0), intArrayOf(0, -1, 0), + intArrayOf(0, 0, 1), intArrayOf(0, 0, -1) + ) + return offsets.mapNotNull { (dx, dy, dz) -> + getBlock(Location(location.world, (location.blockX + dx).toDouble(), (location.blockY + dy).toDouble(), (location.blockZ + dz).toDouble())) + } + } + + fun getAllBlocksWithIds(): List> { + return blocks.entries.mapNotNull { entry -> + val block = entry.value + val blockId = blockIds[entry.key] + if (blockId != null) Pair(block, blockId) else null + } + } + + fun getAllBlocks(): Collection { + return blocks.values + } + + fun stopAll() { + plugin.logger.info("Stopping ${blocks.size} blocks...") + blocks.values.forEach { it.stop() } + blocks.clear() + } +} diff --git a/src/main/kotlin/com/coderjoe/atlas/core/BlockSystem.kt b/src/main/kotlin/com/coderjoe/atlas/core/BlockSystem.kt new file mode 100644 index 0000000..22cc42f --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/core/BlockSystem.kt @@ -0,0 +1,19 @@ +package com.coderjoe.atlas.core + +import org.bukkit.entity.Player + +class BlockSystem( + val name: String, + val registry: BlockRegistry, + val factory: BlockFactory, + val descriptors: Map, + val showDialog: (Player, AtlasBlock) -> Unit +) { + fun findDescriptorForBlockId(blockId: String): BlockDescriptor? { + return descriptors.values.find { blockId in it.allRegistrableIds } + } + + fun findDescriptorByBaseId(blockId: String): BlockDescriptor? { + return descriptors[blockId] + } +} diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlock.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlock.kt index 407ecba..1073fcc 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlock.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlock.kt @@ -1,24 +1,13 @@ package com.coderjoe.atlas.fluid -import com.coderjoe.atlas.Atlas -import com.nexomc.nexo.api.NexoBlocks +import com.coderjoe.atlas.core.AtlasBlock +import com.coderjoe.atlas.core.BlockRegistry import org.bukkit.Location -import org.bukkit.Material -import org.bukkit.plugin.java.JavaPlugin -import org.bukkit.scheduler.BukkitTask abstract class FluidBlock( - val location: Location, + location: Location, var storedFluid: FluidType = FluidType.NONE -) { - private var updateTask: BukkitTask? = null - protected val plugin: JavaPlugin get() = testPlugin ?: JavaPlugin.getPlugin(Atlas::class.java) - protected open val updateIntervalTicks: Long = 20L - - companion object { - @JvmStatic - internal var testPlugin: JavaPlugin? = null - } +) : AtlasBlock(location) { open fun hasFluid(): Boolean = storedFluid != FluidType.NONE @@ -35,47 +24,12 @@ abstract class FluidBlock( } protected abstract fun fluidUpdate() - abstract fun getVisualStateBlockId(): String - private var currentVisualState: String? = null - - protected fun updateVisualState() { - val newState = getVisualStateBlockId() - if (newState != currentVisualState) { - val key = FluidBlockRegistry.locationKey(location) - val registry = FluidBlockRegistry.instance ?: return - registry.updatingLocations.add(key) - try { - location.block.setType(Material.AIR, false) - NexoBlocks.place(newState, location) - currentVisualState = newState - } finally { - registry.updatingLocations.remove(key) - } - } - } - - fun start() { - currentVisualState = NexoBlocks.customBlockMechanic(location.block)?.itemID - - plugin.server.scheduler.runTask(plugin, Runnable { - updateVisualState() - }) - - updateTask = plugin.server.scheduler.runTaskTimer(plugin, Runnable { - try { - fluidUpdate() - updateVisualState() - } catch (e: Exception) { - plugin.logger.warning("Error in fluid block tick at ${location.blockX},${location.blockY},${location.blockZ}: ${e.message}") - } - }, updateIntervalTicks, updateIntervalTicks) - plugin.logger.info("${this::class.simpleName} at ${location.blockX},${location.blockY},${location.blockZ} started") + override fun blockUpdate() { + fluidUpdate() } - fun stop() { - updateTask?.cancel() - updateTask = null - plugin.logger.info("${this::class.simpleName} at ${location.blockX},${location.blockY},${location.blockZ} stopped") + override fun getRegistry(): BlockRegistry<*> { + return FluidBlockRegistry.instance ?: throw IllegalStateException("FluidBlockRegistry not initialized") } } diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockData.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockData.kt index 18450e5..3a6d6bc 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockData.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockData.kt @@ -1,8 +1,6 @@ package com.coderjoe.atlas.fluid import com.coderjoe.atlas.fluid.block.FluidContainer -import com.coderjoe.atlas.fluid.block.FluidPipe -import com.coderjoe.atlas.fluid.block.FluidPump import org.bukkit.Location import org.bukkit.block.BlockFace import org.bukkit.plugin.java.JavaPlugin @@ -20,11 +18,7 @@ data class FluidBlockData( companion object { fun fromFluidBlock(fluidBlock: FluidBlock, blockId: String): FluidBlockData { val loc = fluidBlock.location - val facing = when (fluidBlock) { - is FluidPipe -> fluidBlock.facing.name - is FluidContainer -> fluidBlock.facing.name - else -> null - } + val facing = fluidBlock.facing.let { if (it == BlockFace.SELF) null else it.name } val storedAmount = when (fluidBlock) { is FluidContainer -> fluidBlock.storedAmount else -> null diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt index bf03f36..75b5eb1 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt @@ -1,5 +1,7 @@ package com.coderjoe.atlas.fluid +import com.coderjoe.atlas.core.AtlasBlockDialog +import com.coderjoe.atlas.core.BlockRegistry import com.coderjoe.atlas.fluid.block.FluidContainer import com.coderjoe.atlas.fluid.block.FluidPipe import com.coderjoe.atlas.fluid.block.FluidPump @@ -16,45 +18,24 @@ import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextDecoration import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin -import org.bukkit.scheduler.BukkitTask -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap object FluidBlockDialog { - private lateinit var plugin: JavaPlugin - private val activeDialogs = ConcurrentHashMap() - fun init(plugin: JavaPlugin) { - this.plugin = plugin + AtlasBlockDialog.init(plugin) } - fun showFluidDialog(player: Player, fluidBlock: FluidBlock) { - activeDialogs.remove(player.uniqueId)?.cancel() - - sendDialog(player, fluidBlock) - - val task = plugin.server.scheduler.runTaskTimer(plugin, Runnable { - if (!player.isOnline) { - activeDialogs.remove(player.uniqueId)?.cancel() - return@Runnable - } - if (player.location.distance(fluidBlock.location) > 10) { - activeDialogs.remove(player.uniqueId)?.cancel() - return@Runnable - } - val registry = FluidBlockRegistry.instance - if (registry == null || registry.getFluidBlock(fluidBlock.location) == null) { - activeDialogs.remove(player.uniqueId)?.cancel() - return@Runnable - } - sendDialog(player, fluidBlock) - }, 10L, 10L) + fun showFluidDialog(player: Player, fluidBlock: FluidBlock, registry: BlockRegistry<*>) { + AtlasBlockDialog.showDialog(player, fluidBlock, registry) { p, block, onClose -> + sendDialog(p, block as FluidBlock, onClose) + } + } - activeDialogs[player.uniqueId] = task + fun cleanup() { + AtlasBlockDialog.cleanup() } - private fun sendDialog(player: Player, fluidBlock: FluidBlock) { + private fun sendDialog(player: Player, fluidBlock: FluidBlock, onClose: (Player) -> Unit) { val title = Component.text(getBlockDisplayName(fluidBlock)) val bodyText = buildFluidInfo(fluidBlock) val body = DialogBody.plainMessage(bodyText) @@ -62,7 +43,7 @@ object FluidBlockDialog { val closeAction = DialogAction.customClick( DialogActionCallback { _, audience -> val p = audience as? Player ?: return@DialogActionCallback - activeDialogs.remove(p.uniqueId)?.cancel() + onClose(p) }, ClickCallback.Options.builder().build() ) @@ -86,11 +67,6 @@ object FluidBlockDialog { player.showDialog(dialog) } - fun cleanup() { - activeDialogs.values.forEach { it.cancel() } - activeDialogs.clear() - } - private fun getBlockDisplayName(fluidBlock: FluidBlock): String = when (fluidBlock) { is FluidPump -> "Fluid Pump" is FluidPipe -> "Fluid Pipe (${fluidBlock.facing.name.lowercase().replaceFirstChar { it.uppercase() }})" diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockFactory.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockFactory.kt index 33d8898..6adb444 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockFactory.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockFactory.kt @@ -1,25 +1,12 @@ package com.coderjoe.atlas.fluid +import com.coderjoe.atlas.core.BlockFactory import org.bukkit.Location import org.bukkit.block.BlockFace -object FluidBlockFactory { - private val blockConstructors = mutableMapOf FluidBlock>() - - fun register(blockId: String, constructor: (Location, BlockFace) -> FluidBlock) { - blockConstructors[blockId] = constructor - println("FluidBlockFactory: Registered block ID '$blockId'") - } +object FluidBlockFactory : BlockFactory() { fun createFluidBlock(blockId: String, location: Location, facing: BlockFace = BlockFace.SELF): FluidBlock? { - return blockConstructors[blockId]?.invoke(location, facing) - } - - fun isRegistered(blockId: String): Boolean { - return blockConstructors.containsKey(blockId) - } - - fun getRegisteredBlockIds(): Set { - return blockConstructors.keys + return create(blockId, location, facing) } } diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializer.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializer.kt deleted file mode 100644 index 9a26598..0000000 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializer.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.coderjoe.atlas.fluid - -import com.coderjoe.atlas.fluid.block.FluidContainer -import com.coderjoe.atlas.fluid.block.FluidPipe -import com.coderjoe.atlas.fluid.block.FluidPump -import org.bukkit.plugin.java.JavaPlugin - -object FluidBlockInitializer { - - fun initialize(plugin: JavaPlugin) { - plugin.logger.info("FluidBlockInitializer starting...") - - plugin.logger.info("Registering FluidPump...") - FluidBlockFactory.register(FluidPump.BLOCK_ID) { location, _ -> - FluidPump(location) - } - FluidBlockFactory.register(FluidPump.BLOCK_ID_ACTIVE) { location, _ -> - FluidPump(location) - } - FluidBlockFactory.register(FluidPump.BLOCK_ID_ACTIVE_LAVA) { location, _ -> - FluidPump(location) - } - - plugin.logger.info("Registering FluidPipe variants...") - for ((face, variantId) in FluidPipe.DIRECTIONAL_IDS) { - FluidBlockFactory.register(variantId) { location, facing -> - FluidPipe(location, facing) - } - } - for ((face, variantId) in FluidPipe.WATER_FILLED_IDS) { - FluidBlockFactory.register(variantId) { location, facing -> - FluidPipe(location, facing) - } - } - for ((face, variantId) in FluidPipe.LAVA_FILLED_IDS) { - FluidBlockFactory.register(variantId) { location, facing -> - FluidPipe(location, facing) - } - } - - plugin.logger.info("Registering FluidContainer variants...") - for (variantId in FluidContainer.ALL_VARIANT_IDS) { - FluidBlockFactory.register(variantId) { location, facing -> - FluidContainer(location, facing) - } - } - - val registeredBlocks = FluidBlockFactory.getRegisteredBlockIds() - plugin.logger.info("Initialized ${registeredBlocks.size} fluid block type(s): ${registeredBlocks.joinToString(", ")}") - plugin.logger.info("FluidBlockInitializer complete") - } -} diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockListener.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockListener.kt deleted file mode 100644 index c80026d..0000000 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockListener.kt +++ /dev/null @@ -1,180 +0,0 @@ -package com.coderjoe.atlas.fluid - -import com.coderjoe.atlas.fluid.block.FluidContainer -import com.coderjoe.atlas.fluid.block.FluidPipe -import com.coderjoe.atlas.fluid.block.FluidPump -import com.nexomc.nexo.api.NexoBlocks -import com.nexomc.nexo.api.NexoItems -import org.bukkit.Material -import org.bukkit.block.BlockFace -import org.bukkit.event.EventHandler -import org.bukkit.event.Listener -import org.bukkit.event.block.Action -import org.bukkit.event.block.BlockBreakEvent -import org.bukkit.event.block.BlockPlaceEvent -import org.bukkit.event.player.PlayerInteractEvent -import org.bukkit.plugin.java.JavaPlugin - -class FluidBlockListener( - private val plugin: JavaPlugin, - private val registry: FluidBlockRegistry -) : Listener { - - @EventHandler - fun onBlockPlace(event: BlockPlaceEvent) { - val key = FluidBlockRegistry.locationKey(event.block.location) - if (registry.updatingLocations.contains(key)) return - - val mechanic = NexoBlocks.customBlockMechanic(event.block) ?: return - val blockId = mechanic.itemID - - // Handle fluid_pump placement - if (blockId == FluidPump.BLOCK_ID) { - val location = event.block.location.clone() - val fluidBlock = FluidBlockFactory.createFluidBlock(blockId, location) - if (fluidBlock != null) { - registry.registerFluidBlock(fluidBlock, blockId) - } else { - plugin.logger.warning("Failed to create fluid block for: $blockId") - } - return - } - - // Handle fluid_container base item: swap to directional variant - if (blockId == FluidContainer.BLOCK_ID) { - val facing = getPlayerFacing(event) - val variantId = FluidContainer.DIRECTIONAL_IDS[facing] - if (variantId == null) { - plugin.logger.warning("No directional variant for facing $facing") - return - } - - plugin.logger.info("Swapping fluid_container to directional variant: $variantId (facing $facing)") - - val location = event.block.location.clone() - plugin.server.scheduler.runTask(plugin, Runnable { - location.block.setType(Material.AIR, false) - NexoBlocks.place(variantId, location) - - val fluidBlock = FluidBlockFactory.createFluidBlock(variantId, location, facing) - if (fluidBlock != null) { - registry.registerFluidBlock(fluidBlock, variantId) - } else { - plugin.logger.warning("Failed to create fluid block for variant: $variantId") - } - }) - return - } - - // Handle fluid_pipe base item: swap to directional variant - if (blockId == FluidPipe.BLOCK_ID) { - val facing = getPlayerFacing(event) - val variantId = FluidPipe.DIRECTIONAL_IDS[facing] - if (variantId == null) { - plugin.logger.warning("No directional variant for facing $facing") - return - } - - plugin.logger.info("Swapping fluid_pipe to directional variant: $variantId (facing $facing)") - - val location = event.block.location.clone() - plugin.server.scheduler.runTask(plugin, Runnable { - location.block.setType(Material.AIR, false) - NexoBlocks.place(variantId, location) - - val fluidBlock = FluidBlockFactory.createFluidBlock(variantId, location, facing) - if (fluidBlock != null) { - registry.registerFluidBlock(fluidBlock, variantId) - } else { - plugin.logger.warning("Failed to create fluid block for variant: $variantId") - } - }) - return - } - - // Handle directional variant placed directly (pipe) - val pipeFacing = FluidPipe.facingFromBlockId(blockId) - if (pipeFacing != null) { - plugin.logger.info("Directional fluid pipe placed: $blockId (facing $pipeFacing)") - val fluidBlock = FluidBlockFactory.createFluidBlock(blockId, event.block.location, pipeFacing) - if (fluidBlock != null) { - registry.registerFluidBlock(fluidBlock, blockId) - } - return - } - - // Handle directional variant placed directly (container) - val containerFacing = FluidContainer.facingFromBlockId(blockId) - if (containerFacing != null) { - plugin.logger.info("Directional fluid container placed: $blockId (facing $containerFacing)") - val fluidBlock = FluidBlockFactory.createFluidBlock(blockId, event.block.location, containerFacing) - if (fluidBlock != null) { - registry.registerFluidBlock(fluidBlock, blockId) - } - return - } - - // Handle any other registered fluid block - if (FluidBlockFactory.isRegistered(blockId)) { - val fluidBlock = FluidBlockFactory.createFluidBlock(blockId, event.block.location.clone()) - if (fluidBlock != null) { - registry.registerFluidBlock(fluidBlock, blockId) - } - } - } - - @EventHandler - fun onBlockBreak(event: BlockBreakEvent) { - val key = FluidBlockRegistry.locationKey(event.block.location) - if (registry.updatingLocations.contains(key)) return - - val fluidBlock = registry.unregisterFluidBlock(event.block.location) ?: return - - plugin.logger.info("Fluid block removed: ${fluidBlock::class.simpleName} at ${event.block.location}") - - val baseItemId = when (fluidBlock) { - is FluidPump -> FluidPump.BLOCK_ID - is FluidPipe -> FluidPipe.BLOCK_ID - is FluidContainer -> FluidContainer.BLOCK_ID - else -> null - } - - if (baseItemId != null) { - val itemBuilder = NexoItems.itemFromId(baseItemId) - if (itemBuilder != null) { - val dropLocation = event.block.location.add(0.5, 0.5, 0.5) - event.block.world.dropItemNaturally(dropLocation, itemBuilder.build()) - event.isDropItems = false - } - } - } - - @EventHandler - fun onPlayerInteract(event: PlayerInteractEvent) { - if (event.action != Action.RIGHT_CLICK_BLOCK) return - if (event.player.isSneaking) return - val block = event.clickedBlock ?: return - val fluidBlock = registry.getFluidBlock(block.location) ?: return - - FluidBlockDialog.showFluidDialog(event.player, fluidBlock) - event.isCancelled = true - } - - private fun getPlayerFacing(event: BlockPlaceEvent): BlockFace { - val against = event.blockAgainst.location - val placed = event.block.location - val dx = placed.blockX - against.blockX - val dy = placed.blockY - against.blockY - val dz = placed.blockZ - against.blockZ - - return when { - dy > 0 -> BlockFace.UP - dy < 0 -> BlockFace.DOWN - dx > 0 -> BlockFace.EAST - dx < 0 -> BlockFace.WEST - dz > 0 -> BlockFace.SOUTH - dz < 0 -> BlockFace.NORTH - else -> event.player.facing - } - } -} diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistence.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistence.kt index 1dc4695..97f2f48 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistence.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistence.kt @@ -1,105 +1,40 @@ package com.coderjoe.atlas.fluid +import com.coderjoe.atlas.core.BlockPersistence import com.coderjoe.atlas.fluid.block.FluidContainer -import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.plugin.java.JavaPlugin -import java.io.File -class FluidBlockPersistence(private val plugin: JavaPlugin) { - private val dataFile = File(plugin.dataFolder, "fluid_blocks.yml") - - fun save(registry: FluidBlockRegistry) { - val config = YamlConfiguration() - val fluidBlocksWithIds = registry.getAllFluidBlocksWithIds() - - plugin.logger.info("Saving ${fluidBlocksWithIds.size} fluid blocks to disk...") - - val blockDataList = mutableListOf>() - - for ((fluidBlock, blockId) in fluidBlocksWithIds) { - val data = FluidBlockData.fromFluidBlock(fluidBlock, blockId) +class FluidBlockPersistence(plugin: JavaPlugin) { + private val persistence = BlockPersistence( + plugin = plugin, + fileName = "fluid_blocks.yml", + yamlKey = "fluid_blocks", + factory = FluidBlockFactory, + serialize = { block, _ -> val map = mutableMapOf( - "blockId" to data.blockId, - "world" to data.world, - "x" to data.x, - "y" to data.y, - "z" to data.z, - "fluidType" to data.fluidType + "fluidType" to block.storedFluid.name ) - if (data.facing != null) { - map["facing"] = data.facing - } - if (data.storedAmount != null) { - map["storedAmount"] = data.storedAmount + if (block is FluidContainer) { + map["storedAmount"] = block.storedAmount } - blockDataList.add(map) - } - - config.set("fluid_blocks", blockDataList) - - try { - config.save(dataFile) - plugin.logger.info("Successfully saved ${blockDataList.size} fluid blocks") - } catch (e: Exception) { - plugin.logger.severe("Failed to save fluid blocks: ${e.message}") - e.printStackTrace() - } - } - - fun load(registry: FluidBlockRegistry) { - if (!dataFile.exists()) { - plugin.logger.info("No fluid blocks data file found, starting fresh") - return - } - - val config = YamlConfiguration.loadConfiguration(dataFile) - val blockDataList = config.getMapList("fluid_blocks") - - plugin.logger.info("Loading ${blockDataList.size} fluid blocks from disk...") - - var loadedCount = 0 - var failedCount = 0 - - for (blockDataMap in blockDataList) { - try { - val blockId = blockDataMap["blockId"] as? String ?: continue - val world = blockDataMap["world"] as? String ?: continue - val x = (blockDataMap["x"] as? Number)?.toInt() ?: continue - val y = (blockDataMap["y"] as? Number)?.toInt() ?: continue - val z = (blockDataMap["z"] as? Number)?.toInt() ?: continue - val fluidType = blockDataMap["fluidType"] as? String ?: "NONE" - val facing = blockDataMap["facing"] as? String - val storedAmount = (blockDataMap["storedAmount"] as? Number)?.toInt() - - val data = FluidBlockData(blockId, world, x, y, z, fluidType, facing, storedAmount) - val location = data.toLocation(plugin) - - if (location == null) { - plugin.logger.warning("Failed to load fluid block at $world $x,$y,$z - world not found") - failedCount++ - continue - } - - val fluidBlock = FluidBlockFactory.createFluidBlock(blockId, location, data.toBlockFace()) - - if (fluidBlock != null) { - if (fluidBlock is FluidContainer && data.storedAmount != null) { - fluidBlock.restoreState(data.toFluidType(), data.storedAmount) - } else { - fluidBlock.storedFluid = data.toFluidType() - } - registry.registerFluidBlock(fluidBlock, blockId) - loadedCount++ + map + }, + restore = { block, data -> + val fluidTypeName = data["fluidType"] as? String ?: "NONE" + val fluidType = try { FluidType.valueOf(fluidTypeName) } catch (_: Exception) { FluidType.NONE } + if (block is FluidContainer) { + val storedAmount = (data["storedAmount"] as? Number)?.toInt() + if (storedAmount != null) { + block.restoreState(fluidType, storedAmount) } else { - plugin.logger.warning("Failed to create fluid block for ID: $blockId at $x,$y,$z") - failedCount++ + block.storedFluid = fluidType } - } catch (e: Exception) { - plugin.logger.warning("Failed to load fluid block: ${e.message}") - failedCount++ + } else { + block.storedFluid = fluidType } } + ) - plugin.logger.info("Loaded $loadedCount fluid blocks, $failedCount failed") - } + fun save(registry: FluidBlockRegistry) = persistence.save(registry) + fun load(registry: FluidBlockRegistry) = persistence.load(registry) } diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockRegistry.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockRegistry.kt index 740bcba..8496eeb 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockRegistry.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockRegistry.kt @@ -1,71 +1,30 @@ package com.coderjoe.atlas.fluid +import com.coderjoe.atlas.core.BlockRegistry import org.bukkit.Location import org.bukkit.block.BlockFace import org.bukkit.plugin.java.JavaPlugin -import java.util.concurrent.ConcurrentHashMap -class FluidBlockRegistry(private val plugin: JavaPlugin) { - private val fluidBlocks = ConcurrentHashMap() - private val blockIds = ConcurrentHashMap() - val updatingLocations: MutableSet = ConcurrentHashMap.newKeySet() +class FluidBlockRegistry(plugin: JavaPlugin) : BlockRegistry(plugin) { companion object { var instance: FluidBlockRegistry? = null private set - fun locationKey(location: Location): String { - return "${location.world?.name}:${location.blockX},${location.blockY},${location.blockZ}" - } + fun locationKey(location: Location): String = BlockRegistry.locationKey(location) } init { instance = this } - fun registerFluidBlock(fluidBlock: FluidBlock, blockId: String) { - val key = locationKey(fluidBlock.location) - fluidBlocks[key] = fluidBlock - blockIds[key] = blockId - fluidBlock.start() - plugin.logger.info("Registered ${fluidBlock::class.simpleName} at ${fluidBlock.location.blockX},${fluidBlock.location.blockY},${fluidBlock.location.blockZ}") - } - - fun unregisterFluidBlock(location: Location): FluidBlock? { - val key = locationKey(location) - val fluidBlock = fluidBlocks.remove(key) - blockIds.remove(key) - fluidBlock?.stop() - if (fluidBlock != null) { - plugin.logger.info("Unregistered ${fluidBlock::class.simpleName} at ${location.blockX},${location.blockY},${location.blockZ}") - } - return fluidBlock - } - - fun getFluidBlock(location: Location): FluidBlock? { - return fluidBlocks[locationKey(location)] - } + fun registerFluidBlock(fluidBlock: FluidBlock, blockId: String) = register(fluidBlock, blockId) - fun getAdjacentFluidBlock(location: Location, face: BlockFace): FluidBlock? { - val offset = face.direction - return getFluidBlock(Location(location.world, - (location.blockX + offset.blockX).toDouble(), - (location.blockY + offset.blockY).toDouble(), - (location.blockZ + offset.blockZ).toDouble())) - } + fun unregisterFluidBlock(location: Location): FluidBlock? = unregister(location) - fun stopAll() { - plugin.logger.info("Stopping ${fluidBlocks.size} fluid blocks...") - fluidBlocks.values.forEach { it.stop() } - fluidBlocks.clear() - } + fun getFluidBlock(location: Location): FluidBlock? = getBlock(location) - fun getAllFluidBlocksWithIds(): List> { - return fluidBlocks.entries.mapNotNull { entry -> - val fluidBlock = entry.value - val blockId = blockIds[entry.key] - if (blockId != null) Pair(fluidBlock, blockId) else null - } - } + fun getAdjacentFluidBlock(location: Location, face: BlockFace): FluidBlock? = getAdjacentBlock(location, face) + fun getAllFluidBlocksWithIds(): List> = getAllBlocksWithIds() } diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidContainer.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidContainer.kt index 4ea37a9..6cce10b 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidContainer.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidContainer.kt @@ -1,12 +1,14 @@ package com.coderjoe.atlas.fluid.block +import com.coderjoe.atlas.core.BlockDescriptor +import com.coderjoe.atlas.core.PlacementType import com.coderjoe.atlas.fluid.FluidBlock import com.coderjoe.atlas.fluid.FluidBlockRegistry import com.coderjoe.atlas.fluid.FluidType import org.bukkit.Location import org.bukkit.block.BlockFace -class FluidContainer(location: Location, val facing: BlockFace) : FluidBlock(location) { +class FluidContainer(location: Location, override val facing: BlockFace) : FluidBlock(location) { var storedAmount: Int = 0 private set @@ -67,8 +69,20 @@ class FluidContainer(location: Location, val facing: BlockFace) : FluidBlock(loc } return null } + + val descriptor = BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "Fluid Container", + description = "Container - stores up to $MAX_CAPACITY units of fluid", + placementType = PlacementType.DIRECTIONAL, + directionalVariants = DIRECTIONAL_IDS, + allRegistrableIds = ALL_VARIANT_IDS, + constructor = { loc, facing -> FluidContainer(loc, facing) } + ) } + override val baseBlockId: String = BLOCK_ID + override fun hasFluid(): Boolean = storedAmount > 0 override fun storeFluid(type: FluidType): Boolean { diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPipe.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPipe.kt index 56c7575..43860c2 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPipe.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPipe.kt @@ -1,12 +1,14 @@ package com.coderjoe.atlas.fluid.block +import com.coderjoe.atlas.core.BlockDescriptor +import com.coderjoe.atlas.core.PlacementType import com.coderjoe.atlas.fluid.FluidBlock import com.coderjoe.atlas.fluid.FluidBlockRegistry import com.coderjoe.atlas.fluid.FluidType import org.bukkit.Location import org.bukkit.block.BlockFace -class FluidPipe(location: Location, val facing: BlockFace) : FluidBlock(location) { +class FluidPipe(location: Location, override val facing: BlockFace) : FluidBlock(location) { override val updateIntervalTicks: Long = 20L @@ -43,8 +45,20 @@ class FluidPipe(location: Location, val facing: BlockFace) : FluidBlock(location ) fun facingFromBlockId(blockId: String): BlockFace? = ID_TO_FACING[blockId] + + val descriptor = BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "Fluid Pipe", + description = "Pipe - transports fluid in facing direction", + placementType = PlacementType.DIRECTIONAL, + directionalVariants = DIRECTIONAL_IDS, + allRegistrableIds = DIRECTIONAL_IDS.values.toList() + WATER_FILLED_IDS.values.toList() + LAVA_FILLED_IDS.values.toList(), + constructor = { loc, facing -> FluidPipe(loc, facing) } + ) } + override val baseBlockId: String = BLOCK_ID + override fun getVisualStateBlockId(): String = when (storedFluid) { FluidType.WATER -> WATER_FILLED_IDS[facing]!! FluidType.LAVA -> LAVA_FILLED_IDS[facing]!! diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPump.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPump.kt index 2c542ff..386b6ab 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPump.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPump.kt @@ -1,5 +1,7 @@ package com.coderjoe.atlas.fluid.block +import com.coderjoe.atlas.core.BlockDescriptor +import com.coderjoe.atlas.core.PlacementType import com.coderjoe.atlas.fluid.FluidBlock import com.coderjoe.atlas.fluid.FluidType import com.coderjoe.atlas.power.PowerBlockRegistry @@ -36,8 +38,20 @@ class FluidPump(location: Location) : FluidBlock(location) { BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.UP, BlockFace.DOWN ) + + val descriptor = BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "Fluid Pump", + description = "Pump - extracts fluid from adjacent cauldrons (1 power/s)", + placementType = PlacementType.SIMPLE, + directionalVariants = emptyMap(), + allRegistrableIds = listOf(BLOCK_ID, BLOCK_ID_ACTIVE, BLOCK_ID_ACTIVE_LAVA), + constructor = { loc, _ -> FluidPump(loc) } + ) } + override val baseBlockId: String = BLOCK_ID + fun canRemoveFluidFrom(direction: BlockFace): Boolean { val cauldron = cauldronFace ?: return false return direction == cauldron.oppositeFace && hasFluid() diff --git a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlock.kt b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlock.kt index 22278fb..671de83 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlock.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlock.kt @@ -1,28 +1,15 @@ package com.coderjoe.atlas.power -import com.coderjoe.atlas.Atlas -import com.nexomc.nexo.api.NexoBlocks +import com.coderjoe.atlas.core.AtlasBlock +import com.coderjoe.atlas.core.BlockRegistry import org.bukkit.Location -import org.bukkit.Material -import org.bukkit.plugin.java.JavaPlugin -import org.bukkit.scheduler.BukkitTask -/** - * Represents a block that participates in the power network - */ abstract class PowerBlock( - val location: Location, + location: Location, val maxStorage: Int, var currentPower: Int = 0 -) { - private var updateTask: BukkitTask? = null - protected val plugin: JavaPlugin get() = testPlugin ?: JavaPlugin.getPlugin(Atlas::class.java) - protected open val updateIntervalTicks: Long = 100L +) : AtlasBlock(location) { - companion object { - @JvmStatic - internal var testPlugin: JavaPlugin? = null - } protected open val canReceivePower: Boolean = true fun hasPower(): Boolean = currentPower > 0 @@ -43,49 +30,12 @@ abstract class PowerBlock( } protected abstract fun powerUpdate() - abstract fun getVisualStateBlockId(): String - private var currentVisualState: String? = null - - protected fun updateVisualState() { - val newState = getVisualStateBlockId() - if (newState != currentVisualState) { - val key = PowerBlockRegistry.locationKey(location) - val registry = PowerBlockRegistry.instance ?: return - registry.updatingLocations.add(key) - try { - location.block.setType(Material.AIR, false) - NexoBlocks.place(newState, location) - currentVisualState = newState - } finally { - registry.updatingLocations.remove(key) - } - } - } - - fun start() { - // Snapshot the current block so the deferred update is a no-op when already correct - currentVisualState = NexoBlocks.customBlockMechanic(location.block)?.itemID - - // Defer to next tick — corrects visual state if it doesn't match (e.g. after persistence load) - plugin.server.scheduler.runTask(plugin, Runnable { - updateVisualState() - }) - - updateTask = plugin.server.scheduler.runTaskTimer(plugin, Runnable { - try { - powerUpdate() - updateVisualState() - } catch (e: Exception) { - plugin.logger.warning("Error in power block tick at ${location.blockX},${location.blockY},${location.blockZ}: ${e.message}") - } - }, updateIntervalTicks, updateIntervalTicks) - plugin.logger.info("${this::class.simpleName} at ${location.blockX},${location.blockY},${location.blockZ} started - updating every ${updateIntervalTicks / 20} seconds") + override fun blockUpdate() { + powerUpdate() } - fun stop() { - updateTask?.cancel() - updateTask = null - plugin.logger.info("${this::class.simpleName} at ${location.blockX},${location.blockY},${location.blockZ} stopped") + override fun getRegistry(): BlockRegistry<*> { + return PowerBlockRegistry.instance ?: throw IllegalStateException("PowerBlockRegistry not initialized") } } diff --git a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockData.kt b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockData.kt index ebdb0da..ff79f0d 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockData.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockData.kt @@ -1,7 +1,5 @@ package com.coderjoe.atlas.power -import com.coderjoe.atlas.power.block.PowerCable -import com.coderjoe.atlas.power.block.SmallBattery import com.coderjoe.atlas.power.block.SmallDrill import org.bukkit.Location import org.bukkit.block.BlockFace @@ -19,12 +17,7 @@ data class PowerBlockData( companion object { fun fromPowerBlock(powerBlock: PowerBlock, blockId: String): PowerBlockData { val loc = powerBlock.location - val facing = when (powerBlock) { - is PowerCable -> powerBlock.facing.name - is SmallDrill -> powerBlock.miningDirection.name - is SmallBattery -> powerBlock.facing.name - else -> null - } + val facing = powerBlock.facing.let { if (it == BlockFace.SELF) null else it.name } val enabled = if (powerBlock is SmallDrill) powerBlock.enabled else null return PowerBlockData( blockId = blockId, diff --git a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt index 50e30fe..0a87402 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt @@ -1,5 +1,7 @@ package com.coderjoe.atlas.power +import com.coderjoe.atlas.core.AtlasBlockDialog +import com.coderjoe.atlas.core.BlockRegistry import com.coderjoe.atlas.power.block.PowerCable import com.coderjoe.atlas.power.block.SmallBattery import com.coderjoe.atlas.power.block.SmallDrill @@ -17,56 +19,32 @@ import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextDecoration import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin -import org.bukkit.scheduler.BukkitTask -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap object PowerBlockDialog { - private lateinit var plugin: JavaPlugin - private val activeDialogs = ConcurrentHashMap() - fun init(plugin: JavaPlugin) { - this.plugin = plugin + AtlasBlockDialog.init(plugin) } - fun showPowerDialog(player: Player, powerBlock: PowerBlock) { - // Cancel any existing refresh task for this player - activeDialogs.remove(player.uniqueId)?.cancel() - - // Send initial dialog - sendDialog(player, powerBlock) - - // Start refresh task (every 10 ticks / 0.5s) - val task = plugin.server.scheduler.runTaskTimer(plugin, Runnable { - if (!player.isOnline) { - activeDialogs.remove(player.uniqueId)?.cancel() - return@Runnable - } - if (player.location.distance(powerBlock.location) > 10) { - activeDialogs.remove(player.uniqueId)?.cancel() - return@Runnable - } - val registry = PowerBlockRegistry.instance - if (registry == null || registry.getPowerBlock(powerBlock.location) == null) { - activeDialogs.remove(player.uniqueId)?.cancel() - return@Runnable - } - sendDialog(player, powerBlock) - }, 10L, 10L) + fun showPowerDialog(player: Player, powerBlock: PowerBlock, registry: BlockRegistry<*>) { + AtlasBlockDialog.showDialog(player, powerBlock, registry) { p, block, onClose -> + sendDialog(p, block as PowerBlock, onClose) + } + } - activeDialogs[player.uniqueId] = task + fun cleanup() { + AtlasBlockDialog.cleanup() } - private fun sendDialog(player: Player, powerBlock: PowerBlock) { + private fun sendDialog(player: Player, powerBlock: PowerBlock, onClose: (Player) -> Unit) { if (powerBlock is SmallDrill) { - sendDrillDialog(player, powerBlock) + sendDrillDialog(player, powerBlock, onClose) } else { - sendDefaultDialog(player, powerBlock) + sendDefaultDialog(player, powerBlock, onClose) } } - private fun sendDefaultDialog(player: Player, powerBlock: PowerBlock) { + private fun sendDefaultDialog(player: Player, powerBlock: PowerBlock, onClose: (Player) -> Unit) { val title = Component.text(getBlockDisplayName(powerBlock)) val bodyText = buildPowerInfo(powerBlock) val body = DialogBody.plainMessage(bodyText) @@ -74,7 +52,7 @@ object PowerBlockDialog { val closeAction = DialogAction.customClick( DialogActionCallback { _, audience -> val p = audience as? Player ?: return@DialogActionCallback - activeDialogs.remove(p.uniqueId)?.cancel() + onClose(p) }, ClickCallback.Options.builder().build() ) @@ -98,7 +76,7 @@ object PowerBlockDialog { player.showDialog(dialog) } - private fun sendDrillDialog(player: Player, drill: SmallDrill) { + private fun sendDrillDialog(player: Player, drill: SmallDrill, onClose: (Player) -> Unit) { val title = Component.text("Small Drill") val bodyText = buildPowerInfo(drill) val body = DialogBody.plainMessage(bodyText) @@ -117,7 +95,7 @@ object PowerBlockDialog { val closeAction = DialogAction.customClick( DialogActionCallback { _, audience -> val p = audience as? Player ?: return@DialogActionCallback - activeDialogs.remove(p.uniqueId)?.cancel() + onClose(p) }, ClickCallback.Options.builder().build() ) @@ -140,11 +118,6 @@ object PowerBlockDialog { player.showDialog(dialog) } - fun cleanup() { - activeDialogs.values.forEach { it.cancel() } - activeDialogs.clear() - } - private fun getBlockDisplayName(powerBlock: PowerBlock): String = when (powerBlock) { is SmallSolarPanel -> "Small Solar Panel" is SmallBattery -> "Small Battery" diff --git a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockFactory.kt b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockFactory.kt index 7944c40..e8e20bf 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockFactory.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockFactory.kt @@ -1,43 +1,12 @@ package com.coderjoe.atlas.power +import com.coderjoe.atlas.core.BlockFactory import org.bukkit.Location import org.bukkit.block.BlockFace -/** - * Factory for creating PowerBlock instances based on block IDs - */ -object PowerBlockFactory { - private val blockConstructors = mutableMapOf PowerBlock>() +object PowerBlockFactory : BlockFactory() { - /** - * Registers a PowerBlock type with its block ID - * @param blockId The Nexo block ID (e.g., "small_solar_panel") - * @param constructor A function that creates the PowerBlock instance given a location and facing direction - */ - fun register(blockId: String, constructor: (Location, BlockFace) -> PowerBlock) { - blockConstructors[blockId] = constructor - println("PowerBlockFactory: Registered block ID '$blockId'") - } - - /** - * Creates a PowerBlock instance for the given block ID, location, and facing direction - * @return PowerBlock instance, or null if the block ID is not registered - */ fun createPowerBlock(blockId: String, location: Location, facing: BlockFace = BlockFace.SELF): PowerBlock? { - return blockConstructors[blockId]?.invoke(location, facing) - } - - /** - * Checks if a block ID is registered as a power block - */ - fun isRegistered(blockId: String): Boolean { - return blockConstructors.containsKey(blockId) - } - - /** - * Gets all registered block IDs - */ - fun getRegisteredBlockIds(): Set { - return blockConstructors.keys + return create(blockId, location, facing) } } diff --git a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockInitializer.kt b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockInitializer.kt deleted file mode 100644 index 4b03abc..0000000 --- a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockInitializer.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.coderjoe.atlas.power - -import com.coderjoe.atlas.power.block.PowerCable -import com.coderjoe.atlas.power.block.SmallBattery -import com.coderjoe.atlas.power.block.SmallDrill -import com.coderjoe.atlas.power.block.SmallSolarPanel -import org.bukkit.plugin.java.JavaPlugin - -object PowerBlockInitializer { - - fun initialize(plugin: JavaPlugin) { - plugin.logger.info("PowerBlockInitializer starting...") - - plugin.logger.info("Registering SmallSolarPanel...") - PowerBlockFactory.register("small_solar_panel") { location, _ -> - SmallSolarPanel(location) - } - - plugin.logger.info("Registering SmallDrill variants...") - for (variantId in SmallDrill.ALL_DIRECTIONAL_IDS) { - PowerBlockFactory.register(variantId) { location, facing -> - SmallDrill(location, facing) - } - } - - plugin.logger.info("Registering SmallBattery variants...") - for (variantId in SmallBattery.ALL_VARIANT_IDS) { - PowerBlockFactory.register(variantId) { location, facing -> - SmallBattery(location, facing) - } - } - - plugin.logger.info("Registering PowerCable variants...") - // Register all 6 directional variants - for ((face, variantId) in PowerCable.DIRECTIONAL_IDS) { - PowerBlockFactory.register(variantId) { location, facing -> - PowerCable(location, facing) - } - } - - val registeredBlocks = PowerBlockFactory.getRegisteredBlockIds() - plugin.logger.info("Initialized ${registeredBlocks.size} power block type(s): ${registeredBlocks.joinToString(", ")}") - plugin.logger.info("PowerBlockInitializer complete") - } -} diff --git a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockListener.kt b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockListener.kt deleted file mode 100644 index 810e23d..0000000 --- a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockListener.kt +++ /dev/null @@ -1,207 +0,0 @@ -package com.coderjoe.atlas.power - -import com.coderjoe.atlas.power.block.PowerCable -import com.coderjoe.atlas.power.block.SmallBattery -import com.coderjoe.atlas.power.block.SmallDrill -import com.coderjoe.atlas.power.block.SmallSolarPanel -import com.nexomc.nexo.api.NexoBlocks -import com.nexomc.nexo.api.NexoItems -import org.bukkit.Material -import org.bukkit.block.BlockFace -import org.bukkit.event.EventHandler -import org.bukkit.event.Listener -import org.bukkit.event.block.Action -import org.bukkit.event.block.BlockBreakEvent -import org.bukkit.event.block.BlockPlaceEvent -import org.bukkit.event.player.PlayerInteractEvent -import org.bukkit.plugin.java.JavaPlugin - -/** - * Listens for block placement and breaking to manage PowerBlock lifecycle - * Automatically detects and registers any block registered with PowerBlockFactory - */ -class PowerBlockListener( - private val plugin: JavaPlugin, - private val registry: PowerBlockRegistry -) : Listener { - - @EventHandler - fun onBlockPlace(event: BlockPlaceEvent) { - val key = PowerBlockRegistry.locationKey(event.block.location) - if (registry.updatingLocations.contains(key)) return - - plugin.logger.info("Block placed event triggered at ${event.block.location}") - - val mechanic = NexoBlocks.customBlockMechanic(event.block) - if (mechanic == null) { - plugin.logger.info("Not a Nexo custom block") - return - } - - val blockId = mechanic.itemID - plugin.logger.info("Nexo block placed with ID: $blockId") - - // Handle small_battery base item: determine facing for pull direction - if (blockId == SmallBattery.BLOCK_ID) { - val facing = getPlayerFacing(event) - plugin.logger.info("SmallBattery placed facing $facing") - - val location = event.block.location.clone() - val powerBlock = PowerBlockFactory.createPowerBlock(blockId, location, facing) - if (powerBlock != null) { - registry.registerPowerBlock(powerBlock, blockId) - } else { - plugin.logger.warning("Failed to create power block for: $blockId") - } - return - } - - // Handle power_cable base item: swap to directional variant - if (blockId == PowerCable.BLOCK_ID) { - val facing = getPlayerFacing(event) - val variantId = PowerCable.DIRECTIONAL_IDS[facing] - if (variantId == null) { - plugin.logger.warning("No directional variant for facing $facing") - return - } - - plugin.logger.info("Swapping power_cable to directional variant: $variantId (facing $facing)") - - // Schedule the swap for next tick so it doesn't conflict with the place event - val location = event.block.location.clone() - plugin.server.scheduler.runTask(plugin, Runnable { - // Set to air without using NexoBlocks.remove() to prevent item drops - location.block.setType(Material.AIR, false) - NexoBlocks.place(variantId, location) - - val powerBlock = PowerBlockFactory.createPowerBlock(variantId, location, facing) - if (powerBlock != null) { - registry.registerPowerBlock(powerBlock, variantId) - } else { - plugin.logger.warning("Failed to create power block for variant: $variantId") - } - }) - return - } - - // Handle small_drill: direction based on placement face, swap to directional variant - if (blockId == SmallDrill.BLOCK_ID) { - val facing = getPlayerFacing(event).oppositeFace - val variantId = SmallDrill.DIRECTIONAL_IDS[facing] ?: SmallDrill.BLOCK_ID - plugin.logger.info("SmallDrill placed, mining direction: $facing, variant: $variantId") - - val location = event.block.location.clone() - plugin.server.scheduler.runTask(plugin, Runnable { - location.block.setType(Material.AIR, false) - NexoBlocks.place(variantId, location) - - val powerBlock = PowerBlockFactory.createPowerBlock(variantId, location, facing) - if (powerBlock != null) { - registry.registerPowerBlock(powerBlock, variantId) - } else { - plugin.logger.warning("Failed to create power block for variant: $variantId") - } - }) - return - } - - // Handle directional variant placed directly (e.g., from Nexo) - val facing = PowerCable.facingFromBlockId(blockId) - if (facing != null) { - plugin.logger.info("Directional power cable placed: $blockId (facing $facing)") - val powerBlock = PowerBlockFactory.createPowerBlock(blockId, event.block.location, facing) - if (powerBlock != null) { - registry.registerPowerBlock(powerBlock, blockId) - } else { - plugin.logger.warning("Failed to create power block for ID: $blockId") - } - return - } - - plugin.logger.info("Registered power blocks: ${PowerBlockFactory.getRegisteredBlockIds()}") - - if (PowerBlockFactory.isRegistered(blockId)) { - plugin.logger.info("Power block placed: $blockId at ${event.block.location}") - - val powerBlock = PowerBlockFactory.createPowerBlock(blockId, event.block.location.clone()) - - if (powerBlock != null) { - registry.registerPowerBlock(powerBlock, blockId) - } else { - plugin.logger.warning("Failed to create power block for ID: $blockId") - } - } else { - plugin.logger.info("Block ID '$blockId' is not registered as a power block") - } - } - - @EventHandler - fun onBlockBreak(event: BlockBreakEvent) { - val key = PowerBlockRegistry.locationKey(event.block.location) - if (registry.updatingLocations.contains(key)) return - - val mechanic = NexoBlocks.customBlockMechanic(event.block) - - if (mechanic != null) { - plugin.logger.info("Block broken: ${mechanic.itemID} at ${event.block.location}") - } - - val powerBlock = registry.unregisterPowerBlock(event.block.location) - - if (powerBlock != null) { - plugin.logger.info("Power block removed with ${powerBlock.currentPower}/${powerBlock.maxStorage} power") - - // Manually drop the base item for visual-state variants - // since Nexo may not handle drops for programmatically-placed blocks - val baseItemId = when (powerBlock) { - is SmallBattery -> SmallBattery.BLOCK_ID - is PowerCable -> PowerCable.BLOCK_ID - is SmallDrill -> SmallDrill.BLOCK_ID - is SmallSolarPanel -> SmallSolarPanel.BLOCK_ID - else -> null - } - - if (baseItemId != null) { - val itemBuilder = NexoItems.itemFromId(baseItemId) - if (itemBuilder != null) { - val dropLocation = event.block.location.add(0.5, 0.5, 0.5) - event.block.world.dropItemNaturally(dropLocation, itemBuilder.build()) - event.isDropItems = false // prevent any default drops - } else { - plugin.logger.warning("Could not find Nexo item for $baseItemId") - } - } - } - } - - @EventHandler - fun onPlayerInteract(event: PlayerInteractEvent) { - if (event.action != Action.RIGHT_CLICK_BLOCK) return - if (event.player.isSneaking) return // allow placing blocks against power blocks - val block = event.clickedBlock ?: return - val powerBlock = registry.getPowerBlock(block.location) ?: return - - PowerBlockDialog.showPowerDialog(event.player, powerBlock) - event.isCancelled = true - } - - private fun getPlayerFacing(event: BlockPlaceEvent): BlockFace { - // Use the face the player clicked on — the cable faces away from the surface - // e.g., clicking the top of a block places a cable facing UP - val against = event.blockAgainst.location - val placed = event.block.location - val dx = placed.blockX - against.blockX - val dy = placed.blockY - against.blockY - val dz = placed.blockZ - against.blockZ - - return when { - dy > 0 -> BlockFace.UP - dy < 0 -> BlockFace.DOWN - dx > 0 -> BlockFace.EAST - dx < 0 -> BlockFace.WEST - dz > 0 -> BlockFace.SOUTH - dz < 0 -> BlockFace.NORTH - else -> event.player.facing - } - } -} diff --git a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockPersistence.kt b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockPersistence.kt index cf7ec6a..edfa771 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockPersistence.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockPersistence.kt @@ -1,107 +1,35 @@ package com.coderjoe.atlas.power +import com.coderjoe.atlas.core.BlockPersistence import com.coderjoe.atlas.power.block.SmallDrill -import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.plugin.java.JavaPlugin -import java.io.File -/** - * Handles saving and loading PowerBlock data to/from disk - */ -class PowerBlockPersistence(private val plugin: JavaPlugin) { - private val dataFile = File(plugin.dataFolder, "power_blocks.yml") - - fun save(registry: PowerBlockRegistry) { - val config = YamlConfiguration() - val powerBlocksWithIds = registry.getAllPowerBlocksWithIds() - - plugin.logger.info("Saving ${powerBlocksWithIds.size} power blocks to disk...") - - val blockDataList = mutableListOf>() - - for ((powerBlock, blockId) in powerBlocksWithIds) { - val data = PowerBlockData.fromPowerBlock(powerBlock, blockId) +class PowerBlockPersistence(plugin: JavaPlugin) { + private val persistence = BlockPersistence( + plugin = plugin, + fileName = "power_blocks.yml", + yamlKey = "power_blocks", + factory = PowerBlockFactory, + serialize = { block, _ -> val map = mutableMapOf( - "blockId" to data.blockId, - "world" to data.world, - "x" to data.x, - "y" to data.y, - "z" to data.z, - "currentPower" to data.currentPower + "currentPower" to block.currentPower ) - if (data.facing != null) { - map["facing"] = data.facing - } - if (data.enabled != null) { - map["enabled"] = data.enabled + if (block is SmallDrill) { + map["enabled"] = block.enabled } - blockDataList.add(map) - } - - config.set("power_blocks", blockDataList) - - try { - config.save(dataFile) - plugin.logger.info("Successfully saved ${blockDataList.size} power blocks") - } catch (e: Exception) { - plugin.logger.severe("Failed to save power blocks: ${e.message}") - e.printStackTrace() - } - } - - fun load(registry: PowerBlockRegistry) { - if (!dataFile.exists()) { - plugin.logger.info("No power blocks data file found, starting fresh") - return - } - - val config = YamlConfiguration.loadConfiguration(dataFile) - val blockDataList = config.getMapList("power_blocks") - - plugin.logger.info("Loading ${blockDataList.size} power blocks from disk...") - - var loadedCount = 0 - var failedCount = 0 - - for (blockDataMap in blockDataList) { - try { - val blockId = blockDataMap["blockId"] as? String ?: continue - val world = blockDataMap["world"] as? String ?: continue - val x = (blockDataMap["x"] as? Number)?.toInt() ?: continue - val y = (blockDataMap["y"] as? Number)?.toInt() ?: continue - val z = (blockDataMap["z"] as? Number)?.toInt() ?: continue - val currentPower = (blockDataMap["currentPower"] as? Number)?.toInt() ?: 0 - val facing = blockDataMap["facing"] as? String - val enabled = blockDataMap["enabled"] as? Boolean - - val data = PowerBlockData(blockId, world, x, y, z, currentPower, facing, enabled) - val location = data.toLocation(plugin) - - if (location == null) { - plugin.logger.warning("Failed to load power block at $world $x,$y,$z - world not found") - failedCount++ - continue - } - - val powerBlock = PowerBlockFactory.createPowerBlock(blockId, location, data.toBlockFace()) - - if (powerBlock != null) { - powerBlock.currentPower = currentPower - if (powerBlock is SmallDrill && data.enabled != null) { - powerBlock.enabled = data.enabled - } - registry.registerPowerBlock(powerBlock, blockId) - loadedCount++ - } else { - plugin.logger.warning("Failed to create power block for ID: $blockId at $x,$y,$z") - failedCount++ + map + }, + restore = { block, data -> + block.currentPower = (data["currentPower"] as? Number)?.toInt() ?: 0 + if (block is SmallDrill) { + val enabled = data["enabled"] as? Boolean + if (enabled != null) { + block.enabled = enabled } - } catch (e: Exception) { - plugin.logger.warning("Failed to load power block: ${e.message}") - failedCount++ } } + ) - plugin.logger.info("Loaded $loadedCount power blocks, $failedCount failed") - } + fun save(registry: PowerBlockRegistry) = persistence.save(registry) + fun load(registry: PowerBlockRegistry) = persistence.load(registry) } diff --git a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockRegistry.kt b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockRegistry.kt index 7deccec..da1c0b9 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockRegistry.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockRegistry.kt @@ -1,117 +1,34 @@ package com.coderjoe.atlas.power +import com.coderjoe.atlas.core.BlockRegistry import org.bukkit.Location import org.bukkit.block.BlockFace import org.bukkit.plugin.java.JavaPlugin -import java.util.concurrent.ConcurrentHashMap -/** - * Registry to track all active power blocks in the world - */ -class PowerBlockRegistry(private val plugin: JavaPlugin) { - private val powerBlocks = ConcurrentHashMap() - private val blockIds = ConcurrentHashMap() // Maps location key to block ID - val updatingLocations: MutableSet = ConcurrentHashMap.newKeySet() +class PowerBlockRegistry(plugin: JavaPlugin) : BlockRegistry(plugin) { companion object { var instance: PowerBlockRegistry? = null private set - fun locationKey(location: Location): String { - return "${location.world?.name}:${location.blockX},${location.blockY},${location.blockZ}" - } + fun locationKey(location: Location): String = BlockRegistry.locationKey(location) } init { instance = this } - /** - * Registers and starts a power block - */ - fun registerPowerBlock(powerBlock: PowerBlock, blockId: String) { - val key = locationKey(powerBlock.location) - powerBlocks[key] = powerBlock - blockIds[key] = blockId - powerBlock.start() - plugin.logger.info("Registered ${powerBlock::class.simpleName} at ${powerBlock.location.blockX},${powerBlock.location.blockY},${powerBlock.location.blockZ}") - } - - /** - * Unregisters and stops a power block at the given location - */ - fun unregisterPowerBlock(location: Location): PowerBlock? { - val key = locationKey(location) - val powerBlock = powerBlocks.remove(key) - blockIds.remove(key) - powerBlock?.stop() - if (powerBlock != null) { - plugin.logger.info("Unregistered ${powerBlock::class.simpleName} at ${location.blockX},${location.blockY},${location.blockZ}") - } - return powerBlock - } - - /** - * Gets a power block at the given location - */ - fun getPowerBlock(location: Location): PowerBlock? { - return powerBlocks[locationKey(location)] - } + fun registerPowerBlock(powerBlock: PowerBlock, blockId: String) = register(powerBlock, blockId) - /** - * Stops all power blocks (called on plugin disable) - */ - fun stopAll() { - plugin.logger.info("Stopping ${powerBlocks.size} power blocks...") - powerBlocks.values.forEach { it.stop() } - powerBlocks.clear() - } + fun unregisterPowerBlock(location: Location): PowerBlock? = unregister(location) - /** - * Gets all registered power blocks - */ - fun getAllPowerBlocks(): Collection { - return powerBlocks.values - } + fun getPowerBlock(location: Location): PowerBlock? = getBlock(location) - /** - * Gets all power blocks with their block IDs for persistence - */ - fun getAllPowerBlocksWithIds(): List> { - return powerBlocks.entries.mapNotNull { entry -> - val powerBlock = entry.value - val blockId = blockIds[entry.key] - if (blockId != null) { - Pair(powerBlock, blockId) - } else { - null - } - } - } + fun getAdjacentPowerBlock(location: Location, face: BlockFace): PowerBlock? = getAdjacentBlock(location, face) - /** - * Gets the power block adjacent to the given location in the specified direction - */ - fun getAdjacentPowerBlock(location: Location, face: BlockFace): PowerBlock? { - val offset = face.direction - return getPowerBlock(Location(location.world, - (location.blockX + offset.blockX).toDouble(), - (location.blockY + offset.blockY).toDouble(), - (location.blockZ + offset.blockZ).toDouble())) - } + fun getAdjacentPowerBlocks(location: Location): List = getAdjacentBlocks(location) - /** - * Gets all power blocks adjacent (6 directions) to the given location - */ - fun getAdjacentPowerBlocks(location: Location): List { - val offsets = listOf( - intArrayOf(1, 0, 0), intArrayOf(-1, 0, 0), - intArrayOf(0, 1, 0), intArrayOf(0, -1, 0), - intArrayOf(0, 0, 1), intArrayOf(0, 0, -1) - ) - return offsets.mapNotNull { (dx, dy, dz) -> - getPowerBlock(Location(location.world, (location.blockX + dx).toDouble(), (location.blockY + dy).toDouble(), (location.blockZ + dz).toDouble())) - } - } + fun getAllPowerBlocksWithIds(): List> = getAllBlocksWithIds() + fun getAllPowerBlocks(): Collection = getAllBlocks() } diff --git a/src/main/kotlin/com/coderjoe/atlas/power/block/PowerCable.kt b/src/main/kotlin/com/coderjoe/atlas/power/block/PowerCable.kt index dc57c91..2af5b8c 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/block/PowerCable.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/block/PowerCable.kt @@ -1,11 +1,13 @@ package com.coderjoe.atlas.power.block +import com.coderjoe.atlas.core.BlockDescriptor +import com.coderjoe.atlas.core.PlacementType import com.coderjoe.atlas.power.PowerBlock import com.coderjoe.atlas.power.PowerBlockRegistry import org.bukkit.Location import org.bukkit.block.BlockFace -class PowerCable(location: Location, val facing: BlockFace) : PowerBlock(location, maxStorage = 1) { +class PowerCable(location: Location, override val facing: BlockFace) : PowerBlock(location, maxStorage = 1) { companion object { const val BLOCK_ID = "power_cable" @@ -31,8 +33,20 @@ class PowerCable(location: Location, val facing: BlockFace) : PowerBlock(locatio ) fun facingFromBlockId(blockId: String): BlockFace? = ID_TO_FACING[blockId] + + val descriptor = BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "Power Cable", + description = "Cable - transfers power in facing direction", + placementType = PlacementType.DIRECTIONAL, + directionalVariants = DIRECTIONAL_IDS, + allRegistrableIds = DIRECTIONAL_IDS.values.toList(), + constructor = { loc, facing -> PowerCable(loc, facing) } + ) } + override val baseBlockId: String = BLOCK_ID + override val updateIntervalTicks: Long = 20L // 1 second override fun getVisualStateBlockId(): String = diff --git a/src/main/kotlin/com/coderjoe/atlas/power/block/SmallBattery.kt b/src/main/kotlin/com/coderjoe/atlas/power/block/SmallBattery.kt index 997cffb..7706b8a 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/block/SmallBattery.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/block/SmallBattery.kt @@ -1,5 +1,7 @@ package com.coderjoe.atlas.power.block +import com.coderjoe.atlas.core.BlockDescriptor +import com.coderjoe.atlas.core.PlacementType import com.coderjoe.atlas.power.PowerBlock import com.coderjoe.atlas.power.PowerBlockRegistry import org.bukkit.Location @@ -7,7 +9,7 @@ import org.bukkit.block.BlockFace class SmallBattery(location: Location, facing: BlockFace) : PowerBlock(location, maxStorage = 10) { - val facing: BlockFace = if (facing == BlockFace.SELF) BlockFace.DOWN else facing + override val facing: BlockFace = if (facing == BlockFace.SELF) BlockFace.DOWN else facing override val canReceivePower: Boolean = true override val updateIntervalTicks: Long = 20L @@ -23,8 +25,20 @@ class SmallBattery(location: Location, facing: BlockFace) : PowerBlock(location, ) val ALL_VARIANT_IDS: List = CHARGE_VARIANT_IDS.values.toList() + + val descriptor = BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "Small Battery", + description = "Storage - holds up to 10 power", + placementType = PlacementType.SIMPLE, + directionalVariants = emptyMap(), + allRegistrableIds = ALL_VARIANT_IDS, + constructor = { loc, facing -> SmallBattery(loc, facing) } + ) } + override val baseBlockId: String = BLOCK_ID + private fun chargeLevel(): Int = when (currentPower) { 0 -> 0 in 1..3 -> 1 diff --git a/src/main/kotlin/com/coderjoe/atlas/power/block/SmallDrill.kt b/src/main/kotlin/com/coderjoe/atlas/power/block/SmallDrill.kt index 0aa37d0..a58ff68 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/block/SmallDrill.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/block/SmallDrill.kt @@ -1,5 +1,7 @@ package com.coderjoe.atlas.power.block +import com.coderjoe.atlas.core.BlockDescriptor +import com.coderjoe.atlas.core.PlacementType import com.coderjoe.atlas.power.PowerBlock import com.coderjoe.atlas.power.PowerBlockRegistry import org.bukkit.Location @@ -28,8 +30,21 @@ class SmallDrill(location: Location, facing: BlockFace? = null) : PowerBlock(loc ) val ALL_DIRECTIONAL_IDS: List = DIRECTIONAL_IDS.values.toList() + + val descriptor = BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "Small Drill", + description = "Machine - consumes 10 power/s", + placementType = PlacementType.DIRECTIONAL_OPPOSITE, + directionalVariants = DIRECTIONAL_IDS, + allRegistrableIds = ALL_DIRECTIONAL_IDS, + constructor = { loc, facing -> SmallDrill(loc, facing) } + ) } + override val baseBlockId: String = BLOCK_ID + override val facing: BlockFace get() = miningDirection + override fun getVisualStateBlockId(): String = DIRECTIONAL_IDS[miningDirection] ?: BLOCK_ID diff --git a/src/main/kotlin/com/coderjoe/atlas/power/block/SmallSolarPanel.kt b/src/main/kotlin/com/coderjoe/atlas/power/block/SmallSolarPanel.kt index 4a22cd1..4d878f3 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/block/SmallSolarPanel.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/block/SmallSolarPanel.kt @@ -1,8 +1,10 @@ package com.coderjoe.atlas.power.block +import com.coderjoe.atlas.core.BlockDescriptor +import com.coderjoe.atlas.core.PlacementType import com.coderjoe.atlas.power.PowerBlock -import com.coderjoe.atlas.power.PowerBlockFactory import org.bukkit.Location +import org.bukkit.block.BlockFace class SmallSolarPanel(location: Location): PowerBlock(location, maxStorage = 1) { @@ -11,8 +13,20 @@ class SmallSolarPanel(location: Location): PowerBlock(location, maxStorage = 1) companion object { const val BLOCK_ID = "small_solar_panel" + + val descriptor = BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "Small Solar Panel", + description = "Generator - produces 1 power/min during daytime", + placementType = PlacementType.SIMPLE, + directionalVariants = emptyMap(), + allRegistrableIds = listOf(BLOCK_ID), + constructor = { loc, _ -> SmallSolarPanel(loc) } + ) } + override val baseBlockId: String = BLOCK_ID + override fun getVisualStateBlockId(): String = when (currentPower) { 0 -> "small_solar_panel" else -> "small_solar_panel_full" diff --git a/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt b/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt index bd5466e..5f0d697 100644 --- a/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt @@ -2,11 +2,9 @@ package com.coderjoe.atlas import com.coderjoe.atlas.fluid.FluidBlockDialog import com.coderjoe.atlas.fluid.FluidBlockFactory -import com.coderjoe.atlas.fluid.FluidBlockInitializer import com.coderjoe.atlas.fluid.FluidBlockRegistry import com.coderjoe.atlas.power.PowerBlockDialog import com.coderjoe.atlas.power.PowerBlockFactory -import com.coderjoe.atlas.power.PowerBlockInitializer import com.coderjoe.atlas.power.PowerBlockRegistry import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* @@ -27,13 +25,13 @@ class AtlasPluginTest { @Test fun `power system initializes with 17 block types`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() assertEquals(17, PowerBlockFactory.getRegisteredBlockIds().size) } @Test fun `fluid system initializes with 63 block types`() { - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() assertEquals(63, FluidBlockFactory.getRegisteredBlockIds().size) } @@ -61,7 +59,7 @@ class AtlasPluginTest { @Test fun `stopAll clears power blocks`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() val registry = PowerBlockRegistry(TestHelper.mockPlugin) registry.stopAll() assertEquals(0, registry.getAllPowerBlocks().size) @@ -69,7 +67,7 @@ class AtlasPluginTest { @Test fun `stopAll clears fluid blocks`() { - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() val registry = FluidBlockRegistry(TestHelper.mockPlugin) registry.stopAll() assertEquals(0, registry.getAllFluidBlocksWithIds().size) diff --git a/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt b/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt index 0d3e177..922b542 100644 --- a/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt +++ b/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt @@ -1,11 +1,20 @@ package com.coderjoe.atlas +import com.coderjoe.atlas.core.AtlasBlock +import com.coderjoe.atlas.core.BlockRegistry import com.coderjoe.atlas.fluid.FluidBlock import com.coderjoe.atlas.fluid.FluidBlockFactory import com.coderjoe.atlas.fluid.FluidBlockRegistry +import com.coderjoe.atlas.fluid.block.FluidContainer +import com.coderjoe.atlas.fluid.block.FluidPipe +import com.coderjoe.atlas.fluid.block.FluidPump import com.coderjoe.atlas.power.PowerBlock import com.coderjoe.atlas.power.PowerBlockFactory import com.coderjoe.atlas.power.PowerBlockRegistry +import com.coderjoe.atlas.power.block.PowerCable +import com.coderjoe.atlas.power.block.SmallBattery +import com.coderjoe.atlas.power.block.SmallDrill +import com.coderjoe.atlas.power.block.SmallSolarPanel import io.mockk.* import org.bukkit.Location import org.bukkit.Server @@ -33,9 +42,8 @@ object TestHelper { dataFolder = File(System.getProperty("java.io.tmpdir"), "atlas-test-${System.nanoTime()}") dataFolder.mkdirs() - // Set test plugin hooks on base classes (avoids JavaPlugin.getPlugin() call) - PowerBlock.testPlugin = mockPlugin - FluidBlock.testPlugin = mockPlugin + // Set test plugin hook on base class (avoids JavaPlugin.getPlugin() call) + AtlasBlock.testPlugin = mockPlugin every { mockPlugin.server } returns mockServer every { mockPlugin.logger } returns Logger.getLogger("TestAtlas") @@ -57,8 +65,7 @@ object TestHelper { fun teardown() { unmockkAll() - PowerBlock.testPlugin = null - FluidBlock.testPlugin = null + AtlasBlock.testPlugin = null clearRegistries() clearFactories() dataFolder.deleteRecursively() @@ -69,46 +76,46 @@ object TestHelper { } fun PowerBlock.callPowerUpdate() { - val method = this::class.java.getDeclaredMethod("powerUpdate") + val method = PowerBlock::class.java.getDeclaredMethod("powerUpdate") method.isAccessible = true method.invoke(this) } fun FluidBlock.callFluidUpdate() { - val method = this::class.java.getDeclaredMethod("fluidUpdate") + val method = FluidBlock::class.java.getDeclaredMethod("fluidUpdate") method.isAccessible = true method.invoke(this) } fun addToRegistry(registry: PowerBlockRegistry, block: PowerBlock, blockId: String) { - val powerBlocksField = PowerBlockRegistry::class.java.getDeclaredField("powerBlocks") - powerBlocksField.isAccessible = true + val blocksField = BlockRegistry::class.java.getDeclaredField("blocks") + blocksField.isAccessible = true @Suppress("UNCHECKED_CAST") - val powerBlocks = powerBlocksField.get(registry) as java.util.concurrent.ConcurrentHashMap + val blocks = blocksField.get(registry) as java.util.concurrent.ConcurrentHashMap - val blockIdsField = PowerBlockRegistry::class.java.getDeclaredField("blockIds") + val blockIdsField = BlockRegistry::class.java.getDeclaredField("blockIds") blockIdsField.isAccessible = true @Suppress("UNCHECKED_CAST") val blockIds = blockIdsField.get(registry) as java.util.concurrent.ConcurrentHashMap val key = PowerBlockRegistry.locationKey(block.location) - powerBlocks[key] = block + blocks[key] = block blockIds[key] = blockId } fun addToRegistry(registry: FluidBlockRegistry, block: FluidBlock, blockId: String) { - val fluidBlocksField = FluidBlockRegistry::class.java.getDeclaredField("fluidBlocks") - fluidBlocksField.isAccessible = true + val blocksField = BlockRegistry::class.java.getDeclaredField("blocks") + blocksField.isAccessible = true @Suppress("UNCHECKED_CAST") - val fluidBlocks = fluidBlocksField.get(registry) as java.util.concurrent.ConcurrentHashMap + val blocks = blocksField.get(registry) as java.util.concurrent.ConcurrentHashMap - val blockIdsField = FluidBlockRegistry::class.java.getDeclaredField("blockIds") + val blockIdsField = BlockRegistry::class.java.getDeclaredField("blockIds") blockIdsField.isAccessible = true @Suppress("UNCHECKED_CAST") val blockIds = blockIdsField.get(registry) as java.util.concurrent.ConcurrentHashMap val key = FluidBlockRegistry.locationKey(block.location) - fluidBlocks[key] = block + blocks[key] = block blockIds[key] = blockId } @@ -126,19 +133,22 @@ object TestHelper { } catch (_: Exception) {} } - private fun clearFactories() { - try { - val field = PowerBlockFactory::class.java.getDeclaredField("blockConstructors") - field.isAccessible = true - @Suppress("UNCHECKED_CAST") - (field.get(PowerBlockFactory) as MutableMap<*, *>).clear() - } catch (_: Exception) {} + fun initPowerFactory() { + PowerBlockFactory.registerFromDescriptors(listOf( + SmallSolarPanel.descriptor, SmallDrill.descriptor, + SmallBattery.descriptor, PowerCable.descriptor + )) + } - try { - val field = FluidBlockFactory::class.java.getDeclaredField("blockConstructors") - field.isAccessible = true - @Suppress("UNCHECKED_CAST") - (field.get(FluidBlockFactory) as MutableMap<*, *>).clear() - } catch (_: Exception) {} + fun initFluidFactory() { + FluidBlockFactory.registerFromDescriptors(listOf( + FluidPump.descriptor, FluidPipe.descriptor, + FluidContainer.descriptor + )) + } + + private fun clearFactories() { + PowerBlockFactory.clear() + FluidBlockFactory.clear() } } diff --git a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt index b3dd804..1eda9f4 100644 --- a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt @@ -7,7 +7,7 @@ import org.bukkit.block.BlockFace import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* -class FluidBlockInitializerTest { +class FluidBlockRegistrationTest { @BeforeEach fun setup() { @@ -21,7 +21,7 @@ class FluidBlockInitializerTest { @Test fun `initialize registers all expected IDs`() { - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() val ids = FluidBlockFactory.getRegisteredBlockIds() // 3 pump + 6 directional pipe + 6 water-filled + 6 lava-filled + 42 container = 63 @@ -30,7 +30,7 @@ class FluidBlockInitializerTest { @Test fun `pump IDs are registered`() { - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() assertTrue(FluidBlockFactory.isRegistered(FluidPump.BLOCK_ID)) assertTrue(FluidBlockFactory.isRegistered(FluidPump.BLOCK_ID_ACTIVE)) assertTrue(FluidBlockFactory.isRegistered(FluidPump.BLOCK_ID_ACTIVE_LAVA)) @@ -38,7 +38,7 @@ class FluidBlockInitializerTest { @Test fun `all pipe directional IDs are registered`() { - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() for (id in FluidPipe.DIRECTIONAL_IDS.values) { assertTrue(FluidBlockFactory.isRegistered(id), "Missing: $id") } @@ -46,7 +46,7 @@ class FluidBlockInitializerTest { @Test fun `all pipe water-filled IDs are registered`() { - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() for (id in FluidPipe.WATER_FILLED_IDS.values) { assertTrue(FluidBlockFactory.isRegistered(id), "Missing: $id") } @@ -54,7 +54,7 @@ class FluidBlockInitializerTest { @Test fun `all pipe lava-filled IDs are registered`() { - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() for (id in FluidPipe.LAVA_FILLED_IDS.values) { assertTrue(FluidBlockFactory.isRegistered(id), "Missing: $id") } @@ -62,14 +62,14 @@ class FluidBlockInitializerTest { @Test fun `pump ID creates FluidPump`() { - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() val block = FluidBlockFactory.createFluidBlock("fluid_pump", TestHelper.createLocation()) assertTrue(block is FluidPump) } @Test fun `pipe ID creates FluidPipe`() { - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() val block = FluidBlockFactory.createFluidBlock("fluid_pipe_north", TestHelper.createLocation(), BlockFace.NORTH) assertTrue(block is FluidPipe) } diff --git a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockListenerTest.kt b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockListenerTest.kt index 8318f9c..785c848 100644 --- a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockListenerTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockListenerTest.kt @@ -1,8 +1,9 @@ package com.coderjoe.atlas.fluid import com.coderjoe.atlas.TestHelper +import com.coderjoe.atlas.core.AtlasBlockListener +import com.coderjoe.atlas.core.BlockSystem import com.coderjoe.atlas.fluid.block.FluidPump -import com.nexomc.nexo.api.NexoBlocks import io.mockk.* import org.bukkit.block.Block import org.bukkit.block.BlockFace @@ -17,13 +18,20 @@ import org.junit.jupiter.api.Assertions.* class FluidBlockListenerTest { private lateinit var registry: FluidBlockRegistry - private lateinit var listener: FluidBlockListener + private lateinit var listener: AtlasBlockListener @BeforeEach fun setup() { TestHelper.setup() registry = FluidBlockRegistry(TestHelper.mockPlugin) - listener = FluidBlockListener(TestHelper.mockPlugin, registry) + val system = BlockSystem( + name = "fluid", + registry = registry, + factory = FluidBlockFactory, + descriptors = emptyMap(), + showDialog = { _, _ -> } + ) + listener = AtlasBlockListener(TestHelper.mockPlugin, listOf(system)) } @AfterEach @@ -72,8 +80,6 @@ class FluidBlockListenerTest { val event = mockk(relaxed = true) every { event.block } returns block - // NexoItems.itemFromId() will throw NoClassDefFoundError in test env - // but the block should still be unregistered before that call try { listener.onBlockBreak(event) } catch (_: NoClassDefFoundError) {} @@ -131,9 +137,6 @@ class FluidBlockListenerTest { @Test fun `getPlayerFacing returns UP when dy positive`() { - val method = FluidBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(0.0, 65.0, 0.0) @@ -143,14 +146,11 @@ class FluidBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - assertEquals(BlockFace.UP, method.invoke(listener, event) as BlockFace) + assertEquals(BlockFace.UP, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns EAST when dx positive`() { - val method = FluidBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(1.0, 64.0, 0.0) @@ -160,14 +160,11 @@ class FluidBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - assertEquals(BlockFace.EAST, method.invoke(listener, event) as BlockFace) + assertEquals(BlockFace.EAST, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns DOWN when dy negative`() { - val method = FluidBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(0.0, 63.0, 0.0) @@ -177,14 +174,11 @@ class FluidBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - assertEquals(BlockFace.DOWN, method.invoke(listener, event) as BlockFace) + assertEquals(BlockFace.DOWN, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns WEST when dx negative`() { - val method = FluidBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(-1.0, 64.0, 0.0) @@ -194,14 +188,11 @@ class FluidBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - assertEquals(BlockFace.WEST, method.invoke(listener, event) as BlockFace) + assertEquals(BlockFace.WEST, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns SOUTH when dz positive`() { - val method = FluidBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(0.0, 64.0, 1.0) @@ -211,14 +202,11 @@ class FluidBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - assertEquals(BlockFace.SOUTH, method.invoke(listener, event) as BlockFace) + assertEquals(BlockFace.SOUTH, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns NORTH when dz negative`() { - val method = FluidBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(0.0, 64.0, -1.0) @@ -228,6 +216,6 @@ class FluidBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - assertEquals(BlockFace.NORTH, method.invoke(listener, event) as BlockFace) + assertEquals(BlockFace.NORTH, AtlasBlockListener.getPlayerFacing(event)) } } diff --git a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistenceTest.kt b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistenceTest.kt index 10638bf..6cf6a8e 100644 --- a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistenceTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistenceTest.kt @@ -18,7 +18,7 @@ class FluidBlockPersistenceTest { TestHelper.setup() registry = FluidBlockRegistry(TestHelper.mockPlugin) persistence = FluidBlockPersistence(TestHelper.mockPlugin) - FluidBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initFluidFactory() } @AfterEach diff --git a/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt b/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt index 8cb4eae..bf677a2 100644 --- a/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt @@ -9,7 +9,7 @@ import org.bukkit.block.BlockFace import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* -class PowerBlockInitializerTest { +class PowerBlockRegistrationTest { @BeforeEach fun setup() { @@ -23,7 +23,7 @@ class PowerBlockInitializerTest { @Test fun `initialize registers all expected IDs`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() val ids = PowerBlockFactory.getRegisteredBlockIds() // 1 solar + 6 drill + 4 battery + 6 cable = 17 @@ -32,13 +32,13 @@ class PowerBlockInitializerTest { @Test fun `solar panel ID is registered`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() assertTrue(PowerBlockFactory.isRegistered("small_solar_panel")) } @Test fun `all drill directional IDs are registered`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() for (id in SmallDrill.ALL_DIRECTIONAL_IDS) { assertTrue(PowerBlockFactory.isRegistered(id), "Missing drill ID: $id") } @@ -46,7 +46,7 @@ class PowerBlockInitializerTest { @Test fun `all battery variant IDs are registered`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() for (id in SmallBattery.ALL_VARIANT_IDS) { assertTrue(PowerBlockFactory.isRegistered(id), "Missing battery ID: $id") } @@ -54,7 +54,7 @@ class PowerBlockInitializerTest { @Test fun `all cable directional IDs are registered`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() for (id in PowerCable.DIRECTIONAL_IDS.values) { assertTrue(PowerBlockFactory.isRegistered(id), "Missing cable ID: $id") } @@ -62,28 +62,28 @@ class PowerBlockInitializerTest { @Test fun `solar panel ID creates SmallSolarPanel`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() val block = PowerBlockFactory.createPowerBlock("small_solar_panel", TestHelper.createLocation()) assertTrue(block is SmallSolarPanel) } @Test fun `drill ID creates SmallDrill`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() val block = PowerBlockFactory.createPowerBlock("small_drill_north", TestHelper.createLocation(), BlockFace.NORTH) assertTrue(block is SmallDrill) } @Test fun `battery ID creates SmallBattery`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() val block = PowerBlockFactory.createPowerBlock("small_battery", TestHelper.createLocation(), BlockFace.DOWN) assertTrue(block is SmallBattery) } @Test fun `cable ID creates PowerCable`() { - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() val block = PowerBlockFactory.createPowerBlock("power_cable_north", TestHelper.createLocation(), BlockFace.NORTH) assertTrue(block is PowerCable) } diff --git a/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockListenerTest.kt b/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockListenerTest.kt index 8d5baf7..0710a41 100644 --- a/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockListenerTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockListenerTest.kt @@ -1,10 +1,10 @@ package com.coderjoe.atlas.power import com.coderjoe.atlas.TestHelper +import com.coderjoe.atlas.core.AtlasBlockListener +import com.coderjoe.atlas.core.BlockSystem import com.coderjoe.atlas.power.block.SmallSolarPanel -import com.nexomc.nexo.api.NexoBlocks import io.mockk.* -import org.bukkit.Location import org.bukkit.block.Block import org.bukkit.block.BlockFace import org.bukkit.entity.Player @@ -12,21 +12,26 @@ import org.bukkit.event.block.Action import org.bukkit.event.block.BlockBreakEvent import org.bukkit.event.block.BlockPlaceEvent import org.bukkit.event.player.PlayerInteractEvent -import org.bukkit.inventory.EquipmentSlot -import org.bukkit.inventory.ItemStack import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* class PowerBlockListenerTest { private lateinit var registry: PowerBlockRegistry - private lateinit var listener: PowerBlockListener + private lateinit var listener: AtlasBlockListener @BeforeEach fun setup() { TestHelper.setup() registry = PowerBlockRegistry(TestHelper.mockPlugin) - listener = PowerBlockListener(TestHelper.mockPlugin, registry) + val system = BlockSystem( + name = "power", + registry = registry, + factory = PowerBlockFactory, + descriptors = emptyMap(), + showDialog = { _, _ -> } + ) + listener = AtlasBlockListener(TestHelper.mockPlugin, listOf(system)) } @AfterEach @@ -46,7 +51,6 @@ class PowerBlockListenerTest { every { event.block } returns block listener.onBlockPlace(event) - // No block should be registered assertNull(registry.getPowerBlock(loc)) } @@ -62,7 +66,6 @@ class PowerBlockListenerTest { every { event.block } returns block listener.onBlockBreak(event) - // Should not crash or unregister } @Test @@ -77,8 +80,6 @@ class PowerBlockListenerTest { val event = mockk(relaxed = true) every { event.block } returns block - // NexoItems.itemFromId() will throw NoClassDefFoundError in test env - // but the block should still be unregistered before that call try { listener.onBlockBreak(event) } catch (_: NoClassDefFoundError) {} @@ -136,9 +137,6 @@ class PowerBlockListenerTest { @Test fun `getPlayerFacing returns UP when dy positive`() { - val method = PowerBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(0.0, 65.0, 0.0) @@ -148,15 +146,11 @@ class PowerBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - val result = method.invoke(listener, event) as BlockFace - assertEquals(BlockFace.UP, result) + assertEquals(BlockFace.UP, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns DOWN when dy negative`() { - val method = PowerBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(0.0, 63.0, 0.0) @@ -166,15 +160,11 @@ class PowerBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - val result = method.invoke(listener, event) as BlockFace - assertEquals(BlockFace.DOWN, result) + assertEquals(BlockFace.DOWN, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns EAST when dx positive`() { - val method = PowerBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(1.0, 64.0, 0.0) @@ -184,15 +174,11 @@ class PowerBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - val result = method.invoke(listener, event) as BlockFace - assertEquals(BlockFace.EAST, result) + assertEquals(BlockFace.EAST, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns WEST when dx negative`() { - val method = PowerBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(-1.0, 64.0, 0.0) @@ -202,15 +188,11 @@ class PowerBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - val result = method.invoke(listener, event) as BlockFace - assertEquals(BlockFace.WEST, result) + assertEquals(BlockFace.WEST, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns SOUTH when dz positive`() { - val method = PowerBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(0.0, 64.0, 1.0) @@ -220,15 +202,11 @@ class PowerBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - val result = method.invoke(listener, event) as BlockFace - assertEquals(BlockFace.SOUTH, result) + assertEquals(BlockFace.SOUTH, AtlasBlockListener.getPlayerFacing(event)) } @Test fun `getPlayerFacing returns NORTH when dz negative`() { - val method = PowerBlockListener::class.java.getDeclaredMethod("getPlayerFacing", BlockPlaceEvent::class.java) - method.isAccessible = true - val placed = mockk(relaxed = true) val against = mockk(relaxed = true) every { placed.location } returns TestHelper.createLocation(0.0, 64.0, -1.0) @@ -238,7 +216,6 @@ class PowerBlockListenerTest { every { event.block } returns placed every { event.blockAgainst } returns against - val result = method.invoke(listener, event) as BlockFace - assertEquals(BlockFace.NORTH, result) + assertEquals(BlockFace.NORTH, AtlasBlockListener.getPlayerFacing(event)) } } diff --git a/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockPersistenceTest.kt b/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockPersistenceTest.kt index d91141e..c6d7363 100644 --- a/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockPersistenceTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockPersistenceTest.kt @@ -22,7 +22,7 @@ class PowerBlockPersistenceTest { persistence = PowerBlockPersistence(TestHelper.mockPlugin) // Initialize factory so load() can create blocks - PowerBlockInitializer.initialize(TestHelper.mockPlugin) + TestHelper.initPowerFactory() } @AfterEach