Skip to content
61 changes: 50 additions & 11 deletions src/main/kotlin/com/coderjoe/atlas/Atlas.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -44,6 +44,32 @@ class Atlas : JavaPlugin() {
initPowerSystem()
initFluidSystem()

// Register unified listener
val powerSystem = BlockSystem<com.coderjoe.atlas.power.PowerBlock>(
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<com.coderjoe.atlas.fluid.FluidBlock>(
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)
Expand Down Expand Up @@ -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<String, com.coderjoe.atlas.core.BlockDescriptor> {
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<String, com.coderjoe.atlas.core.BlockDescriptor> {
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 }
}
}
127 changes: 34 additions & 93 deletions src/main/kotlin/com/coderjoe/atlas/NexoIntegration.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<String> {
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()
}
}

Expand Down
71 changes: 71 additions & 0 deletions src/main/kotlin/com/coderjoe/atlas/core/AtlasBlock.kt
Original file line number Diff line number Diff line change
@@ -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")
}
}
52 changes: 52 additions & 0 deletions src/main/kotlin/com/coderjoe/atlas/core/AtlasBlockDialog.kt
Original file line number Diff line number Diff line change
@@ -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<UUID, BukkitTask>()

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
}
}
Loading