feat: fluid evaporator implemented

This commit is contained in:
Andrew 2026-05-18 00:46:44 +07:00
parent 53d8e8bc91
commit 1b3348a809
36 changed files with 1276 additions and 55 deletions

View file

@ -1,2 +1,3 @@
// 1.21.1 2026-05-17T14:12:39.0216262 Item Models: effmeks // 1.21.1 2026-05-18T00:05:01.6787423 Item Models: effmeks
7cc789e853aec0d6b20612dcee9da3445a8bdfc4 assets/effmeks/models/item/fluid_evaporator.json
8476e8e89257ef1bfcd9f568099f069a86522360 assets/effmeks/models/item/fluid_filter.json 8476e8e89257ef1bfcd9f568099f069a86522360 assets/effmeks/models/item/fluid_filter.json

View file

@ -1,3 +1,5 @@
// 1.21.1 2026-05-17T22:53:43.7145767 Recipes // 1.21.1 2026-05-18T00:37:09.2581698 Recipes
c9ee48071b50617af2ec250deadd7bff323b8d78 data/effmeks/advancement/recipes/tools/rec_fct1.json e0d19d76a4c7e34a6dae0a00aa1e2ea656b557d1 data/effmeks/advancement/recipes/tools/rec_fct1.json
31470570446dd68bc3175b4e396c83efc5f3225f data/effmeks/advancement/recipes/tools/rec_fct2.json
1ad62af4d88b8110dc39d966c289b1ff3d585397 data/effmeks/recipe/rec_fct1.json 1ad62af4d88b8110dc39d966c289b1ff3d585397 data/effmeks/recipe/rec_fct1.json
66b10868ac96c9ba142eccd738045590635fdc7a data/effmeks/recipe/rec_fct2.json

View file

@ -1,2 +1,2 @@
// 1.21.1 2026-05-17T22:37:23.9653189 Languages: en_us for mod: effmeks // 1.21.1 2026-05-18T00:05:01.6767446 Languages: en_us for mod: effmeks
9b4d5b6a375bca8cb2f716c84981d5764fa820fc assets/effmeks/lang/en_us.json e7b5f5a11b2b8e20d51ab74953d44e99c27f2157 assets/effmeks/lang/en_us.json

View file

@ -1,2 +1,2 @@
// 1.21.1 2026-05-17T22:37:24.0183182 Languages: ru_ru for mod: effmeks // 1.21.1 2026-05-18T00:05:01.6817443 Languages: ru_ru for mod: effmeks
b430721f5d5170ca82c0af7ea851ab1c7c9bc91d assets/effmeks/lang/ru_ru.json 17e71ed1d366cfe8aef316788e612b5b5d21877d assets/effmeks/lang/ru_ru.json

View file

