diff --git a/src/main/kotlin/com/coderjoe/atlas/Atlas.kt b/src/main/kotlin/com/coderjoe/atlas/Atlas.kt index f889028..7e4efa3 100644 --- a/src/main/kotlin/com/coderjoe/atlas/Atlas.kt +++ b/src/main/kotlin/com/coderjoe/atlas/Atlas.kt @@ -129,7 +129,8 @@ class Atlas : JavaPlugin() { 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 + com.coderjoe.atlas.power.block.PowerCable.descriptor, + com.coderjoe.atlas.power.block.LavaGenerator.descriptor ).associateBy { it.baseBlockId } } diff --git a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt index 0a87402..7bd7e55 100644 --- a/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt +++ b/src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt @@ -2,6 +2,7 @@ package com.coderjoe.atlas.power import com.coderjoe.atlas.core.AtlasBlockDialog import com.coderjoe.atlas.core.BlockRegistry +import com.coderjoe.atlas.power.block.LavaGenerator import com.coderjoe.atlas.power.block.PowerCable import com.coderjoe.atlas.power.block.SmallBattery import com.coderjoe.atlas.power.block.SmallDrill @@ -123,6 +124,7 @@ object PowerBlockDialog { is SmallBattery -> "Small Battery" is SmallDrill -> "Small Drill" is PowerCable -> "Power Cable (${powerBlock.facing.name.lowercase().replaceFirstChar { it.uppercase() }})" + is LavaGenerator -> "Lava Generator" else -> "Power Block" } @@ -165,6 +167,8 @@ object PowerBlockDialog { } is PowerCable -> Component.text("Cable - transfers power in facing direction") .color(NamedTextColor.GRAY) + is LavaGenerator -> Component.text("Generator - produces ${LavaGenerator.POWER_PER_LAVA} power per lava unit") + .color(NamedTextColor.GRAY) else -> Component.text("Power block") .color(NamedTextColor.GRAY) } diff --git a/src/main/kotlin/com/coderjoe/atlas/power/block/LavaGenerator.kt b/src/main/kotlin/com/coderjoe/atlas/power/block/LavaGenerator.kt new file mode 100644 index 0000000..f07dff1 --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/power/block/LavaGenerator.kt @@ -0,0 +1,90 @@ +package com.coderjoe.atlas.power.block + +import com.coderjoe.atlas.atlasInfo +import com.coderjoe.atlas.core.BlockDescriptor +import com.coderjoe.atlas.core.PlacementType +import com.coderjoe.atlas.fluid.FluidBlockRegistry +import com.coderjoe.atlas.fluid.FluidType +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 org.bukkit.Location +import org.bukkit.block.BlockFace + +class LavaGenerator(location: Location) : PowerBlock(location, maxStorage = 50) { + + override val canReceivePower: Boolean = false + override val updateIntervalTicks: Long = 20L + + companion object { + const val BLOCK_ID = "lava_generator" + const val BLOCK_ID_ACTIVE = "lava_generator_active" + const val POWER_PER_LAVA = 5 + + private val ADJACENT_FACES = listOf( + BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, + BlockFace.WEST, BlockFace.UP, BlockFace.DOWN + ) + + val descriptor = BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "Lava Generator", + description = "Generator - produces $POWER_PER_LAVA power per lava unit", + placementType = PlacementType.SIMPLE, + directionalVariants = emptyMap(), + allRegistrableIds = listOf(BLOCK_ID, BLOCK_ID_ACTIVE), + constructor = { loc, _ -> LavaGenerator(loc) } + ) + } + + override val baseBlockId: String = BLOCK_ID + + override fun getVisualStateBlockId(): String = when { + currentPower > 0 -> BLOCK_ID_ACTIVE + else -> BLOCK_ID + } + + override fun powerUpdate() { + if (currentPower >= maxStorage) return + + val fluidRegistry = FluidBlockRegistry.instance ?: return + + for (face in ADJACENT_FACES) { + val spaceAvailable = maxStorage - currentPower + if (spaceAvailable < POWER_PER_LAVA) break + + val source = fluidRegistry.getAdjacentFluidBlock(location, face) ?: continue + + val lava = tryPullLava(source, face) + if (lava) { + val generated = addPower(POWER_PER_LAVA) + plugin.logger.atlasInfo("LavaGenerator at ${location.blockX},${location.blockY},${location.blockZ} consumed 1 lava, generated $generated power (now $currentPower/$maxStorage)") + } + } + } + + private fun tryPullLava(source: com.coderjoe.atlas.fluid.FluidBlock, face: BlockFace): Boolean { + when (source) { + is FluidPump -> { + if (source.canRemoveFluidFrom(face.oppositeFace) && source.storedFluid == FluidType.LAVA) { + source.removeFluid() + return true + } + } + is FluidPipe -> { + if (source.hasFluid() && source.storedFluid == FluidType.LAVA) { + source.removeFluid() + return true + } + } + is FluidContainer -> { + if (source.canRemoveFluidFrom(face.oppositeFace) && source.storedFluid == FluidType.LAVA) { + source.removeFluid() + return true + } + } + } + return false + } +} diff --git a/src/main/resources/nexo/items/atlas_blocks.yml b/src/main/resources/nexo/items/atlas_blocks.yml index ba632ac..a3365e9 100644 --- a/src/main/resources/nexo/items/atlas_blocks.yml +++ b/src/main/resources/nexo/items/atlas_blocks.yml @@ -2648,3 +2648,64 @@ fluid_container_down_lava_full: loots: - nexo_item: fluid_container probability: 1.0 + +# ─── Lava Generator ─────────────────────────────────────────────── +lava_generator: + itemname: "Lava Generator" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/lava_generator_side + south: atlas:block/lava_generator_side + east: atlas:block/lava_generator_side + west: atlas:block/lava_generator_side + up: atlas:block/lava_generator_top + down: atlas:block/lava_generator_bottom + Mechanics: + custom_block: + type: NOTEBLOCK + custom_variation: 152 + hardness: 5 + block_sounds: + break_sound: block.metal.break + place_sound: block.metal.place + hit_sound: block.metal.hit + step_sound: block.metal.step + fall_sound: block.metal.fall + drop: + silktouch: false + loots: + - nexo_item: lava_generator + probability: 1.0 + +lava_generator_active: + itemname: "Lava Generator" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/lava_generator_side_active + south: atlas:block/lava_generator_side_active + east: atlas:block/lava_generator_side_active + west: atlas:block/lava_generator_side_active + up: atlas:block/lava_generator_top_active + down: atlas:block/lava_generator_bottom + Mechanics: + custom_block: + type: NOTEBLOCK + custom_variation: 153 + hardness: 5 + block_sounds: + break_sound: block.metal.break + place_sound: block.metal.place + hit_sound: block.metal.hit + step_sound: block.metal.step + fall_sound: block.metal.fall + drop: + silktouch: false + loots: + - nexo_item: lava_generator + probability: 1.0 diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_bottom.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_bottom.png new file mode 100644 index 0000000..1eec457 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_bottom.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_side.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_side.png new file mode 100644 index 0000000..c045caf Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_side.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_side_active.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_side_active.png new file mode 100644 index 0000000..dedb720 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_side_active.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_top.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_top.png new file mode 100644 index 0000000..94e7de6 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_top.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_top_active.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_top_active.png new file mode 100644 index 0000000..60040e7 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/lava_generator_top_active.png differ diff --git a/src/main/resources/nexo/recipes/shapeless/atlas_recipes.yml b/src/main/resources/nexo/recipes/shapeless/atlas_recipes.yml index 9c5665b..703928b 100644 --- a/src/main/resources/nexo/recipes/shapeless/atlas_recipes.yml +++ b/src/main/resources/nexo/recipes/shapeless/atlas_recipes.yml @@ -92,3 +92,18 @@ fluid_container_recipe: C: amount: 1 minecraft_type: BUCKET + +lava_generator_recipe: + result: + nexo_item: lava_generator + amount: 1 + ingredients: + A: + amount: 3 + minecraft_type: IRON_INGOT + B: + amount: 3 + minecraft_type: REDSTONE + C: + amount: 1 + minecraft_type: MAGMA_BLOCK diff --git a/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt b/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt index 5f0d697..52f6155 100644 --- a/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt @@ -24,9 +24,9 @@ class AtlasPluginTest { } @Test - fun `power system initializes with 17 block types`() { + fun `power system initializes with 19 block types`() { TestHelper.initPowerFactory() - assertEquals(17, PowerBlockFactory.getRegisteredBlockIds().size) + assertEquals(19, PowerBlockFactory.getRegisteredBlockIds().size) } @Test diff --git a/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt b/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt index 922b542..16413dc 100644 --- a/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt +++ b/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt @@ -11,6 +11,7 @@ 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.LavaGenerator import com.coderjoe.atlas.power.block.PowerCable import com.coderjoe.atlas.power.block.SmallBattery import com.coderjoe.atlas.power.block.SmallDrill @@ -136,7 +137,8 @@ object TestHelper { fun initPowerFactory() { PowerBlockFactory.registerFromDescriptors(listOf( SmallSolarPanel.descriptor, SmallDrill.descriptor, - SmallBattery.descriptor, PowerCable.descriptor + SmallBattery.descriptor, PowerCable.descriptor, + LavaGenerator.descriptor )) } diff --git a/src/test/kotlin/com/coderjoe/atlas/power/LavaGeneratorTest.kt b/src/test/kotlin/com/coderjoe/atlas/power/LavaGeneratorTest.kt new file mode 100644 index 0000000..8ad1aea --- /dev/null +++ b/src/test/kotlin/com/coderjoe/atlas/power/LavaGeneratorTest.kt @@ -0,0 +1,228 @@ +package com.coderjoe.atlas.power + +import com.coderjoe.atlas.TestHelper +import com.coderjoe.atlas.TestHelper.callPowerUpdate +import com.coderjoe.atlas.fluid.FluidBlockRegistry +import com.coderjoe.atlas.fluid.FluidType +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.block.LavaGenerator +import org.bukkit.block.BlockFace +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* + +class LavaGeneratorTest { + + @BeforeEach + fun setup() { + TestHelper.setup() + } + + @AfterEach + fun teardown() { + TestHelper.teardown() + } + + @Test + fun `lava generator maxStorage is 50`() { + val gen = LavaGenerator(TestHelper.createLocation()) + assertEquals(50, gen.maxStorage) + } + + @Test + fun `lava generator canReceivePower is false`() { + val gen = LavaGenerator(TestHelper.createLocation()) + assertFalse(gen.canAcceptPower()) + } + + @Test + fun `lava generator visual state idle when no power`() { + val gen = LavaGenerator(TestHelper.createLocation()) + assertEquals("lava_generator", gen.getVisualStateBlockId()) + } + + @Test + fun `lava generator visual state active when has power`() { + val gen = LavaGenerator(TestHelper.createLocation()) + gen.currentPower = 5 + assertEquals("lava_generator_active", gen.getVisualStateBlockId()) + } + + @Test + fun `lava generator consumes lava from adjacent fluid pipe`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + val powerRegistry = PowerBlockRegistry(TestHelper.mockPlugin) + + val genLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val gen = LavaGenerator(genLoc) + TestHelper.addToRegistry(powerRegistry, gen, "lava_generator") + + // Place a lava-filled pipe to the north (z-1) + val pipeLoc = TestHelper.createLocation(0.0, 64.0, -1.0) + val pipe = FluidPipe(pipeLoc, BlockFace.SOUTH) + pipe.storeFluid(FluidType.LAVA) + TestHelper.addToRegistry(fluidRegistry, pipe, "fluid_pipe_south_filled_lava") + + gen.callPowerUpdate() + + assertEquals(5, gen.currentPower) + assertFalse(pipe.hasFluid()) + } + + @Test + fun `lava generator ignores water from adjacent fluid pipe`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + val powerRegistry = PowerBlockRegistry(TestHelper.mockPlugin) + + val genLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val gen = LavaGenerator(genLoc) + TestHelper.addToRegistry(powerRegistry, gen, "lava_generator") + + val pipeLoc = TestHelper.createLocation(0.0, 64.0, -1.0) + val pipe = FluidPipe(pipeLoc, BlockFace.SOUTH) + pipe.storeFluid(FluidType.WATER) + TestHelper.addToRegistry(fluidRegistry, pipe, "fluid_pipe_south_filled") + + gen.callPowerUpdate() + + assertEquals(0, gen.currentPower) + assertTrue(pipe.hasFluid()) + } + + @Test + fun `lava generator consumes lava from fluid container facing toward it`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + val powerRegistry = PowerBlockRegistry(TestHelper.mockPlugin) + + val genLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val gen = LavaGenerator(genLoc) + TestHelper.addToRegistry(powerRegistry, gen, "lava_generator") + + // Container to the south (z+1) facing north (toward generator) + val containerLoc = TestHelper.createLocation(0.0, 64.0, 1.0) + val container = FluidContainer(containerLoc, BlockFace.NORTH) + container.restoreState(FluidType.LAVA, 3) + TestHelper.addToRegistry(fluidRegistry, container, "fluid_container_north_lava_low") + + gen.callPowerUpdate() + + assertEquals(5, gen.currentPower) + assertEquals(2, container.storedAmount) + } + + @Test + fun `lava generator does not consume from container facing away`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + val powerRegistry = PowerBlockRegistry(TestHelper.mockPlugin) + + val genLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val gen = LavaGenerator(genLoc) + TestHelper.addToRegistry(powerRegistry, gen, "lava_generator") + + // Container to the south (z+1) facing south (away from generator) + val containerLoc = TestHelper.createLocation(0.0, 64.0, 1.0) + val container = FluidContainer(containerLoc, BlockFace.SOUTH) + container.restoreState(FluidType.LAVA, 3) + TestHelper.addToRegistry(fluidRegistry, container, "fluid_container_south_lava_low") + + gen.callPowerUpdate() + + assertEquals(0, gen.currentPower) + assertEquals(3, container.storedAmount) + } + + @Test + fun `lava generator stops consuming when full`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + val powerRegistry = PowerBlockRegistry(TestHelper.mockPlugin) + + val genLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val gen = LavaGenerator(genLoc) + gen.currentPower = 48 + TestHelper.addToRegistry(powerRegistry, gen, "lava_generator") + + // Two lava pipes adjacent - only one should be consumed (48 + 5 = 53 > 50, but 48+5=53, wait no - space is 2, less than 5) + val pipeLoc = TestHelper.createLocation(0.0, 64.0, -1.0) + val pipe = FluidPipe(pipeLoc, BlockFace.SOUTH) + pipe.storeFluid(FluidType.LAVA) + TestHelper.addToRegistry(fluidRegistry, pipe, "fluid_pipe_south_filled_lava") + + gen.callPowerUpdate() + + // Should NOT consume because space (2) < POWER_PER_LAVA (5) + assertEquals(48, gen.currentPower) + assertTrue(pipe.hasFluid()) + } + + @Test + fun `lava generator consumes from multiple sources in one tick`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + val powerRegistry = PowerBlockRegistry(TestHelper.mockPlugin) + + val genLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val gen = LavaGenerator(genLoc) + TestHelper.addToRegistry(powerRegistry, gen, "lava_generator") + + // Pipe to the north + val pipe1Loc = TestHelper.createLocation(0.0, 64.0, -1.0) + val pipe1 = FluidPipe(pipe1Loc, BlockFace.SOUTH) + pipe1.storeFluid(FluidType.LAVA) + TestHelper.addToRegistry(fluidRegistry, pipe1, "fluid_pipe_south_filled_lava") + + // Pipe to the south + val pipe2Loc = TestHelper.createLocation(0.0, 64.0, 1.0) + val pipe2 = FluidPipe(pipe2Loc, BlockFace.NORTH) + pipe2.storeFluid(FluidType.LAVA) + TestHelper.addToRegistry(fluidRegistry, pipe2, "fluid_pipe_north_filled_lava") + + gen.callPowerUpdate() + + assertEquals(10, gen.currentPower) + assertFalse(pipe1.hasFluid()) + assertFalse(pipe2.hasFluid()) + } + + @Test + fun `lava generator does nothing when no adjacent fluid blocks`() { + FluidBlockRegistry(TestHelper.mockPlugin) + PowerBlockRegistry(TestHelper.mockPlugin) + + val gen = LavaGenerator(TestHelper.createLocation()) + + gen.callPowerUpdate() + + assertEquals(0, gen.currentPower) + } + + @Test + fun `lava generator does nothing when already at max storage`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + val powerRegistry = PowerBlockRegistry(TestHelper.mockPlugin) + + val genLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val gen = LavaGenerator(genLoc) + gen.currentPower = 50 + TestHelper.addToRegistry(powerRegistry, gen, "lava_generator") + + val pipeLoc = TestHelper.createLocation(0.0, 64.0, -1.0) + val pipe = FluidPipe(pipeLoc, BlockFace.SOUTH) + pipe.storeFluid(FluidType.LAVA) + TestHelper.addToRegistry(fluidRegistry, pipe, "fluid_pipe_south_filled_lava") + + gen.callPowerUpdate() + + assertEquals(50, gen.currentPower) + assertTrue(pipe.hasFluid()) + } + + @Test + fun `lava generator descriptor has correct properties`() { + val desc = LavaGenerator.descriptor + assertEquals("lava_generator", desc.baseBlockId) + assertEquals("Lava Generator", desc.displayName) + assertEquals(2, desc.allRegistrableIds.size) + assertTrue(desc.allRegistrableIds.contains("lava_generator")) + assertTrue(desc.allRegistrableIds.contains("lava_generator_active")) + } +} diff --git a/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt b/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt index bf677a2..8021de9 100644 --- a/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt @@ -26,8 +26,8 @@ class PowerBlockRegistrationTest { TestHelper.initPowerFactory() val ids = PowerBlockFactory.getRegisteredBlockIds() - // 1 solar + 6 drill + 4 battery + 6 cable = 17 - assertEquals(17, ids.size) + // 1 solar + 6 drill + 4 battery + 6 cable + 2 lava generator = 19 + assertEquals(19, ids.size) } @Test