diff --git a/.claude/commands/new-block.md b/.claude/commands/new-block.md index 5d3d1d3..a704eb3 100644 --- a/.claude/commands/new-block.md +++ b/.claude/commands/new-block.md @@ -16,11 +16,11 @@ Before writing any code, determine from the user's description: 1. **Block name** (snake_case ID, e.g., `lava_generator`) 2. **Display name** (e.g., "Lava Generator") -3. **System type**: `power` or `fluid` — determines base class (`PowerBlock` or `FluidBlock`) and which registry/factory/dialog to integrate with +3. **System type**: `power`, `fluid`, `transport`, or `utility` — determines base class (`PowerBlock`, `FluidBlock`, `TransportBlock`) and which registry/factory/dialog to integrate with. Utility blocks use the power system (extend `PowerBlock`) but live in the `utility` package. 4. **Placement type**: `SIMPLE` (no direction), `DIRECTIONAL` (faces toward placement direction), or `DIRECTIONAL_OPPOSITE` (faces away) 5. **Visual states**: What variants does the block have? (e.g., inactive/active, charge levels, facing directions) 6. **Key properties**: maxStorage, updateIntervalTicks, canReceivePower, etc. -7. **Behavior**: What happens during powerUpdate()/fluidUpdate()? +7. **Behavior**: What happens during powerUpdate()/fluidUpdate()/transportUpdate()? 8. **Recipe ingredients**: What materials craft it? 9. **Gradient colors** for the item name (hex pair, e.g., `#FF4500:#FF8C00`) @@ -51,15 +51,15 @@ class {ClassName}(location: Location) : {BaseClass}(location, maxStorage = {N}) // Add variant IDs as needed, e.g.: // const val BLOCK_ID_ACTIVE = "{block_id}_active" - val descriptor = BlockDescriptor( - baseBlockId = BLOCK_ID, - displayName = "{Display Name}", - description = "{short description}", - placementType = PlacementType.SIMPLE, - directionalVariants = emptyMap(), - allRegistrableIds = listOf(BLOCK_ID), // include all variant IDs - constructor = { loc, _ -> {ClassName}(loc) } - ) + val descriptor = + BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "{Display Name}", + description = "{short description}", + placementType = PlacementType.SIMPLE, + additionalBlockIds = listOf(), // include any variant IDs beyond the base + constructor = { loc, _ -> {ClassName}(loc) }, + ) } override val baseBlockId: String = BLOCK_ID @@ -72,17 +72,21 @@ class {ClassName}(location: Location) : {BaseClass}(location, maxStorage = {N}) } ``` -**DIRECTIONAL block** (like PowerCable, FluidPipe): Include `facing` property, `DIRECTIONAL_IDS` map for all 6 faces (NORTH, SOUTH, EAST, WEST, UP, DOWN), and use `ID_TO_FACING` reverse lookup. Constructor takes `(Location, BlockFace)`. +**DIRECTIONAL block** (like PowerCable, FluidPipe): Include `facing` property via `override val facing: BlockFace` in the constructor. Constructor takes `(Location, BlockFace)`. Use `ADJACENT_FACES` (inherited from `AtlasBlock`) when iterating over all 6 block faces — never hardcode the face list. **DIRECTIONAL_OPPOSITE block** (like SmallDrill): Same directional pattern but placement type is `DIRECTIONAL_OPPOSITE`. +### Important code patterns + +- **Fluid transfer ordering**: When transferring fluid between blocks, always `removeFluid()` first to capture the value, then call `target.storeFluid(fluid)`. If `storeFluid` fails, restore with `storeFluid(fluid)` on self. Never pass `storedFluid` directly to another block's `storeFluid()` — the mutable field could become stale. +- **Face iteration**: Use the inherited `ADJACENT_FACES` constant from `AtlasBlock` instead of creating a new `listOf(BlockFace.NORTH, ...)`. Filter it as needed (e.g., `ADJACENT_FACES.filter { it != facing.oppositeFace }`). +- **BlockDescriptor fields**: The descriptor uses `additionalBlockIds` (not `allRegistrableIds`) for variant IDs beyond the base. The total registered count = 1 (base) + additionalBlockIds.size. + ### Step 3: Add CraftEngine Block Configuration Create `src/main/resources/atlas/configuration/{block_id}.yml`. -Follow the CraftEngine configuration format. Each variant gets its own `items` section (use `items#1`, `items#2`, etc. for additional variants). The base variant uses `loot: template: default:loot_table/self`, while other variants use explicit loot pools that drop the base item. - -Reference existing configuration files (e.g., `small_solar_panel.yml`, `lava_generator.yml`) for the exact format. Here's the general structure: +Follow the CraftEngine configuration format. Reference existing configuration files for the exact format. Here's the general structure for a **non-directional** block: ```yaml items: @@ -121,13 +125,12 @@ items: side: minecraft:block/custom/{block_id}_side ``` -For additional variant entries (active states, directional, etc.), add `items#1`, `items#2`, etc. sections with explicit loot that drops the base item: -```yaml -items#1: - atlas:{block_id}_{variant}: - # ... same structure but with: - # loot pools that drop atlas:{block_id} (the base item) -``` +For **directional** blocks, use `states` with `properties`, `appearances`, and `variants` sections. Reference `power_splitter.yml` (facing + boolean property), `fluid_merger.yml` (facing + string property), or `fluid_pipe.yml` for the exact format. Key pattern: +- Define properties (facing: direction, plus any state property like powered/fluid) +- Define appearances for each direction (north uses full generation block, south/east/west use y-rotation, up/down use separate models with remapped textures) +- Map variants to appearances + +For additional variant entries (active states, etc.), add `items#1`, `items#2`, etc. sections with explicit loot that drops the base item. ### Step 4: Add Recipe @@ -146,16 +149,20 @@ recipes: count: 1 ``` +Ensure the recipe ingredients are unique — no two blocks should have identical shapeless recipes. + ### Step 5: Register in Atlas.kt Add the descriptor to `src/main/kotlin/com/coderjoe/atlas/Atlas.kt`: -- **Power blocks**: Add `com.coderjoe.atlas.power.block.{ClassName}.descriptor` to the list in `powerDescriptors()` -- **Fluid blocks**: Add `com.coderjoe.atlas.fluid.block.{ClassName}.descriptor` to the list in `fluidDescriptors()` +- **Power blocks**: Add to the list in `powerDescriptors()` +- **Fluid blocks**: Add to the list in `fluidDescriptors()` +- **Transport blocks**: Add to the list in `transportDescriptors()` +- **Utility blocks**: Add to `powerDescriptors()` (utility blocks use the power system) ### Step 6: Add Dialog Integration -For **power blocks**, edit `src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt`: +For **power blocks** (including utility), edit `src/main/kotlin/com/coderjoe/atlas/power/PowerBlockDialog.kt`: 1. Add import for the new class 2. Add `is {ClassName} -> "{Display Name}"` case in `getBlockDisplayName()` 3. Add description case in `getBlockDescription()`: @@ -166,21 +173,23 @@ For **power blocks**, edit `src/main/kotlin/com/coderjoe/atlas/power/PowerBlockD For **fluid blocks**, edit `src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt` following the same pattern. +For **transport blocks**, edit `src/main/kotlin/com/coderjoe/atlas/transport/TransportBlockDialog.kt` following the same pattern. + ### Step 7: Update Tests -1. **Update `TestHelper.initPowerFactory()`** (or `initFluidFactory()`) in `src/test/kotlin/com/coderjoe/atlas/TestHelper.kt` — add the new descriptor to the registration list. +1. **Update `TestHelper.initPowerFactory()`** (or `initFluidFactory()` / `initTransportFactory()`) in `src/test/kotlin/com/coderjoe/atlas/TestHelper.kt` — add the new descriptor to the registration list. 2. **Update block count assertions** in: - - `src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt` — update the count in `power system initializes with N block types` (or fluid equivalent) - - `src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt` (or fluid equivalent) — update the count and comment + - `src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt` — update the count in the relevant `{system} system initializes with N block types` test + - `src/test/kotlin/com/coderjoe/atlas/power/PowerBlockInitializerTest.kt` (or fluid/transport equivalent) — update the count and comment - The count = total number of IDs across all `allRegistrableIds` lists. Add the number of new variant IDs to the existing count. + The count = total number of registered IDs (1 base + additionalBlockIds per block). Add the number of new IDs to the existing count. 3. **Create block test file** at `src/test/kotlin/com/coderjoe/atlas/{system}/{ClassName}Test.kt` with tests for: - Key properties (maxStorage, canReceivePower, etc.) - Visual state transitions - - Core behavior (powerUpdate/fluidUpdate logic) - - Descriptor properties (baseBlockId, displayName, allRegistrableIds count) + - Core behavior (powerUpdate/fluidUpdate/transportUpdate logic) + - Descriptor properties (baseBlockId, displayName) - Edge cases (full storage, no adjacent blocks, wrong fluid type, etc.) ### Step 8: Generate Placeholder Textures @@ -200,9 +209,11 @@ Name textures following the convention: `{block_id}_{face}.png`, `{block_id}_{fa Before finishing, verify: - [ ] Block class created with correct descriptor -- [ ] All variant IDs listed in `allRegistrableIds` +- [ ] `additionalBlockIds` includes all variant IDs beyond the base +- [ ] Uses `ADJACENT_FACES` constant (not hardcoded face lists) when iterating faces +- [ ] Fluid transfers use remove-first-then-store pattern - [ ] CraftEngine configuration YAML created with all variants -- [ ] Recipe added in the configuration file +- [ ] Recipe added in the configuration file (with unique ingredients) - [ ] Descriptor registered in Atlas.kt - [ ] Dialog cases added - [ ] TestHelper updated with new descriptor diff --git a/src/main/kotlin/com/coderjoe/atlas/Atlas.kt b/src/main/kotlin/com/coderjoe/atlas/Atlas.kt index a3ca166..e1a31ee 100644 --- a/src/main/kotlin/com/coderjoe/atlas/Atlas.kt +++ b/src/main/kotlin/com/coderjoe/atlas/Atlas.kt @@ -12,6 +12,7 @@ import com.coderjoe.atlas.fluid.block.FluidContainer import com.coderjoe.atlas.fluid.block.FluidMerger import com.coderjoe.atlas.fluid.block.FluidPipe import com.coderjoe.atlas.fluid.block.FluidPump +import com.coderjoe.atlas.fluid.block.FluidSplitter import com.coderjoe.atlas.guide.GuideBook import com.coderjoe.atlas.guide.GuideBookListener import com.coderjoe.atlas.power.PowerBlock @@ -218,6 +219,7 @@ class Atlas : JavaPlugin() { FluidPipe.descriptor, FluidContainer.descriptor, FluidMerger.descriptor, + FluidSplitter.descriptor, ).associateBy { it.baseBlockId } } } diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt index 718446e..48bfcd8 100644 --- a/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/FluidBlockDialog.kt @@ -7,6 +7,7 @@ import com.coderjoe.atlas.fluid.block.FluidContainer import com.coderjoe.atlas.fluid.block.FluidMerger import com.coderjoe.atlas.fluid.block.FluidPipe import com.coderjoe.atlas.fluid.block.FluidPump +import com.coderjoe.atlas.fluid.block.FluidSplitter import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextDecoration @@ -49,6 +50,7 @@ object FluidBlockDialog { is FluidPipe -> "Fluid Pipe (${fluidBlock.facing.displayName()})" is FluidContainer -> "Fluid Container (${fluidBlock.facing.displayName()})" is FluidMerger -> "Fluid Merger (${fluidBlock.facing.displayName()})" + is FluidSplitter -> "Fluid Splitter (${fluidBlock.facing.displayName()})" else -> "Fluid Block" } @@ -95,6 +97,9 @@ object FluidBlockDialog { is FluidMerger -> Component.text("Merger - merges fluid from all sides, outputs in facing direction") .color(NamedTextColor.GRAY) + is FluidSplitter -> + Component.text("Splitter - distributes fluid to all adjacent faces") + .color(NamedTextColor.GRAY) else -> Component.text("Fluid block") .color(NamedTextColor.GRAY) diff --git a/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidSplitter.kt b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidSplitter.kt new file mode 100644 index 0000000..bcc7802 --- /dev/null +++ b/src/main/kotlin/com/coderjoe/atlas/fluid/block/FluidSplitter.kt @@ -0,0 +1,82 @@ +package com.coderjoe.atlas.fluid.block + +import com.coderjoe.atlas.atlasInfo +import com.coderjoe.atlas.core.BlockDescriptor +import com.coderjoe.atlas.core.CraftEngineHelper +import com.coderjoe.atlas.core.PlacementType +import com.coderjoe.atlas.fluid.FluidBlock +import com.coderjoe.atlas.fluid.FluidBlockRegistry +import com.coderjoe.atlas.fluid.FluidType +import org.bukkit.Location +import org.bukkit.block.BlockFace + +class FluidSplitter(location: Location, override val facing: BlockFace) : FluidBlock(location) { + override val updateIntervalTicks: Long = 20L + + companion object { + const val BLOCK_ID = "atlas:fluid_splitter" + + val descriptor = + BlockDescriptor( + baseBlockId = BLOCK_ID, + displayName = "Fluid Splitter", + description = "Splitter - distributes fluid to all adjacent faces", + placementType = PlacementType.DIRECTIONAL, + constructor = { loc, facing -> FluidSplitter(loc, facing) }, + ) + } + + override val baseBlockId: String = BLOCK_ID + + override fun getVisualStateBlockId(): String = BLOCK_ID + + private fun updateFluidState() { + val fluidValue = + when (storedFluid) { + FluidType.WATER -> "water" + FluidType.LAVA -> "lava" + FluidType.NONE -> "none" + } + CraftEngineHelper.setStringProperty(location, "fluid", fluidValue) + } + + override fun fluidUpdate() { + val registry = FluidBlockRegistry.instance ?: return + + if (!hasFluid()) { + val source = registry.getAdjacentFluidBlock(location, facing.oppositeFace) + + if (source != null && source.canProvideFluid(facing)) { + val fluid = source.removeFluid() + storeFluid(fluid) + plugin.logger.atlasInfo( + "FluidSplitter at ${location.blockX},${location.blockY},${location.blockZ} " + + "pulled ${fluid.name} from ${source::class.simpleName}", + ) + } + } + + if (hasFluid()) { + val outputFaces = ADJACENT_FACES.filter { it != facing.oppositeFace } + + for (face in outputFaces) { + if (!hasFluid()) break + val target = registry.getAdjacentFluidBlock(location, face) ?: continue + if (!target.hasFluid()) { + val fluid = removeFluid() + if (target.storeFluid(fluid)) { + plugin.logger.atlasInfo( + "FluidSplitter at ${location.blockX},${location.blockY},${location.blockZ} " + + "pushed ${fluid.name} to ${target::class.simpleName} at ${face.name}", + ) + } else { + storeFluid(fluid) + } + break + } + } + } + + updateFluidState() + } +} diff --git a/src/main/resources/atlas/configuration/fluid_splitter.yml b/src/main/resources/atlas/configuration/fluid_splitter.yml new file mode 100644 index 0000000..a39910e --- /dev/null +++ b/src/main/resources/atlas/configuration/fluid_splitter.yml @@ -0,0 +1,247 @@ +items: + atlas:fluid_splitter: + material: paper + data: + item-name: "Fluid Splitter" + model: minecraft:block/custom/fluid_splitter + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + hardness: 2.0 + resistance: 2.0 + is-suffocating: true + is-redstone-conductor: false + push-reaction: push_only + tags: ["minecraft:mineable/pickaxe"] + sounds: + break: minecraft:block.metal.break + step: minecraft:block.metal.step + place: minecraft:block.metal.place + hit: minecraft:block.metal.hit + fall: minecraft:block.metal.fall + states: + properties: + facing: + type: direction + default: north + fluid: + type: string + values: [none, water, lava] + default: none + appearances: + north: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter + generation: + parent: minecraft:block/cube + textures: + north: minecraft:block/custom/fluid_splitter_front + south: minecraft:block/custom/fluid_splitter_back + east: minecraft:block/custom/fluid_splitter_side + west: minecraft:block/custom/fluid_splitter_side + up: minecraft:block/custom/fluid_splitter_top + down: minecraft:block/custom/fluid_splitter_bottom + south: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter + y: 180 + east: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter + y: 90 + west: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter + y: 270 + up: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_up + generation: + parent: minecraft:block/cube + textures: + north: minecraft:block/custom/fluid_splitter_side + south: minecraft:block/custom/fluid_splitter_side + east: minecraft:block/custom/fluid_splitter_side + west: minecraft:block/custom/fluid_splitter_side + up: minecraft:block/custom/fluid_splitter_back + down: minecraft:block/custom/fluid_splitter_front + down: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_down + generation: + parent: minecraft:block/cube + textures: + north: minecraft:block/custom/fluid_splitter_side + south: minecraft:block/custom/fluid_splitter_side + east: minecraft:block/custom/fluid_splitter_side + west: minecraft:block/custom/fluid_splitter_side + up: minecraft:block/custom/fluid_splitter_front + down: minecraft:block/custom/fluid_splitter_back + north_water: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_water + generation: + parent: minecraft:block/cube + textures: + north: minecraft:block/custom/fluid_splitter_front_water + south: minecraft:block/custom/fluid_splitter_back_water + east: minecraft:block/custom/fluid_splitter_side_water + west: minecraft:block/custom/fluid_splitter_side_water + up: minecraft:block/custom/fluid_splitter_top_water + down: minecraft:block/custom/fluid_splitter_bottom_water + south_water: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_water + y: 180 + east_water: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_water + y: 90 + west_water: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_water + y: 270 + up_water: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_up_water + generation: + parent: minecraft:block/cube + textures: + north: minecraft:block/custom/fluid_splitter_side_water + south: minecraft:block/custom/fluid_splitter_side_water + east: minecraft:block/custom/fluid_splitter_side_water + west: minecraft:block/custom/fluid_splitter_side_water + up: minecraft:block/custom/fluid_splitter_back_water + down: minecraft:block/custom/fluid_splitter_front_water + down_water: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_down_water + generation: + parent: minecraft:block/cube + textures: + north: minecraft:block/custom/fluid_splitter_side_water + south: minecraft:block/custom/fluid_splitter_side_water + east: minecraft:block/custom/fluid_splitter_side_water + west: minecraft:block/custom/fluid_splitter_side_water + up: minecraft:block/custom/fluid_splitter_front_water + down: minecraft:block/custom/fluid_splitter_back_water + north_lava: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_lava + generation: + parent: minecraft:block/cube + textures: + north: minecraft:block/custom/fluid_splitter_front_lava + south: minecraft:block/custom/fluid_splitter_back_lava + east: minecraft:block/custom/fluid_splitter_side_lava + west: minecraft:block/custom/fluid_splitter_side_lava + up: minecraft:block/custom/fluid_splitter_top_lava + down: minecraft:block/custom/fluid_splitter_bottom_lava + south_lava: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_lava + y: 180 + east_lava: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_lava + y: 90 + west_lava: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_lava + y: 270 + up_lava: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_up_lava + generation: + parent: minecraft:block/cube + textures: + north: minecraft:block/custom/fluid_splitter_side_lava + south: minecraft:block/custom/fluid_splitter_side_lava + east: minecraft:block/custom/fluid_splitter_side_lava + west: minecraft:block/custom/fluid_splitter_side_lava + up: minecraft:block/custom/fluid_splitter_back_lava + down: minecraft:block/custom/fluid_splitter_front_lava + down_lava: + auto-state: solid + model: + path: minecraft:block/custom/fluid_splitter_down_lava + generation: + parent: minecraft:block/cube + textures: + north: minecraft:block/custom/fluid_splitter_side_lava + south: minecraft:block/custom/fluid_splitter_side_lava + east: minecraft:block/custom/fluid_splitter_side_lava + west: minecraft:block/custom/fluid_splitter_side_lava + up: minecraft:block/custom/fluid_splitter_front_lava + down: minecraft:block/custom/fluid_splitter_back_lava + variants: + facing=north,fluid=none: + appearance: north + facing=south,fluid=none: + appearance: south + facing=east,fluid=none: + appearance: east + facing=west,fluid=none: + appearance: west + facing=up,fluid=none: + appearance: up + facing=down,fluid=none: + appearance: down + facing=north,fluid=water: + appearance: north_water + facing=south,fluid=water: + appearance: south_water + facing=east,fluid=water: + appearance: east_water + facing=west,fluid=water: + appearance: west_water + facing=up,fluid=water: + appearance: up_water + facing=down,fluid=water: + appearance: down_water + facing=north,fluid=lava: + appearance: north_lava + facing=south,fluid=lava: + appearance: south_lava + facing=east,fluid=lava: + appearance: east_lava + facing=west,fluid=lava: + appearance: west_lava + facing=up,fluid=lava: + appearance: up_lava + facing=down,fluid=lava: + appearance: down_lava + +recipes: + atlas:fluid_splitter: + type: shapeless + category: misc + unlock-on-ingredient-obtained: true + ingredients: + - minecraft:iron_ingot + - minecraft:iron_ingot + - minecraft:iron_ingot + - minecraft:iron_ingot + result: + id: atlas:fluid_splitter + count: 1 diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_back.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_back.png new file mode 100644 index 0000000..24b6760 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_back.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_back_lava.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_back_lava.png new file mode 100644 index 0000000..0359e89 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_back_lava.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_back_water.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_back_water.png new file mode 100644 index 0000000..3b6a648 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_back_water.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_bottom.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_bottom.png new file mode 100644 index 0000000..f63b041 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_bottom.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_bottom_lava.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_bottom_lava.png new file mode 100644 index 0000000..864e526 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_bottom_lava.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_bottom_water.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_bottom_water.png new file mode 100644 index 0000000..18bea58 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_bottom_water.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_front.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_front.png new file mode 100644 index 0000000..9a206dd Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_front.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_front_lava.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_front_lava.png new file mode 100644 index 0000000..7aaf191 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_front_lava.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_front_water.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_front_water.png new file mode 100644 index 0000000..b1bf5dc Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_front_water.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_side.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_side.png new file mode 100644 index 0000000..53551a9 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_side.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_side_lava.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_side_lava.png new file mode 100644 index 0000000..0bdd9f7 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_side_lava.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_side_water.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_side_water.png new file mode 100644 index 0000000..bcb72b9 Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_side_water.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_top.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_top.png new file mode 100644 index 0000000..82a27fb Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_top.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_top_lava.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_top_lava.png new file mode 100644 index 0000000..471ddff Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_top_lava.png differ diff --git a/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_top_water.png b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_top_water.png new file mode 100644 index 0000000..5b75b2f Binary files /dev/null and b/src/main/resources/atlas/resourcepack/assets/minecraft/textures/block/custom/fluid_splitter_top_water.png differ diff --git a/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt b/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt index 09246c3..8b3b2c4 100644 --- a/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/AtlasPluginTest.kt @@ -38,9 +38,9 @@ class AtlasPluginTest { } @Test - fun `fluid system initializes with 6 block types`() { + fun `fluid system initializes with 7 block types`() { TestHelper.initFluidFactory() - assertEquals(6, FluidBlockFactory.getRegisteredBlockIds().size) + assertEquals(7, FluidBlockFactory.getRegisteredBlockIds().size) } @Test diff --git a/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt b/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt index 2e0efd3..2fc7136 100644 --- a/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt +++ b/src/test/kotlin/com/coderjoe/atlas/TestHelper.kt @@ -9,6 +9,7 @@ import com.coderjoe.atlas.fluid.block.FluidContainer import com.coderjoe.atlas.fluid.block.FluidMerger import com.coderjoe.atlas.fluid.block.FluidPipe import com.coderjoe.atlas.fluid.block.FluidPump +import com.coderjoe.atlas.fluid.block.FluidSplitter import com.coderjoe.atlas.power.PowerBlock import com.coderjoe.atlas.power.PowerBlockFactory import com.coderjoe.atlas.power.PowerBlockRegistry @@ -212,6 +213,7 @@ object TestHelper { FluidPipe.descriptor, FluidContainer.descriptor, FluidMerger.descriptor, + FluidSplitter.descriptor, ), ) } diff --git a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt index 8bae9fe..f52561c 100644 --- a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt +++ b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidBlockInitializerTest.kt @@ -30,8 +30,9 @@ class FluidBlockInitializerTest { // FluidPipe: 1 (base only) // FluidContainer: 1 (base only) // FluidMerger: 1 (base only) - // Total: 6 - assertEquals(6, ids.size) + // FluidSplitter: 1 (base only) + // Total: 7 + assertEquals(7, ids.size) } @Test diff --git a/src/test/kotlin/com/coderjoe/atlas/fluid/FluidSplitterTest.kt b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidSplitterTest.kt new file mode 100644 index 0000000..5b4f2d0 --- /dev/null +++ b/src/test/kotlin/com/coderjoe/atlas/fluid/FluidSplitterTest.kt @@ -0,0 +1,175 @@ +package com.coderjoe.atlas.fluid + +import com.coderjoe.atlas.TestHelper +import com.coderjoe.atlas.TestHelper.callFluidUpdate +import com.coderjoe.atlas.fluid.block.FluidPipe +import com.coderjoe.atlas.fluid.block.FluidSplitter +import org.bukkit.block.BlockFace +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class FluidSplitterTest { + private lateinit var registry: FluidBlockRegistry + + @BeforeEach + fun setup() { + TestHelper.setup() + registry = FluidBlockRegistry(TestHelper.mockPlugin) + } + + @AfterEach + fun teardown() { + TestHelper.teardown() + } + + @Test + fun `visual state always returns BLOCK_ID`() { + val splitter = FluidSplitter(TestHelper.createLocation(), BlockFace.NORTH) + assertEquals("atlas:fluid_splitter", splitter.getVisualStateBlockId()) + } + + @Test + fun `visual state returns BLOCK_ID when holding water`() { + val splitter = FluidSplitter(TestHelper.createLocation(), BlockFace.NORTH) + splitter.storeFluid(FluidType.WATER) + assertEquals("atlas:fluid_splitter", splitter.getVisualStateBlockId()) + } + + @Test + fun `visual state returns BLOCK_ID when holding lava`() { + val splitter = FluidSplitter(TestHelper.createLocation(), BlockFace.NORTH) + splitter.storeFluid(FluidType.LAVA) + assertEquals("atlas:fluid_splitter", splitter.getVisualStateBlockId()) + } + + @Test + fun `pulls fluid from opposite of facing direction`() { + val splitterLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val splitter = FluidSplitter(splitterLoc, BlockFace.NORTH) + TestHelper.addToRegistry(registry, splitter, "atlas:fluid_splitter") + + // Pipe to the south (opposite of facing = input) + val pipeLoc = TestHelper.createLocation(0.0, 64.0, 1.0) + val pipe = FluidPipe(pipeLoc, BlockFace.NORTH) + pipe.storeFluid(FluidType.WATER) + TestHelper.addToRegistry(registry, pipe, "atlas:fluid_pipe") + + splitter.callFluidUpdate() + + assertTrue(splitter.hasFluid()) + assertEquals(FluidType.WATER, splitter.storedFluid) + assertFalse(pipe.hasFluid()) + } + + @Test + fun `does not pull fluid from facing direction`() { + val splitterLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val splitter = FluidSplitter(splitterLoc, BlockFace.NORTH) + TestHelper.addToRegistry(registry, splitter, "atlas:fluid_splitter") + + // Pipe to the north (facing direction, not input) + val pipeLoc = TestHelper.createLocation(0.0, 64.0, -1.0) + val pipe = FluidPipe(pipeLoc, BlockFace.SOUTH) + pipe.storeFluid(FluidType.WATER) + TestHelper.addToRegistry(registry, pipe, "atlas:fluid_pipe") + + splitter.callFluidUpdate() + + assertFalse(splitter.hasFluid()) + assertTrue(pipe.hasFluid()) + } + + @Test + fun `pushes fluid to output face neighbor`() { + val splitterLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val splitter = FluidSplitter(splitterLoc, BlockFace.NORTH) + splitter.storeFluid(FluidType.WATER) + TestHelper.addToRegistry(registry, splitter, "atlas:fluid_splitter") + + // Pipe to the east (output face) + val pipeLoc = TestHelper.createLocation(1.0, 64.0, 0.0) + val pipe = FluidPipe(pipeLoc, BlockFace.EAST) + TestHelper.addToRegistry(registry, pipe, "atlas:fluid_pipe") + + splitter.callFluidUpdate() + + assertFalse(splitter.hasFluid()) + assertTrue(pipe.hasFluid()) + assertEquals(FluidType.WATER, pipe.storedFluid) + } + + @Test + fun `does not push fluid to input face`() { + val splitterLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val splitter = FluidSplitter(splitterLoc, BlockFace.NORTH) + splitter.storeFluid(FluidType.WATER) + TestHelper.addToRegistry(registry, splitter, "atlas:fluid_splitter") + + // Pipe to the south (input face = opposite of facing) + val pipeLoc = TestHelper.createLocation(0.0, 64.0, 1.0) + val pipe = FluidPipe(pipeLoc, BlockFace.NORTH) + TestHelper.addToRegistry(registry, pipe, "atlas:fluid_pipe") + + splitter.callFluidUpdate() + + // Splitter should still have fluid since only output face neighbor is input + assertTrue(splitter.hasFluid()) + assertFalse(pipe.hasFluid()) + } + + @Test + fun `does not push to neighbor that already has fluid`() { + val splitterLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val splitter = FluidSplitter(splitterLoc, BlockFace.NORTH) + splitter.storeFluid(FluidType.WATER) + TestHelper.addToRegistry(registry, splitter, "atlas:fluid_splitter") + + // Pipe to the east already has fluid + val pipeLoc = TestHelper.createLocation(1.0, 64.0, 0.0) + val pipe = FluidPipe(pipeLoc, BlockFace.EAST) + pipe.storeFluid(FluidType.LAVA) + TestHelper.addToRegistry(registry, pipe, "atlas:fluid_pipe") + + splitter.callFluidUpdate() + + assertTrue(splitter.hasFluid()) + assertEquals(FluidType.WATER, splitter.storedFluid) + assertEquals(FluidType.LAVA, pipe.storedFluid) + } + + @Test + fun `pulls and pushes in same update`() { + val splitterLoc = TestHelper.createLocation(0.0, 64.0, 0.0) + val splitter = FluidSplitter(splitterLoc, BlockFace.NORTH) + TestHelper.addToRegistry(registry, splitter, "atlas:fluid_splitter") + + // Source pipe to the south (input face) + val sourceLoc = TestHelper.createLocation(0.0, 64.0, 1.0) + val source = FluidPipe(sourceLoc, BlockFace.NORTH) + source.storeFluid(FluidType.LAVA) + TestHelper.addToRegistry(registry, source, "atlas:fluid_pipe") + + // Target pipe to the east (output face) + val targetLoc = TestHelper.createLocation(1.0, 64.0, 0.0) + val target = FluidPipe(targetLoc, BlockFace.EAST) + TestHelper.addToRegistry(registry, target, "atlas:fluid_pipe") + + splitter.callFluidUpdate() + + assertFalse(source.hasFluid()) + assertFalse(splitter.hasFluid()) + assertTrue(target.hasFluid()) + assertEquals(FluidType.LAVA, target.storedFluid) + } + + @Test + fun `descriptor has correct properties`() { + val desc = FluidSplitter.descriptor + assertEquals("atlas:fluid_splitter", desc.baseBlockId) + assertEquals("Fluid Splitter", desc.displayName) + } +}