diff --git a/src/main/kotlin/com/coderjoe/atlas/NexoIntegration.kt b/src/main/kotlin/com/coderjoe/atlas/NexoIntegration.kt index 4572ee8..77cab81 100644 --- a/src/main/kotlin/com/coderjoe/atlas/NexoIntegration.kt +++ b/src/main/kotlin/com/coderjoe/atlas/NexoIntegration.kt @@ -95,7 +95,35 @@ class NexoIntegration(private val plugin: JavaPlugin) { "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_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") diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlock.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlock.kt index abd9894..407ecba 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlock.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlock.kt @@ -20,15 +20,15 @@ abstract class FluidBlock( internal var testPlugin: JavaPlugin? = null } - fun hasFluid(): Boolean = storedFluid != FluidType.NONE + open fun hasFluid(): Boolean = storedFluid != FluidType.NONE - fun storeFluid(type: FluidType): Boolean { + open fun storeFluid(type: FluidType): Boolean { if (storedFluid != FluidType.NONE) return false storedFluid = type return true } - fun removeFluid(): FluidType { + open fun removeFluid(): FluidType { val fluid = storedFluid storedFluid = FluidType.NONE return fluid diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockData.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockData.kt index e2f9b55..18450e5 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockData.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockData.kt @@ -1,5 +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 @@ -13,13 +14,19 @@ data class FluidBlockData( val y: Int, val z: Int, val fluidType: String, - val facing: String? = null + val facing: String? = null, + val storedAmount: Int? = null ) { 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 storedAmount = when (fluidBlock) { + is FluidContainer -> fluidBlock.storedAmount else -> null } return FluidBlockData( @@ -29,7 +36,8 @@ data class FluidBlockData( y = loc.blockY, z = loc.blockZ, fluidType = fluidBlock.storedFluid.name, - facing = facing + facing = facing, + storedAmount = storedAmount ) } } diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt index 79d9f4d..bf03f36 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt @@ -1,5 +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 io.papermc.paper.dialog.Dialog @@ -93,14 +94,23 @@ object FluidBlockDialog { private fun getBlockDisplayName(fluidBlock: FluidBlock): String = when (fluidBlock) { is FluidPump -> "Fluid Pump" is FluidPipe -> "Fluid Pipe (${fluidBlock.facing.name.lowercase().replaceFirstChar { it.uppercase() }})" + is FluidContainer -> "Fluid Container (${fluidBlock.facing.name.lowercase().replaceFirstChar { it.uppercase() }})" else -> "Fluid Block" } private fun buildFluidInfo(fluidBlock: FluidBlock): Component { - val fluidName = when (fluidBlock.storedFluid) { - FluidType.WATER -> "Water" - FluidType.LAVA -> "Lava" - FluidType.NONE -> "Empty" + val fluidName = if (fluidBlock is FluidContainer && fluidBlock.storedAmount > 0) { + when (fluidBlock.storedFluid) { + FluidType.WATER -> "Water (${fluidBlock.storedAmount}/${FluidContainer.MAX_CAPACITY})" + FluidType.LAVA -> "Lava (${fluidBlock.storedAmount}/${FluidContainer.MAX_CAPACITY})" + FluidType.NONE -> "Empty" + } + } else { + when (fluidBlock.storedFluid) { + FluidType.WATER -> "Water" + FluidType.LAVA -> "Lava" + FluidType.NONE -> "Empty" + } } val fluidColor = when (fluidBlock.storedFluid) { @@ -119,6 +129,8 @@ object FluidBlockDialog { .color(NamedTextColor.GRAY) is FluidPipe -> Component.text("Pipe - transports fluid in facing direction") .color(NamedTextColor.GRAY) + is FluidContainer -> Component.text("Container - stores up to ${FluidContainer.MAX_CAPACITY} units of fluid") + .color(NamedTextColor.GRAY) else -> Component.text("Fluid block") .color(NamedTextColor.GRAY) } diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializer.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializer.kt index 0970fd6..9a26598 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializer.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializer.kt @@ -1,5 +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.plugin.java.JavaPlugin @@ -37,6 +38,13 @@ object FluidBlockInitializer { } } + 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 index 6337a93..c80026d 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockListener.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockListener.kt @@ -1,5 +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 com.nexomc.nexo.api.NexoBlocks @@ -39,6 +40,32 @@ class FluidBlockListener( 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) @@ -65,11 +92,22 @@ class FluidBlockListener( return } - // Handle directional variant placed directly - val facing = FluidPipe.facingFromBlockId(blockId) - if (facing != null) { - plugin.logger.info("Directional fluid pipe placed: $blockId (facing $facing)") - val fluidBlock = FluidBlockFactory.createFluidBlock(blockId, event.block.location, facing) + // 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) } @@ -97,6 +135,7 @@ class FluidBlockListener( val baseItemId = when (fluidBlock) { is FluidPump -> FluidPump.BLOCK_ID is FluidPipe -> FluidPipe.BLOCK_ID + is FluidContainer -> FluidContainer.BLOCK_ID else -> null } diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistence.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistence.kt index 390d954..1dc4695 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistence.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockPersistence.kt @@ -1,5 +1,6 @@ package com.coderjoe.atlas.fluid +import com.coderjoe.atlas.fluid.block.FluidContainer import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.plugin.java.JavaPlugin import java.io.File @@ -28,6 +29,9 @@ class FluidBlockPersistence(private val plugin: JavaPlugin) { if (data.facing != null) { map["facing"] = data.facing } + if (data.storedAmount != null) { + map["storedAmount"] = data.storedAmount + } blockDataList.add(map) } @@ -65,8 +69,9 @@ class FluidBlockPersistence(private val plugin: JavaPlugin) { 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) + val data = FluidBlockData(blockId, world, x, y, z, fluidType, facing, storedAmount) val location = data.toLocation(plugin) if (location == null) { @@ -78,7 +83,11 @@ class FluidBlockPersistence(private val plugin: JavaPlugin) { val fluidBlock = FluidBlockFactory.createFluidBlock(blockId, location, data.toBlockFace()) if (fluidBlock != null) { - fluidBlock.storedFluid = data.toFluidType() + if (fluidBlock is FluidContainer && data.storedAmount != null) { + fluidBlock.restoreState(data.toFluidType(), data.storedAmount) + } else { + fluidBlock.storedFluid = data.toFluidType() + } registry.registerFluidBlock(fluidBlock, blockId) loadedCount++ } else { diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidContainer.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidContainer.kt new file mode 100644 index 0000000..4ea37a9 --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidContainer.kt @@ -0,0 +1,155 @@ +package com.coderjoe.atlas.fluid.block + +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) { + + var storedAmount: Int = 0 + private set + + override val updateIntervalTicks: Long = 20L + + companion object { + const val BLOCK_ID = "fluid_container" + const val MAX_CAPACITY = 10 + + val DIRECTIONAL_IDS = mapOf( + BlockFace.NORTH to "fluid_container_north", + BlockFace.SOUTH to "fluid_container_south", + BlockFace.EAST to "fluid_container_east", + BlockFace.WEST to "fluid_container_west", + BlockFace.UP to "fluid_container_up", + BlockFace.DOWN to "fluid_container_down" + ) + + val ID_TO_FACING = DIRECTIONAL_IDS.entries.associate { (face, id) -> id to face } + + private val FILL_LEVELS = listOf("low", "medium", "full") + private val FLUID_TYPES = listOf("water", "lava") + + val FILLED_IDS: Map>> = BlockFace.values() + .filter { DIRECTIONAL_IDS.containsKey(it) } + .associateWith { face -> + val dir = face.name.lowercase() + mapOf( + FluidType.WATER to mapOf( + "low" to "fluid_container_${dir}_water_low", + "medium" to "fluid_container_${dir}_water_medium", + "full" to "fluid_container_${dir}_water_full" + ), + FluidType.LAVA to mapOf( + "low" to "fluid_container_${dir}_lava_low", + "medium" to "fluid_container_${dir}_lava_medium", + "full" to "fluid_container_${dir}_lava_full" + ) + ) + } + + val ALL_VARIANT_IDS: List = buildList { + addAll(DIRECTIONAL_IDS.values) + for (face in FILLED_IDS.keys) { + for (fluidMap in FILLED_IDS[face]!!.values) { + addAll(fluidMap.values) + } + } + } + + fun facingFromBlockId(blockId: String): BlockFace? { + ID_TO_FACING[blockId]?.let { return it } + for ((face, fluidMap) in FILLED_IDS) { + for (levelMap in fluidMap.values) { + if (blockId in levelMap.values) return face + } + } + return null + } + } + + override fun hasFluid(): Boolean = storedAmount > 0 + + override fun storeFluid(type: FluidType): Boolean { + if (storedAmount >= MAX_CAPACITY) return false + if (storedFluid != FluidType.NONE && storedFluid != type) return false + storedFluid = type + storedAmount++ + return true + } + + override fun removeFluid(): FluidType { + if (storedAmount <= 0) return FluidType.NONE + val fluid = storedFluid + storedAmount-- + if (storedAmount == 0) { + storedFluid = FluidType.NONE + } + return fluid + } + + fun canRemoveFluidFrom(direction: BlockFace): Boolean { + return direction == facing && hasFluid() + } + + fun getFillLevel(): String = when (storedAmount) { + 0 -> "empty" + in 1..3 -> "low" + in 4..7 -> "medium" + else -> "full" + } + + override fun getVisualStateBlockId(): String { + if (storedAmount == 0 || storedFluid == FluidType.NONE) { + return DIRECTIONAL_IDS[facing]!! + } + return FILLED_IDS[facing]!![storedFluid]!![getFillLevel()]!! + } + + override fun fluidUpdate() { + if (storedAmount >= MAX_CAPACITY) return + + val registry = FluidBlockRegistry.instance ?: return + val behind = facing.oppositeFace + val source = registry.getAdjacentFluidBlock(location, behind) ?: return + + when (source) { + is FluidPump -> { + if (source.canRemoveFluidFrom(facing)) { + val fluid = source.removeFluid() + if (storeFluid(fluid)) { + plugin.logger.info("FluidContainer at ${location.blockX},${location.blockY},${location.blockZ} pulled ${fluid.name} from FluidPump") + } else { + source.storeFluid(fluid) + } + } + } + is FluidPipe -> { + if (source.hasFluid()) { + val fluid = source.removeFluid() + if (storeFluid(fluid)) { + plugin.logger.info("FluidContainer at ${location.blockX},${location.blockY},${location.blockZ} pulled ${fluid.name} from FluidPipe") + } else { + source.storeFluid(fluid) + } + } + } + is FluidContainer -> { + if (source.canRemoveFluidFrom(facing)) { + val fluid = source.removeFluid() + if (storeFluid(fluid)) { + plugin.logger.info("FluidContainer at ${location.blockX},${location.blockY},${location.blockZ} pulled ${fluid.name} from FluidContainer") + } else { + source.storeFluid(fluid) + } + } + } + } + } + + fun restoreState(type: FluidType, amount: Int) { + storedFluid = type + storedAmount = amount.coerceIn(0, MAX_CAPACITY) + } +} 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 575391b..56c7575 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPipe.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidPipe.kt @@ -73,6 +73,13 @@ class FluidPipe(location: Location, val facing: BlockFace) : FluidBlock(location plugin.logger.info("FluidPipe at ${location.blockX},${location.blockY},${location.blockZ} pulled ${fluid.name} from FluidPipe") } } + is FluidContainer -> { + if (source.canRemoveFluidFrom(facing)) { + val fluid = source.removeFluid() + storeFluid(fluid) + plugin.logger.info("FluidPipe at ${location.blockX},${location.blockY},${location.blockZ} pulled ${fluid.name} from FluidContainer") + } + } } } } diff --git a/src/main/resources/nexo/items/atlas_blocks.yml b/src/main/resources/nexo/items/atlas_blocks.yml index d38440a..ba632ac 100644 --- a/src/main/resources/nexo/items/atlas_blocks.yml +++ b/src/main/resources/nexo/items/atlas_blocks.yml @@ -1402,3 +1402,1249 @@ fluid_pipe_down_filled_lava: loots: - nexo_item: fluid_pipe probability: 1.0 + +# ==================== Fluid Container ==================== + +fluid_container: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube_all + textures: + all: atlas:block/fluid_container_side + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 1 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 + +fluid_container_north: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_front + south: atlas:block/fluid_container_back + east: atlas:block/fluid_container_side + west: atlas:block/fluid_container_side + up: atlas:block/fluid_container_top + down: atlas:block/fluid_container_top + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 2 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_south: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_back + south: atlas:block/fluid_container_front + east: atlas:block/fluid_container_side + west: atlas:block/fluid_container_side + up: atlas:block/fluid_container_top + down: atlas:block/fluid_container_top + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 3 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_east: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side + south: atlas:block/fluid_container_side + east: atlas:block/fluid_container_front + west: atlas:block/fluid_container_back + up: atlas:block/fluid_container_top + down: atlas:block/fluid_container_top + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 4 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_west: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side + south: atlas:block/fluid_container_side + east: atlas:block/fluid_container_back + west: atlas:block/fluid_container_front + up: atlas:block/fluid_container_top + down: atlas:block/fluid_container_top + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 5 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_up: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side + south: atlas:block/fluid_container_side + east: atlas:block/fluid_container_side + west: atlas:block/fluid_container_side + up: atlas:block/fluid_container_front + down: atlas:block/fluid_container_back + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 6 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_down: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side + south: atlas:block/fluid_container_side + east: atlas:block/fluid_container_side + west: atlas:block/fluid_container_side + up: atlas:block/fluid_container_back + down: atlas:block/fluid_container_front + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 7 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_north_water_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_front_water_low + south: atlas:block/fluid_container_back_water_low + east: atlas:block/fluid_container_side_water_low + west: atlas:block/fluid_container_side_water_low + up: atlas:block/fluid_container_top_water_low + down: atlas:block/fluid_container_top_water_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 8 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_north_water_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_front_water_medium + south: atlas:block/fluid_container_back_water_medium + east: atlas:block/fluid_container_side_water_medium + west: atlas:block/fluid_container_side_water_medium + up: atlas:block/fluid_container_top_water_medium + down: atlas:block/fluid_container_top_water_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 9 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_north_water_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_front_water_full + south: atlas:block/fluid_container_back_water_full + east: atlas:block/fluid_container_side_water_full + west: atlas:block/fluid_container_side_water_full + up: atlas:block/fluid_container_top_water_full + down: atlas:block/fluid_container_top_water_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 10 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_north_lava_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_front_lava_low + south: atlas:block/fluid_container_back_lava_low + east: atlas:block/fluid_container_side_lava_low + west: atlas:block/fluid_container_side_lava_low + up: atlas:block/fluid_container_top_lava_low + down: atlas:block/fluid_container_top_lava_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 11 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_north_lava_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_front_lava_medium + south: atlas:block/fluid_container_back_lava_medium + east: atlas:block/fluid_container_side_lava_medium + west: atlas:block/fluid_container_side_lava_medium + up: atlas:block/fluid_container_top_lava_medium + down: atlas:block/fluid_container_top_lava_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 12 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_north_lava_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_front_lava_full + south: atlas:block/fluid_container_back_lava_full + east: atlas:block/fluid_container_side_lava_full + west: atlas:block/fluid_container_side_lava_full + up: atlas:block/fluid_container_top_lava_full + down: atlas:block/fluid_container_top_lava_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 13 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_south_water_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_back_water_low + south: atlas:block/fluid_container_front_water_low + east: atlas:block/fluid_container_side_water_low + west: atlas:block/fluid_container_side_water_low + up: atlas:block/fluid_container_top_water_low + down: atlas:block/fluid_container_top_water_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 14 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_south_water_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_back_water_medium + south: atlas:block/fluid_container_front_water_medium + east: atlas:block/fluid_container_side_water_medium + west: atlas:block/fluid_container_side_water_medium + up: atlas:block/fluid_container_top_water_medium + down: atlas:block/fluid_container_top_water_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 15 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_south_water_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_back_water_full + south: atlas:block/fluid_container_front_water_full + east: atlas:block/fluid_container_side_water_full + west: atlas:block/fluid_container_side_water_full + up: atlas:block/fluid_container_top_water_full + down: atlas:block/fluid_container_top_water_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 16 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_south_lava_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_back_lava_low + south: atlas:block/fluid_container_front_lava_low + east: atlas:block/fluid_container_side_lava_low + west: atlas:block/fluid_container_side_lava_low + up: atlas:block/fluid_container_top_lava_low + down: atlas:block/fluid_container_top_lava_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 17 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_south_lava_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_back_lava_medium + south: atlas:block/fluid_container_front_lava_medium + east: atlas:block/fluid_container_side_lava_medium + west: atlas:block/fluid_container_side_lava_medium + up: atlas:block/fluid_container_top_lava_medium + down: atlas:block/fluid_container_top_lava_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 18 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_south_lava_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_back_lava_full + south: atlas:block/fluid_container_front_lava_full + east: atlas:block/fluid_container_side_lava_full + west: atlas:block/fluid_container_side_lava_full + up: atlas:block/fluid_container_top_lava_full + down: atlas:block/fluid_container_top_lava_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 19 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_east_water_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_low + south: atlas:block/fluid_container_side_water_low + east: atlas:block/fluid_container_front_water_low + west: atlas:block/fluid_container_back_water_low + up: atlas:block/fluid_container_top_water_low + down: atlas:block/fluid_container_top_water_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 20 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_east_water_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_medium + south: atlas:block/fluid_container_side_water_medium + east: atlas:block/fluid_container_front_water_medium + west: atlas:block/fluid_container_back_water_medium + up: atlas:block/fluid_container_top_water_medium + down: atlas:block/fluid_container_top_water_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 21 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_east_water_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_full + south: atlas:block/fluid_container_side_water_full + east: atlas:block/fluid_container_front_water_full + west: atlas:block/fluid_container_back_water_full + up: atlas:block/fluid_container_top_water_full + down: atlas:block/fluid_container_top_water_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 22 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_east_lava_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_low + south: atlas:block/fluid_container_side_lava_low + east: atlas:block/fluid_container_front_lava_low + west: atlas:block/fluid_container_back_lava_low + up: atlas:block/fluid_container_top_lava_low + down: atlas:block/fluid_container_top_lava_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 23 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_east_lava_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_medium + south: atlas:block/fluid_container_side_lava_medium + east: atlas:block/fluid_container_front_lava_medium + west: atlas:block/fluid_container_back_lava_medium + up: atlas:block/fluid_container_top_lava_medium + down: atlas:block/fluid_container_top_lava_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 24 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_east_lava_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_full + south: atlas:block/fluid_container_side_lava_full + east: atlas:block/fluid_container_front_lava_full + west: atlas:block/fluid_container_back_lava_full + up: atlas:block/fluid_container_top_lava_full + down: atlas:block/fluid_container_top_lava_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 25 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_west_water_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_low + south: atlas:block/fluid_container_side_water_low + east: atlas:block/fluid_container_back_water_low + west: atlas:block/fluid_container_front_water_low + up: atlas:block/fluid_container_top_water_low + down: atlas:block/fluid_container_top_water_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 26 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_west_water_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_medium + south: atlas:block/fluid_container_side_water_medium + east: atlas:block/fluid_container_back_water_medium + west: atlas:block/fluid_container_front_water_medium + up: atlas:block/fluid_container_top_water_medium + down: atlas:block/fluid_container_top_water_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 27 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_west_water_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_full + south: atlas:block/fluid_container_side_water_full + east: atlas:block/fluid_container_back_water_full + west: atlas:block/fluid_container_front_water_full + up: atlas:block/fluid_container_top_water_full + down: atlas:block/fluid_container_top_water_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 28 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_west_lava_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_low + south: atlas:block/fluid_container_side_lava_low + east: atlas:block/fluid_container_back_lava_low + west: atlas:block/fluid_container_front_lava_low + up: atlas:block/fluid_container_top_lava_low + down: atlas:block/fluid_container_top_lava_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 29 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_west_lava_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_medium + south: atlas:block/fluid_container_side_lava_medium + east: atlas:block/fluid_container_back_lava_medium + west: atlas:block/fluid_container_front_lava_medium + up: atlas:block/fluid_container_top_lava_medium + down: atlas:block/fluid_container_top_lava_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 30 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_west_lava_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_full + south: atlas:block/fluid_container_side_lava_full + east: atlas:block/fluid_container_back_lava_full + west: atlas:block/fluid_container_front_lava_full + up: atlas:block/fluid_container_top_lava_full + down: atlas:block/fluid_container_top_lava_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 31 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_up_water_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_low + south: atlas:block/fluid_container_side_water_low + east: atlas:block/fluid_container_side_water_low + west: atlas:block/fluid_container_side_water_low + up: atlas:block/fluid_container_front_water_low + down: atlas:block/fluid_container_back_water_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 32 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_up_water_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_medium + south: atlas:block/fluid_container_side_water_medium + east: atlas:block/fluid_container_side_water_medium + west: atlas:block/fluid_container_side_water_medium + up: atlas:block/fluid_container_front_water_medium + down: atlas:block/fluid_container_back_water_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 33 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_up_water_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_full + south: atlas:block/fluid_container_side_water_full + east: atlas:block/fluid_container_side_water_full + west: atlas:block/fluid_container_side_water_full + up: atlas:block/fluid_container_front_water_full + down: atlas:block/fluid_container_back_water_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 34 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_up_lava_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_low + south: atlas:block/fluid_container_side_lava_low + east: atlas:block/fluid_container_side_lava_low + west: atlas:block/fluid_container_side_lava_low + up: atlas:block/fluid_container_front_lava_low + down: atlas:block/fluid_container_back_lava_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 35 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_up_lava_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_medium + south: atlas:block/fluid_container_side_lava_medium + east: atlas:block/fluid_container_side_lava_medium + west: atlas:block/fluid_container_side_lava_medium + up: atlas:block/fluid_container_front_lava_medium + down: atlas:block/fluid_container_back_lava_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 36 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_up_lava_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_full + south: atlas:block/fluid_container_side_lava_full + east: atlas:block/fluid_container_side_lava_full + west: atlas:block/fluid_container_side_lava_full + up: atlas:block/fluid_container_front_lava_full + down: atlas:block/fluid_container_back_lava_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 37 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_down_water_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_low + south: atlas:block/fluid_container_side_water_low + east: atlas:block/fluid_container_side_water_low + west: atlas:block/fluid_container_side_water_low + up: atlas:block/fluid_container_back_water_low + down: atlas:block/fluid_container_front_water_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 38 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_down_water_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_medium + south: atlas:block/fluid_container_side_water_medium + east: atlas:block/fluid_container_side_water_medium + west: atlas:block/fluid_container_side_water_medium + up: atlas:block/fluid_container_back_water_medium + down: atlas:block/fluid_container_front_water_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 39 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_down_water_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_water_full + south: atlas:block/fluid_container_side_water_full + east: atlas:block/fluid_container_side_water_full + west: atlas:block/fluid_container_side_water_full + up: atlas:block/fluid_container_back_water_full + down: atlas:block/fluid_container_front_water_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 40 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_down_lava_low: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_low + south: atlas:block/fluid_container_side_lava_low + east: atlas:block/fluid_container_side_lava_low + west: atlas:block/fluid_container_side_lava_low + up: atlas:block/fluid_container_back_lava_low + down: atlas:block/fluid_container_front_lava_low + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 41 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_down_lava_medium: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_medium + south: atlas:block/fluid_container_side_lava_medium + east: atlas:block/fluid_container_side_lava_medium + west: atlas:block/fluid_container_side_lava_medium + up: atlas:block/fluid_container_back_lava_medium + down: atlas:block/fluid_container_front_lava_medium + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 42 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 +fluid_container_down_lava_full: + itemname: "Fluid Container" + material: paper + Pack: + generate_model: true + parent_model: block/cube + textures: + north: atlas:block/fluid_container_side_lava_full + south: atlas:block/fluid_container_side_lava_full + east: atlas:block/fluid_container_side_lava_full + west: atlas:block/fluid_container_side_lava_full + up: atlas:block/fluid_container_back_lava_full + down: atlas:block/fluid_container_front_lava_full + Mechanics: + custom_block: + type: CHORUSBLOCK + custom_variation: 43 + hardness: 3 + block_sounds: + break_sound: block.glass.break + place_sound: block.glass.place + hit_sound: block.glass.hit + step_sound: block.glass.step + fall_sound: block.glass.fall + drop: + silktouch: false + loots: + - nexo_item: fluid_container + probability: 1.0 diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back.png new file mode 100644 index 0000000..40c60ea Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_lava_full.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_lava_full.png new file mode 100644 index 0000000..aa2514c Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_lava_full.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_lava_low.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_lava_low.png new file mode 100644 index 0000000..520bf0d Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_lava_low.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_lava_medium.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_lava_medium.png new file mode 100644 index 0000000..bb802b1 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_lava_medium.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_water_full.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_water_full.png new file mode 100644 index 0000000..81971f8 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_water_full.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_water_low.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_water_low.png new file mode 100644 index 0000000..9c99165 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_water_low.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_water_medium.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_water_medium.png new file mode 100644 index 0000000..0d8a782 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_back_water_medium.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front.png new file mode 100644 index 0000000..a6e37ad Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_lava_full.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_lava_full.png new file mode 100644 index 0000000..16d8611 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_lava_full.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_lava_low.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_lava_low.png new file mode 100644 index 0000000..0b24532 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_lava_low.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_lava_medium.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_lava_medium.png new file mode 100644 index 0000000..90646d8 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_lava_medium.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_water_full.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_water_full.png new file mode 100644 index 0000000..3ae7c34 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_water_full.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_water_low.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_water_low.png new file mode 100644 index 0000000..f737cbe Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_water_low.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_water_medium.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_water_medium.png new file mode 100644 index 0000000..f0fecfe Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_front_water_medium.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side.png new file mode 100644 index 0000000..2f79c8e Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_lava_full.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_lava_full.png new file mode 100644 index 0000000..986586e Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_lava_full.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_lava_low.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_lava_low.png new file mode 100644 index 0000000..2e15365 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_lava_low.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_lava_medium.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_lava_medium.png new file mode 100644 index 0000000..b87aeaf Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_lava_medium.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_water_full.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_water_full.png new file mode 100644 index 0000000..cd36d45 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_water_full.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_water_low.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_water_low.png new file mode 100644 index 0000000..c3ff069 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_water_low.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_water_medium.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_water_medium.png new file mode 100644 index 0000000..cf13a0a Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_side_water_medium.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top.png new file mode 100644 index 0000000..5f45dbf Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_lava_full.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_lava_full.png new file mode 100644 index 0000000..18107eb Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_lava_full.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_lava_low.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_lava_low.png new file mode 100644 index 0000000..a344191 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_lava_low.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_lava_medium.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_lava_medium.png new file mode 100644 index 0000000..817d515 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_lava_medium.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_water_full.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_water_full.png new file mode 100644 index 0000000..b78ad84 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_water_full.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_water_low.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_water_low.png new file mode 100644 index 0000000..ffe5537 Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_water_low.png differ diff --git a/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_water_medium.png b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_water_medium.png new file mode 100644 index 0000000..31c57ef Binary files /dev/null and b/src/main/resources/nexo/pack/assets/atlas/textures/block/fluid_container_top_water_medium.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 6724925..9c5665b 100644 --- a/src/main/resources/nexo/recipes/shapeless/atlas_recipes.yml +++ b/src/main/resources/nexo/recipes/shapeless/atlas_recipes.yml @@ -77,3 +77,18 @@ fluid_pipe_recipe: A: amount: 1 minecraft_type: IRON_INGOT + +fluid_container_recipe: + result: + nexo_item: fluid_container + amount: 1 + ingredients: + A: + amount: 4 + minecraft_type: IRON_INGOT + B: + amount: 4 + minecraft_type: GLASS + C: + amount: 1 + minecraft_type: BUCKET diff --git a/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt b/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt index 0b091a6..bd5466e 100644 --- a/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt @@ -32,9 +32,9 @@ class AtlasPluginTest { } @Test - fun `fluid system initializes with 21 block types`() { + fun `fluid system initializes with 63 block types`() { FluidBlockInitializer.initialize(TestHelper.mockPlugin) - assertEquals(21, FluidBlockFactory.getRegisteredBlockIds().size) + assertEquals(63, FluidBlockFactory.getRegisteredBlockIds().size) } @Test diff --git a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt index 431b9ee..b3dd804 100644 --- a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt @@ -24,8 +24,8 @@ class FluidBlockInitializerTest { FluidBlockInitializer.initialize(TestHelper.mockPlugin) val ids = FluidBlockFactory.getRegisteredBlockIds() - // 3 pump + 6 directional pipe + 6 water-filled + 6 lava-filled = 21 - assertEquals(21, ids.size) + // 3 pump + 6 directional pipe + 6 water-filled + 6 lava-filled + 42 container = 63 + assertEquals(63, ids.size) } @Test diff --git a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidContainerTest.kt b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidContainerTest.kt new file mode 100644 index 0000000..0fa6c78 --- /dev/null +++ b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidContainerTest.kt @@ -0,0 +1,406 @@ +package com.coderjoe.atlas.fluid + +import com.coderjoe.atlas.TestHelper +import com.coderjoe.atlas.TestHelper.callFluidUpdate +import com.coderjoe.atlas.fluid.block.FluidContainer +import com.coderjoe.atlas.fluid.block.FluidPipe +import com.coderjoe.atlas.fluid.block.FluidPump +import org.bukkit.block.BlockFace +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* + +class FluidContainerTest { + + @BeforeEach + fun setup() { + TestHelper.setup() + } + + @AfterEach + fun teardown() { + TestHelper.teardown() + } + + // --- Store/Remove multi-unit --- + + @Test + fun `store fluid increments amount`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + assertTrue(container.storeFluid(FluidType.WATER)) + assertEquals(1, container.storedAmount) + assertEquals(FluidType.WATER, container.storedFluid) + } + + @Test + fun `store multiple units of same fluid`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + for (i in 1..5) { + assertTrue(container.storeFluid(FluidType.WATER)) + } + assertEquals(5, container.storedAmount) + } + + @Test + fun `store up to max capacity`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + for (i in 1..FluidContainer.MAX_CAPACITY) { + assertTrue(container.storeFluid(FluidType.WATER)) + } + assertEquals(FluidContainer.MAX_CAPACITY, container.storedAmount) + } + + @Test + fun `store rejects when full`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + for (i in 1..FluidContainer.MAX_CAPACITY) { + container.storeFluid(FluidType.WATER) + } + assertFalse(container.storeFluid(FluidType.WATER)) + assertEquals(FluidContainer.MAX_CAPACITY, container.storedAmount) + } + + @Test + fun `store rejects different fluid type`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + container.storeFluid(FluidType.WATER) + assertFalse(container.storeFluid(FluidType.LAVA)) + assertEquals(1, container.storedAmount) + assertEquals(FluidType.WATER, container.storedFluid) + } + + @Test + fun `remove fluid decrements amount`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + container.storeFluid(FluidType.WATER) + container.storeFluid(FluidType.WATER) + container.storeFluid(FluidType.WATER) + + val removed = container.removeFluid() + assertEquals(FluidType.WATER, removed) + assertEquals(2, container.storedAmount) + } + + @Test + fun `remove fluid clears type at zero`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + container.storeFluid(FluidType.WATER) + + val removed = container.removeFluid() + assertEquals(FluidType.WATER, removed) + assertEquals(0, container.storedAmount) + assertEquals(FluidType.NONE, container.storedFluid) + } + + @Test + fun `remove from empty returns NONE`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + assertEquals(FluidType.NONE, container.removeFluid()) + } + + @Test + fun `hasFluid returns true when amount greater than zero`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + assertFalse(container.hasFluid()) + container.storeFluid(FluidType.WATER) + assertTrue(container.hasFluid()) + } + + // --- canRemoveFluidFrom --- + + @Test + fun `canRemoveFluidFrom returns true for front face`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + container.storeFluid(FluidType.WATER) + assertTrue(container.canRemoveFluidFrom(BlockFace.NORTH)) + } + + @Test + fun `canRemoveFluidFrom returns false for non-front face`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + container.storeFluid(FluidType.WATER) + assertFalse(container.canRemoveFluidFrom(BlockFace.SOUTH)) + assertFalse(container.canRemoveFluidFrom(BlockFace.EAST)) + } + + @Test + fun `canRemoveFluidFrom returns false when empty`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + assertFalse(container.canRemoveFluidFrom(BlockFace.NORTH)) + } + + // --- Fill level --- + + @Test + fun `fill level empty at 0`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + assertEquals("empty", container.getFillLevel()) + } + + @Test + fun `fill level low at 1 to 3`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + for (i in 1..3) { + container.storeFluid(FluidType.WATER) + assertEquals("low", container.getFillLevel(), "Expected low at amount $i") + } + } + + @Test + fun `fill level medium at 4 to 7`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + repeat(4) { container.storeFluid(FluidType.WATER) } + assertEquals("medium", container.getFillLevel()) + repeat(3) { container.storeFluid(FluidType.WATER) } + assertEquals("medium", container.getFillLevel()) + } + + @Test + fun `fill level full at 8 to 10`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + repeat(8) { container.storeFluid(FluidType.WATER) } + assertEquals("full", container.getFillLevel()) + repeat(2) { container.storeFluid(FluidType.WATER) } + assertEquals("full", container.getFillLevel()) + } + + // --- Visual state --- + + @Test + fun `visual state empty for all directions`() { + for ((face, id) in FluidContainer.DIRECTIONAL_IDS) { + val container = FluidContainer(TestHelper.createLocation(), face) + assertEquals(id, container.getVisualStateBlockId()) + } + } + + @Test + fun `visual state water low`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + container.storeFluid(FluidType.WATER) + assertEquals("fluid_container_north_water_low", container.getVisualStateBlockId()) + } + + @Test + fun `visual state water medium`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.SOUTH) + repeat(5) { container.storeFluid(FluidType.WATER) } + assertEquals("fluid_container_south_water_medium", container.getVisualStateBlockId()) + } + + @Test + fun `visual state water full`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.EAST) + repeat(10) { container.storeFluid(FluidType.WATER) } + assertEquals("fluid_container_east_water_full", container.getVisualStateBlockId()) + } + + @Test + fun `visual state lava low`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.WEST) + container.storeFluid(FluidType.LAVA) + assertEquals("fluid_container_west_lava_low", container.getVisualStateBlockId()) + } + + @Test + fun `visual state returns empty after draining all fluid`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.UP) + container.storeFluid(FluidType.WATER) + container.removeFluid() + assertEquals("fluid_container_up", container.getVisualStateBlockId()) + } + + // --- facingFromBlockId --- + + @Test + fun `facingFromBlockId returns correct face for directional IDs`() { + for ((face, id) in FluidContainer.DIRECTIONAL_IDS) { + assertEquals(face, FluidContainer.facingFromBlockId(id)) + } + } + + @Test + fun `facingFromBlockId returns correct face for filled variant IDs`() { + assertEquals(BlockFace.NORTH, FluidContainer.facingFromBlockId("fluid_container_north_water_low")) + assertEquals(BlockFace.SOUTH, FluidContainer.facingFromBlockId("fluid_container_south_lava_full")) + } + + @Test + fun `facingFromBlockId returns null for unknown`() { + assertNull(FluidContainer.facingFromBlockId("unknown_block")) + } + + // --- ALL_VARIANT_IDS --- + + @Test + fun `ALL_VARIANT_IDS contains 42 entries`() { + assertEquals(42, FluidContainer.ALL_VARIANT_IDS.size) + } + + @Test + fun `ALL_VARIANT_IDS contains all directional IDs`() { + for (id in FluidContainer.DIRECTIONAL_IDS.values) { + assertTrue(id in FluidContainer.ALL_VARIANT_IDS, "Missing $id") + } + } + + // --- Directional pull --- + + @Test + fun `container pulls from pipe behind it`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + + val container = FluidContainer(TestHelper.createLocation(0.0, 64.0, 0.0), BlockFace.SOUTH) + val pipe = FluidPipe(TestHelper.createLocation(0.0, 64.0, -1.0), BlockFace.SOUTH) + pipe.storeFluid(FluidType.WATER) + + TestHelper.addToRegistry(fluidRegistry, container, "fluid_container_south") + TestHelper.addToRegistry(fluidRegistry, pipe, "fluid_pipe_south") + + container.callFluidUpdate() + assertEquals(FluidType.WATER, container.storedFluid) + assertEquals(1, container.storedAmount) + assertEquals(FluidType.NONE, pipe.storedFluid) + } + + @Test + fun `container pulls from pump behind it`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + + val container = FluidContainer(TestHelper.createLocation(0.0, 64.0, 1.0), BlockFace.SOUTH) + val pump = FluidPump(TestHelper.createLocation(0.0, 64.0, 0.0)) + pump.storeFluid(FluidType.WATER) + + val cauldronField = FluidPump::class.java.getDeclaredField("cauldronFace") + cauldronField.isAccessible = true + cauldronField.set(pump, BlockFace.NORTH) + + TestHelper.addToRegistry(fluidRegistry, container, "fluid_container_south") + TestHelper.addToRegistry(fluidRegistry, pump, "fluid_pump") + + container.callFluidUpdate() + assertEquals(FluidType.WATER, container.storedFluid) + assertEquals(1, container.storedAmount) + } + + @Test + fun `container pulls from another container behind it`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + + val container1 = FluidContainer(TestHelper.createLocation(0.0, 64.0, 0.0), BlockFace.SOUTH) + container1.storeFluid(FluidType.LAVA) + + val container2 = FluidContainer(TestHelper.createLocation(0.0, 64.0, 1.0), BlockFace.SOUTH) + + TestHelper.addToRegistry(fluidRegistry, container1, "fluid_container_south") + TestHelper.addToRegistry(fluidRegistry, container2, "fluid_container_south") + + container2.callFluidUpdate() + assertEquals(FluidType.LAVA, container2.storedFluid) + assertEquals(1, container2.storedAmount) + assertEquals(0, container1.storedAmount) + } + + @Test + fun `container does not pull when full`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + + val container = FluidContainer(TestHelper.createLocation(0.0, 64.0, 0.0), BlockFace.SOUTH) + repeat(FluidContainer.MAX_CAPACITY) { container.storeFluid(FluidType.WATER) } + + val pipe = FluidPipe(TestHelper.createLocation(0.0, 64.0, -1.0), BlockFace.SOUTH) + pipe.storeFluid(FluidType.WATER) + + TestHelper.addToRegistry(fluidRegistry, container, "fluid_container_south") + TestHelper.addToRegistry(fluidRegistry, pipe, "fluid_pipe_south") + + container.callFluidUpdate() + assertTrue(pipe.hasFluid()) // pipe still has fluid + } + + @Test + fun `container rejects mismatched fluid from pipe`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + + val container = FluidContainer(TestHelper.createLocation(0.0, 64.0, 0.0), BlockFace.SOUTH) + container.storeFluid(FluidType.WATER) + + val pipe = FluidPipe(TestHelper.createLocation(0.0, 64.0, -1.0), BlockFace.SOUTH) + pipe.storeFluid(FluidType.LAVA) + + TestHelper.addToRegistry(fluidRegistry, container, "fluid_container_south") + TestHelper.addToRegistry(fluidRegistry, pipe, "fluid_pipe_south") + + container.callFluidUpdate() + assertTrue(pipe.hasFluid()) // pipe keeps its lava since container rejected it + assertEquals(1, container.storedAmount) // container unchanged + } + + // --- Pipe pulling from container --- + + @Test + fun `pipe pulls from container front face`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + + // Container facing SOUTH (front=SOUTH), pipe at z=1 facing SOUTH (pulls from NORTH=z-1=container) + val container = FluidContainer(TestHelper.createLocation(0.0, 64.0, 0.0), BlockFace.SOUTH) + container.storeFluid(FluidType.WATER) + + val pipe = FluidPipe(TestHelper.createLocation(0.0, 64.0, 1.0), BlockFace.SOUTH) + + TestHelper.addToRegistry(fluidRegistry, container, "fluid_container_south") + TestHelper.addToRegistry(fluidRegistry, pipe, "fluid_pipe_south") + + pipe.callFluidUpdate() + assertEquals(FluidType.WATER, pipe.storedFluid) + assertEquals(0, container.storedAmount) + } + + @Test + fun `pipe cannot pull from container non-front face`() { + val fluidRegistry = FluidBlockRegistry(TestHelper.mockPlugin) + + // Container facing NORTH (front=NORTH), pipe behind at z=1 facing SOUTH pulls from NORTH=z-1 + // But container faces NORTH, so front is NORTH. Pipe pulling from SOUTH direction won't match. + val container = FluidContainer(TestHelper.createLocation(0.0, 64.0, 0.0), BlockFace.NORTH) + container.storeFluid(FluidType.WATER) + + // Pipe at z=1, facing SOUTH, pulls from behind (NORTH side = z-1 = container) + // It calls canRemoveFluidFrom(SOUTH) on container — container facing NORTH, so front is NORTH, not SOUTH + val pipe = FluidPipe(TestHelper.createLocation(0.0, 64.0, 1.0), BlockFace.SOUTH) + + TestHelper.addToRegistry(fluidRegistry, container, "fluid_container_north") + TestHelper.addToRegistry(fluidRegistry, pipe, "fluid_pipe_south") + + pipe.callFluidUpdate() + assertEquals(FluidType.NONE, pipe.storedFluid) // could not pull + assertEquals(1, container.storedAmount) // unchanged + } + + // --- Persistence --- + + @Test + fun `restoreState sets type and amount`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + container.restoreState(FluidType.LAVA, 7) + assertEquals(FluidType.LAVA, container.storedFluid) + assertEquals(7, container.storedAmount) + } + + @Test + fun `restoreState clamps to max capacity`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.NORTH) + container.restoreState(FluidType.WATER, 99) + assertEquals(FluidContainer.MAX_CAPACITY, container.storedAmount) + } + + @Test + fun `FluidBlockData captures container facing and storedAmount`() { + val container = FluidContainer(TestHelper.createLocation(), BlockFace.EAST) + repeat(5) { container.storeFluid(FluidType.WATER) } + + val data = FluidBlockData.fromFluidBlock(container, "fluid_container_east") + assertEquals("EAST", data.facing) + assertEquals(5, data.storedAmount) + assertEquals("WATER", data.fluidType) + } +}