@ -1,4 +1,5 @@
{ {
"block.effmeks.fluid_evaporator": "Evaporator",
"block.effmeks.fluid_filter": "Fluid filter", "block.effmeks.fluid_filter": "Fluid filter",
"tooltip.fluid.amount.format": "%s / %s mB of ", "tooltip.fluid.amount.format": "%s / %s mB of ",
"tooltip.fluid.empty": "Empty", "tooltip.fluid.empty": "Empty",

View file

@ -1,4 +1,5 @@
{ {
"block.effmeks.fluid_evaporator": "Испаритель",
"block.effmeks.fluid_filter": "Жидкостный фильтр", "block.effmeks.fluid_filter": "Жидкостный фильтр",
"tooltip.fluid.amount.format": "%s / %s мБ ", "tooltip.fluid.amount.format": "%s / %s мБ ",
"tooltip.fluid.empty": "Пусто", "tooltip.fluid.empty": "Пусто",

View file

@ -0,0 +1,3 @@
{
"parent": "effmeks:block/fluid_evaporator"
}

View file

@ -1,7 +1,7 @@
{ {
"parent": "minecraft:recipes/root", "parent": "minecraft:recipes/root",
"criteria": { "criteria": {
"has_iron_block": { "has_electric_pump": {
"conditions": { "conditions": {
"items": [ "items": [
{ {
@ -21,7 +21,7 @@
"requirements": [ "requirements": [
[ [
"has_the_recipe", "has_the_recipe",
"has_iron_block" "has_electric_pump"
] ]
], ],
"rewards": { "rewards": {

View file

@ -0,0 +1,32 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_tep_controller": {
"conditions": {
"items": [
{
"items": "mekanism:thermal_evaporation_controller"
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "effmeks:rec_fct2"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_the_recipe",
"has_tep_controller"
]
],
"rewards": {
"recipes": [
"effmeks:rec_fct2"
]
}
}

View file

@ -0,0 +1,27 @@
{
"type": "minecraft:crafting_shaped",
"category": "equipment",
"key": {
"C": {
"item": "mekanism:ultimate_control_circuit"
},
"E": {
"item": "mekanism:thermal_evaporation_controller"
},
"T": {
"item": "mekanism:ultimate_thermodynamic_conductor"
},
"V": {
"item": "mekanism:thermal_evaporation_valve"
}
},
"pattern": [
"CVC",
"TET",
"CVC"
],
"result": {
"count": 1,
"id": "effmeks:fluid_evaporator"
}
}

View file

@ -1,14 +1,11 @@
package xyz.nuark.mcmod.effectivemekanisms package xyz.nuark.mcmod.effectivemekanisms
import xyz.nuark.mcmod.effectivemekanisms.block.ModBlocks import xyz.nuark.mcmod.effectivemekanisms.block.ModBlocks
import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.CreativeModeTabs import net.minecraft.world.item.CreativeModeTabs
import net.neoforged.bus.api.SubscribeEvent import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.fml.common.EventBusSubscriber import net.neoforged.fml.common.EventBusSubscriber
import net.neoforged.fml.common.Mod import net.neoforged.fml.common.Mod
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent
import net.neoforged.fml.event.lifecycle.FMLDedicatedServerSetupEvent
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent
import net.neoforged.neoforge.data.event.GatherDataEvent import net.neoforged.neoforge.data.event.GatherDataEvent
import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent
@ -17,7 +14,6 @@ import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
import thedarkcolour.kotlinforforge.neoforge.forge.MOD_BUS import thedarkcolour.kotlinforforge.neoforge.forge.MOD_BUS
import thedarkcolour.kotlinforforge.neoforge.forge.runForDist
import xyz.nuark.mcmod.effectivemekanisms.blockentity.ModBlockEntities import xyz.nuark.mcmod.effectivemekanisms.blockentity.ModBlockEntities
import xyz.nuark.mcmod.effectivemekanisms.datagen.ModItemModelProvider import xyz.nuark.mcmod.effectivemekanisms.datagen.ModItemModelProvider
import xyz.nuark.mcmod.effectivemekanisms.datagen.ModLanguageProviders import xyz.nuark.mcmod.effectivemekanisms.datagen.ModLanguageProviders
@ -26,6 +22,7 @@ import xyz.nuark.mcmod.effectivemekanisms.item.ModItems
import xyz.nuark.mcmod.effectivemekanisms.menu.ModMenuTypes import xyz.nuark.mcmod.effectivemekanisms.menu.ModMenuTypes
import xyz.nuark.mcmod.effectivemekanisms.network.FluidFilterOverclockPacket import xyz.nuark.mcmod.effectivemekanisms.network.FluidFilterOverclockPacket
import xyz.nuark.mcmod.effectivemekanisms.network.FluidFilterSyncPacket import xyz.nuark.mcmod.effectivemekanisms.network.FluidFilterSyncPacket
import xyz.nuark.mcmod.effectivemekanisms.screen.FluidEvaporatorScreen
import xyz.nuark.mcmod.effectivemekanisms.screen.FluidFilterScreen import xyz.nuark.mcmod.effectivemekanisms.screen.FluidFilterScreen
@Mod(EffMeks.ID) @Mod(EffMeks.ID)
@ -36,45 +33,24 @@ object EffMeks {
val LOGGER: Logger = LogManager.getLogger(ID) val LOGGER: Logger = LogManager.getLogger(ID)
init { init {
LOGGER.log(Level.INFO, "Hello world!")
ModBlocks.REGISTRY.register(MOD_BUS) ModBlocks.REGISTRY.register(MOD_BUS)
ModBlockEntities.REGISTRY.register(MOD_BUS) ModBlockEntities.REGISTRY.register(MOD_BUS)
ModItems.REGISTRY.register(MOD_BUS) ModItems.REGISTRY.register(MOD_BUS)
ModMenuTypes.register(MOD_BUS) ModMenuTypes.register(MOD_BUS)
val obj = runForDist(
clientTarget = {
MOD_BUS.addListener(::onClientSetup)
Minecraft.getInstance()
},
serverTarget = {
MOD_BUS.addListener(::onServerSetup)
"test"
})
println(obj)
}
private fun onClientSetup(event: FMLClientSetupEvent) {
LOGGER.log(Level.INFO, "Initializing client...")
}
private fun onServerSetup(event: FMLDedicatedServerSetupEvent) {
LOGGER.log(Level.INFO, "Server starting...")
} }
@SubscribeEvent @SubscribeEvent
fun buildContents(event: BuildCreativeModeTabContentsEvent) { fun buildContents(event: BuildCreativeModeTabContentsEvent) {
if (event.tabKey === CreativeModeTabs.FUNCTIONAL_BLOCKS) { if (event.tabKey === CreativeModeTabs.FUNCTIONAL_BLOCKS) {
event.accept(ModItems.FLUID_FILTER_BLOCK.get()) event.accept(ModItems.FLUID_FILTER_BLOCK.get())
event.accept(ModItems.FLUID_EVAPORATOR_BLOCK.get())
} }
} }
@SubscribeEvent @SubscribeEvent
fun registerScreens(event: RegisterMenuScreensEvent) { fun registerScreens(event: RegisterMenuScreensEvent) {
event.register(ModMenuTypes.FLUID_FILTER.get(), ::FluidFilterScreen) event.register(ModMenuTypes.FLUID_FILTER.get(), ::FluidFilterScreen)
event.register(ModMenuTypes.FLUID_EVAPORATOR.get(), ::FluidEvaporatorScreen)
} }
@SubscribeEvent @SubscribeEvent

View file

@ -0,0 +1,8 @@
package xyz.nuark.mcmod.effectivemekanisms.api.blockentity
import net.neoforged.neoforge.fluids.capability.templates.FluidTank
interface IFluidSyncableBlockEntity {
val inputTank: FluidTank
val outputTank: FluidTank
}

View file

@ -0,0 +1,9 @@
package xyz.nuark.mcmod.effectivemekanisms.api.blockentity
import net.neoforged.neoforge.energy.EnergyStorage
interface IOverclockableMachine {
val energyStorage: EnergyStorage
var overclockTier: Int
var ticksUntilOperation: Int
}

View file

@ -0,0 +1,7 @@
package xyz.nuark.mcmod.effectivemekanisms.api.menu
import xyz.nuark.mcmod.effectivemekanisms.blockentity.FluidFilterBlockEntity
interface IOverclockableMachineMenu {
fun setOverclockTier(tier: Int)
}

View file

@ -0,0 +1,72 @@
package xyz.nuark.mcmod.effectivemekanisms.block
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import xyz.nuark.mcmod.effectivemekanisms.blockentity.FluidEvaporatorBlockEntity
import xyz.nuark.mcmod.effectivemekanisms.blockentity.ModBlockEntities
import java.util.stream.Stream
class FluidEvaporatorBlock(properties: Properties) : BaseEntityBlock(properties) {
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity =
FluidEvaporatorBlockEntity(pos, state)
override fun codec(): MapCodec<out BaseEntityBlock?> = simpleCodec(::FluidEvaporatorBlock)
override fun getRenderShape(state: BlockState): RenderShape = RenderShape.MODEL
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
if (level.isClientSide) return InteractionResult.SUCCESS
val be = level.getBlockEntity(pos) as? FluidEvaporatorBlockEntity
?: return InteractionResult.PASS
if (player is ServerPlayer) {
player.openMenu(be) { buf -> buf.writeBlockPos(pos) }
}
return InteractionResult.CONSUME
}
override fun <T : BlockEntity> getTicker(
level: Level, state: BlockState, type: BlockEntityType<T>
): BlockEntityTicker<T>? =
createTickerHelper(type, ModBlockEntities.FLUID_EVAPORATOR.get(), FluidEvaporatorBlockEntity::tick)
override fun getShape(
state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext
): VoxelShape = SHAPE
override fun getCollisionShape(
state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext
): VoxelShape = SHAPE
companion object {
val SHAPE: VoxelShape = Stream.of(
box(2.0, 2.0, 2.0, 14.0, 14.0, 14.0),
box(4.0, 14.0, 4.0, 12.0, 16.0, 12.0),
box(3.0, 0.0, 3.0, 13.0, 2.0, 13.0),
box(14.0, 5.0, 5.0, 16.0, 11.0, 11.0),
box(5.0, 5.0, 0.0, 11.0, 11.0, 2.0),
box(0.0, 5.0, 5.0, 2.0, 11.0, 11.0),
box(5.0, 5.0, 14.0, 11.0, 11.0, 16.0)
).reduce { v1, v2 -> Shapes.or(v1, v2) }.get()
}
}

View file

@ -14,4 +14,7 @@ object ModBlocks {
val FLUID_FILTER: DeferredBlock<FluidFilterBlock> = REGISTRY.register("fluid_filter") { -> val FLUID_FILTER: DeferredBlock<FluidFilterBlock> = REGISTRY.register("fluid_filter") { ->
FluidFilterBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK)) FluidFilterBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK))
} }
val FLUID_EVAPORATOR: DeferredBlock<FluidEvaporatorBlock> = REGISTRY.register("fluid_evaporator") { ->
FluidEvaporatorBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.IRON_BLOCK))
}
} }

View file

@ -0,0 +1,162 @@
package xyz.nuark.mcmod.effectivemekanisms.blockentity
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.ContainerData
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.neoforged.neoforge.energy.EnergyStorage
import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.fluids.capability.IFluidHandler
import net.neoforged.neoforge.fluids.capability.templates.FluidTank
import xyz.nuark.mcmod.effectivemekanisms.api.blockentity.IFluidSyncableBlockEntity
import xyz.nuark.mcmod.effectivemekanisms.api.blockentity.IOverclockableMachine
import xyz.nuark.mcmod.effectivemekanisms.menu.FluidEvaporatorMenu
import xyz.nuark.mcmod.effectivemekanisms.utils.EvaporationFluidConversion
import kotlin.math.pow
import kotlin.math.roundToInt
class FluidEvaporatorBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(ModBlockEntities.FLUID_EVAPORATOR.get(), pos, state), MenuProvider, IFluidSyncableBlockEntity,
IOverclockableMachine {
override var ticksUntilOperation = TICKS_PER_OPERATION
override var overclockTier: Int = 0
set(value) {
field = value.coerceIn(0, MAX_OVERCLOCK_TIER)
setChanged()
}
val overclockMultiplier: Int
get() = 1 shl overclockTier
val energyCostMultiplier: Double
get() = 2.5.pow(overclockTier.toDouble())
val energyUsageThisTick: Int
get() = (ENERGY_USAGE_PER_TICK * energyCostMultiplier).roundToInt()
override val energyStorage = object : EnergyStorage(ENERGY_CAPACITY) {
override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int {
val received = super.receiveEnergy(maxReceive, simulate)
if (received > 0 && !simulate) setChanged()
return received
}
}
override val inputTank = object : FluidTank(FLUID_CAPACITY, { fluidStack ->
EvaporationFluidConversion.evaporationConversions.any { fluidStack.`is`(it.from) }
}) {
override fun onContentsChanged() {
setChanged()
}
}
override val outputTank = object : FluidTank(FLUID_CAPACITY, { fluidStack ->
EvaporationFluidConversion.evaporationConversions.any { fluidStack.`is`(it.to) }
}) {
override fun onContentsChanged() {
setChanged()
}
}
private fun processConversion(): Boolean {
val conversion = canProcess(inputTank.fluid) ?: return false
energyStorage.extractEnergy(energyUsageThisTick, false)
inputTank.drain(conversion.consume, IFluidHandler.FluidAction.EXECUTE)
outputTank.fill(FluidStack(conversion.to, conversion.produce), IFluidHandler.FluidAction.EXECUTE)
setChanged()
return true
}
private fun canProcess(input: FluidStack): EvaporationFluidConversion.ConversionRate? {
if (energyStorage.energyStored < energyUsageThisTick) return null
val base = EvaporationFluidConversion.evaporationConversions
.firstOrNull { input.`is`(it.from) } ?: return null
val mult = overclockMultiplier
val conversion = EvaporationFluidConversion.ConversionRate(
base.from, base.to,
base.consume * TICKS_PER_OPERATION * mult,
base.produce * TICKS_PER_OPERATION * mult
)
if (inputTank.fluidAmount < conversion.consume) return null
val outputTarget = FluidStack(conversion.to, conversion.produce)
if (outputTank.fill(outputTarget, IFluidHandler.FluidAction.SIMULATE) == 0) return null
if (outputTank.space < conversion.produce) return null
return conversion
}
override fun getDisplayName(): Component =
Component.translatable("block.effmeks.fluid_evaporator")
override fun createMenu(containerId: Int, playerInventory: Inventory, player: Player): AbstractContainerMenu =
FluidEvaporatorMenu(containerId, playerInventory, this, buildContainerData())
private fun buildContainerData() = object : ContainerData {
override fun get(index: Int): Int = when (index) {
FluidEvaporatorMenu.SLOT_ENERGY -> energyStorage.energyStored
FluidEvaporatorMenu.SLOT_ENERGY_MAX -> energyStorage.maxEnergyStored
FluidEvaporatorMenu.SLOT_INPUT_FLUID -> inputTank.fluidAmount
FluidEvaporatorMenu.SLOT_OUTPUT_FLUID -> outputTank.fluidAmount
FluidEvaporatorMenu.SLOT_FLUID_CAP -> FLUID_CAPACITY
FluidEvaporatorMenu.SLOT_OVERCLOCK -> overclockTier
FluidEvaporatorMenu.SLOT_PROGRESS -> ticksUntilOperation
else -> 0
}
override fun set(index: Int, value: Int) {
// Client can only update overclock tier, so only that handled here
if (index == FluidEvaporatorMenu.SLOT_OVERCLOCK) overclockTier = value
}
override fun getCount() = FluidEvaporatorMenu.DATA_SLOT_COUNT
}
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
tag.put("Energy", energyStorage.serializeNBT(registries))
tag.put("InputTank", inputTank.writeToNBT(registries, CompoundTag()))
tag.put("OutputTank", outputTank.writeToNBT(registries, CompoundTag()))
tag.putInt("OverclockTier", overclockTier)
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
energyStorage.deserializeNBT(registries, tag.get("Energy")!!)
inputTank.readFromNBT(registries, tag.getCompound("InputTank"))
outputTank.readFromNBT(registries, tag.getCompound("OutputTank"))
overclockTier = tag.getInt("OverclockTier")
}
companion object {
const val ENERGY_CAPACITY = 100_000_000
const val FLUID_CAPACITY = 40_000_000
const val ENERGY_USAGE_PER_TICK = 2_000
const val TICKS_PER_OPERATION = 20
const val MAX_OVERCLOCK_TIER = 12
fun tick(level: Level, pos: BlockPos, state: BlockState, be: FluidEvaporatorBlockEntity) {
if (level.isClientSide) return
if (be.ticksUntilOperation > 0) {
be.ticksUntilOperation--; return
}
be.ticksUntilOperation = TICKS_PER_OPERATION
be.processConversion()
}
}
}

View file

@ -16,17 +16,20 @@ import net.neoforged.neoforge.energy.EnergyStorage
import net.neoforged.neoforge.fluids.FluidStack import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.fluids.capability.IFluidHandler import net.neoforged.neoforge.fluids.capability.IFluidHandler
import net.neoforged.neoforge.fluids.capability.templates.FluidTank import net.neoforged.neoforge.fluids.capability.templates.FluidTank
import xyz.nuark.mcmod.effectivemekanisms.api.blockentity.IFluidSyncableBlockEntity
import xyz.nuark.mcmod.effectivemekanisms.api.blockentity.IOverclockableMachine
import xyz.nuark.mcmod.effectivemekanisms.menu.FluidFilterMenu import xyz.nuark.mcmod.effectivemekanisms.menu.FluidFilterMenu
import xyz.nuark.mcmod.effectivemekanisms.utils.FilterFluidConversion import xyz.nuark.mcmod.effectivemekanisms.utils.FilterFluidConversion
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
class FluidFilterBlockEntity(pos: BlockPos, state: BlockState) : class FluidFilterBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(ModBlockEntities.FLUID_FILTER.get(), pos, state), MenuProvider { BlockEntity(ModBlockEntities.FLUID_FILTER.get(), pos, state), MenuProvider, IFluidSyncableBlockEntity,
IOverclockableMachine {
var ticksUntilOperation = TICKS_PER_OPERATION override var ticksUntilOperation = TICKS_PER_OPERATION
var overclockTier: Int = 0 override var overclockTier: Int = 0
set(value) { set(value) {
field = value.coerceIn(0, MAX_OVERCLOCK_TIER) field = value.coerceIn(0, MAX_OVERCLOCK_TIER)
setChanged() setChanged()
@ -41,7 +44,7 @@ class FluidFilterBlockEntity(pos: BlockPos, state: BlockState) :
val energyUsageThisTick: Int val energyUsageThisTick: Int
get() = (ENERGY_USAGE_PER_TICK * energyCostMultiplier).roundToInt() get() = (ENERGY_USAGE_PER_TICK * energyCostMultiplier).roundToInt()
val energyStorage = object : EnergyStorage(ENERGY_CAPACITY) { override val energyStorage = object : EnergyStorage(ENERGY_CAPACITY) {
override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int { override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int {
val received = super.receiveEnergy(maxReceive, simulate) val received = super.receiveEnergy(maxReceive, simulate)
if (received > 0 && !simulate) setChanged() if (received > 0 && !simulate) setChanged()
@ -49,7 +52,7 @@ class FluidFilterBlockEntity(pos: BlockPos, state: BlockState) :
} }
} }
val inputTank = object : FluidTank(FLUID_CAPACITY, { fluidStack -> override val inputTank = object : FluidTank(FLUID_CAPACITY, { fluidStack ->
FilterFluidConversion.filterConversions.any { fluidStack.`is`(it.from) } FilterFluidConversion.filterConversions.any { fluidStack.`is`(it.from) }
}) { }) {
override fun onContentsChanged() { override fun onContentsChanged() {
@ -57,7 +60,7 @@ class FluidFilterBlockEntity(pos: BlockPos, state: BlockState) :
} }
} }
val outputTank = object : FluidTank(FLUID_CAPACITY, { fluidStack -> override val outputTank = object : FluidTank(FLUID_CAPACITY, { fluidStack ->
FilterFluidConversion.filterConversions.any { fluidStack.`is`(it.to) } FilterFluidConversion.filterConversions.any { fluidStack.`is`(it.to) }
}) { }) {
override fun onContentsChanged() { override fun onContentsChanged() {
@ -98,7 +101,7 @@ class FluidFilterBlockEntity(pos: BlockPos, state: BlockState) :
} }
override fun getDisplayName(): Component = override fun getDisplayName(): Component =
Component.translatable("block.effectivemekanisms.fluid_filter") Component.translatable("block.effmeks.fluid_filter")
override fun createMenu(containerId: Int, playerInventory: Inventory, player: Player): AbstractContainerMenu = override fun createMenu(containerId: Int, playerInventory: Inventory, player: Player): AbstractContainerMenu =
FluidFilterMenu(containerId, playerInventory, this, buildContainerData()) FluidFilterMenu(containerId, playerInventory, this, buildContainerData())

View file

@ -15,4 +15,7 @@ object ModBlockEntities {
val FLUID_FILTER: DeferredHolder<BlockEntityType<*>, BlockEntityType<FluidFilterBlockEntity>> = REGISTRY.register("fluid_filter") { -> val FLUID_FILTER: DeferredHolder<BlockEntityType<*>, BlockEntityType<FluidFilterBlockEntity>> = REGISTRY.register("fluid_filter") { ->
BlockEntityType.Builder.of(::FluidFilterBlockEntity, ModBlocks.FLUID_FILTER.get()).build(null) BlockEntityType.Builder.of(::FluidFilterBlockEntity, ModBlocks.FLUID_FILTER.get()).build(null)
} }
val FLUID_EVAPORATOR: DeferredHolder<BlockEntityType<*>, BlockEntityType<FluidEvaporatorBlockEntity>> = REGISTRY.register("fluid_evaporator") { ->
BlockEntityType.Builder.of(::FluidEvaporatorBlockEntity, ModBlocks.FLUID_EVAPORATOR.get()).build(null)
}
} }

View file

@ -30,5 +30,25 @@ object ModCapabilities {
} }
} }
} }
ModBlockEntities.FLUID_EVAPORATOR.get().let {
event.registerBlockEntity(
Capabilities.EnergyStorage.BLOCK,
it
) { blockEntity, direction ->
blockEntity.energyStorage
}
event.registerBlockEntity(
Capabilities.FluidHandler.BLOCK,
it
) { blockEntity, direction ->
when (direction) {
Direction.DOWN -> blockEntity.outputTank
Direction.UP -> blockEntity.inputTank
else -> blockEntity.inputTank
}
}
}
} }
} }

View file

@ -9,5 +9,6 @@ import xyz.nuark.mcmod.effectivemekanisms.block.ModBlocks
class ModItemModelProvider(output: PackOutput, existingFileHelper: ExistingFileHelper) : ItemModelProvider(output, EffMeks.ID, existingFileHelper) { class ModItemModelProvider(output: PackOutput, existingFileHelper: ExistingFileHelper) : ItemModelProvider(output, EffMeks.ID, existingFileHelper) {
override fun registerModels() { override fun registerModels() {
simpleBlockItem(ModBlocks.FLUID_FILTER.get()) simpleBlockItem(ModBlocks.FLUID_FILTER.get())
simpleBlockItem(ModBlocks.FLUID_EVAPORATOR.get())
} }
} }

View file

@ -10,6 +10,7 @@ object ModLanguageProviders {
class ModRuRuLanguageProvider(output: PackOutput) : LanguageProvider(output, EffMeks.ID, "ru_ru") { class ModRuRuLanguageProvider(output: PackOutput) : LanguageProvider(output, EffMeks.ID, "ru_ru") {
override fun addTranslations() { override fun addTranslations() {
add(ModItems.FLUID_FILTER_BLOCK.get(), "Жидкостный фильтр") add(ModItems.FLUID_FILTER_BLOCK.get(), "Жидкостный фильтр")
add(ModItems.FLUID_EVAPORATOR_BLOCK.get(), "Испаритель")
add("tooltip.fluid.empty", "Пусто") add("tooltip.fluid.empty", "Пусто")
add("tooltip.fluid.amount.format", "%s / %s мБ ") add("tooltip.fluid.amount.format", "%s / %s мБ ")
add("tooltip.machine.overclock.tier", "Уровень разгона: %s") add("tooltip.machine.overclock.tier", "Уровень разгона: %s")
@ -19,6 +20,7 @@ object ModLanguageProviders {
class ModEnUsLanguageProvider(output: PackOutput) : LanguageProvider(output, EffMeks.ID, "en_us") { class ModEnUsLanguageProvider(output: PackOutput) : LanguageProvider(output, EffMeks.ID, "en_us") {
override fun addTranslations() { override fun addTranslations() {
add(ModItems.FLUID_FILTER_BLOCK.get(), "Fluid filter") add(ModItems.FLUID_FILTER_BLOCK.get(), "Fluid filter")
add(ModItems.FLUID_EVAPORATOR_BLOCK.get(), "Evaporator")
add("tooltip.fluid.empty", "Empty") add("tooltip.fluid.empty", "Empty")
add("tooltip.fluid.amount.format", "%s / %s mB of ") add("tooltip.fluid.amount.format", "%s / %s mB of ")
add("tooltip.machine.overclock.tier", "Overclock tier: %s") add("tooltip.machine.overclock.tier", "Overclock tier: %s")

View file

@ -22,7 +22,18 @@ class ModRecipeProvider(output: PackOutput, registries: CompletableFuture<Holder
.define('P', MekanismBlocks.ELECTRIC_PUMP) .define('P', MekanismBlocks.ELECTRIC_PUMP)
.define('F', MekanismItems.FILTER_UPGRADE) .define('F', MekanismItems.FILTER_UPGRADE)
.define('C', MekanismItems.ULTIMATE_CONTROL_CIRCUIT) .define('C', MekanismItems.ULTIMATE_CONTROL_CIRCUIT)
.unlockedBy("has_iron_block", has(MekanismBlocks.ELECTRIC_PUMP)) .unlockedBy("has_electric_pump", has(MekanismBlocks.ELECTRIC_PUMP))
.save(recipeOutput, EffMeks.resource("rec_fct1")) .save(recipeOutput, EffMeks.resource("rec_fct1"))
ShapedRecipeBuilder.shaped(RecipeCategory.TOOLS, ModItems.FLUID_EVAPORATOR_BLOCK.get())
.pattern("CVC")
.pattern("TET")
.pattern("CVC")
.define('E', MekanismBlocks.THERMAL_EVAPORATION_CONTROLLER)
.define('V', MekanismBlocks.THERMAL_EVAPORATION_VALVE)
.define('T', MekanismBlocks.ULTIMATE_THERMODYNAMIC_CONDUCTOR)
.define('C', MekanismItems.ULTIMATE_CONTROL_CIRCUIT)
.unlockedBy("has_tep_controller", has(MekanismBlocks.THERMAL_EVAPORATION_CONTROLLER))
.save(recipeOutput, EffMeks.resource("rec_fct2"))
} }
} }

View file

@ -10,4 +10,5 @@ object ModItems {
val REGISTRY = DeferredRegister.createItems(EffMeks.ID) val REGISTRY = DeferredRegister.createItems(EffMeks.ID)
val FLUID_FILTER_BLOCK: DeferredItem<BlockItem> = REGISTRY.registerSimpleBlockItem(ModBlocks.FLUID_FILTER) val FLUID_FILTER_BLOCK: DeferredItem<BlockItem> = REGISTRY.registerSimpleBlockItem(ModBlocks.FLUID_FILTER)
val FLUID_EVAPORATOR_BLOCK: DeferredItem<BlockItem> = REGISTRY.registerSimpleBlockItem(ModBlocks.FLUID_EVAPORATOR)
} }

View file

@ -0,0 +1,116 @@
package xyz.nuark.mcmod.effectivemekanisms.menu
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.ContainerData
import net.minecraft.world.inventory.SimpleContainerData
import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.network.PacketDistributor
import xyz.nuark.mcmod.effectivemekanisms.api.menu.IOverclockableMachineMenu
import xyz.nuark.mcmod.effectivemekanisms.blockentity.FluidEvaporatorBlockEntity
import xyz.nuark.mcmod.effectivemekanisms.network.FluidFilterSyncPacket
class FluidEvaporatorMenu(
containerId: Int,
private val inventory: Inventory,
val blockEntity: FluidEvaporatorBlockEntity,
private val data: ContainerData
) : AbstractContainerMenu(
ModMenuTypes.FLUID_EVAPORATOR.get(),
containerId
), IOverclockableMachineMenu {
constructor(containerId: Int, inventory: Inventory, extraData: FriendlyByteBuf) : this(
containerId,
inventory,
inventory.player.level().getBlockEntity(extraData.readBlockPos()) as FluidEvaporatorBlockEntity,
SimpleContainerData(DATA_SLOT_COUNT)
)
init {
checkContainerDataCount(data, DATA_SLOT_COUNT)
addDataSlots(data)
}
companion object {
const val DATA_SLOT_COUNT = 11
const val SLOT_ENERGY = 0 // energyStored
const val SLOT_ENERGY_MAX = 1 // maxEnergy
const val SLOT_INPUT_FLUID = 2 // inputAmount
const val SLOT_OUTPUT_FLUID = 3 // outputAmount
const val SLOT_FLUID_CAP = 4 // fluidCapacity
const val SLOT_OVERCLOCK = 5 // overclock tier
const val SLOT_PROGRESS = 6 // overclock tier
}
val energyStored: Int get() = data.get(SLOT_ENERGY)
val energyCapacity: Int get() = data.get(SLOT_ENERGY_MAX)
val inputFluidAmount: Int get() = data.get(SLOT_INPUT_FLUID)
val outputFluidAmount: Int get() = data.get(SLOT_OUTPUT_FLUID)
val fluidCapacity: Int get() = data.get(SLOT_FLUID_CAP)
val overclockTier: Int get() = data.get(SLOT_OVERCLOCK)
val ticksUntilOperation: Int get() = data.get(SLOT_PROGRESS)
val inputFluidStack get() = blockEntity.inputTank.fluid
val outputFluidStack get() = blockEntity.outputTank.fluid
private var lastInputFluid: FluidStack = FluidStack.EMPTY
private var lastOutputFluid: FluidStack = FluidStack.EMPTY
val processingProgress: Float
get() {
val total = FluidEvaporatorBlockEntity.TICKS_PER_OPERATION
val remaining = ticksUntilOperation.coerceIn(0, total)
return 1f - remaining.toFloat() / total.toFloat()
}
override fun broadcastChanges() {
if (blockEntity.level?.isClientSide == false) {
data.set(SLOT_ENERGY, blockEntity.energyStorage.energyStored)
data.set(SLOT_ENERGY_MAX, blockEntity.energyStorage.maxEnergyStored)
data.set(SLOT_INPUT_FLUID, blockEntity.inputTank.fluidAmount)
data.set(SLOT_OUTPUT_FLUID, blockEntity.outputTank.fluidAmount)
data.set(SLOT_FLUID_CAP, FluidEvaporatorBlockEntity.FLUID_CAPACITY)
data.set(SLOT_OVERCLOCK, blockEntity.overclockTier)
data.set(SLOT_PROGRESS, blockEntity.ticksUntilOperation)
val input = blockEntity.inputTank.fluid
val output = blockEntity.outputTank.fluid
if (!FluidStack.isSameFluidSameComponents(input, lastInputFluid) ||
!FluidStack.isSameFluidSameComponents(output, lastOutputFluid)
) {
lastInputFluid = input.copy()
lastOutputFluid = output.copy()
PacketDistributor.sendToPlayer(
inventory.player as ServerPlayer,
FluidFilterSyncPacket(blockEntity.blockPos, input, output)
)
}
}
super.broadcastChanges()
}
override fun quickMoveStack(
p0: Player,
p1: Int
): ItemStack = ItemStack.EMPTY // We are not moving any items
override fun setOverclockTier(tier: Int) {
blockEntity.overclockTier = tier.coerceIn(0, FluidEvaporatorBlockEntity.MAX_OVERCLOCK_TIER)
blockEntity.setChanged()
}
override fun stillValid(player: Player): Boolean =
player.distanceToSqr(
blockEntity.blockPos.x + 0.5,
blockEntity.blockPos.y + 0.5,
blockEntity.blockPos.z + 0.5
) < 64.0
}

View file

@ -10,6 +10,7 @@ import net.minecraft.world.inventory.SimpleContainerData
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.fluids.FluidStack import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.network.PacketDistributor import net.neoforged.neoforge.network.PacketDistributor
import xyz.nuark.mcmod.effectivemekanisms.api.menu.IOverclockableMachineMenu
import xyz.nuark.mcmod.effectivemekanisms.blockentity.FluidFilterBlockEntity import xyz.nuark.mcmod.effectivemekanisms.blockentity.FluidFilterBlockEntity
import xyz.nuark.mcmod.effectivemekanisms.network.FluidFilterSyncPacket import xyz.nuark.mcmod.effectivemekanisms.network.FluidFilterSyncPacket
@ -18,7 +19,10 @@ class FluidFilterMenu(
private val inventory: Inventory, private val inventory: Inventory,
val blockEntity: FluidFilterBlockEntity, val blockEntity: FluidFilterBlockEntity,
private val data: ContainerData private val data: ContainerData
) : AbstractContainerMenu(ModMenuTypes.FLUID_FILTER.get(), containerId) { ) : AbstractContainerMenu(
ModMenuTypes.FLUID_FILTER.get(),
containerId
), IOverclockableMachineMenu {
constructor(containerId: Int, inventory: Inventory, extraData: FriendlyByteBuf) : this( constructor(containerId: Int, inventory: Inventory, extraData: FriendlyByteBuf) : this(
containerId, containerId,
@ -98,7 +102,7 @@ class FluidFilterMenu(
): ItemStack = ItemStack.EMPTY // We are not moving any items ): ItemStack = ItemStack.EMPTY // We are not moving any items
fun setOverclockTier(tier: Int) { override fun setOverclockTier(tier: Int) {
blockEntity.overclockTier = tier.coerceIn(0, FluidFilterBlockEntity.MAX_OVERCLOCK_TIER) blockEntity.overclockTier = tier.coerceIn(0, FluidFilterBlockEntity.MAX_OVERCLOCK_TIER)
blockEntity.setChanged() blockEntity.setChanged()
} }

View file

@ -15,6 +15,9 @@ object ModMenuTypes {
val FLUID_FILTER: DeferredHolder<MenuType<*>, MenuType<FluidFilterMenu>> = MENU_TYPES.register("fluid_filter") { -> val FLUID_FILTER: DeferredHolder<MenuType<*>, MenuType<FluidFilterMenu>> = MENU_TYPES.register("fluid_filter") { ->
IMenuTypeExtension.create(::FluidFilterMenu) IMenuTypeExtension.create(::FluidFilterMenu)
} }
val FLUID_EVAPORATOR: DeferredHolder<MenuType<*>, MenuType<FluidEvaporatorMenu>> = MENU_TYPES.register("fluid_evaporator") { ->
IMenuTypeExtension.create(::FluidEvaporatorMenu)
}
fun register(eventBus: IEventBus) = MENU_TYPES.register(eventBus) fun register(eventBus: IEventBus) = MENU_TYPES.register(eventBus)
} }

View file

@ -7,6 +7,7 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.neoforged.neoforge.network.handling.IPayloadContext import net.neoforged.neoforge.network.handling.IPayloadContext
import xyz.nuark.mcmod.effectivemekanisms.EffMeks import xyz.nuark.mcmod.effectivemekanisms.EffMeks
import xyz.nuark.mcmod.effectivemekanisms.api.menu.IOverclockableMachineMenu
import xyz.nuark.mcmod.effectivemekanisms.menu.FluidFilterMenu import xyz.nuark.mcmod.effectivemekanisms.menu.FluidFilterMenu
data class FluidFilterOverclockPacket(val tier: Int) : CustomPacketPayload { data class FluidFilterOverclockPacket(val tier: Int) : CustomPacketPayload {
@ -27,7 +28,7 @@ data class FluidFilterOverclockPacket(val tier: Int) : CustomPacketPayload {
fun handle(packet: FluidFilterOverclockPacket, context: IPayloadContext) { fun handle(packet: FluidFilterOverclockPacket, context: IPayloadContext) {
context.enqueueWork { context.enqueueWork {
val player = context.player() as? ServerPlayer ?: return@enqueueWork val player = context.player() as? ServerPlayer ?: return@enqueueWork
val menu = player.containerMenu as? FluidFilterMenu ?: return@enqueueWork val menu = player.containerMenu as? IOverclockableMachineMenu ?: return@enqueueWork
menu.setOverclockTier(packet.tier) menu.setOverclockTier(packet.tier)
} }
} }

View file

@ -9,7 +9,7 @@ import net.minecraft.resources.ResourceLocation
import net.neoforged.neoforge.fluids.FluidStack import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.network.handling.IPayloadContext import net.neoforged.neoforge.network.handling.IPayloadContext
import xyz.nuark.mcmod.effectivemekanisms.EffMeks import xyz.nuark.mcmod.effectivemekanisms.EffMeks
import xyz.nuark.mcmod.effectivemekanisms.blockentity.FluidFilterBlockEntity import xyz.nuark.mcmod.effectivemekanisms.api.blockentity.IFluidSyncableBlockEntity
class FluidFilterSyncPacket( class FluidFilterSyncPacket(
val pos: BlockPos, val pos: BlockPos,
@ -27,15 +27,15 @@ class FluidFilterSyncPacket(
val STREAM_CODEC: StreamCodec<RegistryFriendlyByteBuf, FluidFilterSyncPacket> = val STREAM_CODEC: StreamCodec<RegistryFriendlyByteBuf, FluidFilterSyncPacket> =
StreamCodec.composite( StreamCodec.composite(
BlockPos.STREAM_CODEC, FluidFilterSyncPacket::pos, BlockPos.STREAM_CODEC, FluidFilterSyncPacket::pos,
FluidStack.STREAM_CODEC, FluidFilterSyncPacket::inputFluid, FluidStack.OPTIONAL_STREAM_CODEC, FluidFilterSyncPacket::inputFluid,
FluidStack.STREAM_CODEC, FluidFilterSyncPacket::outputFluid, FluidStack.OPTIONAL_STREAM_CODEC, FluidFilterSyncPacket::outputFluid,
::FluidFilterSyncPacket ::FluidFilterSyncPacket
) )
fun handle(packet: FluidFilterSyncPacket, context: IPayloadContext) { fun handle(packet: FluidFilterSyncPacket, context: IPayloadContext) {
context.enqueueWork { context.enqueueWork {
val level = Minecraft.getInstance().level ?: return@enqueueWork val level = Minecraft.getInstance().level ?: return@enqueueWork
val be = level.getBlockEntity(packet.pos) as? FluidFilterBlockEntity ?: return@enqueueWork val be = level.getBlockEntity(packet.pos) as? IFluidSyncableBlockEntity ?: return@enqueueWork
be.inputTank.fluid = packet.inputFluid be.inputTank.fluid = packet.inputFluid
be.outputTank.fluid = packet.outputFluid be.outputTank.fluid = packet.outputFluid
} }

View file

@ -0,0 +1,336 @@
package xyz.nuark.mcmod.effectivemekanisms.screen
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory
import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions
import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.network.PacketDistributor
import xyz.nuark.mcmod.effectivemekanisms.EffMeks
import xyz.nuark.mcmod.effectivemekanisms.menu.FluidEvaporatorMenu
import xyz.nuark.mcmod.effectivemekanisms.network.FluidFilterOverclockPacket
import kotlin.math.pow
import kotlin.math.roundToInt
class FluidEvaporatorScreen(
menu: FluidEvaporatorMenu,
inventory: Inventory,
title: Component
) : AbstractContainerScreen<FluidEvaporatorMenu>(menu, inventory, title) {
companion object {
val GUI_TEXTURE: ResourceLocation = ResourceLocation.fromNamespaceAndPath(
EffMeks.ID, "textures/gui/fluid_filter.png"
)
const val BG_WIDTH = 195
const val BG_HEIGHT = 159
const val ROW1_Y = 27 // top of all main-row widgets
// Energy bar
const val ENERGY_X = 7
const val ENERGY_W = 12
const val ENERGY_H = 80
// Input tank
const val INPUT_X = 28
const val TANK_W = 32
const val TANK_H = 80
// Progress arrow (centred between tanks)
const val ARROW_X = 70
const val ARROW_Y = ROW1_Y + (TANK_H - 16) / 2 // vertically centred
const val ARROW_W = 24
const val ARROW_H = 16
// Output tank
const val OUTPUT_X = 104
// Overclock row
const val OC_LABEL_Y = 122
const val OC_Y = 132
const val OC_GAP = 8
const val OC_START_X = 8
const val TIER_COUNT = 12
const val COL_PANEL_BORDER = 0xFF45475A.toInt()
const val COL_TANK_EMPTY = 0xFF11111B.toInt()
const val COL_ARROW_EMPTY = 0xFF313244.toInt()
const val COL_ARROW_FILL = 0xFF89DCEB.toInt()
const val COL_TEXT = 0xFFCDD6F4.toInt()
const val COL_TEXT_DIM = 0xFF6C7086.toInt()
const val COL_SEPARATOR = 0xFF45475A.toInt()
}
private var hoveredOcBtn = -1
override fun init() {
super.init()
imageWidth = BG_WIDTH
imageHeight = BG_HEIGHT
titleLabelX = BG_WIDTH / 2
titleLabelY = 6
}
override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
renderBackground(graphics, mouseX, mouseY, partialTick)
super.render(graphics, mouseX, mouseY, partialTick)
renderTooltip(graphics, mouseX, mouseY)
}
override fun renderBg(graphics: GuiGraphics, partialTick: Float, mouseX: Int, mouseY: Int) {
val x = leftPos
val y = topPos
drawBackground(graphics, x, y)
graphics.drawString(font, "En.", x + ENERGY_X, y + ROW1_Y - 9, COL_TEXT_DIM, false)
graphics.drawString(font, "Input", x + INPUT_X, y + ROW1_Y - 9, COL_TEXT_DIM, false)
graphics.drawString(font, "Output", x + OUTPUT_X, y + ROW1_Y - 9, COL_TEXT_DIM, false)
drawEnergyBar(graphics, x + ENERGY_X, y + ROW1_Y, menu.energyStored, menu.energyCapacity)
drawFluidTank(
graphics,
x + INPUT_X,
y + ROW1_Y,
menu.inputFluidAmount,
menu.fluidCapacity,
menu.inputFluidStack
)
drawProgressArrow(graphics, x + ARROW_X, y + ARROW_Y, menu.processingProgress)
drawFluidTank(
graphics,
x + OUTPUT_X,
y + ROW1_Y,
menu.outputFluidAmount,
menu.fluidCapacity,
menu.outputFluidStack
)
graphics.fill(x + 4, y + OC_LABEL_Y - 4, x + BG_WIDTH - 4, y + OC_LABEL_Y - 3, COL_SEPARATOR)
graphics.drawString(font, "Overclock", x + OC_START_X, y + OC_LABEL_Y, COL_TEXT_DIM, false)
for (i in 0 until TIER_COUNT) {
val bx = x + OC_START_X + i * (OC_GAP + 8)
val by = y + OC_Y + 5
val isActive = i == menu.overclockTier
val hovered = i == hoveredOcBtn
SliderSectorButton.draw(
graphics, bx, by, isActive, hovered
)
}
val tier = menu.overclockTier
val outMult = 1 shl tier
val eMult = energyCostMultiplier(tier)
val statsLine = "I/O ×$outMult | Energy ×${String.format("%.1f", eMult)}"
graphics.drawString(font, statsLine, x + OC_START_X, y + OC_Y + 13, COL_TEXT_DIM, false)
}
override fun renderTooltip(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
val x = leftPos
val y = topPos
// Energy bar
if (isHovering(ENERGY_X, ROW1_Y, ENERGY_W, ENERGY_H, mouseX.toDouble(), mouseY.toDouble())) {
graphics.renderTooltip(
font, Component.literal(
"${fmtLarge(menu.energyStored)} / ${fmtLarge(menu.energyCapacity)} FE"
), mouseX, mouseY
)
return
}
// Input tank
if (isHovering(INPUT_X, ROW1_Y, TANK_W, TANK_H, mouseX.toDouble(), mouseY.toDouble())) {
val fs = menu.inputFluidStack
val name = if (fs.isEmpty) Component.translatable("tooltip.fluid.empty") else fs.hoverName
graphics.renderTooltip(
font, Component.translatable(
"tooltip.fluid.amount.format", fmtLarge(menu.inputFluidAmount), fmtLarge(menu.fluidCapacity)
).append(name), mouseX, mouseY
)
return
}
// Output tank
if (isHovering(OUTPUT_X, ROW1_Y, TANK_W, TANK_H, mouseX.toDouble(), mouseY.toDouble())) {
val fs = menu.outputFluidStack
val name = if (fs.isEmpty) Component.translatable("tooltip.fluid.empty") else fs.hoverName
graphics.renderTooltip(
font, Component.translatable(
"tooltip.fluid.amount.format", fmtLarge(menu.outputFluidAmount), fmtLarge(menu.fluidCapacity)
).append(name), mouseX, mouseY
)
return
}
// Overclock buttons
for (i in 0 until TIER_COUNT) {
val bx = OC_START_X + i * (OC_GAP + 8)
val by = OC_Y + 5
if (isHovering(bx, by, 8, 8, mouseX.toDouble(), mouseY.toDouble())) {
graphics.renderTooltip(
font,
Component.translatable("tooltip.machine.overclock.tier", (i + 1).toString()),
mouseX,
mouseY
)
return
}
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
for (i in 0 until TIER_COUNT) {
val bx = OC_START_X + i * (OC_GAP + 8)
val by = OC_Y + 5
if (isHovering(bx, by, 8, 8, mouseX, mouseY)) {
PacketDistributor.sendToServer(FluidFilterOverclockPacket(i))
return true
}
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseMoved(mouseX: Double, mouseY: Double) {
hoveredOcBtn = (0 until TIER_COUNT).firstOrNull { i ->
val bx = OC_START_X + i * (OC_GAP + 8)
val by = OC_Y + 5
isHovering(bx, by, 8, 8, mouseX, mouseY)
} ?: -1
super.mouseMoved(mouseX, mouseY)
}
override fun renderLabels(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
val tw = font.width(title)
graphics.drawString(font, title, (imageWidth - tw) / 2, titleLabelY, COL_TEXT, false)
}
private fun drawBackground(graphics: GuiGraphics, x: Int, y: Int) {
graphics.blit(GUI_TEXTURE, x, y, 0, 0, BG_WIDTH, BG_HEIGHT)
}
private fun drawEnergyBar(graphics: GuiGraphics, x: Int, y: Int, stored: Int, capacity: Int) {
val filledH = (ENERGY_H * stored.toLong() / capacity.coerceAtLeast(1)).toInt().coerceIn(0, ENERGY_H)
val srcY = 0 + (ENERGY_H - filledH)
graphics.blit(
GUI_TEXTURE,
x, y + ENERGY_H - filledH,
195, srcY,
ENERGY_W, filledH
)
}
private fun drawFluidTank(
graphics: GuiGraphics, x: Int, y: Int,
amount: Int, capacity: Int, fluid: FluidStack
) {
graphics.fill(x, y, x + TANK_W, y + TANK_H, COL_TANK_EMPTY)
// Fluid fill
if (amount > 0 && capacity > 0) {
val colour = getFluidColor(fluid)
val filled = (TANK_H * amount.toLong() / capacity).toInt().coerceIn(0, TANK_H)
graphics.fill(x, y + TANK_H - filled, x + TANK_W, y + TANK_H, colour)
// Highlight shimmer at fill line
graphics.fill(x, y + TANK_H - filled, x + TANK_W, y + TANK_H - filled + 1, 0x55FFFFFF)
}
border(graphics, x, y, TANK_W, TANK_H, COL_PANEL_BORDER)
val pct = if (capacity > 0) (amount * 100L / capacity).toInt() else 0
graphics.drawString(font, "$pct%", x + 4, y + TANK_H + 2, COL_TEXT_DIM, false)
}
private fun drawProgressArrow(graphics: GuiGraphics, x: Int, y: Int, progress: Float) {
// Empty arrow body
graphics.fill(x, y, x + ARROW_W, y + ARROW_H, COL_ARROW_EMPTY)
// Filled portion
val filledW = (ARROW_W * progress).roundToInt().coerceIn(0, ARROW_W)
if (filledW > 0) {
graphics.fill(x, y, x + filledW, y + ARROW_H, COL_ARROW_FILL)
}
border(graphics, x, y, ARROW_W, ARROW_H, COL_PANEL_BORDER)
}
private fun energyCostMultiplier(tier: Int): Double = 2.5.pow(tier.toDouble())
private fun getFluidColor(fluid: FluidStack): Int {
if (fluid.isEmpty) return COL_TANK_EMPTY
return try {
IClientFluidTypeExtensions.of(fluid.fluid).tintColor or 0xFF000000.toInt()
} catch (_: Exception) {
0xFF3A7CC0.toInt()
}
}
private fun fmtLarge(v: Int): String = when {
v >= 1_000_000_000 -> "${v / 1_000_000_000}B"
v >= 1_000_000 -> "${v / 1_000_000}M"
v >= 1_000 -> "${v / 1_000}k"
else -> v.toString()
}
private fun border(graphics: GuiGraphics, x: Int, y: Int, w: Int, h: Int, col: Int) {
graphics.fill(x, y, x + w, y + 1, col)
graphics.fill(x, y + h - 1, x + w, y + h, col)
graphics.fill(x, y + 1, x + 1, y + h - 1, col)
graphics.fill(x + w - 1, y + 1, x + w, y + h - 1, col)
}
object SliderSectorButton {
private const val NORMAL_U = 207
private const val NORMAL_V = 0
private const val NORMAL_SIZE = 4
private const val HOVERED_U = 211
private const val HOVERED_V = 0
private const val HOVERED_SIZE = 6
private const val ACTIVE_U = 217
private const val ACTIVE_V = 0
private const val ACTIVE_SIZE = 8
fun draw(graphics: GuiGraphics, x: Int, y: Int, selected: Boolean, hovered: Boolean) {
if (selected) {
graphics.blit(
GUI_TEXTURE,
x - ACTIVE_SIZE / 2, y - ACTIVE_SIZE / 2,
ACTIVE_U, ACTIVE_V,
ACTIVE_SIZE, ACTIVE_SIZE
)
} else if (hovered) {
graphics.blit(
GUI_TEXTURE,
x - HOVERED_SIZE / 2,
y - HOVERED_SIZE / 2,
HOVERED_U,
HOVERED_V,
HOVERED_SIZE,
HOVERED_SIZE
)
} else {
graphics.blit(
GUI_TEXTURE,
x - NORMAL_SIZE / 2,
y - NORMAL_SIZE / 2,
NORMAL_U,
NORMAL_V,
NORMAL_SIZE,
NORMAL_SIZE
)
}
}
}
}

View file

@ -17,8 +17,8 @@ import kotlin.math.roundToInt
class FluidFilterScreen( class FluidFilterScreen(
menu: FluidFilterMenu, menu: FluidFilterMenu,
inventory: Inventory, inventory: Inventory,
@Suppress("unused") title: Component title: Component
) : AbstractContainerScreen<FluidFilterMenu>(menu, inventory, Component.translatable("block.effmeks.fluid_filter")) { ) : AbstractContainerScreen<FluidFilterMenu>(menu, inventory, title) {
companion object { companion object {
val GUI_TEXTURE: ResourceLocation = ResourceLocation.fromNamespaceAndPath( val GUI_TEXTURE: ResourceLocation = ResourceLocation.fromNamespaceAndPath(
EffMeks.ID, "textures/gui/fluid_filter.png" EffMeks.ID, "textures/gui/fluid_filter.png"

View file

@ -9,9 +9,8 @@ import net.minecraft.world.level.material.Fluid
object ModTags { object ModTags {
object Fluids { object Fluids {
val BRINE = commonTag("brine") val BRINE = commonTag("brine")
val LITHIUM = commonTag("lithium")
private fun commonTag(name: String): TagKey<Fluid> { private fun commonTag(@Suppress("SameParameterValue") name: String): TagKey<Fluid> {
return FluidTags.create(ResourceLocation.fromNamespaceAndPath("c", name)) return FluidTags.create(ResourceLocation.fromNamespaceAndPath("c", name))
} }
} }

View file

@ -0,0 +1,22 @@
package xyz.nuark.mcmod.effectivemekanisms.utils
import mekanism.common.registries.MekanismFluids
import net.minecraft.tags.TagKey
import net.minecraft.world.level.material.Fluid
import net.neoforged.neoforge.common.Tags
import net.neoforged.neoforge.fluids.BaseFlowingFluid
import xyz.nuark.mcmod.effectivemekanisms.tags.ModTags
object EvaporationFluidConversion {
data class ConversionRate(
val from: TagKey<Fluid>,
val to: BaseFlowingFluid.Source,
val consume: Int,
val produce: Int
)
val evaporationConversions = listOf(
ConversionRate(Tags.Fluids.WATER, MekanismFluids.BRINE.get(), 10, 1),
ConversionRate(ModTags.Fluids.BRINE, MekanismFluids.LITHIUM.get(), 10, 1),
)
}

View file

@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "effmeks:block/fluid_evaporator"
}
}
}

View file

@ -0,0 +1,388 @@
{
"format_version": "1.9.0",
"credit": "Made with Blockbench",
"texture_size": [64, 64],
"textures": {
"0": "effmeks:block/fluid_evaporator",
"particle": "effmeks:block/fluid_evaporator"
},
"elements": [
{
"name": "body",
"from": [2, 2, 2],
"to": [14, 14, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [2, 2, 2]},
"faces": {
"north": {"uv": [4, 0, 8, 4], "texture": "#0"},
"east": {"uv": [4, 0, 8, 4], "texture": "#0"},
"south": {"uv": [4, 0, 8, 4], "texture": "#0"},
"west": {"uv": [4, 0, 8, 4], "texture": "#0"},
"up": {"uv": [4, 4, 0, 0], "texture": "#0"},
"down": {"uv": [4, 0, 0, 4], "texture": "#0"}
}
},
{
"name": "fluid_in",
"from": [4, 14, 4],
"to": [12, 16, 12],
"rotation": {"angle": 0, "axis": "y", "origin": [4, 14, 4]},
"faces": {
"north": {"uv": [1.5, 4.5, 3, 5], "texture": "#0"},
"east": {"uv": [1.5, 5, 3, 5.5], "texture": "#0"},
"south": {"uv": [1.5, 4.5, 3, 5], "texture": "#0"},
"west": {"uv": [1.5, 5, 3, 5.5], "texture": "#0"},
"up": {"uv": [0, 4.5, 1.5, 6], "texture": "#0"},
"down": {"uv": [0.25, 4.75, 1.25, 5.75], "texture": "#0"}
}
},
{
"name": "fluid_out",
"from": [3, 0, 3],
"to": [13, 2, 13],
"rotation": {"angle": 0, "axis": "y", "origin": [3, 0, 3]},
"faces": {
"north": {"uv": [1.5, 8, 3, 8.5], "texture": "#0"},
"east": {"uv": [1.5, 7.5, 3, 8], "texture": "#0"},
"south": {"uv": [1.5, 8, 3, 8.5], "texture": "#0"},
"west": {"uv": [1.5, 7.5, 3, 8], "texture": "#0"},
"up": {"uv": [3, 3, 6, 6], "texture": "#0"},
"down": {"uv": [0, 7.5, 1.5, 9], "texture": "#0"}
}
},
{
"name": "energyIn",
"from": [5, 5, 1],
"to": [11, 11, 2],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 5, 1]},
"faces": {
"north": {"uv": [0.25, 6.25, 1.25, 7.25], "texture": "#0"},
"east": {"uv": [1, 6.25, 1.25, 7.25], "texture": "#0"},
"south": {"uv": [0.25, 6.25, 1.25, 7.25], "texture": "#0"},
"west": {"uv": [0.25, 6.25, 0.5, 7.25], "texture": "#0"},
"up": {"uv": [0.25, 6.25, 1.25, 6.5], "texture": "#0"},
"down": {"uv": [0.25, 7, 1.25, 7.25], "texture": "#0"}
}
},
{
"name": "frame",
"from": [5, 5, 0],
"to": [11, 6, 1],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 5, 0]},
"faces": {
"north": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"east": {"uv": [0, 7.25, 0.25, 7.5], "texture": "#0"},
"south": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"west": {"uv": [1.25, 7.25, 1.5, 7.5], "texture": "#0"},
"up": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"down": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"}
}
},
{
"name": "frame",
"from": [5, 10, 0],
"to": [11, 11, 1],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 10, 0]},
"faces": {
"north": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"east": {"uv": [0, 6, 0.25, 6.25], "texture": "#0"},
"south": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"west": {"uv": [1.25, 6, 1.5, 6.25], "texture": "#0"},
"up": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"down": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"}
}
},
{
"name": "frame",
"from": [10, 6, 0],
"to": [11, 10, 1],
"rotation": {"angle": 0, "axis": "y", "origin": [10, 6, 0]},
"faces": {
"north": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"east": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"south": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"west": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"up": {"uv": [0, 7.25, 0.25, 7.5], "texture": "#0"},
"down": {"uv": [0, 6, 0.25, 6.25], "texture": "#0"}
}
},
{
"name": "frame",
"from": [5, 6, 0],
"to": [6, 10, 1],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 6, 0]},
"faces": {
"north": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"east": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"south": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"west": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"up": {"uv": [1.25, 7.25, 1.5, 7.5], "texture": "#0"},
"down": {"uv": [1.25, 6, 1.5, 6.25], "texture": "#0"}
}
},
{
"name": "energyIn",
"from": [5, 5, 14],
"to": [11, 11, 15],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 5, 14]},
"faces": {
"north": {"uv": [0.25, 6.25, 1.25, 7.25], "texture": "#0"},
"east": {"uv": [1, 6.25, 1.25, 7.25], "texture": "#0"},
"south": {"uv": [0.25, 6.25, 1.25, 7.25], "texture": "#0"},
"west": {"uv": [0.25, 6.25, 0.5, 7.25], "texture": "#0"},
"up": {"uv": [0.25, 6.25, 1.25, 6.5], "texture": "#0"},
"down": {"uv": [0.25, 7, 1.25, 7.25], "texture": "#0"}
}
},
{
"name": "frame",
"from": [5, 5, 15],
"to": [11, 6, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 5, 15]},
"faces": {
"north": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"east": {"uv": [0, 7.25, 0.25, 7.5], "texture": "#0"},
"south": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"west": {"uv": [1.25, 7.25, 1.5, 7.5], "texture": "#0"},
"up": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"down": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"}
}
},
{
"name": "frame",
"from": [5, 10, 15],
"to": [11, 11, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 10, 15]},
"faces": {
"north": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"east": {"uv": [0, 6, 0.25, 6.25], "texture": "#0"},
"south": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"west": {"uv": [1.25, 6, 1.5, 6.25], "texture": "#0"},
"up": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"down": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"}
}
},
{
"name": "frame",
"from": [10, 6, 15],
"to": [11, 10, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [10, 6, 15]},
"faces": {
"north": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"east": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"south": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"west": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"up": {"uv": [0, 7.25, 0.25, 7.5], "texture": "#0"},
"down": {"uv": [0, 6, 0.25, 6.25], "texture": "#0"}
}
},
{
"name": "frame",
"from": [5, 6, 15],
"to": [6, 10, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 6, 15]},
"faces": {
"north": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"east": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"south": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"west": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"up": {"uv": [1.25, 7.25, 1.5, 7.5], "texture": "#0"},
"down": {"uv": [1.25, 6, 1.5, 6.25], "texture": "#0"}
}
},
{
"name": "energyIn",
"from": [1, 5, 5],
"to": [2, 11, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [2, 5, 5]},
"faces": {
"north": {"uv": [0.25, 6.25, 0.5, 7.25], "texture": "#0"},
"east": {"uv": [0.25, 6.25, 1.25, 7.25], "texture": "#0"},
"south": {"uv": [1, 6.25, 1.25, 7.25], "texture": "#0"},
"west": {"uv": [0.25, 6.25, 1.25, 7.25], "texture": "#0"},
"up": {"uv": [0.25, 6.25, 1.25, 6.5], "rotation": 90, "texture": "#0"},
"down": {"uv": [0.25, 7, 1.25, 7.25], "rotation": 270, "texture": "#0"}
}
},
{
"name": "frame",
"from": [0, 5, 5],
"to": [1, 6, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [1, 5, 5]},
"faces": {
"north": {"uv": [1.25, 7.25, 1.5, 7.5], "texture": "#0"},
"east": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"south": {"uv": [0, 7.25, 0.25, 7.5], "texture": "#0"},
"west": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"up": {"uv": [0, 6, 1.5, 6.25], "rotation": 90, "texture": "#0"},
"down": {"uv": [0, 6, 1.5, 6.25], "rotation": 270, "texture": "#0"}
}
},
{
"name": "frame",
"from": [0, 10, 5],
"to": [1, 11, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [1, 10, 5]},
"faces": {
"north": {"uv": [1.25, 6, 1.5, 6.25], "texture": "#0"},
"east": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"south": {"uv": [0, 6, 0.25, 6.25], "texture": "#0"},
"west": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"up": {"uv": [0, 7.25, 1.5, 7.5], "rotation": 90, "texture": "#0"},
"down": {"uv": [0, 7.25, 1.5, 7.5], "rotation": 270, "texture": "#0"}
}
},
{
"name": "frame",
"from": [0, 6, 10],
"to": [1, 10, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [0, 6, 10]},
"faces": {
"north": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"east": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"south": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"west": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"up": {"uv": [0, 7.25, 0.25, 7.5], "texture": "#0"},
"down": {"uv": [0, 6, 0.25, 6.25], "texture": "#0"}
}
},
{
"name": "frame",
"from": [0, 6, 5],
"to": [1, 10, 6],
"rotation": {"angle": 0, "axis": "y", "origin": [0, 6, 5]},
"faces": {
"north": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"east": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"south": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"west": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"up": {"uv": [1.25, 7.25, 1.5, 7.5], "texture": "#0"},
"down": {"uv": [1.25, 6, 1.5, 6.25], "texture": "#0"}
}
},
{
"name": "energyIn",
"from": [14, 5, 5],
"to": [15, 11, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [15, 5, 5]},
"faces": {
"north": {"uv": [0.25, 6.25, 0.5, 7.25], "texture": "#0"},
"east": {"uv": [0.25, 6.25, 1.25, 7.25], "texture": "#0"},
"south": {"uv": [1, 6.25, 1.25, 7.25], "texture": "#0"},
"west": {"uv": [0.25, 6.25, 1.25, 7.25], "texture": "#0"},
"up": {"uv": [0.25, 6.25, 1.25, 6.5], "rotation": 90, "texture": "#0"},
"down": {"uv": [0.25, 7, 1.25, 7.25], "rotation": 270, "texture": "#0"}
}
},
{
"name": "frame",
"from": [15, 5, 5],
"to": [16, 6, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [16, 5, 5]},
"faces": {
"north": {"uv": [1.25, 7.25, 1.5, 7.5], "texture": "#0"},
"east": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"south": {"uv": [0, 7.25, 0.25, 7.5], "texture": "#0"},
"west": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"up": {"uv": [0, 6, 1.5, 6.25], "rotation": 90, "texture": "#0"},
"down": {"uv": [0, 6, 1.5, 6.25], "rotation": 270, "texture": "#0"}
}
},
{
"name": "frame",
"from": [15, 10, 5],
"to": [16, 11, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [16, 10, 5]},
"faces": {
"north": {"uv": [1.25, 6, 1.5, 6.25], "texture": "#0"},
"east": {"uv": [0, 6, 1.5, 6.25], "texture": "#0"},
"south": {"uv": [0, 6, 0.25, 6.25], "texture": "#0"},
"west": {"uv": [0, 7.25, 1.5, 7.5], "texture": "#0"},
"up": {"uv": [0, 7.25, 1.5, 7.5], "rotation": 90, "texture": "#0"},
"down": {"uv": [0, 7.25, 1.5, 7.5], "rotation": 270, "texture": "#0"}
}
},
{
"name": "frame",
"from": [15, 6, 10],
"to": [16, 10, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [15, 6, 10]},
"faces": {
"north": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"east": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"south": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"west": {"uv": [0, 6.25, 0.25, 7.25], "texture": "#0"},
"up": {"uv": [0, 7.25, 0.25, 7.5], "texture": "#0"},
"down": {"uv": [0, 6, 0.25, 6.25], "texture": "#0"}
}
},
{
"name": "frame",
"from": [15, 6, 5],
"to": [16, 10, 6],
"rotation": {"angle": 0, "axis": "y", "origin": [15, 6, 5]},
"faces": {
"north": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"east": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"south": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"west": {"uv": [1.25, 6.25, 1.5, 7.25], "texture": "#0"},
"up": {"uv": [1.25, 7.25, 1.5, 7.5], "texture": "#0"},
"down": {"uv": [1.25, 6, 1.5, 6.25], "texture": "#0"}
}
}
],
"display": {
"thirdperson_righthand": {
"scale": [0.6, 0.6, 0.6]
},
"thirdperson_lefthand": {
"scale": [0.6, 0.6, 0.6]
},
"firstperson_righthand": {
"scale": [0.6, 0.6, 0.6]
},
"ground": {
"translation": [0, 5, 0],
"scale": [0.75, 0.75, 0.75]
},
"gui": {
"rotation": [22.5, 45, 0],
"scale": [0.75, 0.75, 0.75]
},
"head": {
"scale": [1.5, 1.5, 1.5]
}
},
"groups": [
0,
1,
2,
{
"name": "energyIn",
"origin": [14, 5, 5],
"scope": 0,
"color": 0,
"children": [3, 4, 5, 6, 7]
},
{
"name": "energyIn",
"origin": [14, 5, 5],
"scope": 0,
"color": 0,
"children": [8, 9, 10, 11, 12]
},
{
"name": "energyIn",
"origin": [14, 5, 5],
"scope": 0,
"color": 0,
"children": [13, 14, 15, 16, 17]
},
{
"name": "energyIn",
"origin": [14, 5, 5],
"scope": 0,
"color": 0,
"children": [18, 19, 20, 21, 22]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB