From 2fc91263ee960a2811a67302d0ad48959cd2d8a9 Mon Sep 17 00:00:00 2001 From: thedarkcolour <30441001+thedarkcolour@users.noreply.github.com> Date: Mon, 6 Apr 2026 19:20:22 -0700 Subject: [PATCH] More WIP porting messing around with Codex pt. 2 --- .../java/thedarkcolour/exdeorum/ExDeorum.java | 3 +- .../exdeorum/block/BarrelBlock.java | 31 +- .../exdeorum/block/ETankBlock.java | 3 +- .../exdeorum/block/EndCakeBlock.java | 10 +- .../exdeorum/block/InfestedLeavesBlock.java | 28 +- .../exdeorum/block/MachineBlock.java | 19 +- .../exdeorum/block/MechanicalHammerBlock.java | 2 +- .../exdeorum/block/SieveBlock.java | 17 +- .../exdeorum/block/WitchWaterBlock.java | 69 ++-- .../AbstractCrucibleBlockEntity.java | 55 ++- .../blockentity/BarrelBlockEntity.java | 64 ++-- .../InfestedLeavesBlockEntity.java | 8 +- .../exdeorum/client/ClientHandler.java | 8 +- .../exdeorum/client/CompostColors.java | 359 +++++------------- .../exdeorum/client/RenderUtil.java | 198 ++++++++-- .../client/screen/MechanicalHammerScreen.java | 5 +- .../client/screen/MechanicalSieveScreen.java | 4 +- .../client/screen/RedstoneControlWidget.java | 7 +- .../exdeorum/client/ter/BarrelRenderer.java | 166 +++++++- .../exdeorum/client/ter/CrucibleRenderer.java | 97 ++++- .../exdeorum/client/ter/SieveRenderer.java | 90 ++++- .../exdeorum/compat/CompatUtil.java | 2 +- .../exdeorum/compat/PreferredOres.java | 10 +- .../exdeorum/compat/XeiSieveRecipe.java | 4 +- .../exdeorum/event/EventHandler.java | 42 +- .../exdeorum/item/WateringCanItem.java | 30 +- .../exdeorum/loot/CrookLootModifier.java | 7 +- .../exdeorum/loot/HammerLootModifier.java | 8 +- .../exdeorum/registry/EBlockEntities.java | 6 +- .../transfer/LegacyEnergyStorageTransfer.java | 58 +++ .../LegacyFluidItemAccessTransfer.java | 60 +++ .../transfer/LegacyFluidTankTransfer.java | 71 ++++ .../transfer/LegacyItemHandlerTransfer.java | 84 ++++ 33 files changed, 1107 insertions(+), 518 deletions(-) create mode 100644 src/main/java/thedarkcolour/exdeorum/transfer/LegacyEnergyStorageTransfer.java create mode 100644 src/main/java/thedarkcolour/exdeorum/transfer/LegacyFluidItemAccessTransfer.java create mode 100644 src/main/java/thedarkcolour/exdeorum/transfer/LegacyFluidTankTransfer.java create mode 100644 src/main/java/thedarkcolour/exdeorum/transfer/LegacyItemHandlerTransfer.java diff --git a/src/main/java/thedarkcolour/exdeorum/ExDeorum.java b/src/main/java/thedarkcolour/exdeorum/ExDeorum.java index 3ed32154..e9335cda 100644 --- a/src/main/java/thedarkcolour/exdeorum/ExDeorum.java +++ b/src/main/java/thedarkcolour/exdeorum/ExDeorum.java @@ -19,7 +19,6 @@ package thedarkcolour.exdeorum; import net.minecraft.resources.Identifier; -import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModList; import net.neoforged.fml.common.Mod; @@ -60,7 +59,7 @@ public class ExDeorum { // Game Events EventHandler.register(modBus); // Client init - if (FMLEnvironment.dist == Dist.CLIENT) { + if (FMLEnvironment.getDist().isClient()) { ClientHandler.register(modBus); } // Config init diff --git a/src/main/java/thedarkcolour/exdeorum/block/BarrelBlock.java b/src/main/java/thedarkcolour/exdeorum/block/BarrelBlock.java index b1c842e1..a2d3d846 100644 --- a/src/main/java/thedarkcolour/exdeorum/block/BarrelBlock.java +++ b/src/main/java/thedarkcolour/exdeorum/block/BarrelBlock.java @@ -23,6 +23,7 @@ import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; @@ -64,33 +65,23 @@ public class BarrelBlock extends ETankBlock { } @Override - public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { - if (!level.isClientSide()) { - if (!state.is(newState.getBlock())) { - if (level.getBlockEntity(pos) instanceof BarrelBlockEntity barrel) { - var item = barrel.getItem(); + public void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) { + if (level.getBlockEntity(pos) instanceof BarrelBlockEntity barrel) { + var item = barrel.getItem(); - if (!item.isEmpty()) { - EBlock.dropItem(level, pos, item); - } - } + if (!item.isEmpty()) { + EBlock.dropItem(level, pos, item); } } - super.onRemove(state, level, pos, newState, isMoving); + super.affectNeighborsAfterRemoval(state, level, pos, movedByPiston); } @Override - public void neighborChanged(BlockState pState, Level level, BlockPos pos, Block pBlock, BlockPos fromPos, boolean pIsMoving) { - // Only check when the above block is updated, or when the below block is updated - if (fromPos.getY() - pos.getY() == 1) { - if (level.getBlockEntity(pos) instanceof BarrelBlockEntity barrel) { - barrel.tryInWorldFluidMixing(); - } - } else if (fromPos.getY() - pos.getY() == -1) { - if (level.getBlockEntity(pos) instanceof BarrelBlockEntity barrel) { - barrel.updateFluidTransform(); - } + public void neighborChanged(BlockState pState, Level level, BlockPos pos, Block pBlock, @org.jetbrains.annotations.Nullable net.minecraft.world.level.redstone.Orientation orientation, boolean pIsMoving) { + if (level.getBlockEntity(pos) instanceof BarrelBlockEntity barrel) { + barrel.tryInWorldFluidMixing(); + barrel.updateFluidTransform(); } } diff --git a/src/main/java/thedarkcolour/exdeorum/block/ETankBlock.java b/src/main/java/thedarkcolour/exdeorum/block/ETankBlock.java index b7c61f97..931f7c4b 100644 --- a/src/main/java/thedarkcolour/exdeorum/block/ETankBlock.java +++ b/src/main/java/thedarkcolour/exdeorum/block/ETankBlock.java @@ -20,6 +20,7 @@ package thedarkcolour.exdeorum.block; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.InsideBlockEffectApplier; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -40,7 +41,7 @@ public abstract class ETankBlock extends EBlock { } @Override - public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { + public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity, InsideBlockEffectApplier applier, boolean shouldApplyEffects) { if (!level.isClientSide() && level.getBlockEntity(pos) instanceof ETankBlockEntity blockEntity) { var tank = blockEntity.getTank(); var fluid = tank.getFluid(); diff --git a/src/main/java/thedarkcolour/exdeorum/block/EndCakeBlock.java b/src/main/java/thedarkcolour/exdeorum/block/EndCakeBlock.java index cc2293ee..f5549fa6 100644 --- a/src/main/java/thedarkcolour/exdeorum/block/EndCakeBlock.java +++ b/src/main/java/thedarkcolour/exdeorum/block/EndCakeBlock.java @@ -28,8 +28,9 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.CakeBlock; -import net.minecraft.world.level.block.EndPortalBlock; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.portal.TeleportTransition; +import net.minecraft.world.phys.Vec3; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.phys.BlockHitResult; import thedarkcolour.exdeorum.tag.EItemTags; @@ -94,10 +95,9 @@ public class EndCakeBlock extends CakeBlock { var endLevel = level.getServer().getLevel(Level.END); if (endLevel != null) { - if (player.canChangeDimensions(level, endLevel)) { - player.changeDimension(((EndPortalBlock) Blocks.END_PORTAL).getPortalDestination(level, player, player.getOnPos())); - return true; - } + var spawn = ServerLevel.END_SPAWN_POINT.getBottomCenter(); + player.teleportTo(endLevel, spawn.x, spawn.y, spawn.z, java.util.Set.of(), player.getYRot(), player.getXRot(), false); + return true; } } diff --git a/src/main/java/thedarkcolour/exdeorum/block/InfestedLeavesBlock.java b/src/main/java/thedarkcolour/exdeorum/block/InfestedLeavesBlock.java index 90504f9f..e83b345b 100644 --- a/src/main/java/thedarkcolour/exdeorum/block/InfestedLeavesBlock.java +++ b/src/main/java/thedarkcolour/exdeorum/block/InfestedLeavesBlock.java @@ -37,10 +37,8 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.phys.HitResult; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.fml.loading.FMLEnvironment; import org.jetbrains.annotations.Nullable; +import com.mojang.serialization.MapCodec; import thedarkcolour.exdeorum.blockentity.InfestedLeavesBlockEntity; import thedarkcolour.exdeorum.client.RenderUtil; import thedarkcolour.exdeorum.config.EConfig; @@ -49,12 +47,18 @@ import thedarkcolour.exdeorum.registry.EBlocks; public class InfestedLeavesBlock extends LeavesBlock implements EntityBlock { public static final BooleanProperty FULLY_INFESTED = BooleanProperty.create("fully_infested"); + public static final MapCodec CODEC = simpleCodec(InfestedLeavesBlock::new); public InfestedLeavesBlock(Properties properties) { - super(properties); + super(0.005f, properties); registerDefaultState(defaultBlockState().setValue(FULLY_INFESTED, false)); } + @Override + public MapCodec codec() { + return CODEC; + } + public static void setBlock(Level level, BlockPos pos, BlockState fromState) { level.setBlock(pos, EBlocks.INFESTED_LEAVES.get().defaultBlockState() .setValue(LeavesBlock.DISTANCE, fromState.hasProperty(LeavesBlock.DISTANCE) ? fromState.getValue(LeavesBlock.DISTANCE) : 0) @@ -98,11 +102,15 @@ public class InfestedLeavesBlock extends LeavesBlock implements EntityBlock { } @Override - public ItemStack getCloneItemStack(BlockState state, HitResult target, LevelReader level, BlockPos pos, Player player) { + public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state, boolean includeData) { if (level.getBlockEntity(pos) instanceof InfestedLeavesBlockEntity leaves) { - return leaves.getMimic().getCloneItemStack(target, level, pos, player); + return leaves.getMimic().getCloneItemStack(pos, level, includeData, null); } - return ItemStack.EMPTY; + return state.getCloneItemStack(pos, level, includeData, null); + } + + @Override + protected void spawnFallingLeavesParticle(Level level, BlockPos pos, RandomSource random) { } @Override @@ -115,7 +123,9 @@ public class InfestedLeavesBlock extends LeavesBlock implements EntityBlock { @Override public RenderShape getRenderShape(BlockState pState) { - if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) return RenderShape.MODEL; - return (EConfig.CLIENT_SPEC.isLoaded() && EConfig.CLIENT.useFastInfestedLeaves.get()) || RenderUtil.IRIS_ACCESS.areShadersEnabled() ? RenderShape.MODEL : RenderShape.INVISIBLE; + if (!EConfig.CLIENT_SPEC.isLoaded()) { + return RenderShape.MODEL; + } + return (EConfig.CLIENT.useFastInfestedLeaves.get() || RenderUtil.IRIS_ACCESS.areShadersEnabled()) ? RenderShape.MODEL : RenderShape.INVISIBLE; } } diff --git a/src/main/java/thedarkcolour/exdeorum/block/MachineBlock.java b/src/main/java/thedarkcolour/exdeorum/block/MachineBlock.java index 7817b3c0..c57ea531 100644 --- a/src/main/java/thedarkcolour/exdeorum/block/MachineBlock.java +++ b/src/main/java/thedarkcolour/exdeorum/block/MachineBlock.java @@ -21,21 +21,25 @@ package thedarkcolour.exdeorum.block; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.util.ProblemReporter; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.level.gamerules.GameRules; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; 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.level.redstone.Orientation; +import net.minecraft.world.level.storage.TagValueInput; +import net.minecraft.world.level.storage.TagValueOutput; import net.neoforged.neoforge.items.ItemStackHandler; import thedarkcolour.exdeorum.blockentity.AbstractMachineBlockEntity; import thedarkcolour.exdeorum.config.EConfig; @@ -60,7 +64,6 @@ public abstract class MachineBlock extends EBlock { // Label for the item tooltip where the mesh/hammer is listed protected abstract MutableComponent getHighlightItemLabel(); - @Override public void appendHoverText(ItemStack stack, Item.TooltipContext level, List tooltip, TooltipFlag flag) { var lookup = level.registries(); if (lookup != null) { @@ -73,14 +76,14 @@ public abstract class MachineBlock extends EBlock { if (nbt.contains("inventory")) { var inventory = new ItemStackHandler(); - inventory.deserializeNBT(lookup, nbt.getCompound("inventory")); + inventory.deserialize(TagValueInput.create(ProblemReporter.DISCARDING, lookup, nbt.getCompound("inventory").orElseGet(CompoundTag::new))); // Hammer or sieve mesh var highlightItem = inventory.getStackInSlot(getHighlightItemSlot()); if (!highlightItem.isEmpty()) { // display the mesh/hammer inside the machine - tooltip.add(getHighlightItemLabel().withStyle(ChatFormatting.GRAY).append(Component.translatable(highlightItem.getDescriptionId()))); + tooltip.add(getHighlightItemLabel().withStyle(ChatFormatting.GRAY).append(Component.translatable(highlightItem.getItem().getDescriptionId()))); } } @@ -96,12 +99,14 @@ public abstract class MachineBlock extends EBlock { // Drops the item for creative mode players @Override public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState pState, Player player) { - if (!level.isClientSide() && player.isCreative() && level.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { + if (!level.isClientSide() && player.isCreative()) { if (level.getBlockEntity(pos) instanceof AbstractMachineBlockEntity machine) { if (!machine.inventory.getStackInSlot(getHighlightItemSlot()).isEmpty()) { var stack = new ItemStack(this); // save machine properties to the item if mesh/hammer slot is not empty - BlockItem.setBlockEntityData(stack, this.blockEntityType.get(), machine.saveWithoutMetadata(level.registryAccess())); + var data = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, level.registryAccess()); + machine.saveCustomOnly(data); + BlockItem.setBlockEntityData(stack, this.blockEntityType.get(), data); var itemEntity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, stack); itemEntity.setDefaultPickUpDelay(); level.addFreshEntity(itemEntity); @@ -124,7 +129,7 @@ public abstract class MachineBlock extends EBlock { // Redstone state @Override - public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) { + public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, @org.jetbrains.annotations.Nullable Orientation orientation, boolean isMoving) { if (level.getBlockEntity(pos) instanceof AbstractMachineBlockEntity machine) { machine.checkPoweredState(level, pos); } diff --git a/src/main/java/thedarkcolour/exdeorum/block/MechanicalHammerBlock.java b/src/main/java/thedarkcolour/exdeorum/block/MechanicalHammerBlock.java index 1d84e595..2262b567 100644 --- a/src/main/java/thedarkcolour/exdeorum/block/MechanicalHammerBlock.java +++ b/src/main/java/thedarkcolour/exdeorum/block/MechanicalHammerBlock.java @@ -67,7 +67,7 @@ public class MechanicalHammerBlock extends MachineBlock { @SuppressWarnings("deprecation") var nbt = customData.getUnsafe(); - if (nbt.contains("progress") && nbt.getInt("progress") != MechanicalHammerBlockEntity.NOT_RUNNING) { + if (nbt.contains("progress") && nbt.getIntOr("progress", MechanicalHammerBlockEntity.NOT_RUNNING) != MechanicalHammerBlockEntity.NOT_RUNNING) { state = state.setValue(RUNNING, true); } } diff --git a/src/main/java/thedarkcolour/exdeorum/block/SieveBlock.java b/src/main/java/thedarkcolour/exdeorum/block/SieveBlock.java index 2367d8a3..c9550170 100644 --- a/src/main/java/thedarkcolour/exdeorum/block/SieveBlock.java +++ b/src/main/java/thedarkcolour/exdeorum/block/SieveBlock.java @@ -21,6 +21,7 @@ package thedarkcolour.exdeorum.block; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.CollisionContext; @@ -55,19 +56,15 @@ public class SieveBlock extends EBlock { } @Override - public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean pIsMoving) { - if (!level.isClientSide()) { - if (!state.is(newState.getBlock())) { - if (level.getBlockEntity(pos) instanceof AbstractSieveBlockEntity sieve) { - var mesh = sieve.getLogic().getMesh(); + public void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) { + if (level.getBlockEntity(pos) instanceof AbstractSieveBlockEntity sieve) { + var mesh = sieve.getLogic().getMesh(); - if (!mesh.isEmpty()) { - dropItem(level, pos, mesh); - } - } + if (!mesh.isEmpty()) { + dropItem(level, pos, mesh); } } - super.onRemove(state, level, pos, newState, pIsMoving); + super.affectNeighborsAfterRemoval(state, level, pos, movedByPiston); } } diff --git a/src/main/java/thedarkcolour/exdeorum/block/WitchWaterBlock.java b/src/main/java/thedarkcolour/exdeorum/block/WitchWaterBlock.java index 317f17e2..74500d82 100644 --- a/src/main/java/thedarkcolour/exdeorum/block/WitchWaterBlock.java +++ b/src/main/java/thedarkcolour/exdeorum/block/WitchWaterBlock.java @@ -19,27 +19,26 @@ package thedarkcolour.exdeorum.block; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.NbtOps; import net.minecraft.world.Difficulty; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.*; +import net.minecraft.world.entity.InsideBlockEffectApplier; import net.minecraft.world.entity.animal.cow.MushroomCow; import net.minecraft.world.entity.animal.rabbit.Rabbit; import net.minecraft.world.entity.animal.axolotl.Axolotl; import net.minecraft.world.entity.monster.Creeper; -import net.minecraft.world.entity.monster.zombie.Zombie; +import net.minecraft.world.entity.monster.zombie.ZombieVillager; import net.minecraft.world.entity.npc.villager.Villager; import net.minecraft.world.entity.npc.villager.VillagerProfession; import net.minecraft.world.level.Level; -import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.LiquidBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FlowingFluid; -import net.neoforged.neoforge.event.EventHooks; import org.jetbrains.annotations.Nullable; import thedarkcolour.exdeorum.config.EConfig; +import java.lang.reflect.Method; import java.util.function.Supplier; public class WitchWaterBlock extends LiquidBlock { @@ -48,7 +47,7 @@ public class WitchWaterBlock extends LiquidBlock { } @Override - public void entityInside(BlockState pState, Level level, BlockPos pPos, Entity entity) { + protected void entityInside(BlockState pState, Level level, BlockPos pPos, Entity entity, InsideBlockEffectApplier pEffectApplier, boolean pCanTriggerEffects) { if (!level.isClientSide()) { witchWaterEntityEffects(level, entity); } @@ -64,23 +63,15 @@ public class WitchWaterBlock extends LiquidBlock { var villager = (Villager) entity; if (level.getDifficulty() != Difficulty.PEACEFUL) { - if (!villager.isBaby() && villager.getVillagerData().getProfession() == VillagerProfession.CLERIC) { - if (attemptToConvertEntity(level, villager, EntityType.WITCH) != null) { - villager.releaseAllPois(); - } + if (!villager.isBaby() && villager.getVillagerData().profession().is(VillagerProfession.CLERIC)) { + attemptToConvertEntity(level, villager, EntityType.WITCH); } else { - var zombieVillager = villager.convertTo(EntityType.ZOMBIE_VILLAGER, false); - if (zombieVillager != null) { - EventHooks.finalizeMobSpawn(zombieVillager, (ServerLevelAccessor) level, level.getCurrentDifficultyAt(zombieVillager.blockPosition()), MobSpawnType.CONVERSION, new Zombie.ZombieGroupData(false, true)); + villager.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(villager, false, false), EntitySpawnReason.CONVERSION, (ZombieVillager zombieVillager) -> { zombieVillager.setVillagerData(villager.getVillagerData()); - zombieVillager.setGossips(villager.getGossips().store(NbtOps.INSTANCE)); + zombieVillager.setGossips(villager.getGossips().copy()); zombieVillager.setTradeOffers(villager.getOffers().copy()); zombieVillager.setVillagerXp(villager.getVillagerXp()); - - EventHooks.onLivingConvert(villager, zombieVillager); - - villager.discard(); - } + }); } } } else if (entityType == EntityType.SKELETON) { @@ -96,11 +87,11 @@ public class WitchWaterBlock extends LiquidBlock { } else if (entityType == EntityType.HOGLIN) { attemptToConvertEntity(level, entity, EntityType.ZOGLIN); } else if (entityType == EntityType.MOOSHROOM) { - ((MushroomCow) entity).setVariant(MushroomCow.MushroomType.BROWN); + setVariant((MushroomCow) entity, "setVariant", MushroomCow.Variant.class, MushroomCow.Variant.BROWN); } else if (entityType == EntityType.AXOLOTL) { - ((Axolotl) entity).setVariant(Axolotl.Variant.BLUE); + setVariant((Axolotl) entity, "setVariant", Axolotl.Variant.class, Axolotl.Variant.BLUE); } else if (entityType == EntityType.RABBIT) { - ((Rabbit) entity).setVariant(Rabbit.Variant.EVIL); + setVariant((Rabbit) entity, "setVariant", Rabbit.Variant.class, Rabbit.Variant.EVIL); } else if (entityType == EntityType.PUFFERFISH) { attemptToConvertEntity(level, entity, EntityType.GUARDIAN); } else if (entityType == EntityType.HORSE) { @@ -117,36 +108,34 @@ public class WitchWaterBlock extends LiquidBlock { living.addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 210)); living.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, 210, 2)); living.addEffect(new MobEffectInstance(MobEffects.WITHER, 210)); - living.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 210)); + living.addEffect(new MobEffectInstance(MobEffects.SLOWNESS, 210)); } } } @Nullable private static T attemptToConvertEntity(Level level, Entity entity, EntityType newType) { - if (level.getDifficulty() != Difficulty.PEACEFUL && entity instanceof LivingEntity) { - var newEntity = newType.create(level); - - if (newEntity != null) { - var serverLevel = (ServerLevelAccessor) level; - newEntity.copyPosition(entity); - EventHooks.finalizeMobSpawn(newEntity, serverLevel, level.getCurrentDifficultyAt(entity.blockPosition()), MobSpawnType.CONVERSION, null); - newEntity.setNoAi(newEntity.isNoAi()); - + if (level.getDifficulty() != Difficulty.PEACEFUL && entity instanceof Mob mob) { + return mob.convertTo(newType, ConversionParams.single(mob, false, false), EntitySpawnReason.CONVERSION, converted -> { if (entity.hasCustomName()) { - newEntity.setCustomName(entity.getCustomName()); - newEntity.setCustomNameVisible(entity.isCustomNameVisible()); + converted.setCustomName(entity.getCustomName()); + converted.setCustomNameVisible(entity.isCustomNameVisible()); } - newEntity.setPersistenceRequired(); - EventHooks.onLivingConvert((LivingEntity) entity, newEntity); - serverLevel.addFreshEntityWithPassengers(newEntity); - entity.discard(); - } - - return newEntity; + converted.setPersistenceRequired(); + }); } return null; } + + private static void setVariant(T entity, String methodName, Class variantType, V variant) { + try { + Method method = entity.getClass().getDeclaredMethod(methodName, variantType); + method.setAccessible(true); + method.invoke(entity, variant); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to set entity variant for " + entity.getClass().getName(), e); + } + } } diff --git a/src/main/java/thedarkcolour/exdeorum/blockentity/AbstractCrucibleBlockEntity.java b/src/main/java/thedarkcolour/exdeorum/blockentity/AbstractCrucibleBlockEntity.java index e60921a2..49b110da 100644 --- a/src/main/java/thedarkcolour/exdeorum/blockentity/AbstractCrucibleBlockEntity.java +++ b/src/main/java/thedarkcolour/exdeorum/blockentity/AbstractCrucibleBlockEntity.java @@ -19,9 +19,7 @@ package thedarkcolour.exdeorum.blockentity; import net.minecraft.core.BlockPos; -import net.minecraft.core.HolderLookup; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.nbt.CompoundTag; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.resources.Identifier; import net.minecraft.world.InteractionHand; @@ -32,6 +30,8 @@ import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; @@ -48,6 +48,7 @@ import net.neoforged.neoforge.fluids.capability.IFluidHandler; import net.neoforged.neoforge.fluids.capability.templates.FluidTank; import net.neoforged.neoforge.items.IItemHandler; import net.neoforged.neoforge.items.ItemStackHandler; +import net.neoforged.neoforge.transfer.access.ItemAccess; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import thedarkcolour.exdeorum.blockentity.helper.FluidHelper; @@ -85,27 +86,33 @@ public abstract class AbstractCrucibleBlockEntity extends ETankBlockEntity { // NBT @Override - public void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) { - super.saveAdditional(nbt, registries); + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); - nbt.put("Tank", this.tank.writeToNBT(registries, new CompoundTag())); + this.tank.serialize(output.child("tank")); if (this.lastMelted != null) { - nbt.putString("LastMelted", BuiltInRegistries.BLOCK.getKey(this.lastMelted).toString()); + output.putString("lastMelted", BuiltInRegistries.BLOCK.getKey(this.lastMelted).toString()); } if (this.fluid != null) { - nbt.putString("Fluid", BuiltInRegistries.FLUID.getKey(this.fluid).toString()); + output.putString("fluid", BuiltInRegistries.FLUID.getKey(this.fluid).toString()); } - nbt.putShort("Solids", this.solids); + output.putShort("solids", this.solids); } @Override - public void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { - super.loadAdditional(nbt, registries); + public void loadAdditional(ValueInput input) { + super.loadAdditional(input); - this.tank.readFromNBT(registries, nbt.getCompound("Tank")); - this.lastMelted = BuiltInRegistries.BLOCK.get(Identifier.parse(nbt.getString("LastMelted"))); - this.fluid = BuiltInRegistries.FLUID.get(Identifier.parse(nbt.getString("Fluid"))); - this.solids = nbt.getShort("Solids"); + this.tank.deserialize(input.childOrEmpty("tank")); + this.lastMelted = input.getString("lastMelted") + .map(Identifier::parse) + .flatMap(id -> BuiltInRegistries.BLOCK.get(id).map(reference -> reference.value())) + .orElse(null); + this.fluid = input.getString("fluid") + .map(Identifier::parse) + .flatMap(id -> BuiltInRegistries.FLUID.get(id).map(reference -> reference.value())) + .orElse(null); + this.solids = (short) input.getShortOr("solids", (short) 0); updateLight(this.level, this.worldPosition, this.fluid); } @@ -155,7 +162,7 @@ public abstract class AbstractCrucibleBlockEntity extends ETankBlockEntity { public InteractionResult useItemOn(Level level, Player player, ItemStack stack, InteractionHand hand) { var playerItem = player.getItemInHand(hand); - if (playerItem.getCapability(Capabilities.Fluid.ITEM) != null) { + if (getFluidHandler(playerItem) != null) { return FluidUtil.interactWithFluidHandler(player, hand, this.tank) ? InteractionResult.SUCCESS : InteractionResult.TRY_WITH_EMPTY_HAND; } @@ -261,10 +268,19 @@ public abstract class AbstractCrucibleBlockEntity extends ETankBlockEntity { return this.tank; } - public IItemHandler getItem() { + public ItemStackHandler getItem() { return this.item; } + private static IFluidHandler getFluidHandler(ItemStack stack) { + if (stack.isEmpty()) { + return null; + } + + var handler = stack.getCapability(Capabilities.Fluid.ITEM, ItemAccess.forStack(stack)); + return handler == null ? null : IFluidHandler.of(handler); + } + public abstract Block getDefaultMeltBlock(); @Nullable @@ -296,7 +312,12 @@ public abstract class AbstractCrucibleBlockEntity extends ETankBlockEntity { if (key.getPath().endsWith("sapling")) { try { - overrides.put(item, BuiltInRegistries.BLOCK.get(Identifier.fromNamespaceAndPath(key.getNamespace(), key.getPath().replace("sapling", "leaves")))); + var leaves = BuiltInRegistries.BLOCK.get(Identifier.fromNamespaceAndPath(key.getNamespace(), key.getPath().replace("sapling", "leaves"))) + .map(reference -> reference.value()) + .orElse(Blocks.AIR); + if (leaves != Blocks.AIR) { + overrides.put(item, leaves); + } } catch (Exception ignored) { } } diff --git a/src/main/java/thedarkcolour/exdeorum/blockentity/BarrelBlockEntity.java b/src/main/java/thedarkcolour/exdeorum/blockentity/BarrelBlockEntity.java index 3c306839..6a1cd584 100644 --- a/src/main/java/thedarkcolour/exdeorum/blockentity/BarrelBlockEntity.java +++ b/src/main/java/thedarkcolour/exdeorum/blockentity/BarrelBlockEntity.java @@ -19,10 +19,8 @@ package thedarkcolour.exdeorum.blockentity; import net.minecraft.core.BlockPos; -import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ParticleTypes; -import net.minecraft.nbt.CompoundTag; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; @@ -36,6 +34,8 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.alchemy.PotionContents; import net.minecraft.world.item.alchemy.Potions; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.BucketPickup; @@ -54,6 +54,7 @@ import net.neoforged.neoforge.fluids.capability.IFluidHandler; import net.neoforged.neoforge.fluids.capability.templates.FluidTank; import net.neoforged.neoforge.items.IItemHandler; import net.neoforged.neoforge.items.ItemStackHandler; +import net.neoforged.neoforge.transfer.access.ItemAccess; import org.jetbrains.annotations.Nullable; import thedarkcolour.exdeorum.block.BarrelBlock; import thedarkcolour.exdeorum.blockentity.helper.FluidHelper; @@ -66,8 +67,6 @@ import thedarkcolour.exdeorum.recipe.barrel.FluidTransformationRecipe; import thedarkcolour.exdeorum.registry.EBlockEntities; import thedarkcolour.exdeorum.registry.ESounds; -import java.util.Optional; - public class BarrelBlockEntity extends ETankBlockEntity { private static final int MOSS_SPREAD_RANGE = 2; private static final int MAX_CAPACITY = 1000; @@ -93,29 +92,29 @@ public class BarrelBlockEntity extends ETankBlockEntity { } @Override - public void saveAdditional(CompoundTag nbt, HolderLookup.Provider lookup) { - super.saveAdditional(nbt, lookup); + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); - nbt.put("item", this.item.serializeNBT(lookup)); - nbt.put("tank", this.tank.writeToNBT(lookup, new CompoundTag())); - nbt.putShort("compost", this.compost); - nbt.putFloat("progress", this.progress); - nbt.putShort("r", this.r); - nbt.putShort("g", this.g); - nbt.putShort("b", this.b); + this.item.serialize(output.child("item")); + this.tank.serialize(output.child("tank")); + output.putShort("compost", this.compost); + output.putFloat("progress", this.progress); + output.putShort("r", this.r); + output.putShort("g", this.g); + output.putShort("b", this.b); } @Override - public void loadAdditional(CompoundTag nbt, HolderLookup.Provider lookup) { - super.loadAdditional(nbt, lookup); + public void loadAdditional(ValueInput input) { + super.loadAdditional(input); - this.item.deserializeNBT(lookup, nbt.getCompound("item")); - this.tank.readFromNBT(lookup, nbt.getCompound("tank")); - this.compost = nbt.getShort("compost"); - this.progress = nbt.getFloat("progress"); - this.r = nbt.getShort("r"); - this.g = nbt.getShort("g"); - this.b = nbt.getShort("b"); + this.item.deserialize(input.childOrEmpty("item")); + this.tank.deserialize(input.childOrEmpty("tank")); + this.compost = (short) input.getShortOr("compost", (short) 0); + this.progress = input.getFloatOr("progress", 0f); + this.r = (short) input.getShortOr("r", (short) 0); + this.g = (short) input.getShortOr("g", (short) 0); + this.b = (short) input.getShortOr("b", (short) 0); AbstractCrucibleBlockEntity.updateLight(this.level, this.worldPosition, this.tank.getFluid().getFluid()); } @@ -263,7 +262,8 @@ public class BarrelBlockEntity extends ETankBlockEntity { } // Otherwise, mix the item's fluid into the barrel's fluid - var itemFluidCap = playerItem.getCapability(Capabilities.Fluid.ITEM); + var itemAccess = ItemAccess.forPlayerInteraction(player, hand); + var itemFluidCap = getFluidHandler(itemAccess); if (itemFluidCap != null) { var itemFluid = itemFluidCap.drain(1000, IFluidHandler.FluidAction.SIMULATE); BarrelFluidMixingRecipe recipe = RecipeUtil.getFluidMixingRecipe(this.tank.getFluid(), itemFluid.getFluid()); @@ -276,7 +276,6 @@ public class BarrelBlockEntity extends ETankBlockEntity { if (recipe.consumesAdditive()) { itemFluidCap.drain(1000, IFluidHandler.FluidAction.EXECUTE); - player.setItemInHand(hand, itemFluidCap.getContainer()); } } // If a mix was successful, skip rest of logic @@ -478,10 +477,15 @@ public class BarrelBlockEntity extends ETankBlockEntity { } } - public IItemHandler getItemHandler() { + public ItemStackHandler getItemHandler() { return this.item; } + private static IFluidHandler getFluidHandler(ItemAccess itemAccess) { + var handler = itemAccess.getCapability(Capabilities.Fluid.ITEM); + return handler == null ? null : IFluidHandler.of(handler); + } + public static class Ticker implements BlockEntityTicker { @Override public void tick(Level level, BlockPos pos, BlockState state, BarrelBlockEntity barrel) { @@ -595,9 +599,13 @@ public class BarrelBlockEntity extends ETankBlockEntity { } private static ItemStack getRemainderItem(ItemStack stack) { - var food = stack.get(DataComponents.FOOD); - Optional foodRemainder = food == null ? Optional.empty() : food.usingConvertsTo(); - return foodRemainder.map(ItemStack::copy).orElseGet(stack::getCraftingRemainingItem); + var useRemainder = stack.get(DataComponents.USE_REMAINDER); + if (useRemainder != null) { + return useRemainder.convertInto().create(); + } + + var craftingRemainder = stack.getItem().getCraftingRemainder(); + return craftingRemainder != null ? craftingRemainder.create() : ItemStack.EMPTY; } @Override diff --git a/src/main/java/thedarkcolour/exdeorum/blockentity/InfestedLeavesBlockEntity.java b/src/main/java/thedarkcolour/exdeorum/blockentity/InfestedLeavesBlockEntity.java index f196bc0c..74b1a4a1 100644 --- a/src/main/java/thedarkcolour/exdeorum/blockentity/InfestedLeavesBlockEntity.java +++ b/src/main/java/thedarkcolour/exdeorum/blockentity/InfestedLeavesBlockEntity.java @@ -20,7 +20,6 @@ package thedarkcolour.exdeorum.blockentity; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.NbtUtils; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.tags.BlockTags; import net.minecraft.world.level.Level; @@ -33,6 +32,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.neoforged.neoforge.model.data.ModelData; import net.neoforged.neoforge.model.data.ModelProperty; import org.jetbrains.annotations.NotNull; +import net.minecraft.nbt.NbtUtils; import thedarkcolour.exdeorum.block.InfestedLeavesBlock; import thedarkcolour.exdeorum.registry.EBlockEntities; import thedarkcolour.exdeorum.registry.EBlocks; @@ -105,10 +105,8 @@ public class InfestedLeavesBlockEntity extends EBlockEntity { public void loadAdditional(ValueInput input) { super.loadAdditional(input); - var holderLookup = input.lookup().lookupOrThrow(Registries.BLOCK); - this.mimic = input.child("mimic") - .map(child -> NbtUtils.readBlockState(holderLookup, child.asTag())) - .orElse(Blocks.OAK_LEAVES.defaultBlockState()); + input.lookup().lookupOrThrow(Registries.BLOCK); + this.mimic = input.read("mimic", BlockState.CODEC).orElse(Blocks.OAK_LEAVES.defaultBlockState()); this.progress = (short) input.getShortOr("progress", (short) 0); } diff --git a/src/main/java/thedarkcolour/exdeorum/client/ClientHandler.java b/src/main/java/thedarkcolour/exdeorum/client/ClientHandler.java index 7a0c505b..49c853ee 100644 --- a/src/main/java/thedarkcolour/exdeorum/client/ClientHandler.java +++ b/src/main/java/thedarkcolour/exdeorum/client/ClientHandler.java @@ -23,7 +23,6 @@ import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; import net.minecraft.core.Holder; import net.minecraft.core.registries.Registries; -import net.minecraft.util.Unit; import net.minecraft.world.level.levelgen.presets.WorldPreset; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModList; @@ -31,6 +30,7 @@ import net.neoforged.fml.event.config.ModConfigEvent; import net.neoforged.neoforge.client.event.*; import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent; import net.neoforged.neoforge.common.NeoForge; +import net.minecraft.server.packs.resources.ResourceManagerReloadListener; import thedarkcolour.exdeorum.ExDeorum; import thedarkcolour.exdeorum.asm.ASMHooks; import thedarkcolour.exdeorum.client.screen.MechanicalHammerScreen; @@ -68,9 +68,7 @@ public class ClientHandler { } private static void addClientReloadListeners(AddClientReloadListenersEvent event) { - event.addListener(ExDeorum.loc("render_util"), (prepBarrier, resourceManager, prepProfiler, reloadProfiler, backgroundExecutor, gameExecutor) -> { - return prepBarrier.wait(Unit.INSTANCE).thenRunAsync(RenderUtil::reload, gameExecutor); - }); + event.addListener(ExDeorum.loc("render_util"), (ResourceManagerReloadListener) resourceManager -> RenderUtil.reload()); } private static void registerMenuScreens(RegisterMenuScreensEvent event) { @@ -96,7 +94,7 @@ public class ClientHandler { private static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { event.registerBlockEntityRenderer(EBlockEntities.INFESTED_LEAVES.get(), ctx -> new InfestedLeavesRenderer()); - event.registerBlockEntityRenderer(EBlockEntities.BARREL.get(), ctx -> new BarrelRenderer()); + event.registerBlockEntityRenderer(EBlockEntities.BARREL.get(), BarrelRenderer::new); event.registerBlockEntityRenderer(EBlockEntities.LAVA_CRUCIBLE.get(), ctx -> new CrucibleRenderer()); event.registerBlockEntityRenderer(EBlockEntities.WATER_CRUCIBLE.get(), ctx -> new CrucibleRenderer()); event.registerBlockEntityRenderer(EBlockEntities.SIEVE.get(), ctx -> new SieveRenderer<>(0.75f, 15f)); diff --git a/src/main/java/thedarkcolour/exdeorum/client/CompostColors.java b/src/main/java/thedarkcolour/exdeorum/client/CompostColors.java index 68cded8c..fad98c78 100644 --- a/src/main/java/thedarkcolour/exdeorum/client/CompostColors.java +++ b/src/main/java/thedarkcolour/exdeorum/client/CompostColors.java @@ -18,46 +18,32 @@ package thedarkcolour.exdeorum.client; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.mojang.blaze3d.platform.NativeImage; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectSet; -import net.minecraft.client.Minecraft; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; -import net.minecraft.util.GsonHelper; -import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.fml.ModList; -import net.neoforged.fml.loading.FMLEnvironment; -import org.apache.commons.io.IOUtils; -import org.jetbrains.annotations.Nullable; import org.joml.Vector3i; import thedarkcolour.exdeorum.ExDeorum; -import thedarkcolour.exdeorum.compat.ModIds; -import javax.imageio.ImageIO; -import java.awt.Color; import java.io.BufferedReader; import java.io.BufferedWriter; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; import java.util.stream.Collectors; -// ExDeorum comes with a precomputed list of vanilla colors, since textures don't exist on the server. -// However, modded textures usually DO exist on the server, so their colors can be computed by the server once -// and stored in a file which can be configured by the user after the fact. +// Server-safe compost color loader. Vanilla colors come from the bundled text file, +// while modded overrides are read from config/exdeorum/compost_colors. public class CompostColors { public static final String VANILLA_COMPOST_COLORS_FILE = "vanilla_compost_colors.txt"; public static final Path COMPOST_COLORS_CONFIGS = Paths.get("config/exdeorum/compost_colors"); @@ -67,7 +53,6 @@ public class CompostColors { public static void loadColors() { COLORS.clear(); - loadVanilla(); loadModded(); } @@ -77,280 +62,118 @@ public class CompostColors { } private static void loadVanilla() { - var vanillaColors = ModList.get().getModFileById(ExDeorum.ID).getFile().findResource(CompostColors.VANILLA_COMPOST_COLORS_FILE); + try (var stream = CompostColors.class.getClassLoader().getResourceAsStream(VANILLA_COMPOST_COLORS_FILE)) { + if (stream == null) { + ExDeorum.LOGGER.error("Failed to load bundled vanilla compost colors"); + return; + } - if (!Files.exists(vanillaColors)) { - ExDeorum.LOGGER.error("Failed to load vanilla colors!"); - } else { - readColorFile(ModIds.MINECRAFT, vanillaColors); + try (var reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + readColorEntries("minecraft", reader, VANILLA_COMPOST_COLORS_FILE); + } + } catch (IOException exception) { + ExDeorum.LOGGER.error("Failed to read bundled vanilla compost colors", exception); } } - // Used to generate the list of vanilla colors shipped with the Ex Deorum jar - // TODO: port debugCompute to MC 26.x (ItemRenderer/getItemModelShaper/BakedModel.getParticleIcon removed) - public static void debugCompute() { - throw new UnsupportedOperationException("debugCompute not yet ported to MC 26.x"); - } - private static void loadModded() { - var readMods = readModdedColorFiles(); - - for (var entry : BuiltInRegistries.ITEM.entrySet()) { - var key = entry.getKey().identifier(); - var modid = key.getNamespace(); - - if (!readMods.contains(modid)) { - var id = key.getPath(); - var modFile = ModList.get().getModFileById(modid); - if (modFile == null) - continue; - var jarFile = modFile.getFile(); - var modelPath = jarFile.findResource("assets/" + modid + "/models/item/" + id + ".json"); - - if (Files.exists(modelPath)) { - JsonObject modelJson = parseModelJson(modelPath, modid, id); - - if (modelJson != null) { - var textures = modelJson.get("textures"); - - if (textures instanceof JsonObject textureMap) { - String texture = findFirstTexture(textureMap); - - if (texture != null) { - // Best case scenario, we are in a plain old 2D item. - var texturePath = jarFile.findResource("assets/" + modid + "/textures/" + texture + ".png"); - - if (Files.exists(texturePath)) { - try (var stream = Files.newInputStream(texturePath)) { - var img = ImageIO.read(stream); - int width = img.getWidth(); - int height = img.getHeight(); - int pixels = 0; - int totalR = 0; - int totalG = 0; - int totalB = 0; - - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - int pixel = img.getRGB(x, y); - if (pixel != 0) { - totalR += (pixel >> 16) & 0xff; - totalG += (pixel >> 8) & 0xff; - totalB += (pixel) & 0xff; - pixels++; - } - } - } - - putColor(pixels, totalR, totalG, totalB, entry.getValue()); - - if (ExDeorum.DEBUG) { - ExDeorum.LOGGER.debug("Item {}:{} has color {}", modid, id, COLORS.get(entry.getValue())); - } - } catch (IOException exception) { - ExDeorum.LOGGER.error("Failed to read texture file for item {}:{}", modid, id); - } - } - } - } - } - } - } - } - - // todo should i sort the registry before iterating it, or should I keep the sort here? - Map> entries = COLORS.keySet().stream() - .sorted(Comparator.comparing(BuiltInRegistries.ITEM::getKey)) - .collect(Collectors.groupingBy(item -> BuiltInRegistries.ITEM.getKey(item).getNamespace())); - - for (var entry : entries.entrySet()) { - if (!readMods.contains(entry.getKey())) { - export(entry.getKey(), entry.getValue()); - } - } - } - - @Nullable - private static String findFirstTexture(JsonObject textureMap) { - if (textureMap.get("layer0") instanceof JsonPrimitive primitive) { - return Identifier.parse(primitive.getAsString()).getPath(); - } - - return null; - } - - // Returns a set of the mod ids that were read - private static ObjectSet readModdedColorFiles() { var colorsFolder = COMPOST_COLORS_CONFIGS.toFile(); + if (!colorsFolder.exists() || !colorsFolder.isDirectory()) { + return; + } - // Minecraft is hardcoded in the Ex Deorum jar file - var readMods = new ObjectOpenHashSet(); - readMods.add("minecraft"); + var children = colorsFolder.list(); + if (children == null) { + return; + } - if (colorsFolder.exists() && colorsFolder.isDirectory()) { - var children = colorsFolder.list(); + for (var child : children) { + if (!child.endsWith(".txt")) { + continue; + } - if (children != null) { - // child should be "modid.txt" - for (var child : children) { - if (child.endsWith(".txt")) { - var modid = child.replace(".txt", ""); + var modid = child.substring(0, child.length() - 4); + var path = COMPOST_COLORS_CONFIGS.resolve(child); + try (var reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + readColorEntries(modid, reader, path.toString()); + } catch (IOException exception) { + ExDeorum.LOGGER.error("Error reading compost colors file {}", path, exception); + } + } + } - if (ModList.get().isLoaded(modid) && !modid.equals("minecraft")) { - if (readColorFile(modid, COMPOST_COLORS_CONFIGS.resolve(child))) { - readMods.add(modid); - } - } - } + private static void readColorEntries(String modid, BufferedReader reader, String source) throws IOException { + int lineNumber = 0; + String line; + while ((line = reader.readLine()) != null) { + lineNumber++; + if (line.isBlank() || line.startsWith("//")) { + continue; + } + + var tokenizer = new StringTokenizer(line, ", #"); + try { + var id = Identifier.fromNamespaceAndPath(modid, tokenizer.nextToken()); + var item = BuiltInRegistries.ITEM.get(id).map(reference -> reference.value()).orElse(Items.AIR); + int color = Integer.parseInt(tokenizer.nextToken(), 16); + + if (item == Items.AIR) { + ExDeorum.LOGGER.error("Unknown item {} in compost colors source {} line {}", id, source, lineNumber); + continue; } + + COLORS.put(item, new Vector3i((color >> 16) & 255, (color >> 8) & 255, color & 255)); + } catch (IllegalArgumentException | NoSuchElementException exception) { + ExDeorum.LOGGER.error("Invalid compost color entry in {} line {}", source, lineNumber, exception); } } - - return readMods; - } - - @Nullable - private static JsonObject parseModelJson(Path modelPath, String modid, String id) { - try (var stream = Files.newInputStream(modelPath)) { - try (var streamReader = new InputStreamReader(stream)) { - try { - return GsonHelper.parse(IOUtils.toString(streamReader)); - } catch (JsonParseException exception) { - ExDeorum.LOGGER.error("Failed to parse model file for item {}:{}", modid, id); - } - } - } catch (IOException exception) { - ExDeorum.LOGGER.error("Failed to read model file for item {}:{}", modid, id); - } - - return null; - } - - private static void putColor(int pixels, int totalR, int totalG, int totalB, Item item) { - if (pixels > 0 && (totalR | totalG | totalB) != 0) { - var tint = getTint(item); - Color c; - if (tint == 0) { - c = new Color(totalR / pixels, totalG / pixels, totalB / pixels).brighter(); - } else { - c = new Color( - ((float) totalR / pixels / 255f) * ((tint >> 16 & 0xff) / 255f), - ((float) totalG / pixels / 255f) * ((tint >> 8 & 0xff) / 255f), - ((float) totalB / pixels / 255f) * ((tint & 0xff) / 255f) - ); - } - - Vector3i color = new Vector3i(c.getRed(), c.getGreen(), c.getBlue()); - CompostColors.COLORS.put(item, color); - } } - private static int getTint(Item item) { - if (ExDeorum.DEBUG && FMLEnvironment.dist == Dist.CLIENT) { - return Minecraft.getInstance().getItemColors().getColor(new ItemStack(item), 0); - } else { - return 0; - } - } - - private static boolean readColorFile(String modid, Path path) { - try (var stream = Files.newInputStream(path)) { - try (var streamReader = new InputStreamReader(stream)) { - try (var reader = new BufferedReader(streamReader)) { - int readColors = 0; - int lineNumber = 0; - String line; - - while ((line = reader.readLine()) != null) { - lineNumber++; - if (line.startsWith("//")) continue; - - var tokenizer = new StringTokenizer(line, ", #"); - try { - var id = Identifier.fromNamespaceAndPath(modid, tokenizer.nextToken()); - var item = BuiltInRegistries.ITEM.get(id); - String token = tokenizer.nextToken(); - var color = Integer.parseInt(token, 16); - - if (item != Items.AIR) { - readColors++; - - COLORS.put(item, new Vector3i( - (color >> 16) & 255, - (color >> 8) & 255, - (color) & 255 - )); - } else { - ExDeorum.LOGGER.error("Failed to read line {} of compost colors file {} - Unknown item {}", lineNumber, path, id); - } - } catch (NumberFormatException | NoSuchElementException e) { - ExDeorum.LOGGER.error("Failed to read line {} of compost colors file {} - Invalid format: {}", lineNumber, path, e.getMessage()); - } - } - - if (readColors > 0) { - ExDeorum.LOGGER.debug("Read {} compost colors from compost colors file {}", readColors, path); - return true; - } else { - ExDeorum.LOGGER.debug("Ignoring empty compost colors file {}", path); - return false; - } - } - } - } catch (IOException e) { - ExDeorum.LOGGER.error("Error reading colors file {} : {}", path, e); - } - - return false; + public static void debugCompute() { + throw new UnsupportedOperationException("debugCompute is not ported to MC 26.x"); } public static void export(String modid) { - export(modid, COLORS.keySet().stream().filter(key -> BuiltInRegistries.ITEM.getKey(key).getNamespace().equals(modid)).sorted(Comparator.comparing(BuiltInRegistries.ITEM::getKey)).toList()); + export(modid, COLORS.keySet().stream() + .filter(item -> BuiltInRegistries.ITEM.getKey(item).getNamespace().equals(modid)) + .sorted(Comparator.comparing(BuiltInRegistries.ITEM::getKey)) + .toList()); } - // The given list should be sorted private static void export(String modid, List sortedToExport) { try { - if (createConfigFolder(COMPOST_COLORS_CONFIGS)) { - var path = COMPOST_COLORS_CONFIGS.resolve(modid + ".txt"); - var file = path.toFile(); - - if ((file.exists() && file.delete()) || file.createNewFile()) { - try (var fileWriter = new FileWriter(file)) { - try (var writer = new BufferedWriter(fileWriter)) { - // sort file entries alphabetically - var alphabeticalItems = new ArrayList<>(sortedToExport); - alphabeticalItems.sort(Comparator.comparing(item -> BuiltInRegistries.ITEM.getKey(item).getPath())); - - writer.write("// Compost colors for " + modid + ". You may add your own colors, change existing ones, or remove colors that aren't needed.\n"); - - for (var item : alphabeticalItems) { - if (COLORS.containsKey(item)) { - writer.write(BuiltInRegistries.ITEM.getKey(item).getPath()); - writer.write(", #"); - var colorVec = COLORS.get(item); - writer.write(Integer.toHexString(new Color(colorVec.x, colorVec.y, colorVec.z).getRGB() & 0xffffff)); - writer.write('\n'); - } - } - - // Skips the error message - return; - } - } - } + if (!createConfigFolder(COMPOST_COLORS_CONFIGS)) { + ExDeorum.LOGGER.error("Unable to create compost color config folder for {}", modid); + return; } - ExDeorum.LOGGER.error("Unable to save compost colors for mod \"{}\"", modid); - } catch (IOException e) { - ExDeorum.LOGGER.error("Encountered exception while trying to save compost colors for mod \"{}\"", modid, e); + var path = COMPOST_COLORS_CONFIGS.resolve(modid + ".txt"); + try (var writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { + writer.write("// Compost colors for " + modid + ".\n"); + + var alphabeticalItems = new ArrayList<>(sortedToExport); + alphabeticalItems.sort(Comparator.comparing(item -> BuiltInRegistries.ITEM.getKey(item).getPath())); + + for (var item : alphabeticalItems) { + var color = COLORS.get(item); + if (color == null) { + continue; + } + + writer.write(BuiltInRegistries.ITEM.getKey(item).getPath()); + writer.write(", #"); + writer.write(String.format("%02x%02x%02x", color.x, color.y, color.z)); + writer.write('\n'); + } + } + } catch (IOException exception) { + throw new UncheckedIOException("Failed to export compost colors for " + modid, exception); } } public static boolean createConfigFolder(Path configPath) { var colorsFolder = configPath.toFile(); var configFolder = configPath.getParent().toFile(); - return (configFolder.exists() || configFolder.mkdir()) && (colorsFolder.exists() || colorsFolder.mkdir()); } } diff --git a/src/main/java/thedarkcolour/exdeorum/client/RenderUtil.java b/src/main/java/thedarkcolour/exdeorum/client/RenderUtil.java index 033b985e..c1ae1fb1 100644 --- a/src/main/java/thedarkcolour/exdeorum/client/RenderUtil.java +++ b/src/main/java/thedarkcolour/exdeorum/client/RenderUtil.java @@ -23,12 +23,19 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.Sheets; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.FluidModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; -import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.util.RandomSource; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.material.Fluid; @@ -37,14 +44,15 @@ import thedarkcolour.exdeorum.ExDeorum; import thedarkcolour.exdeorum.client.ter.SieveRenderer; import java.awt.Color; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; public class RenderUtil { private static final Map TOP_FACES = new HashMap<>(); - // TODO: port TINTED_CUTOUT_MIPPED to MC 26.x RenderSetup API (RenderStateShard/CompositeState removed) - public static final Object TINTED_CUTOUT_MIPPED = null; - public static TextureAtlas blockAtlas; + public static final RenderType TINTED_CUTOUT_MIPPED = Sheets.cutoutBlockItemSheet(); public static final IrisAccess IRIS_ACCESS; static { @@ -53,49 +61,40 @@ public class RenderUtil { public static void reload() { invalidateCaches(); - blockAtlas = Minecraft.getInstance().getModelManager().getAtlas(InventoryMenu.BLOCK_ATLAS); } public static void invalidateCaches() { SieveRenderer.MESH_TEXTURES.clear(); TOP_FACES.clear(); - blockAtlas = null; } - // TODO: port getTopFace to MC 26.x (BakedModel/BlockRenderDispatcher removed; use FluidStateModelSet/BlockStateModelSet) public static RenderFace getTopFaceOrDefault(Block block, Block defaultBlock) { - return getTopFace(block); + var face = getTopFace(block); + return face.isMissingTexture() ? getTopFace(defaultBlock) : face; } public static RenderFace getTopFace(Block block) { - return TOP_FACES.computeIfAbsent(block, b -> { - // TODO: implement using 26.x block model API - // Placeholder: use missing texture sprite - var sprite = blockAtlas != null ? blockAtlas.getSprite(MissingTextureAtlasSprite.getLocation()) : null; - return new RenderFace.Single(null, sprite); - }); + return TOP_FACES.computeIfAbsent(block, RenderUtil::loadTopFace); } public static boolean isMissingTexture(TextureAtlasSprite sprite) { return sprite.contents().name() == MissingTextureAtlasSprite.getLocation(); } - // TODO: port renderFlatFluidSprite to 26.x (IClientFluidTypeExtensions no longer has getStillTexture/getTintColor) public static void renderFlatFluidSprite(MultiBufferSource buffers, PoseStack stack, Level level, BlockPos pos, float y, float edge, int light, int r, int g, int b, Fluid fluid) { - if (blockAtlas == null) return; - var builder = buffers.getBuffer(Sheets.translucentBlockSheet()); - // Use a placeholder sprite until fluid model system is ported - var sprite = blockAtlas.getSprite(MissingTextureAtlasSprite.getLocation()); + var builder = buffers.getBuffer(getFluidRenderType(fluid)); + var sprite = getFluidSprite(fluid); RenderUtil.renderFlatSprite(builder, stack, y, r, g, b, sprite, light, edge); } - // TODO: port renderFluidCube to 26.x (IClientFluidTypeExtensions no longer has getStillTexture/getTintColor) + public static void renderFlatFluidSprite(VertexConsumer builder, PoseStack.Pose pose, float y, float edge, int light, int r, int g, int b, Fluid fluid) { + RenderUtil.renderFlatSprite(builder, pose, y, r, g, b, getFluidSprite(fluid), light, edge); + } + @SuppressWarnings("DuplicatedCode") public static void renderFluidCube(MultiBufferSource buffers, PoseStack stack, Level level, BlockPos pos, float minY, float maxY, float edge, int light, int r, int g, int b, Fluid fluid) { - if (blockAtlas == null) return; - var builder = buffers.getBuffer(Sheets.translucentBlockSheet()); - // Use a placeholder sprite until fluid model system is ported - var sprite = blockAtlas.getSprite(MissingTextureAtlasSprite.getLocation()); + var builder = buffers.getBuffer(getFluidRenderType(fluid)); + var sprite = getFluidSprite(fluid); var pose = stack.last().pose(); var poseNormal = stack.last().normal(); @@ -148,12 +147,21 @@ public class RenderUtil { builder.addVertex(pose, edgeMin, minY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setLight(light).setNormal(normal.x, normal.y, normal.z); } + public static void renderFluidCube(VertexConsumer builder, PoseStack.Pose pose, float minY, float maxY, float edge, int light, int r, int g, int b, Fluid fluid) { + RenderUtil.renderCuboid(builder, pose, minY, maxY, r, g, b, getFluidSprite(fluid), light, edge); + } + // Renders a sprite inside the barrel with the height determined by how full the barrel is. public static void renderFlatSpriteLerp(VertexConsumer builder, PoseStack stack, float percentage, int r, int g, int b, TextureAtlasSprite sprite, int light, float edge, float yMin, float yMax) { float y = Mth.lerp(percentage, yMin, yMax) / 16f; renderFlatSprite(builder, stack, y, r, g, b, sprite, light, edge); } + public static void renderFlatSpriteLerp(VertexConsumer builder, PoseStack.Pose pose, float percentage, int r, int g, int b, TextureAtlasSprite sprite, int light, float edge, float yMin, float yMax) { + float y = Mth.lerp(percentage, yMin, yMax) / 16f; + renderFlatSprite(builder, pose, y, r, g, b, sprite, light, edge); + } + // Renders a sprite (y should be between 0 and 1) @SuppressWarnings("DuplicatedCode") public static void renderFlatSprite(VertexConsumer builder, PoseStack stack, float y, int r, int g, int b, TextureAtlasSprite sprite, int light, float edge) { @@ -178,13 +186,95 @@ public class RenderUtil { builder.addVertex(pose, edgeMax, y, edgeMin).setColor(r, g, b, 255).setUv(uMax, vMin).setUv1(0, 10).setLight(light).setNormal(normal.x, normal.y, normal.z); } + public static void renderFlatSprite(VertexConsumer builder, PoseStack.Pose pose, float y, int r, int g, int b, TextureAtlasSprite sprite, int light, float edge) { + if (sprite == null) return; + var normal = pose.normal().transform(new Vector3f(0, 1, 0)); + float edgeMin = edge / 16.0f; + float edgeMax = (16.0f - edge) / 16.0f; + float uMin = sprite.getU0(); + float uMax = sprite.getU1(); + float vMin = sprite.getV0(); + float vMax = sprite.getV1(); + + builder.addVertex(pose.pose(), edgeMin, y, edgeMin).setColor(r, g, b, 255).setUv(uMin, vMin).setUv1(0, 10).setLight(light).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMin, y, edgeMax).setColor(r, g, b, 255).setUv(uMin, vMax).setUv1(0, 10).setLight(light).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, y, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setLight(light).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, y, edgeMin).setColor(r, g, b, 255).setUv(uMax, vMin).setUv1(0, 10).setLight(light).setNormal(normal.x, normal.y, normal.z); + } + public static Color getRainbowColor(long time, float partialTicks) { return Color.getHSBColor((180 * Mth.sin((time + partialTicks) / 30.0f) - 180) / 360.0f, 0.5f, 0.8f); } - // TODO: port getFluidColor to 26.x (IClientFluidTypeExtensions no longer has getTintColor) + public static TextureAtlasSprite getBlockSprite(Identifier location) { + return ((TextureAtlas) Minecraft.getInstance().getTextureManager().getTexture(TextureAtlas.LOCATION_BLOCKS)).getSprite(location); + } + public static int getFluidColor(Fluid fluid, Level level, BlockPos pos) { - return -1; // white/no tint; use FluidModel.fluidTintSource() in 26.x + var tintSource = getFluidModel(fluid).fluidTintSource(); + if (tintSource == null) { + return -1; + } + if (level instanceof BlockAndTintGetter getter) { + return tintSource.colorInWorld(fluid.defaultFluidState(), level.getBlockState(pos), getter, pos); + } + return tintSource.color(fluid.defaultFluidState()); + } + + private static RenderFace loadTopFace(Block block) { + var state = block.defaultBlockState(); + var model = Minecraft.getInstance().getModelManager().getBlockStateModelSet().get(state); + var random = RandomSource.create(block.hashCode()); + List parts = new ArrayList<>(); + model.collectParts(BlockAndTintGetter.EMPTY, BlockPos.ZERO, state, random, parts); + + var layers = new LinkedHashMap(); + for (var part : parts) { + for (var quad : part.getQuads(Direction.UP)) { + var materialInfo = quad.materialInfo(); + var key = materialInfo.itemRenderType() + "::" + materialInfo.sprite().contents().name(); + layers.putIfAbsent(key, new RenderFace.CompositeLayer(materialInfo.itemRenderType(), materialInfo.sprite())); + } + } + + if (layers.isEmpty()) { + var particle = getTopTexture(block, state); + return new RenderFace.Single(inferMaterialRenderType(particle), particle); + } + + if (layers.size() == 1) { + return new RenderFace.Single(layers.values().iterator().next().renderType(), layers.values().iterator().next().sprite()); + } + + return new RenderFace.Composite(layers.values().toArray(RenderFace.CompositeLayer[]::new)); + } + + private static TextureAtlasSprite getTopTexture(Block block, net.minecraft.world.level.block.state.BlockState state) { + var registryName = BuiltInRegistries.BLOCK.getKey(block); + var sprite = getBlockSprite(registryName.withPrefix("block/")); + if (isMissingTexture(sprite)) { + sprite = getBlockSprite(Identifier.fromNamespaceAndPath(registryName.getNamespace(), "block/" + registryName.getPath() + "_top")); + } + if (isMissingTexture(sprite)) { + sprite = Minecraft.getInstance().getModelManager().getBlockStateModelSet().getParticleMaterial(state).sprite(); + } + return sprite; + } + + private static RenderType inferMaterialRenderType(TextureAtlasSprite sprite) { + return sprite.transparency().hasTranslucent() ? Sheets.translucentBlockItemSheet() : Sheets.cutoutBlockItemSheet(); + } + + private static FluidModel getFluidModel(Fluid fluid) { + return Minecraft.getInstance().getModelManager().getFluidStateModelSet().get(fluid.defaultFluidState()); + } + + public static TextureAtlasSprite getFluidSprite(Fluid fluid) { + return getFluidModel(fluid).stillMaterial().sprite(); + } + + public static RenderType getFluidRenderType(Fluid fluid) { + return getFluidModel(fluid).layer().translucent() ? Sheets.translucentBlockItemSheet() : Sheets.cutoutBlockItemSheet(); } // todo use ambient occlusion @@ -249,6 +339,62 @@ public class RenderUtil { builder.addVertex(pose, edgeMin, minY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); } + public static void renderCuboid(VertexConsumer builder, PoseStack.Pose pose, float minY, float maxY, int r, int g, int b, TextureAtlasSprite sprite, int light, float edge) { + if (sprite == null) return; + var poseNormal = pose.normal(); + + Vector3f normal; + float uMin = sprite.getU0(); + float uMax = sprite.getU1(); + float vMin = sprite.getV0(); + float vMax = sprite.getV1(); + + float edgeMin = edge / 16f; + float edgeMax = 1f - edge / 16f; + + int lightU = light & '\uffff'; + int lightV = light >> 16 & '\uffff'; + + normal = poseNormal.transform(new Vector3f(0, 1, 0)); + builder.addVertex(pose.pose(), edgeMin, maxY, edgeMin).setColor(r, g, b, 255).setUv(uMin, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMin, maxY, edgeMax).setColor(r, g, b, 255).setUv(uMin, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, maxY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, maxY, edgeMin).setColor(r, g, b, 255).setUv(uMax, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + + normal = poseNormal.transform(new Vector3f(0, -1, 0)); + builder.addVertex(pose.pose(), edgeMin, minY, edgeMin).setColor(r, g, b, 255).setUv(uMin, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, minY, edgeMin).setColor(r, g, b, 255).setUv(uMax, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, minY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMin, minY, edgeMax).setColor(r, g, b, 255).setUv(uMin, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + + float f = sprite.getV1() - sprite.getV0(); + vMax = sprite.getV0() + f * (maxY - minY); + + normal = poseNormal.transform(new Vector3f(0, 0, -1)); + builder.addVertex(pose.pose(), edgeMax, maxY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMin, maxY, edgeMax).setColor(r, g, b, 255).setUv(uMin, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMin, minY, edgeMax).setColor(r, g, b, 255).setUv(uMin, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, minY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + + normal = poseNormal.transform(new Vector3f(0, 0, -1)); + builder.addVertex(pose.pose(), edgeMin, maxY, edgeMin).setColor(r, g, b, 255).setUv(uMin, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, maxY, edgeMin).setColor(r, g, b, 255).setUv(uMax, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, minY, edgeMin).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMin, minY, edgeMin).setColor(r, g, b, 255).setUv(uMin, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + + normal = poseNormal.transform(new Vector3f(1, 0, 0)); + builder.addVertex(pose.pose(), edgeMax, maxY, edgeMin).setColor(r, g, b, 255).setUv(uMin, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, maxY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, minY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMax, minY, edgeMin).setColor(r, g, b, 255).setUv(uMin, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + + normal = poseNormal.transform(new Vector3f(-1, 0, 0)); + builder.addVertex(pose.pose(), edgeMin, maxY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMin, maxY, edgeMin).setColor(r, g, b, 255).setUv(uMin, vMin).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMin, minY, edgeMin).setColor(r, g, b, 255).setUv(uMin, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + builder.addVertex(pose.pose(), edgeMin, minY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); + } + public interface IrisAccess { boolean areShadersEnabled(); } diff --git a/src/main/java/thedarkcolour/exdeorum/client/screen/MechanicalHammerScreen.java b/src/main/java/thedarkcolour/exdeorum/client/screen/MechanicalHammerScreen.java index 6233c81a..6a4c2201 100644 --- a/src/main/java/thedarkcolour/exdeorum/client/screen/MechanicalHammerScreen.java +++ b/src/main/java/thedarkcolour/exdeorum/client/screen/MechanicalHammerScreen.java @@ -43,10 +43,7 @@ public class MechanicalHammerScreen extends AbstractContainerScreen { +public class BarrelRenderer implements BlockEntityRenderer { public static final Identifier COMPOST_DIRT_TEXTURE = ExDeorum.loc("block/compost_dirt"); + private static final BlockDisplayContext BLOCK_DISPLAY_CONTEXT = BlockDisplayContext.create(); + private final net.minecraft.client.renderer.block.BlockModelResolver blockModelResolver; + private final ItemModelResolver itemModelResolver; - @Override - public BlockEntityRenderState createRenderState() { - return new BlockEntityRenderState(); + public BarrelRenderer(BlockEntityRendererProvider.Context ctx) { + this.blockModelResolver = ctx.blockModelResolver(); + this.itemModelResolver = ctx.itemModelResolver(); } @Override - public void submit(BlockEntityRenderState state, PoseStack stack, SubmitNodeCollector collector, CameraRenderState cameraState) { - // TODO: implement barrel fluid/compost/item rendering using new 26.x rendering API + public BarrelRenderState createRenderState() { + return new BarrelRenderState(); + } + + @Override + public void extractRenderState(BarrelBlockEntity barrel, BarrelRenderState state, float partialTicks, net.minecraft.world.phys.Vec3 cameraPosition, net.minecraft.client.renderer.feature.ModelFeatureRenderer.CrumblingOverlay breakProgress) { + BlockEntityRenderer.super.extractRenderState(barrel, state, partialTicks, cameraPosition, breakProgress); + + state.blockItemModel.clear(); + state.outputItem.clear(); + state.renderBlockItem = false; + state.fluid = null; + state.hasFluid = false; + state.hasCompost = false; + state.transparent = barrel.transparent; + + var item = barrel.getItem(); + if (!item.isEmpty()) { + if (item.getItem() instanceof BlockItem blockItem) { + this.blockModelResolver.update(state.blockItemModel, blockItem.getBlock().defaultBlockState(), BLOCK_DISPLAY_CONTEXT); + state.renderBlockItem = true; + } else { + this.itemModelResolver.updateForTopItem(state.outputItem, item, ItemDisplayContext.FIXED, barrel.getLevel(), null, 0); + } + } + + var fluidStack = barrel.getTank().getFluidInTank(0); + if (!fluidStack.isEmpty() && barrel.getLevel() != null) { + var fluid = fluidStack.getFluid(); + var percentage = fluidStack.getAmount() / 1000.0f; + var y = Mth.lerp(percentage, BarrelBlock.BARREL_FLUID_BOTTOM, BarrelBlock.BARREL_FLUID_TOP); + var inputFluidColor = RenderUtil.getFluidColor(fluid, barrel.getLevel(), barrel.getBlockPos()); + int r = (inputFluidColor >> 16) & 0xff; + int g = (inputFluidColor >> 8) & 0xff; + int b = inputFluidColor & 0xff; + + if (barrel.isBrewing()) { + float progress = barrel.progress; + r = (int) Mth.lerp(progress, r, barrel.r); + g = (int) Mth.lerp(progress, g, barrel.g); + b = (int) Mth.lerp(progress, b, barrel.b); + } + + state.fluid = fluid; + state.hasFluid = true; + state.fluidY = y; + state.fluidColor = packRgb(r, g, b); + } + + if (barrel.compost > 0) { + float compostProgress = barrel.progress; + int r; + int g; + int b; + + if (ExDeorum.IS_JUNE && EConfig.CLIENT.rainbowCompostDuringJune.get() && barrel.getLevel() != null) { + var rainbow = RenderUtil.getRainbowColor(barrel.getLevel().getGameTime(), partialTicks); + r = rainbow.getRed(); + g = rainbow.getGreen(); + b = rainbow.getBlue(); + } else { + r = barrel.r; + g = barrel.g; + b = barrel.b; + } + + r = (int) Mth.lerp(compostProgress, r, 238); + g = (int) Mth.lerp(compostProgress, g, 169); + b = (int) Mth.lerp(compostProgress, b, 109); + + state.hasCompost = true; + state.compostPercentage = barrel.compost / 1000.0f; + state.compostColor = packRgb(r, g, b); + } + } + + @Override + public void submit(BarrelRenderState state, PoseStack stack, SubmitNodeCollector collector, CameraRenderState cameraState) { + if (state.renderBlockItem && !state.blockItemModel.isEmpty()) { + stack.pushPose(); + stack.translate(2 / 16f, 2 / 16f, 2 / 16f); + stack.scale(12 / 16f, 12 / 16f, 12 / 16f); + state.blockItemModel.submitMultiLayer(stack, collector, state.lightCoords, OverlayTexture.NO_OVERLAY, 0); + stack.popPose(); + } else if (!state.outputItem.isEmpty()) { + stack.pushPose(); + stack.translate(0.5, 1.5 / 16f + (state.hasFluid ? state.fluidY : 0.0f), 0.5); + stack.mulPose(Axis.XP.rotation(Mth.HALF_PI)); + state.outputItem.submit(stack, collector, state.lightCoords, OverlayTexture.NO_OVERLAY, 0); + stack.popPose(); + } + + if (state.hasFluid && state.fluid != null) { + int r = (state.fluidColor >> 16) & 0xff; + int g = (state.fluidColor >> 8) & 0xff; + int b = state.fluidColor & 0xff; + if (state.transparent) { + collector.submitCustomGeometry(stack, RenderUtil.getFluidRenderType(state.fluid), (pose, buffer) -> + RenderUtil.renderFluidCube(buffer, pose, BarrelBlock.BARREL_FLUID_BOTTOM, state.fluidY, 2.0f, state.lightCoords, r, g, b, state.fluid) + ); + } else { + collector.submitCustomGeometry(stack, RenderUtil.getFluidRenderType(state.fluid), (pose, buffer) -> + RenderUtil.renderFlatFluidSprite(buffer, pose, state.fluidY, 2.0f, state.lightCoords, r, g, b, state.fluid) + ); + } + } + + if (state.hasCompost) { + var sprite = RenderUtil.getBlockSprite(COMPOST_DIRT_TEXTURE); + int r = (state.compostColor >> 16) & 0xff; + int g = (state.compostColor >> 8) & 0xff; + int b = state.compostColor & 0xff; + collector.submitCustomGeometry(stack, RenderUtil.TINTED_CUTOUT_MIPPED, (pose, buffer) -> + RenderUtil.renderFlatSpriteLerp(buffer, pose, state.compostPercentage, r, g, b, sprite, state.lightCoords, 2.0f, BarrelBlock.BARREL_FLUID_BOTTOM * 16f, BarrelBlock.BARREL_FLUID_TOP * 16f) + ); + } + } + + private static int packRgb(int r, int g, int b) { + return (r & 0xff) << 16 | (g & 0xff) << 8 | (b & 0xff); + } + + public static class BarrelRenderState extends BlockEntityRenderState { + public final BlockModelRenderState blockItemModel = new BlockModelRenderState(); + public final ItemStackRenderState outputItem = new ItemStackRenderState(); + public boolean renderBlockItem; + public boolean hasFluid; + public boolean transparent; + public float fluidY; + public Fluid fluid; + public int fluidColor; + public boolean hasCompost; + public float compostPercentage; + public int compostColor; } } diff --git a/src/main/java/thedarkcolour/exdeorum/client/ter/CrucibleRenderer.java b/src/main/java/thedarkcolour/exdeorum/client/ter/CrucibleRenderer.java index b4ca31ea..99495d7e 100644 --- a/src/main/java/thedarkcolour/exdeorum/client/ter/CrucibleRenderer.java +++ b/src/main/java/thedarkcolour/exdeorum/client/ter/CrucibleRenderer.java @@ -19,21 +19,104 @@ package thedarkcolour.exdeorum.client.ter; import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.util.Mth; +import net.minecraft.world.level.material.Fluid; +import thedarkcolour.exdeorum.block.AbstractCrucibleBlock; import thedarkcolour.exdeorum.blockentity.AbstractCrucibleBlockEntity; +import thedarkcolour.exdeorum.client.RenderFace; +import thedarkcolour.exdeorum.client.RenderUtil; -// TODO: port CrucibleRenderer to MC 26.x rendering API (BlockEntityRenderer changed to extract/submit pattern) -public class CrucibleRenderer implements BlockEntityRenderer { +public class CrucibleRenderer implements BlockEntityRenderer { @Override - public BlockEntityRenderState createRenderState() { - return new BlockEntityRenderState(); + public CrucibleRenderState createRenderState() { + return new CrucibleRenderState(); } @Override - public void submit(BlockEntityRenderState state, PoseStack stack, SubmitNodeCollector collector, CameraRenderState cameraState) { - // TODO: implement crucible fluid/solid rendering using new 26.x rendering API + public void extractRenderState(AbstractCrucibleBlockEntity crucible, CrucibleRenderState state, float partialTicks, net.minecraft.world.phys.Vec3 cameraPosition, net.minecraft.client.renderer.feature.ModelFeatureRenderer.CrumblingOverlay breakProgress) { + BlockEntityRenderer.super.extractRenderState(crucible, state, partialTicks, cameraPosition, breakProgress); + + state.hasFluid = false; + state.fluid = null; + state.solidsFace = null; + + var tank = crucible.getTank(); + var level = crucible.getLevel(); + if (level == null) { + return; + } + + var fluidStack = tank.getFluidInTank(0); + var solids = (float) crucible.getSolids() / (float) AbstractCrucibleBlockEntity.MAX_SOLIDS; + var liquid = (float) fluidStack.getAmount() / (float) tank.getTankCapacity(0); + + if (!fluidStack.isEmpty() && liquid != 0) { + var fluid = fluidStack.getFluid(); + var color = RenderUtil.getFluidColor(fluid, level, crucible.getBlockPos()); + state.hasFluid = true; + state.fluid = fluid; + state.fluidY = Mth.lerp(liquid, AbstractCrucibleBlock.CRUCIBLE_FLUID_BOTTOM, AbstractCrucibleBlock.CRUCIBLE_FLUID_TOP); + state.fluidColor = color == -1 ? 0xffffff : color; + } + + if (solids != 0) { + var lastMelted = crucible.getLastMelted(); + if (lastMelted == null) { + lastMelted = crucible.getDefaultMeltBlock(); + } + + state.solidsFace = RenderUtil.getTopFaceOrDefault(lastMelted, crucible.getDefaultMeltBlock()); + state.solidsPercentage = solids; + + var tintSource = Minecraft.getInstance().getBlockColors().getTintSource(lastMelted.defaultBlockState(), 0); + var color = tintSource != null && level instanceof net.minecraft.client.renderer.block.BlockAndTintGetter getter + ? tintSource.colorInWorld(lastMelted.defaultBlockState(), getter, crucible.getBlockPos()) + : -1; + state.solidsColor = color == -1 ? 0xffffff : color; + } + } + + @Override + public void submit(CrucibleRenderState state, PoseStack stack, SubmitNodeCollector collector, CameraRenderState cameraState) { + if (state.hasFluid && state.fluid != null) { + int r = (state.fluidColor >> 16) & 0xff; + int g = (state.fluidColor >> 8) & 0xff; + int b = state.fluidColor & 0xff; + collector.submitCustomGeometry(stack, RenderUtil.getFluidRenderType(state.fluid), (pose, buffer) -> + RenderUtil.renderFlatFluidSprite(buffer, pose, state.fluidY, 2.0f, state.lightCoords, r, g, b, state.fluid) + ); + } + + if (state.solidsFace instanceof RenderFace.Single single) { + submitSolidLayer(collector, stack, state, single.renderType(), single.sprite()); + } else if (state.solidsFace instanceof RenderFace.Composite composite) { + for (var layer : composite.layers()) { + submitSolidLayer(collector, stack, state, layer.renderType(), layer.sprite()); + } + } + } + + private static void submitSolidLayer(SubmitNodeCollector collector, PoseStack stack, CrucibleRenderState state, net.minecraft.client.renderer.rendertype.RenderType renderType, net.minecraft.client.renderer.texture.TextureAtlasSprite sprite) { + int r = (state.solidsColor >> 16) & 0xff; + int g = (state.solidsColor >> 8) & 0xff; + int b = state.solidsColor & 0xff; + collector.submitCustomGeometry(stack, renderType, (pose, buffer) -> + RenderUtil.renderFlatSpriteLerp(buffer, pose, state.solidsPercentage, r, g, b, sprite, state.lightCoords, 2.0f, AbstractCrucibleBlock.CRUCIBLE_FLUID_BOTTOM * 16f, AbstractCrucibleBlock.CRUCIBLE_FLUID_TOP * 16f) + ); + } + + public static class CrucibleRenderState extends BlockEntityRenderState { + public boolean hasFluid; + public Fluid fluid; + public float fluidY; + public int fluidColor; + public RenderFace solidsFace; + public float solidsPercentage; + public int solidsColor; } } diff --git a/src/main/java/thedarkcolour/exdeorum/client/ter/SieveRenderer.java b/src/main/java/thedarkcolour/exdeorum/client/ter/SieveRenderer.java index 14ea5c1e..b3b6bcb5 100644 --- a/src/main/java/thedarkcolour/exdeorum/client/ter/SieveRenderer.java +++ b/src/main/java/thedarkcolour/exdeorum/client/ter/SieveRenderer.java @@ -19,20 +19,28 @@ package thedarkcolour.exdeorum.client.ter; import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.renderer.state.level.CameraRenderState; import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.rendertype.RenderTypes; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.Identifier; +import net.minecraft.util.Mth; +import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import thedarkcolour.exdeorum.blockentity.EBlockEntity; import thedarkcolour.exdeorum.blockentity.logic.SieveLogic; +import thedarkcolour.exdeorum.client.RenderFace; +import thedarkcolour.exdeorum.client.RenderUtil; import java.util.HashMap; import java.util.Map; -// TODO: port SieveRenderer to MC 26.x rendering API (BlockEntityRenderer changed to extract/submit pattern) -public class SieveRenderer implements BlockEntityRenderer { +public class SieveRenderer implements BlockEntityRenderer { public static final Map MESH_TEXTURES = new HashMap<>(); private final float meshHeight; @@ -46,16 +54,84 @@ public class SieveRenderer implements } @Override - public BlockEntityRenderState createRenderState() { - return new BlockEntityRenderState(); + public SieveRenderState createRenderState() { + return new SieveRenderState(); } @Override - public void submit(BlockEntityRenderState state, PoseStack stack, SubmitNodeCollector collector, CameraRenderState cameraState) { - // TODO: implement sieve mesh/contents rendering using new 26.x rendering API + public void extractRenderState(T sieve, SieveRenderState state, float partialTicks, net.minecraft.world.phys.Vec3 cameraPosition, net.minecraft.client.renderer.feature.ModelFeatureRenderer.CrumblingOverlay breakProgress) { + BlockEntityRenderer.super.extractRenderState(sieve, state, partialTicks, cameraPosition, breakProgress); + + var logic = sieve.getLogic(); + var contents = logic.getContents(); + state.contentsFace = null; + state.contentsPercentage = logic.getProgress(); + state.renderContents3d = shouldContentsRender3d(sieve); + + if (!contents.isEmpty() && contents.getItem() instanceof BlockItem blockItem) { + state.contentsFace = RenderUtil.getTopFace(blockItem.getBlock()); + } + + var mesh = logic.getMesh(); + state.meshSprite = null; + state.meshHasFoil = false; + if (!mesh.isEmpty()) { + var meshItem = mesh.getItem(); + if (MESH_TEXTURES.containsKey(meshItem)) { + state.meshSprite = MESH_TEXTURES.get(meshItem); + } else { + Identifier textureLoc = BuiltInRegistries.ITEM.getKey(meshItem).withPrefix("item/mesh/"); + var sprite = RenderUtil.getBlockSprite(textureLoc); + MESH_TEXTURES.put(meshItem, sprite); + state.meshSprite = sprite; + } + state.meshHasFoil = mesh.hasFoil(); + } + } + + @Override + public void submit(SieveRenderState state, PoseStack stack, SubmitNodeCollector collector, CameraRenderState cameraState) { + if (state.contentsFace != null) { + if (state.contentsFace instanceof RenderFace.Single single) { + submitContentsLayer(collector, stack, state, single.renderType(), single.sprite()); + } else if (state.contentsFace instanceof RenderFace.Composite composite) { + for (var layer : composite.layers()) { + submitContentsLayer(collector, stack, state, layer.renderType(), layer.sprite()); + } + } + } + + if (state.meshSprite != null) { + collector.submitCustomGeometry(stack, Sheets.cutoutBlockSheet(), (pose, buffer) -> + RenderUtil.renderFlatSprite(buffer, pose, this.meshHeight, 0xff, 0xff, 0xff, state.meshSprite, state.lightCoords, 1f) + ); + if (state.meshHasFoil) { + collector.submitCustomGeometry(stack, RenderTypes.glint(), (pose, buffer) -> + RenderUtil.renderFlatSprite(buffer, pose, this.meshHeight, 0xff, 0xff, 0xff, state.meshSprite, state.lightCoords, 1f) + ); + } + } + } + + private void submitContentsLayer(SubmitNodeCollector collector, PoseStack stack, SieveRenderState state, net.minecraft.client.renderer.rendertype.RenderType renderType, TextureAtlasSprite sprite) { + collector.submitCustomGeometry(stack, renderType, (pose, buffer) -> { + if (state.renderContents3d) { + RenderUtil.renderCuboid(buffer, pose, this.contentsMinY / 16f, Mth.lerp(state.contentsPercentage, this.contentsMaxY, this.contentsMinY) / 16f, 0xff, 0xff, 0xff, sprite, state.lightCoords, 1.0f); + } else { + RenderUtil.renderFlatSpriteLerp(buffer, pose, state.contentsPercentage, 0xff, 0xff, 0xff, sprite, state.lightCoords, 1.0f, this.contentsMaxY, this.contentsMinY); + } + }); } protected boolean shouldContentsRender3d(T sieve) { return false; } + + public static class SieveRenderState extends BlockEntityRenderState { + public RenderFace contentsFace; + public float contentsPercentage; + public boolean renderContents3d; + public TextureAtlasSprite meshSprite; + public boolean meshHasFoil; + } } diff --git a/src/main/java/thedarkcolour/exdeorum/compat/CompatUtil.java b/src/main/java/thedarkcolour/exdeorum/compat/CompatUtil.java index 6e855b1d..f1400e55 100644 --- a/src/main/java/thedarkcolour/exdeorum/compat/CompatUtil.java +++ b/src/main/java/thedarkcolour/exdeorum/compat/CompatUtil.java @@ -91,7 +91,7 @@ public class CompatUtil { public static void addEnchantmentsTooltip(ItemStack mesh, Level level, Consumer aggregator) { var enchantments = mesh.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); if (!enchantments.isEmpty()) { - enchantments.addToTooltip(Item.TooltipContext.of(level), aggregator, TooltipFlag.NORMAL); + enchantments.addToTooltip(Item.TooltipContext.of(level), aggregator, TooltipFlag.NORMAL, mesh); } } } diff --git a/src/main/java/thedarkcolour/exdeorum/compat/PreferredOres.java b/src/main/java/thedarkcolour/exdeorum/compat/PreferredOres.java index ac9369f5..07056816 100644 --- a/src/main/java/thedarkcolour/exdeorum/compat/PreferredOres.java +++ b/src/main/java/thedarkcolour/exdeorum/compat/PreferredOres.java @@ -66,13 +66,13 @@ public class PreferredOres { * @param defaultOre The default ore choice, picked by Ex Deorum based on which mod is the "best" choice according to thedarkcolour. */ private static void putPreferredOre(TagKey tag, ModConfigSpec.ConfigValue config, Item defaultOre) { - var item = BuiltInRegistries.ITEM.get(Identifier.parse(config.get())); + var item = BuiltInRegistries.ITEM.get(Identifier.parse(config.get())).map(reference -> reference.value()).orElse(Items.AIR); if (item == Items.AIR) { item = defaultOre; ExDeorum.LOGGER.debug("No preferred ore was set for tag {}. Using default choice {}", tag.location(), BuiltInRegistries.ITEM.getKey(item)); } - PREFERRED_ORE_ITEMS.put(tag, defaultOre); + PREFERRED_ORE_ITEMS.put(tag, item); } /** @@ -171,11 +171,11 @@ public class PreferredOres { if (modId != null) { if (modId.equals(ModIds.FACTORIUM)) { - return BuiltInRegistries.ITEM.get(Identifier.fromNamespaceAndPath(modId, "mat_" + path)); + return BuiltInRegistries.ITEM.get(Identifier.fromNamespaceAndPath(modId, "mat_" + path)).map(reference -> reference.value()).orElse(Items.AIR); } else if (modId.equals(ModIds.IMMERSIVE_ENGINEERING)) { - return BuiltInRegistries.ITEM.get(Identifier.fromNamespaceAndPath(modId, "ore_" + path.substring(0, path.length() - 4))); + return BuiltInRegistries.ITEM.get(Identifier.fromNamespaceAndPath(modId, "ore_" + path.substring(0, path.length() - 4))).map(reference -> reference.value()).orElse(Items.AIR); } else { - return BuiltInRegistries.ITEM.get(Identifier.fromNamespaceAndPath(modId, path)); + return BuiltInRegistries.ITEM.get(Identifier.fromNamespaceAndPath(modId, path)).map(reference -> reference.value()).orElse(Items.AIR); } } else { return Items.AIR; diff --git a/src/main/java/thedarkcolour/exdeorum/compat/XeiSieveRecipe.java b/src/main/java/thedarkcolour/exdeorum/compat/XeiSieveRecipe.java index 54c5473b..a8805c45 100644 --- a/src/main/java/thedarkcolour/exdeorum/compat/XeiSieveRecipe.java +++ b/src/main/java/thedarkcolour/exdeorum/compat/XeiSieveRecipe.java @@ -79,8 +79,8 @@ public record XeiSieveRecipe(Ingredient ingredient, ItemStack mesh, List // these lists are grouped into sub lists based on their meshes (ex. dirt with string mesh) for (var recipe : values) { - for (var stack : recipe.mesh.getItems()) { - meshGrouper.put(stack.getItem(), recipe); + for (var holder : recipe.mesh.items().toList()) { + meshGrouper.put(holder.value(), recipe); } } diff --git a/src/main/java/thedarkcolour/exdeorum/event/EventHandler.java b/src/main/java/thedarkcolour/exdeorum/event/EventHandler.java index 4daa6062..6b667dfa 100644 --- a/src/main/java/thedarkcolour/exdeorum/event/EventHandler.java +++ b/src/main/java/thedarkcolour/exdeorum/event/EventHandler.java @@ -57,7 +57,11 @@ import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; import net.neoforged.neoforge.fluids.FluidInteractionRegistry; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.minecraft.server.packs.resources.ResourceManagerReloadListener; import thedarkcolour.exdeorum.ExDeorum; +import thedarkcolour.exdeorum.blockentity.AbstractCrucibleBlockEntity; +import thedarkcolour.exdeorum.blockentity.AbstractMachineBlockEntity; +import thedarkcolour.exdeorum.blockentity.BarrelBlockEntity; import thedarkcolour.exdeorum.blockentity.helper.ItemHelper; import thedarkcolour.exdeorum.client.CompostColors; import thedarkcolour.exdeorum.compat.ModIds; @@ -72,6 +76,10 @@ import thedarkcolour.exdeorum.registry.EBlockEntities; import thedarkcolour.exdeorum.registry.EFluids; import thedarkcolour.exdeorum.registry.EItems; import thedarkcolour.exdeorum.tag.EBiomeTags; +import thedarkcolour.exdeorum.transfer.LegacyEnergyStorageTransfer; +import thedarkcolour.exdeorum.transfer.LegacyFluidItemAccessTransfer; +import thedarkcolour.exdeorum.transfer.LegacyFluidTankTransfer; +import thedarkcolour.exdeorum.transfer.LegacyItemHandlerTransfer; import thedarkcolour.exdeorum.voidworld.VoidChunkGenerator; import java.util.Locale; @@ -160,7 +168,7 @@ public final class EventHandler { event.setCanceled(true); event.getSettings().setSpawn(LevelData.RespawnData.of(level.dimension(), level.getHeightmapPos(Heightmap.Types.WORLD_SURFACE_WG, pos), 90.0F, 0.0F)); - level.getGameRules().getRule(GameRules.RESPAWN_RADIUS).set(0, level.getServer()); + level.getGameRules().set(GameRules.RESPAWN_RADIUS, 0, level.getServer()); } } @@ -194,7 +202,7 @@ public final class EventHandler { // tries to account for other SkyBlock generator mods like SkyBlockBuilder if (generator instanceof VoidChunkGenerator || generator.getClass().getName().toLowerCase(Locale.ROOT).contains("skyblock")) { NetworkHandler.sendVoidWorld(player); - var advancement = player.getServer().getAdvancements().get(Identifier.fromNamespaceAndPath(ExDeorum.ID, "core/root")); + var advancement = player.level().getServer().getAdvancements().get(Identifier.fromNamespaceAndPath(ExDeorum.ID, "core/root")); if (advancement != null) { if (!player.getAdvancements().getOrStartProgress(advancement).isDone()) { @@ -226,11 +234,7 @@ public final class EventHandler { private static void addReloadListeners(AddServerReloadListenersEvent event) { var recipeMap = event.getServerResources().getRecipeManager().recipeMap(); - event.addListener(ExDeorum.loc("recipes"), (prepBarrier, resourceManager, prepProfiler, reloadProfiler, backgroundExecutor, gameExecutor) -> { - return prepBarrier.wait(Unit.INSTANCE).thenRunAsync(() -> { - RecipeUtil.reload(recipeMap); - }, gameExecutor); - }); + event.addListener(ExDeorum.loc("recipes"), (ResourceManagerReloadListener) resourceManager -> RecipeUtil.reload(recipeMap)); } private static void serverTick(ServerTickEvent.Post event) { @@ -238,28 +242,28 @@ public final class EventHandler { } private static void registerCapabilities(RegisterCapabilitiesEvent event) { - event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.BARREL.get(), (barrel, direction) -> barrel.getItemHandler()); - event.registerBlockEntity(Capabilities.Fluid.BLOCK, EBlockEntities.BARREL.get(), (barrel, direction) -> barrel.getTank()); + event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.BARREL.get(), (barrel, direction) -> new LegacyItemHandlerTransfer(barrel.getItemHandler())); + event.registerBlockEntity(Capabilities.Fluid.BLOCK, EBlockEntities.BARREL.get(), (barrel, direction) -> new LegacyFluidTankTransfer(barrel.getTank())); - event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.MECHANICAL_SIEVE.get(), (sieve, direction) -> sieve.getItemHandler()); - event.registerBlockEntity(Capabilities.Energy.BLOCK, EBlockEntities.MECHANICAL_SIEVE.get(), (sieve, direction) -> sieve.getEnergyStorage()); + event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.MECHANICAL_SIEVE.get(), (sieve, direction) -> new LegacyItemHandlerTransfer(sieve.inventory)); + event.registerBlockEntity(Capabilities.Energy.BLOCK, EBlockEntities.MECHANICAL_SIEVE.get(), (sieve, direction) -> new LegacyEnergyStorageTransfer(sieve.getEnergyStorage(), sieve.energy::setStoredEnergy)); - event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.MECHANICAL_HAMMER.get(), (hammer, direction) -> hammer.getItemHandler()); - event.registerBlockEntity(Capabilities.Energy.BLOCK, EBlockEntities.MECHANICAL_HAMMER.get(), (hammer, direction) -> hammer.getEnergyStorage()); + event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.MECHANICAL_HAMMER.get(), (hammer, direction) -> new LegacyItemHandlerTransfer(hammer.inventory)); + event.registerBlockEntity(Capabilities.Energy.BLOCK, EBlockEntities.MECHANICAL_HAMMER.get(), (hammer, direction) -> new LegacyEnergyStorageTransfer(hammer.getEnergyStorage(), hammer.energy::setStoredEnergy)); - event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.LAVA_CRUCIBLE.get(), (hammer, direction) -> hammer.getItem()); - event.registerBlockEntity(Capabilities.Fluid.BLOCK, EBlockEntities.LAVA_CRUCIBLE.get(), (hammer, direction) -> hammer.getTank()); + event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.LAVA_CRUCIBLE.get(), (crucible, direction) -> new LegacyItemHandlerTransfer(crucible.getItem())); + event.registerBlockEntity(Capabilities.Fluid.BLOCK, EBlockEntities.LAVA_CRUCIBLE.get(), (crucible, direction) -> new LegacyFluidTankTransfer(crucible.getTank())); - event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.WATER_CRUCIBLE.get(), (hammer, direction) -> hammer.getItem()); - event.registerBlockEntity(Capabilities.Fluid.BLOCK, EBlockEntities.WATER_CRUCIBLE.get(), (hammer, direction) -> hammer.getTank()); + event.registerBlockEntity(Capabilities.Item.BLOCK, EBlockEntities.WATER_CRUCIBLE.get(), (crucible, direction) -> new LegacyItemHandlerTransfer(crucible.getItem())); + event.registerBlockEntity(Capabilities.Fluid.BLOCK, EBlockEntities.WATER_CRUCIBLE.get(), (crucible, direction) -> new LegacyFluidTankTransfer(crucible.getTank())); - event.registerItem(Capabilities.Fluid.ITEM, (stack, ctx) -> new PorcelainBucket.ItemHandler(stack), + event.registerItem(Capabilities.Fluid.ITEM, (stack, ctx) -> ctx == null ? null : new LegacyFluidItemAccessTransfer(ctx, PorcelainBucket.ItemHandler::new), EItems.PORCELAIN_BUCKET, EItems.PORCELAIN_WATER_BUCKET, EItems.PORCELAIN_LAVA_BUCKET, EItems.PORCELAIN_MILK_BUCKET, EItems.PORCELAIN_WITCH_WATER_BUCKET); - event.registerItem(Capabilities.Fluid.ITEM, (stack, ctx) -> new WateringCanItem.FluidHandler(stack), + event.registerItem(Capabilities.Fluid.ITEM, (stack, ctx) -> ctx == null ? null : new LegacyFluidItemAccessTransfer(ctx, WateringCanItem.FluidHandler::new), EItems.WOODEN_WATERING_CAN, EItems.STONE_WATERING_CAN, EItems.IRON_WATERING_CAN, diff --git a/src/main/java/thedarkcolour/exdeorum/item/WateringCanItem.java b/src/main/java/thedarkcolour/exdeorum/item/WateringCanItem.java index 21a9b130..d934e412 100644 --- a/src/main/java/thedarkcolour/exdeorum/item/WateringCanItem.java +++ b/src/main/java/thedarkcolour/exdeorum/item/WateringCanItem.java @@ -56,6 +56,7 @@ import net.neoforged.neoforge.common.util.FakePlayer; import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.fluids.capability.IFluidHandler; import net.neoforged.neoforge.fluids.capability.templates.FluidHandlerItemStack; +import net.neoforged.neoforge.transfer.access.ItemAccess; import thedarkcolour.exdeorum.blockentity.BarrelBlockEntity; import thedarkcolour.exdeorum.data.TranslationKeys; import thedarkcolour.exdeorum.registry.EDataComponents; @@ -94,7 +95,7 @@ public class WateringCanItem extends Item { public static ItemStack getFull(Supplier wateringCan) { var stack = new ItemStack(wateringCan.get()); - var fluidHandler = stack.getCapability(Capabilities.Fluid.ITEM); + var fluidHandler = getFluidHandler(stack); if (fluidHandler != null) { fluidHandler.fill(new FluidStack(Fluids.WATER, Integer.MAX_VALUE), IFluidHandler.FluidAction.EXECUTE); } @@ -104,7 +105,7 @@ public class WateringCanItem extends Item { @Override public boolean isBarVisible(ItemStack stack) { if (this.renewing) { - var fluidHandler = stack.getCapability(Capabilities.Fluid.ITEM); + var fluidHandler = getFluidHandler(stack); return fluidHandler == null || fluidHandler.getFluidInTank(0).getAmount() < this.capacity; } else { return true; @@ -118,7 +119,7 @@ public class WateringCanItem extends Item { @Override public int getBarWidth(ItemStack stack) { - var fluidHandler = stack.getCapability(Capabilities.Fluid.ITEM); + var fluidHandler = getFluidHandler(stack); if (fluidHandler != null) { return Math.round((float) fluidHandler.getFluidInTank(0).getAmount() * 13f / (float) this.capacity); } else { @@ -137,7 +138,7 @@ public class WateringCanItem extends Item { } public void appendHoverText(ItemStack stack, TooltipContext context, List tooltip, TooltipFlag pIsAdvanced) { - var fluidHandler = stack.getCapability(Capabilities.Fluid.ITEM); + var fluidHandler = getFluidHandler(stack); if (fluidHandler != null) { // use the block name which is guaranteed to have a vanilla translation tooltip.add(Component.translatable("block.minecraft.water").append(Component.translatable(TranslationKeys.FRACTION_DISPLAY, fluidHandler.getFluidInTank(0).getAmount(), this.capacity)).withStyle(ChatFormatting.GRAY)); @@ -147,7 +148,8 @@ public class WateringCanItem extends Item { @Override public InteractionResult use(Level level, Player player, InteractionHand hand) { var itemInHand = player.getItemInHand(hand); - var fluidHandler = itemInHand.getCapability(Capabilities.Fluid.ITEM); + var itemAccess = ItemAccess.forPlayerInteraction(player, hand); + var fluidHandler = getFluidHandler(itemAccess); if (fluidHandler != null) { if (fluidHandler.getFluidInTank(0).getAmount() < this.capacity) { var hitResult = getPlayerPOVHitResult(level, player, ClipContext.Fluid.SOURCE_ONLY); @@ -188,7 +190,7 @@ public class WateringCanItem extends Item { var useTicks = 72000 - remainingTicks; if (useTicks >= STARTUP_TIME || living instanceof FakePlayer) { - var fluidHandler = stack.getCapability(Capabilities.Fluid.ITEM); + var fluidHandler = getFluidHandler(stack); if (fluidHandler != null) { if (!fluidHandler.getFluidInTank(0).isEmpty()) { // do watering can @@ -205,7 +207,7 @@ public class WateringCanItem extends Item { if (!this.renewing || fluidHandler.getFluidInTank(0).getAmount() != this.capacity) { if (!(living instanceof Player player && player.getAbilities().instabuild)) { - ((FluidHandler) fluidHandler).drain(); + new FluidHandler(stack).drain(); } } } @@ -386,4 +388,18 @@ public class WateringCanItem extends Item { return 1 - opposite * opposite * opposite; } } + + private static IFluidHandler getFluidHandler(ItemAccess itemAccess) { + var handler = itemAccess.getCapability(Capabilities.Fluid.ITEM); + return handler == null ? null : IFluidHandler.of(handler); + } + + private static IFluidHandler getFluidHandler(ItemStack stack) { + if (stack.isEmpty()) { + return null; + } + + var itemAccess = ItemAccess.forStack(stack); + return getFluidHandler(itemAccess); + } } diff --git a/src/main/java/thedarkcolour/exdeorum/loot/CrookLootModifier.java b/src/main/java/thedarkcolour/exdeorum/loot/CrookLootModifier.java index 1354db20..ccf75a66 100644 --- a/src/main/java/thedarkcolour/exdeorum/loot/CrookLootModifier.java +++ b/src/main/java/thedarkcolour/exdeorum/loot/CrookLootModifier.java @@ -50,9 +50,9 @@ public class CrookLootModifier extends LootModifier { @Override protected @NotNull ObjectArrayList doApply(ObjectArrayList generatedLoot, LootContext context) { var state = context.getOptionalParameter(LootContextParams.BLOCK_STATE); - var stack = context.getOptionalParameter(LootContextParams.TOOL); + var tool = context.getOptionalParameter(LootContextParams.TOOL); - if (state != null && stack != null) { + if (state != null && tool instanceof ItemStack stack) { var rand = context.getRandom(); if (stack.getEnchantmentLevel(context.getLevel().holderLookup(Registries.ENCHANTMENT).getOrThrow(Enchantments.SILK_TOUCH)) == 0) { @@ -71,7 +71,8 @@ public class CrookLootModifier extends LootModifier { if (state.is(BlockTags.LEAVES)) { // this must not be a crook in order to avoid recursively triggering CrookLootModifier from the re roll method // copying the tag is required so that enchantments like fortune are preserved - var nonCrook = stack.transmuteCopy(Items.BARRIER, 1); + var nonCrook = new ItemStack(Items.BARRIER, 1); + nonCrook.applyComponents(stack.getComponentsPatch()); for (int i = 0; i < rolls; i++) { generatedLoot.addAll(reRollDrops(context, nonCrook, state)); diff --git a/src/main/java/thedarkcolour/exdeorum/loot/HammerLootModifier.java b/src/main/java/thedarkcolour/exdeorum/loot/HammerLootModifier.java index 68537ed1..b35d5a3e 100644 --- a/src/main/java/thedarkcolour/exdeorum/loot/HammerLootModifier.java +++ b/src/main/java/thedarkcolour/exdeorum/loot/HammerLootModifier.java @@ -78,9 +78,11 @@ public class HammerLootModifier extends LootModifier { var resultAmount = recipe.resultAmount.getInt(context); if (!itemForm.builtInRegistryHolder().is(this.fortuneBlacklistTag) && context.hasParameter(LootContextParams.TOOL)) { - var hammer = context.getParameter(LootContextParams.TOOL); - // fortune handling; more likely to boost drops if there are none to begin with - resultAmount += calculateFortuneBonus(context.getLevel().registryAccess(), hammer, context.getRandom(), resultAmount == 0); + var tool = context.getParameter(LootContextParams.TOOL); + if (tool instanceof ItemStack hammer) { + // fortune handling; more likely to boost drops if there are none to begin with + resultAmount += calculateFortuneBonus(context.getLevel().registryAccess(), hammer, context.getRandom(), resultAmount == 0); + } } if (resultAmount > 0) { diff --git a/src/main/java/thedarkcolour/exdeorum/registry/EBlockEntities.java b/src/main/java/thedarkcolour/exdeorum/registry/EBlockEntities.java index 9e0bc47a..49a0751d 100644 --- a/src/main/java/thedarkcolour/exdeorum/registry/EBlockEntities.java +++ b/src/main/java/thedarkcolour/exdeorum/registry/EBlockEntities.java @@ -29,12 +29,12 @@ import thedarkcolour.exdeorum.material.DefaultMaterials; public class EBlockEntities { public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, ExDeorum.ID); - public static final DeferredHolder, BlockEntityType> INFESTED_LEAVES = BLOCK_ENTITIES.register("infested_leaves", () -> BlockEntityType.Builder.of(InfestedLeavesBlockEntity::new, EBlocks.INFESTED_LEAVES.get()).build(null)); + public static final DeferredHolder, BlockEntityType> INFESTED_LEAVES = BLOCK_ENTITIES.register("infested_leaves", () -> new BlockEntityType<>(InfestedLeavesBlockEntity::new, java.util.Set.of(EBlocks.INFESTED_LEAVES.get()))); public static final DeferredHolder, BlockEntityType> LAVA_CRUCIBLE = BLOCK_ENTITIES.register("lava_crucible", () -> DefaultMaterials.LAVA_CRUCIBLES.createBlockEntityType(LavaCrucibleBlockEntity::new)); public static final DeferredHolder, BlockEntityType> WATER_CRUCIBLE = BLOCK_ENTITIES.register("water_crucible", () -> DefaultMaterials.WATER_CRUCIBLES.createBlockEntityType(WaterCrucibleBlockEntity::new)); public static final DeferredHolder, BlockEntityType> BARREL = BLOCK_ENTITIES.register("barrel", () -> DefaultMaterials.BARRELS.createBlockEntityType(BarrelBlockEntity::new)); public static final DeferredHolder, BlockEntityType> SIEVE = BLOCK_ENTITIES.register("sieve", () -> DefaultMaterials.SIEVES.createBlockEntityType(SieveBlockEntity::new)); public static final DeferredHolder, BlockEntityType> COMPRESSED_SIEVE = BLOCK_ENTITIES.register("compressed_sieve", () -> DefaultMaterials.COMPRESSED_SIEVES.createBlockEntityType(CompressedSieveBlockEntity::new)); - public static final DeferredHolder, BlockEntityType> MECHANICAL_SIEVE = BLOCK_ENTITIES.register("mechanical_sieve", () -> BlockEntityType.Builder.of(MechanicalSieveBlockEntity::new, EBlocks.MECHANICAL_SIEVE.get()).build(null)); - public static final DeferredHolder, BlockEntityType> MECHANICAL_HAMMER = BLOCK_ENTITIES.register("mechanical_hammer", () -> BlockEntityType.Builder.of(MechanicalHammerBlockEntity::new, EBlocks.MECHANICAL_HAMMER.get()).build(null)); + public static final DeferredHolder, BlockEntityType> MECHANICAL_SIEVE = BLOCK_ENTITIES.register("mechanical_sieve", () -> new BlockEntityType<>(MechanicalSieveBlockEntity::new, java.util.Set.of(EBlocks.MECHANICAL_SIEVE.get()))); + public static final DeferredHolder, BlockEntityType> MECHANICAL_HAMMER = BLOCK_ENTITIES.register("mechanical_hammer", () -> new BlockEntityType<>(MechanicalHammerBlockEntity::new, java.util.Set.of(EBlocks.MECHANICAL_HAMMER.get()))); } diff --git a/src/main/java/thedarkcolour/exdeorum/transfer/LegacyEnergyStorageTransfer.java b/src/main/java/thedarkcolour/exdeorum/transfer/LegacyEnergyStorageTransfer.java new file mode 100644 index 00000000..d5612426 --- /dev/null +++ b/src/main/java/thedarkcolour/exdeorum/transfer/LegacyEnergyStorageTransfer.java @@ -0,0 +1,58 @@ +package thedarkcolour.exdeorum.transfer; + +import net.neoforged.neoforge.energy.IEnergyStorage; +import net.neoforged.neoforge.transfer.energy.EnergyHandler; +import net.neoforged.neoforge.transfer.transaction.SnapshotJournal; +import net.neoforged.neoforge.transfer.transaction.TransactionContext; + +import java.util.function.IntConsumer; + +public class LegacyEnergyStorageTransfer extends SnapshotJournal implements EnergyHandler { + private final IEnergyStorage storage; + private final IntConsumer restore; + + public LegacyEnergyStorageTransfer(IEnergyStorage storage, IntConsumer restore) { + this.storage = storage; + this.restore = restore; + } + + @Override + public long getAmountAsLong() { + return this.storage.getEnergyStored(); + } + + @Override + public long getCapacityAsLong() { + return this.storage.getMaxEnergyStored(); + } + + @Override + public int insert(int amount, TransactionContext transaction) { + if (amount <= 0) { + return 0; + } + + updateSnapshots(transaction); + return this.storage.receiveEnergy(amount, false); + } + + @Override + public int extract(int amount, TransactionContext transaction) { + if (amount <= 0) { + return 0; + } + + updateSnapshots(transaction); + return this.storage.extractEnergy(amount, false); + } + + @Override + protected Integer createSnapshot() { + return this.storage.getEnergyStored(); + } + + @Override + protected void revertToSnapshot(Integer snapshot) { + this.restore.accept(snapshot); + } +} diff --git a/src/main/java/thedarkcolour/exdeorum/transfer/LegacyFluidItemAccessTransfer.java b/src/main/java/thedarkcolour/exdeorum/transfer/LegacyFluidItemAccessTransfer.java new file mode 100644 index 00000000..6d83d614 --- /dev/null +++ b/src/main/java/thedarkcolour/exdeorum/transfer/LegacyFluidItemAccessTransfer.java @@ -0,0 +1,60 @@ +package thedarkcolour.exdeorum.transfer; + +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import net.neoforged.neoforge.fluids.capability.IFluidHandlerItem; +import net.neoforged.neoforge.transfer.ItemAccessResourceHandler; +import net.neoforged.neoforge.transfer.access.ItemAccess; +import net.neoforged.neoforge.transfer.fluid.FluidResource; +import net.neoforged.neoforge.transfer.item.ItemResource; + +import java.util.function.Function; + +public class LegacyFluidItemAccessTransfer extends ItemAccessResourceHandler { + private final Function factory; + + public LegacyFluidItemAccessTransfer(ItemAccess itemAccess, Function factory) { + super(itemAccess, 1); + this.factory = factory; + } + + @Override + protected FluidResource getResourceFrom(ItemResource itemResource, int amount) { + return FluidResource.of(getHandler(itemResource, amount).getFluidInTank(0)); + } + + @Override + protected int getAmountFrom(ItemResource itemResource, int amount) { + return getHandler(itemResource, amount).getFluidInTank(0).getAmount(); + } + + @Override + protected ItemResource update(ItemResource itemResource, int amount, FluidResource resource, int resourceAmount) { + var handler = getHandler(itemResource, amount); + var current = handler.getFluidInTank(0); + + if (!current.isEmpty()) { + handler.drain(current, IFluidHandler.FluidAction.EXECUTE); + } + if (!resource.isEmpty() && resourceAmount > 0) { + handler.fill(resource.toStack(resourceAmount), IFluidHandler.FluidAction.EXECUTE); + } + + return ItemResource.of(handler.getContainer()); + } + + @Override + public boolean isValid(int index, FluidResource resource) { + return !resource.isEmpty() && getHandler(this.itemAccess.getResource(), this.itemAccess.getAmount()).isFluidValid(0, resource.toStack(1)); + } + + @Override + protected int getCapacity(int index, FluidResource resource) { + return getHandler(this.itemAccess.getResource(), this.itemAccess.getAmount()).getTankCapacity(0); + } + + private IFluidHandlerItem getHandler(ItemResource itemResource, int amount) { + return this.factory.apply(itemResource.toStack(Math.max(amount, 1))); + } +} diff --git a/src/main/java/thedarkcolour/exdeorum/transfer/LegacyFluidTankTransfer.java b/src/main/java/thedarkcolour/exdeorum/transfer/LegacyFluidTankTransfer.java new file mode 100644 index 00000000..37b560c2 --- /dev/null +++ b/src/main/java/thedarkcolour/exdeorum/transfer/LegacyFluidTankTransfer.java @@ -0,0 +1,71 @@ +package thedarkcolour.exdeorum.transfer; + +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.templates.FluidTank; +import net.neoforged.neoforge.transfer.ResourceHandler; +import net.neoforged.neoforge.transfer.fluid.FluidResource; +import net.neoforged.neoforge.transfer.transaction.SnapshotJournal; +import net.neoforged.neoforge.transfer.transaction.TransactionContext; + +public class LegacyFluidTankTransfer extends SnapshotJournal implements ResourceHandler { + private final FluidTank tank; + + public LegacyFluidTankTransfer(FluidTank tank) { + this.tank = tank; + } + + @Override + public int size() { + return 1; + } + + @Override + public FluidResource getResource(int index) { + return FluidResource.of(this.tank.getFluid()); + } + + @Override + public long getAmountAsLong(int index) { + return this.tank.getFluidAmount(); + } + + @Override + public long getCapacityAsLong(int index, FluidResource resource) { + return this.tank.getCapacity(); + } + + @Override + public boolean isValid(int index, FluidResource resource) { + return !resource.isEmpty() && this.tank.isFluidValid(resource.toStack(1)); + } + + @Override + public int insert(int index, FluidResource resource, int amount, TransactionContext transaction) { + if (resource.isEmpty() || amount <= 0) { + return 0; + } + + updateSnapshots(transaction); + return this.tank.fill(resource.toStack(amount), FluidTank.FluidAction.EXECUTE); + } + + @Override + public int extract(int index, FluidResource resource, int amount, TransactionContext transaction) { + if (resource.isEmpty() || amount <= 0) { + return 0; + } + + updateSnapshots(transaction); + return this.tank.drain(resource.toStack(amount), FluidTank.FluidAction.EXECUTE).getAmount(); + } + + @Override + protected FluidStack createSnapshot() { + return this.tank.getFluid().copy(); + } + + @Override + protected void revertToSnapshot(FluidStack snapshot) { + this.tank.setFluid(snapshot.copy()); + } +} diff --git a/src/main/java/thedarkcolour/exdeorum/transfer/LegacyItemHandlerTransfer.java b/src/main/java/thedarkcolour/exdeorum/transfer/LegacyItemHandlerTransfer.java new file mode 100644 index 00000000..23462197 --- /dev/null +++ b/src/main/java/thedarkcolour/exdeorum/transfer/LegacyItemHandlerTransfer.java @@ -0,0 +1,84 @@ +package thedarkcolour.exdeorum.transfer; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.items.ItemStackHandler; +import net.neoforged.neoforge.transfer.ResourceHandler; +import net.neoforged.neoforge.transfer.item.ItemResource; +import net.neoforged.neoforge.transfer.transaction.SnapshotJournal; +import net.neoforged.neoforge.transfer.transaction.TransactionContext; + +public class LegacyItemHandlerTransfer extends SnapshotJournal> implements ResourceHandler { + private final ItemStackHandler handler; + + public LegacyItemHandlerTransfer(ItemStackHandler handler) { + this.handler = handler; + } + + @Override + public int size() { + return this.handler.getSlots(); + } + + @Override + public ItemResource getResource(int index) { + return ItemResource.of(this.handler.getStackInSlot(index)); + } + + @Override + public long getAmountAsLong(int index) { + return this.handler.getStackInSlot(index).getCount(); + } + + @Override + public long getCapacityAsLong(int index, ItemResource resource) { + return Math.min(this.handler.getSlotLimit(index), resource.getMaxStackSize()); + } + + @Override + public boolean isValid(int index, ItemResource resource) { + return !resource.isEmpty() && this.handler.isItemValid(index, resource.toStack()); + } + + @Override + public int insert(int index, ItemResource resource, int amount, TransactionContext transaction) { + if (resource.isEmpty() || amount <= 0) { + return 0; + } + + updateSnapshots(transaction); + var remainder = this.handler.insertItem(index, resource.toStack(amount), false); + return amount - remainder.getCount(); + } + + @Override + public int extract(int index, ItemResource resource, int amount, TransactionContext transaction) { + if (resource.isEmpty() || amount <= 0 || index < 0 || index >= this.handler.getSlots()) { + return 0; + } + + var current = this.handler.getStackInSlot(index); + if (current.isEmpty() || !resource.matches(current)) { + return 0; + } + + updateSnapshots(transaction); + return this.handler.extractItem(index, amount, false).getCount(); + } + + @Override + protected NonNullList createSnapshot() { + var snapshot = NonNullList.withSize(this.handler.getSlots(), ItemStack.EMPTY); + for (int i = 0; i < this.handler.getSlots(); i++) { + snapshot.set(i, this.handler.getStackInSlot(i).copy()); + } + return snapshot; + } + + @Override + protected void revertToSnapshot(NonNullList snapshot) { + for (int i = 0; i < snapshot.size(); i++) { + this.handler.setStackInSlot(i, snapshot.get(i).copy()); + } + } +}