Add the compressed sieve

This commit is contained in:
thedarkcolour 2024-03-24 11:26:33 -07:00
parent e0e419f3fe
commit 892a769ee6
25 changed files with 1439 additions and 987 deletions

View File

@ -0,0 +1,46 @@
/*
* Ex Deorum
* Copyright (c) 2024 thedarkcolour
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package thedarkcolour.exdeorum.block;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import thedarkcolour.exdeorum.registry.EBlockEntities;
public class CompressedSieveBlock extends SieveBlock {
public static final VoxelShape SHAPE = Shapes.or(
box(0, 8, 0, 16, 14, 16),
box(1, 0, 1, 3, 8, 3),
box(13, 0, 1, 15, 8, 3),
box(1, 0, 13, 3, 8, 15),
box(13, 0, 13, 15, 8, 15)
);
public CompressedSieveBlock(Properties properties) {
super(properties, EBlockEntities.COMPRESSED_SIEVE);
}
@Override
public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) {
return SHAPE;
}
}

View File

@ -22,13 +22,18 @@ import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.registries.RegistryObject;
import thedarkcolour.exdeorum.blockentity.EBlockEntity;
import thedarkcolour.exdeorum.blockentity.SieveBlockEntity;
import thedarkcolour.exdeorum.registry.EBlockEntities;
import java.util.function.Supplier;
public class SieveBlock extends EBlock {
public static final VoxelShape SHAPE = Shapes.or(
box(0, 11, 0, 16, 16, 16),
@ -42,14 +47,13 @@ public class SieveBlock extends EBlock {
super(properties, EBlockEntities.SIEVE);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
protected SieveBlock(Properties properties, Supplier<? extends BlockEntityType<?>> blockEntityType) {
super(properties, blockEntityType);
}
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new SieveBlockEntity(pos, state);
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override

View File

@ -22,19 +22,29 @@ import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.util.FakePlayer;
import thedarkcolour.exdeorum.blockentity.logic.SieveLogic;
import thedarkcolour.exdeorum.config.EConfig;
import java.util.function.Function;
public abstract class AbstractSieveBlockEntity extends EBlockEntity implements SieveLogic.Owner {
protected final SieveLogic logic;
private final float sieveInterval;
public AbstractSieveBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state, Function<SieveLogic.Owner, SieveLogic> logic) {
public AbstractSieveBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state, float sieveInterval, Function<SieveLogic.Owner, SieveLogic> logic) {
super(type, pos, state);
this.sieveInterval = sieveInterval;
this.logic = logic.apply(this);
}
@ -44,6 +54,16 @@ public abstract class AbstractSieveBlockEntity extends EBlockEntity implements S
return copy;
}
@Override
public boolean handleResultItem(ItemStack result, ServerLevel level, RandomSource rand) {
var pos = this.worldPosition;
var itemEntity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 1.5, pos.getZ() + 0.5, result);
itemEntity.setDeltaMovement(rand.nextGaussian() * 0.05, 0.2, rand.nextGaussian() * 0.05);
level.addFreshEntity(itemEntity);
return true;
}
@SuppressWarnings("DataFlowIssue")
@Override
public ServerLevel getServerLevel() {
return (ServerLevel) this.level;
@ -81,4 +101,143 @@ public abstract class AbstractSieveBlockEntity extends EBlockEntity implements S
this.logic.setProgress(buffer.readFloat());
this.logic.setContents(buffer.readItem());
}
public InteractionResult use(Level level, Player player, InteractionHand hand) {
ItemStack playerItem = player.getItemInHand(hand);
boolean isClientSide = level.isClientSide;
// Try insert mesh
if (this.logic.getMesh().isEmpty()) {
if (this.logic.isValidMesh(playerItem)) {
if (!isClientSide) {
this.logic.setMesh(singleCopy(playerItem));
if (!player.getAbilities().instabuild) {
playerItem.shrink(1);
}
return InteractionResult.CONSUME;
} else {
return InteractionResult.SUCCESS;
}
}
} else if (this.logic.getContents().isEmpty()) {
// remove mesh with sneak right click
if (player.isShiftKeyDown() && player.getMainHandItem().isEmpty()) {
popOutMesh(level, this.worldPosition, this.logic);
}
}
if (!isClientSide) {
// Insert an item
if (this.logic.getContents().isEmpty()) {
// If the input has any sieve drops, insert it into the mesh
if (this.logic.isValidInput(playerItem)) {
playerItem = insertContents(player, hand, this.logic);
var realPlayer = !(player instanceof FakePlayer);
// prevent machines placing in multiple sieves if nerf is off to avoid confusion
if ((realPlayer || !EConfig.SERVER.nerfAutomatedSieves.get()) && canUseSimultaneously()) {
int range = EConfig.SERVER.simultaneousSieveUsageRange.get();
var cursor = this.worldPosition.mutable().move(-range, 0, -range);
var selfType = getType();
// Fill adjacent sieves
otherSieves:
for (int x = -range; x <= range; x++) {
for (int z = -range; z <= range; z++) {
if (playerItem.isEmpty()) {
break otherSieves;
}
if ((x | z) != 0) {
if (level.getBlockEntity(cursor) instanceof AbstractSieveBlockEntity other && other.getType() == selfType) {
var otherLogic = other.logic;
if (otherLogic.getContents().isEmpty()) {
if (this.logic.getMesh().getItem() == otherLogic.getMesh().getItem()) {
playerItem = insertContents(player, hand, otherLogic);
}
}
}
}
cursor.move(0, 0, 1);
}
cursor.move(1, 0, (-2 * range) - 1);
}
}
}
} else {
var time = level.getGameTime();
var realPlayer = !(player instanceof FakePlayer);
if ((realPlayer || !EConfig.SERVER.nerfAutomatedSieves.get()) && canUseSimultaneously()) {
int range = EConfig.SERVER.simultaneousSieveUsageRange.get();
var cursor = this.worldPosition.mutable().move(-range, 0, -range);
var selfType = getType();
// Sieve with adjacent sieves
for (int x = -range; x <= range; x++) {
for (int z = -range; z <= range; z++) {
if (level.getBlockEntity(cursor) instanceof AbstractSieveBlockEntity other && other.getType() == selfType) {
var otherLogic = other.logic;
if (!otherLogic.getContents().isEmpty()) {
if (this.logic.getMesh().getItem() == otherLogic.getMesh().getItem()) {
otherLogic.sift(this.sieveInterval, time);
}
}
}
cursor.move(0, 0, 1);
}
cursor.move(1, 0, (-2 * range) - 1);
}
} else if (realPlayer || EConfig.SERVER.automatedSieves.get()) {
this.logic.sift(this.sieveInterval, time);
}
}
}
return InteractionResult.sidedSuccess(isClientSide);
}
// Fills the sieve (assumes contents is EMPTY) and returns the remaining item, putting it in the player's hand
public static ItemStack insertContents(Player player, InteractionHand hand, SieveLogic logic) {
var consume = !player.getAbilities().instabuild;
var playerItem = player.getItemInHand(hand);
if (consume) {
if (playerItem.getCount() == 1) {
logic.startSifting(playerItem);
player.setItemInHand(hand, ItemStack.EMPTY);
playerItem = ItemStack.EMPTY;
} else {
logic.startSifting(singleCopy(playerItem));
playerItem.shrink(1);
}
} else {
logic.startSifting(singleCopy(playerItem));
}
return playerItem;
}
// Do not call on client side
public static void popOutMesh(Level level, BlockPos sievePos, SieveLogic logic) {
if (!level.isClientSide) {
// Pop out item
var itemEntity = new ItemEntity(level, sievePos.getX() + 0.5, sievePos.getY() + 1.5, sievePos.getZ() + 0.5, logic.getMesh());
var rand = level.random;
itemEntity.setDeltaMovement(rand.nextGaussian() * 0.05, 0.2, rand.nextGaussian() * 0.05);
level.addFreshEntity(itemEntity);
// Empty contents
logic.setMesh(ItemStack.EMPTY);
}
}
protected boolean canUseSimultaneously() {
return false;
}
}

View File

@ -0,0 +1,32 @@
/*
* Ex Deorum
* Copyright (c) 2024 thedarkcolour
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package thedarkcolour.exdeorum.blockentity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
import thedarkcolour.exdeorum.blockentity.logic.CompressedSieveLogic;
import thedarkcolour.exdeorum.registry.EBlockEntities;
public class CompressedSieveBlockEntity extends AbstractSieveBlockEntity {
private static final float COMPRESSED_SIEVE_INTERVAL = 0.075f;
public CompressedSieveBlockEntity(BlockPos pos, BlockState state) {
super(EBlockEntities.COMPRESSED_SIEVE.get(), pos, state, COMPRESSED_SIEVE_INTERVAL, owner -> new CompressedSieveLogic(owner, false));
}
}

View File

@ -49,7 +49,7 @@ public class MechanicalSieveBlockEntity extends AbstractMachineBlockEntity<Mecha
public MechanicalSieveBlockEntity(BlockPos pos, BlockState state) {
super(EBlockEntities.MECHANICAL_SIEVE.get(), pos, state, ItemHandler::new, EConfig.SERVER.mechanicalSieveEnergyStorage.get());
this.logic = new SieveLogic(this, false, true);
this.logic = new SieveLogic(this, true);
}
@Override

View File

@ -19,16 +19,7 @@
package thedarkcolour.exdeorum.blockentity;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.util.FakePlayer;
import thedarkcolour.exdeorum.blockentity.logic.SieveLogic;
import thedarkcolour.exdeorum.config.EConfig;
import thedarkcolour.exdeorum.registry.EBlockEntities;
@ -37,140 +28,11 @@ public class SieveBlockEntity extends AbstractSieveBlockEntity {
public static final float SIEVE_INTERVAL = 0.1f;
public SieveBlockEntity(BlockPos pos, BlockState state) {
super(EBlockEntities.SIEVE.get(), pos, state, (owner) -> new SieveLogic(owner, true, false));
super(EBlockEntities.SIEVE.get(), pos, state, SIEVE_INTERVAL, owner -> new SieveLogic(owner, false));
}
@Override
public boolean handleResultItem(ItemStack result, ServerLevel level, RandomSource rand) {
var pos = this.worldPosition;
var itemEntity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 1.5, pos.getZ() + 0.5, result);
itemEntity.setDeltaMovement(rand.nextGaussian() * 0.05, 0.2, rand.nextGaussian() * 0.05);
level.addFreshEntity(itemEntity);
return true;
}
public InteractionResult use(Level level, Player player, InteractionHand hand) {
ItemStack playerItem = player.getItemInHand(hand);
boolean isClientSide = level.isClientSide;
// Try insert mesh
if (this.logic.getMesh().isEmpty()) {
if (this.logic.isValidMesh(playerItem)) {
if (!isClientSide) {
this.logic.setMesh(singleCopy(playerItem));
if (!player.getAbilities().instabuild) {
playerItem.shrink(1);
}
return InteractionResult.CONSUME;
} else {
return InteractionResult.SUCCESS;
}
}
} else if (this.logic.getContents().isEmpty()) {
// remove mesh with sneak right click
if (player.isShiftKeyDown() && player.getMainHandItem().isEmpty()) {
popOutMesh(level, this.worldPosition, this.logic);
}
}
if (!isClientSide) {
// Insert an item
if (this.logic.getContents().isEmpty()) {
// If the input has any sieve drops, insert it into the mesh
if (this.logic.isValidInput(playerItem)) {
playerItem = insertContents(player, hand, this.logic);
if (EConfig.SERVER.simultaneousSieveUsage.get()) {
int range = EConfig.SERVER.simultaneousSieveUsageRange.get();
var cursor = this.worldPosition.mutable().move(-range, 0, -range);
// Fill adjacent sieves
otherSieves:
for (int x = -range; x <= range; x++) {
for (int z = -range; z <= range; z++) {
if (playerItem.isEmpty()) {
break otherSieves;
}
if ((x | z) != 0) {
if (level.getBlockEntity(cursor) instanceof SieveBlockEntity other) {
if (other.logic.getContents().isEmpty()) {
if (this.logic.getMesh().getItem() == other.logic.getMesh().getItem()) {
playerItem = insertContents(player, hand, other.logic);
}
}
}
}
cursor.move(0, 0, 1);
}
cursor.move(1, 0, (-2 * range) - 1);
}
}
}
} else {
var time = level.getGameTime();
var realPlayer = !(player instanceof FakePlayer);
if ((realPlayer || !EConfig.SERVER.nerfAutomatedSieves.get()) && EConfig.SERVER.simultaneousSieveUsage.get()) {
int range = EConfig.SERVER.simultaneousSieveUsageRange.get();
var cursor = this.worldPosition.mutable().move(-range, 0, -range);
// Sieve with adjacent sieves
for (int x = -range; x <= range; x++) {
for (int z = -range; z <= range; z++) {
if (level.getBlockEntity(cursor) instanceof SieveBlockEntity other) {
if (!other.logic.getContents().isEmpty()) {
if (this.logic.getMesh().getItem() == other.logic.getMesh().getItem()) {
other.logic.sift(SIEVE_INTERVAL, time);
}
}
}
cursor.move(0, 0, 1);
}
cursor.move(1, 0, (-2 * range) - 1);
}
} else if (realPlayer || EConfig.SERVER.automatedSieves.get()) {
this.logic.sift(SIEVE_INTERVAL, time);
}
}
}
return InteractionResult.sidedSuccess(isClientSide);
}
// Fills the sieve (assumes contents is EMPTY) and returns the remaining item, putting it in the player's hand
public static ItemStack insertContents(Player player, InteractionHand hand, SieveLogic logic) {
var consume = !player.getAbilities().instabuild;
var playerItem = player.getItemInHand(hand);
if (consume) {
if (playerItem.getCount() == 1) {
logic.startSifting(playerItem);
player.setItemInHand(hand, ItemStack.EMPTY);
playerItem = ItemStack.EMPTY;
} else {
logic.startSifting(singleCopy(playerItem));
playerItem.shrink(1);
}
} else {
logic.startSifting(singleCopy(playerItem));
}
return playerItem;
}
// Do not call on client side
public static void popOutMesh(Level level, BlockPos sievePos, SieveLogic logic) {
if (!level.isClientSide) {
// Pop out item
var itemEntity = new ItemEntity(level, sievePos.getX() + 0.5, sievePos.getY() + 1.5, sievePos.getZ() + 0.5, logic.getMesh());
var rand = level.random;
itemEntity.setDeltaMovement(rand.nextGaussian() * 0.05, 0.2, rand.nextGaussian() * 0.05);
level.addFreshEntity(itemEntity);
// Empty contents
logic.setMesh(ItemStack.EMPTY);
}
protected boolean canUseSimultaneously() {
return EConfig.SERVER.simultaneousSieveUsage.get();
}
}

View File

@ -0,0 +1,36 @@
/*
* Ex Deorum
* Copyright (c) 2024 thedarkcolour
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package thedarkcolour.exdeorum.blockentity.logic;
import net.minecraft.world.item.ItemStack;
import thedarkcolour.exdeorum.recipe.RecipeUtil;
import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe;
import java.util.List;
public class CompressedSieveLogic extends SieveLogic {
public CompressedSieveLogic(Owner owner, boolean mechanical) {
super(owner, mechanical);
}
@Override
protected List<? extends SieveRecipe> getDropsFor(ItemStack contents) {
return RecipeUtil.getCompressedSieveRecipes(this.mesh.getItem(), contents);
}
}

View File

@ -32,15 +32,16 @@ import thedarkcolour.exdeorum.recipe.RecipeUtil;
import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe;
import thedarkcolour.exdeorum.tag.EItemTags;
import java.util.List;
public class SieveLogic {
private final Owner owner;
private final boolean saveMesh;
private final boolean mechanical;
// block currently being sifted
private ItemStack contents = ItemStack.EMPTY;
// mesh
private ItemStack mesh = ItemStack.EMPTY;
protected ItemStack mesh = ItemStack.EMPTY;
// from 0.0 to 1.0
private float progress;
private float efficiency;
@ -48,9 +49,8 @@ public class SieveLogic {
private long lastTime = 0;
private final long minInterval;
public SieveLogic(Owner owner, boolean saveMesh, boolean mechanical) {
public SieveLogic(Owner owner, boolean mechanical) {
this.owner = owner;
this.saveMesh = saveMesh;
this.mechanical = mechanical;
this.minInterval = EConfig.SERVER.sieveIntervalTicks.get();
}
@ -60,7 +60,7 @@ public class SieveLogic {
}
public boolean isValidInput(ItemStack stack) {
return !RecipeUtil.getSieveRecipes(this.mesh.getItem(), stack).isEmpty();
return !getDropsFor(stack).isEmpty();
}
public boolean isValidMesh(ItemStack stack) {
@ -91,7 +91,7 @@ public class SieveLogic {
var handledAnyDrops = false;
var hasDrops = false;
for (SieveRecipe recipe : RecipeUtil.getSieveRecipes(this.mesh.getItem(), this.contents)) {
for (SieveRecipe recipe : getDropsFor(this.contents)) {
var amount = getResultAmount(recipe, context, rand);
// Split overflowing stacks (64+) into multiple stacks
@ -124,6 +124,10 @@ public class SieveLogic {
this.owner.markUpdated();
}
protected List<? extends SieveRecipe> getDropsFor(ItemStack contents) {
return RecipeUtil.getSieveRecipes(this.mesh.getItem(), contents);
}
protected int getResultAmount(SieveRecipe recipe, LootContext context, RandomSource rand) {
if (recipe.byHandOnly && this.mechanical) return 0;
@ -160,7 +164,7 @@ public class SieveLogic {
if (!this.contents.isEmpty()) {
nbt.put("contents", this.contents.serializeNBT());
}
if (this.saveMesh && !this.mesh.isEmpty()) {
if (!this.mechanical && !this.mesh.isEmpty()) {
nbt.put("mesh", this.mesh.save(new CompoundTag()));
}
nbt.putFloat("progress", this.progress);
@ -177,7 +181,7 @@ public class SieveLogic {
} else {
this.progress = nbt.getFloat("progress");
}
if (this.saveMesh) {
if (!this.mechanical) {
if (nbt.contains("mesh")) {
setMesh(ItemStack.of(nbt.getCompound("mesh")), false);
} else {

View File

@ -45,10 +45,7 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.client.screen.MechanicalHammerScreen;
import thedarkcolour.exdeorum.client.screen.MechanicalSieveScreen;
import thedarkcolour.exdeorum.client.ter.BarrelRenderer;
import thedarkcolour.exdeorum.client.ter.CrucibleRenderer;
import thedarkcolour.exdeorum.client.ter.InfestedLeavesRenderer;
import thedarkcolour.exdeorum.client.ter.SieveRenderer;
import thedarkcolour.exdeorum.client.ter.*;
import thedarkcolour.exdeorum.compat.ModIds;
import thedarkcolour.exdeorum.config.EConfig;
import thedarkcolour.exdeorum.network.ClientMessageHandler;
@ -136,8 +133,9 @@ public class ClientHandler {
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<>());
event.registerBlockEntityRenderer(EBlockEntities.MECHANICAL_SIEVE.get(), ctx -> new SieveRenderer<>());
event.registerBlockEntityRenderer(EBlockEntities.SIEVE.get(), ctx -> new SieveRenderer<>(0.75f, 15f));
event.registerBlockEntityRenderer(EBlockEntities.MECHANICAL_SIEVE.get(), ctx -> new SieveRenderer<>(0.75f, 15f));
event.registerBlockEntityRenderer(EBlockEntities.COMPRESSED_SIEVE.get(), ctx -> new CompressedSieveRenderer<>(0.5625f, 16f));
}
private static void registerShaders(RegisterShadersEvent event) {

View File

@ -19,18 +19,17 @@
package thedarkcolour.exdeorum.client;
import com.mojang.blaze3d.vertex.PoseStack;
import it.unimi.dsi.fastutil.Pair;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import java.util.List;
public interface RenderFace {
void renderFlatSpriteLerp(MultiBufferSource buffers, PoseStack stack, float percentage, int r, int g, int b, int light, float edge, float yStart, float yEnd);
boolean isMissingTexture();
void renderCuboid(MultiBufferSource buffers, PoseStack stack, float minY, float maxY, int r, int g, int b, int light, float edge);
record Single(RenderType renderType, TextureAtlasSprite sprite, boolean isMissingTexture) implements RenderFace {
public Single(RenderType renderType, TextureAtlasSprite sprite) {
this(renderType, sprite, RenderUtil.isMissingTexture(sprite));
@ -40,23 +39,35 @@ public interface RenderFace {
public void renderFlatSpriteLerp(MultiBufferSource buffers, PoseStack stack, float percentage, int r, int g, int b, int light, float edge, float yStart, float yEnd) {
RenderUtil.renderFlatSpriteLerp(buffers.getBuffer(this.renderType), stack, percentage, r, g, b, this.sprite, light, edge, yStart, yEnd);
}
@Override
public void renderCuboid(MultiBufferSource buffers, PoseStack stack, float minY, float maxY, int r, int g, int b, int light, float edge) {
RenderUtil.renderCuboid(buffers.getBuffer(this.renderType), stack, minY, maxY, r, g, b, this.sprite, light, edge);
}
}
record Composite(List<Pair<RenderType, TextureAtlasSprite>> layers, boolean isMissingTexture) implements RenderFace {
public Composite(List<Pair<RenderType, TextureAtlasSprite>> layers) {
record Composite(CompositeLayer[] layers, boolean isMissingTexture) implements RenderFace {
public Composite(CompositeLayer[] layers) {
this(layers, areAnyMissing(layers));
}
@Override
public void renderFlatSpriteLerp(MultiBufferSource buffers, PoseStack stack, float percentage, int r, int g, int b, int light, float edge, float yStart, float yEnd) {
for (var layer : this.layers) {
RenderUtil.renderFlatSpriteLerp(buffers.getBuffer(layer.first()), stack, percentage, r, g, b, layer.second(), light, edge, yStart, yEnd);
RenderUtil.renderFlatSpriteLerp(buffers.getBuffer(layer.renderType), stack, percentage, r, g, b, layer.sprite, light, edge, yStart, yEnd);
}
}
private static boolean areAnyMissing(List<Pair<RenderType, TextureAtlasSprite>> layers) {
@Override
public void renderCuboid(MultiBufferSource buffers, PoseStack stack, float minY, float maxY, int r, int g, int b, int light, float edge) {
for (var layer : this.layers) {
RenderUtil.renderCuboid(buffers.getBuffer(layer.renderType), stack, minY, maxY, r, g, b, layer.sprite, light, edge);
}
}
private static boolean areAnyMissing(CompositeLayer[] layers) {
for (var layer : layers) {
if (RenderUtil.isMissingTexture(layer.second())) {
if (RenderUtil.isMissingTexture(layer.sprite)) {
return true;
}
}
@ -64,4 +75,6 @@ public interface RenderFace {
return false;
}
}
record CompositeLayer(RenderType renderType, TextureAtlasSprite sprite) {}
}

View File

@ -18,25 +18,20 @@
package thedarkcolour.exdeorum.client;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexFormat;
import it.unimi.dsi.fastutil.Pair;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
@ -48,7 +43,6 @@ import net.minecraft.world.level.material.Fluid;
import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions;
import net.minecraftforge.client.model.CompositeModel;
import net.minecraftforge.client.model.data.ModelData;
import net.minecraftforge.registries.ForgeRegistries;
import org.joml.Vector3f;
import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.client.ter.SieveRenderer;
@ -117,14 +111,15 @@ public class RenderUtil {
if (model instanceof CompositeModel.Baked composite) {
@SuppressWarnings("unchecked")
ImmutableMap<String, BakedModel> children = (ImmutableMap<String, BakedModel>) COMPOSITE_MODEL_CHILDREN.get(composite);
var builder = new ImmutableList.Builder<Pair<RenderType, TextureAtlasSprite>>();
RenderFace.CompositeLayer[] layers = new RenderFace.CompositeLayer[children.size()];
int i = 0;
for (var childModel : children.values()) {
var singleFace = getFaceFromModel(block, rand, childModel);
builder.add(Pair.of(singleFace.renderType(), singleFace.sprite()));
layers[i++] = new RenderFace.CompositeLayer(singleFace.renderType(), singleFace.sprite());
}
face = new RenderFace.Composite(builder.build());
face = new RenderFace.Composite(layers);
} else {
face = getFaceFromModel(block, rand, model);
}
@ -147,7 +142,7 @@ public class RenderUtil {
}
private static TextureAtlasSprite getTopTexture(Block block, BakedModel model) {
var registryName = ForgeRegistries.BLOCKS.getKey(block);
var registryName = BuiltInRegistries.BLOCK.getKey(block);
var sprite = blockAtlas.getSprite(registryName.withPrefix("block/"));
// for stuff like azalea bush, retry to get the top texture
if (isMissingTexture(sprite)) {
@ -209,6 +204,10 @@ public class RenderUtil {
//vMin = sprite.getV0();
//vMax = sprite.getV(8);
// Adjust UV based on height of cuboid, rendering from the top down to the bottom of the texture
float f = sprite.getV1() - sprite.getV0();
vMax = sprite.getV0() + f * (maxY - minY);
// South face
normal = poseNormal.transform(new Vector3f(0, 0, 1));
builder.vertex(pose, edgeMax, maxY, edgeMax).color(r, g, b, 255).uv(uMax, vMin).overlayCoords(0, 10).uv2(light).normal(normal.x, normal.y, normal.z).endVertex();
@ -265,6 +264,68 @@ public class RenderUtil {
builder.vertex(pose, edgeMax, y, edgeMin).color(r, g, b, 255).uv(uMax, vMin).overlayCoords(0, 10).uv2(light).normal(normal.x, normal.y, normal.z).endVertex();
}
// todo use ambient occlusion
// Renders a cuboid using the same side sprite on all six sides
@SuppressWarnings("DuplicatedCode")
public static void renderCuboid(VertexConsumer builder, PoseStack stack, float minY, float maxY, int r, int g, int b, TextureAtlasSprite sprite, int light, float edge) {
var pose = stack.last().pose();
var poseNormal = stack.last().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';
// Top face
normal = poseNormal.transform(new Vector3f(0, 1, 0));
builder.vertex(pose, edgeMin, maxY, edgeMin).color(r, g, b, 255).uv(uMin, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMin, maxY, edgeMax).color(r, g, b, 255).uv(uMin, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, maxY, edgeMax).color(r, g, b, 255).uv(uMax, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, maxY, edgeMin).color(r, g, b, 255).uv(uMax, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
// Bottom face
normal = poseNormal.transform(new Vector3f(0, -1, 0));
builder.vertex(pose, edgeMin, minY, edgeMin).color(r, g, b, 255).uv(uMin, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, minY, edgeMin).color(r, g, b, 255).uv(uMax, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, minY, edgeMax).color(r, g, b, 255).uv(uMax, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMin, minY, edgeMax).color(r, g, b, 255).uv(uMin, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
// Adjust UV based on height of cuboid, rendering from the top down to the bottom of the texture
float f = sprite.getV1() - sprite.getV0();
vMax = sprite.getV0() + f * (maxY - minY);
// South face
normal = poseNormal.transform(new Vector3f(0, 0, -1));
builder.vertex(pose, edgeMax, maxY, edgeMax).color(r, g, b, 255).uv(uMax, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMin, maxY, edgeMax).color(r, g, b, 255).uv(uMin, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMin, minY, edgeMax).color(r, g, b, 255).uv(uMin, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, minY, edgeMax).color(r, g, b, 255).uv(uMax, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
// North face
normal = poseNormal.transform(new Vector3f(0, 0, -1));
builder.vertex(pose, edgeMin, maxY, edgeMin).color(r, g, b, 255).uv(uMin, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, maxY, edgeMin).color(r, g, b, 255).uv(uMax, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, minY, edgeMin).color(r, g, b, 255).uv(uMax, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMin, minY, edgeMin).color(r, g, b, 255).uv(uMin, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
// East face
normal = poseNormal.transform(new Vector3f(1, 0, 0));
builder.vertex(pose, edgeMax, maxY, edgeMin).color(r, g, b, 255).uv(uMin, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, maxY, edgeMax).color(r, g, b, 255).uv(uMax, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, minY, edgeMax).color(r, g, b, 255).uv(uMax, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMax, minY, edgeMin).color(r, g, b, 255).uv(uMin, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
// West face
normal = poseNormal.transform(new Vector3f(-1, 0, 0));
builder.vertex(pose, edgeMin, maxY, edgeMax).color(r, g, b, 255).uv(uMax, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMin, maxY, edgeMin).color(r, g, b, 255).uv(uMin, vMin).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMin, minY, edgeMin).color(r, g, b, 255).uv(uMin, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
builder.vertex(pose, edgeMin, minY, edgeMax).color(r, g, b, 255).uv(uMax, vMax).overlayCoords(0, 10).uv2(lightU, lightV).normal(normal.x, normal.y, normal.z).endVertex();
}
public static Color getRainbowColor(long time, float partialTicks) {
return Color.getHSBColor((180 * Mth.sin((time + partialTicks) / 16.0f) - 180) / 360.0f, 0.7f, 0.8f);
}

View File

@ -0,0 +1,34 @@
/*
* Ex Deorum
* Copyright (c) 2024 thedarkcolour
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package thedarkcolour.exdeorum.client.ter;
import thedarkcolour.exdeorum.blockentity.EBlockEntity;
import thedarkcolour.exdeorum.blockentity.logic.SieveLogic;
// mesh y = 10 / 16
public class CompressedSieveRenderer<T extends EBlockEntity & SieveLogic.Owner> extends SieveRenderer<T> {
public CompressedSieveRenderer(float meshHeight, float contentsMaxY) {
super(meshHeight, contentsMaxY);
}
@Override
protected boolean shouldContentsRender3d(T sieve) {
return true;
}
}

View File

@ -25,6 +25,7 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import thedarkcolour.exdeorum.blockentity.EBlockEntity;
@ -37,6 +38,16 @@ import java.util.Map;
public class SieveRenderer<T extends EBlockEntity & SieveLogic.Owner> implements BlockEntityRenderer<T> {
public static final Map<Item, TextureAtlasSprite> MESH_TEXTURES = new HashMap<>();
private final float meshHeight;
private final float contentsMinY;
private final float contentsMaxY;
public SieveRenderer(float meshHeight, float contentsMaxY) {
this.meshHeight = meshHeight;
this.contentsMinY = meshHeight * 16f + 1f;
this.contentsMaxY = contentsMaxY;
}
@Override
public void render(T sieve, float partialTicks, PoseStack stack, MultiBufferSource buffers, int light, int overlay) {
var logic = sieve.getLogic();
@ -46,7 +57,12 @@ public class SieveRenderer<T extends EBlockEntity & SieveLogic.Owner> implements
var block = blockItem.getBlock();
var percentage = logic.getProgress();
var face = RenderUtil.getTopFace(block);
face.renderFlatSpriteLerp(buffers, stack, percentage, 0xff, 0xff, 0xff, light, 1.0f, 15f, 13f);
if (shouldContentsRender3d(sieve)) {
face.renderCuboid(buffers, stack, this.contentsMinY / 16f, Mth.lerp(percentage, this.contentsMaxY, this.contentsMinY) / 16f, 0xff, 0xff, 0xff, light, 1.0f);
} else {
face.renderFlatSpriteLerp(buffers, stack, percentage, 0xff, 0xff, 0xff, light, 1.0f, this.contentsMaxY, this.contentsMinY);
}
}
var mesh = logic.getMesh();
@ -66,11 +82,16 @@ public class SieveRenderer<T extends EBlockEntity & SieveLogic.Owner> implements
MESH_TEXTURES.put(meshItem, meshSprite);
}
RenderUtil.renderFlatSprite(builder, stack, 0.75f, 0xff, 0xff, 0xff, meshSprite, light, 1f);
RenderUtil.renderFlatSprite(builder, stack, this.meshHeight, 0xff, 0xff, 0xff, meshSprite, light, 1f);
if (mesh.hasFoil()) {
RenderUtil.renderFlatSprite(buffers.getBuffer(RenderType.glint()), stack, 0.75f, 0xff, 0xff, 0xff, meshSprite, light, 1f);
RenderUtil.renderFlatSprite(buffers.getBuffer(RenderType.glint()), stack, this.meshHeight, 0xff, 0xff, 0xff, meshSprite, light, 1f);
}
}
}
// todo return true for transparent sieves
protected boolean shouldContentsRender3d(T sieve) {
return false;
}
}

View File

@ -0,0 +1,34 @@
/*
* Ex Deorum
* Copyright (c) 2024 thedarkcolour
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package thedarkcolour.exdeorum.material;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SoundType;
import thedarkcolour.exdeorum.block.CompressedSieveBlock;
public class CompressedSieveMaterial extends SieveMaterial {
protected CompressedSieveMaterial(SoundType soundType, float strength, boolean needsCorrectTool, String requiredModId) {
super(soundType, strength, needsCorrectTool, requiredModId);
}
@Override
protected Block createBlock() {
return new CompressedSieveBlock(props().noOcclusion());
}
}

View File

@ -27,6 +27,7 @@ import thedarkcolour.exdeorum.compat.ModIds;
public class DefaultMaterials {
public static final MaterialRegistry<BarrelMaterial> BARRELS = new MaterialRegistry<>("barrel");
public static final MaterialRegistry<SieveMaterial> SIEVES = new MaterialRegistry<>("sieve");
public static final MaterialRegistry<CompressedSieveMaterial> COMPRESSED_SIEVES = new MaterialRegistry<>("compressed_sieve");
public static final MaterialRegistry<AbstractCrucibleMaterial> LAVA_CRUCIBLES = new MaterialRegistry<>("lava_crucible", "crucible");
public static final MaterialRegistry<AbstractCrucibleMaterial> WATER_CRUCIBLES = new MaterialRegistry<>("water_crucible", "crucible");
@ -105,6 +106,17 @@ public class DefaultMaterials {
public static final SieveMaterial MAPLE_SIEVE = addDefaultSieve("maple", SoundType.WOOD, ModIds.BLUE_SKIES);
public static final SieveMaterial CRYSTALLIZED_SIEVE = addDefaultSieve("crystallized", SoundType.GLASS, true, ModIds.BLUE_SKIES);
// Ex Deorum
public static final CompressedSieveMaterial OAK_COMPRESSED_SIEVE = addDefaultCompressedSieve("oak", SoundType.WOOD, ExDeorum.ID);
public static final CompressedSieveMaterial SPRUCE_COMPRESSED_SIEVE = addDefaultCompressedSieve("spruce", SoundType.WOOD, ExDeorum.ID);
public static final CompressedSieveMaterial BIRCH_COMPRESSED_SIEVE = addDefaultCompressedSieve("birch", SoundType.WOOD, ExDeorum.ID);
public static final CompressedSieveMaterial JUNGLE_COMPRESSED_SIEVE = addDefaultCompressedSieve("jungle", SoundType.WOOD, ExDeorum.ID);
public static final CompressedSieveMaterial ACACIA_COMPRESSED_SIEVE = addDefaultCompressedSieve("acacia", SoundType.WOOD, ExDeorum.ID);
public static final CompressedSieveMaterial DARK_OAK_COMPRESSED_SIEVE = addDefaultCompressedSieve("dark_oak", SoundType.WOOD, ExDeorum.ID);
public static final CompressedSieveMaterial MANGROVE_COMPRESSED_SIEVE = addDefaultCompressedSieve("mangrove", SoundType.WOOD, ExDeorum.ID);
public static final CompressedSieveMaterial CHERRY_COMPRESSED_SIEVE = addDefaultCompressedSieve("cherry", SoundType.CHERRY_WOOD, ExDeorum.ID);
public static final CompressedSieveMaterial BAMBOO_COMPRESSED_SIEVE = addDefaultCompressedSieve("bamboo", SoundType.BAMBOO_WOOD, ExDeorum.ID);
// Ex Deorum
public static final LavaCrucibleMaterial PORCELAIN_CRUCIBLE = addDefaultLavaCrucible("porcelain", SoundType.STONE, 2.0f, false, MapColor.TERRACOTTA_WHITE, ExDeorum.ID, false);
public static final LavaCrucibleMaterial WARPED_CRUCIBLE = addDefaultLavaCrucible("warped", SoundType.STEM, 1.5f, false, MapColor.CRIMSON_STEM, ExDeorum.ID, false);
@ -171,6 +183,16 @@ public class DefaultMaterials {
return material;
}
private static CompressedSieveMaterial addDefaultCompressedSieve(String name, SoundType soundType, String requiredModID) {
return addDefaultCompressedSieve(name, soundType, 2.0f, false, requiredModID);
}
private static CompressedSieveMaterial addDefaultCompressedSieve(String name, SoundType soundType, float strength, boolean needsCorrectTool, String requiredModId) {
var material = new CompressedSieveMaterial(soundType, strength, needsCorrectTool, requiredModId);
COMPRESSED_SIEVES.register(name, material);
return material;
}
private static LavaCrucibleMaterial addDefaultLavaCrucible(String name, SoundType soundType, float strength, boolean needsCorrectTool, MapColor color, String requiredModId, boolean transparent) {
var material = new LavaCrucibleMaterial(soundType, strength, needsCorrectTool, color.id, requiredModId, transparent);
LAVA_CRUCIBLES.register(name, material);

View File

@ -65,6 +65,7 @@ import thedarkcolour.exdeorum.recipe.cache.*;
import thedarkcolour.exdeorum.recipe.crook.CrookRecipe;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleRecipe;
import thedarkcolour.exdeorum.recipe.hammer.HammerRecipe;
import thedarkcolour.exdeorum.recipe.sieve.CompressedSieveRecipe;
import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe;
import thedarkcolour.exdeorum.registry.ENumberProviders;
import thedarkcolour.exdeorum.registry.ERecipeTypes;
@ -86,7 +87,8 @@ public final class RecipeUtil {
private static SingleIngredientRecipeCache<CrucibleRecipe> lavaCrucibleRecipeCache;
private static SingleIngredientRecipeCache<CrucibleRecipe> waterCrucibleRecipeCache;
private static SingleIngredientRecipeCache<HammerRecipe> hammerRecipeCache;
private static SieveRecipeCache sieveRecipeCache;
private static SieveRecipeCache<SieveRecipe> sieveRecipeCache;
private static SieveRecipeCache<CompressedSieveRecipe> compressedSieveRecipeCache;
private static BarrelFluidMixingRecipeCache barrelFluidMixingRecipeCache;
private static FluidTransformationRecipeCache fluidTransformationRecipeCache;
private static CrookRecipeCache crookRecipeCache;
@ -97,7 +99,8 @@ public final class RecipeUtil {
lavaCrucibleRecipeCache = new SingleIngredientRecipeCache<>(recipes, ERecipeTypes.LAVA_CRUCIBLE);
waterCrucibleRecipeCache = new SingleIngredientRecipeCache<>(recipes, ERecipeTypes.WATER_CRUCIBLE);
hammerRecipeCache = new SingleIngredientRecipeCache<>(recipes, ERecipeTypes.HAMMER).trackAllRecipes();
sieveRecipeCache = new SieveRecipeCache(recipes);
sieveRecipeCache = new SieveRecipeCache<>(recipes, ERecipeTypes.SIEVE);
compressedSieveRecipeCache = new SieveRecipeCache<>(recipes, ERecipeTypes.COMPRESSED_SIEVE);
barrelFluidMixingRecipeCache = new BarrelFluidMixingRecipeCache(recipes);
fluidTransformationRecipeCache = new FluidTransformationRecipeCache(recipes);
crookRecipeCache = new CrookRecipeCache(recipes);
@ -121,6 +124,10 @@ public final class RecipeUtil {
return sieveRecipeCache.getRecipe(mesh, item);
}
public static List<CompressedSieveRecipe> getCompressedSieveRecipes(Item mesh, ItemStack item) {
return compressedSieveRecipeCache.getRecipe(mesh, item);
}
@Nullable
public static CrucibleRecipe getLavaCrucibleRecipe(ItemStack item) {
return lavaCrucibleRecipeCache.getRecipe(item);

View File

@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableList;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.item.crafting.RecipeType;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe;
import thedarkcolour.exdeorum.registry.ERecipeTypes;
@ -30,17 +31,20 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class SieveRecipeCache {
public class SieveRecipeCache<T extends SieveRecipe> {
private RecipeManager recipeManager;
private final Supplier<? extends RecipeType<T>> recipeType;
@Nullable
private Map<Item, MeshRecipeCache> meshCaches;
private Map<Item, MeshRecipeCache<T>> meshCaches;
public SieveRecipeCache(RecipeManager recipeManager) {
public SieveRecipeCache(RecipeManager recipeManager, Supplier<? extends RecipeType<T>> recipeType) {
this.recipeManager = recipeManager;
this.recipeType = recipeType;
}
public List<SieveRecipe> getRecipe(Item mesh, ItemStack input) {
public List<T> getRecipe(Item mesh, ItemStack input) {
if (this.meshCaches == null) {
buildRecipes();
}
@ -50,13 +54,13 @@ public class SieveRecipeCache {
private void buildRecipes() {
// Group recipes based on their mesh
var tempMap = new HashMap<Item, List<SieveRecipe>>();
for (var recipe : this.recipeManager.byType(ERecipeTypes.SIEVE.get()).values()) {
var tempMap = new HashMap<Item, List<T>>();
for (var recipe : this.recipeManager.byType(this.recipeType.get()).values()) {
tempMap.computeIfAbsent(recipe.mesh, k -> new ArrayList<>()).add(recipe);
}
this.meshCaches = new HashMap<>();
for (var mesh : tempMap.entrySet()) {
this.meshCaches.put(mesh.getKey(), new MeshRecipeCache(mesh.getValue()));
this.meshCaches.put(mesh.getKey(), new MeshRecipeCache<>(mesh.getValue()));
}
this.recipeManager = null;
}
@ -66,12 +70,12 @@ public class SieveRecipeCache {
// conveying this information in JEI would be difficult (ex. Bottle drops from Sand, but only if the Sand has a
// certain enchantment). Thirdly, I do not see anybody needing this use case, and if they do, they should contact
// me on GitHub or Discord so that I can get around to actually implementing it.
private static class MeshRecipeCache {
private final Map<Item, List<SieveRecipe>> simpleRecipes;
private static class MeshRecipeCache<T extends SieveRecipe> {
private final Map<Item, List<T>> simpleRecipes;
private MeshRecipeCache(List<SieveRecipe> recipes) {
private MeshRecipeCache(List<T> recipes) {
this.simpleRecipes = new HashMap<>();
var temp = new HashMap<Item, ImmutableList.Builder<SieveRecipe>>();
var temp = new HashMap<Item, ImmutableList.Builder<T>>();
for (var recipe : recipes) {
for (var item : recipe.ingredient.getItems()) {
@ -84,7 +88,7 @@ public class SieveRecipeCache {
}
}
public List<SieveRecipe> getRecipes(ItemStack input) {
public List<T> getRecipes(ItemStack input) {
var result = this.simpleRecipes.get(input.getItem());
return result == null ? List.of() : result;
}

View File

@ -0,0 +1,51 @@
/*
* Ex Deorum
* Copyright (c) 2024 thedarkcolour
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package thedarkcolour.exdeorum.recipe.sieve;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.storage.loot.providers.number.NumberProvider;
import thedarkcolour.exdeorum.registry.ERecipeSerializers;
import thedarkcolour.exdeorum.registry.ERecipeTypes;
public class CompressedSieveRecipe extends SieveRecipe {
public CompressedSieveRecipe(ResourceLocation id, Ingredient ingredient, Item mesh, Item result, NumberProvider resultAmount, boolean byHandOnly) {
super(id, ingredient, mesh, result, resultAmount, byHandOnly);
}
@Override
public RecipeSerializer<?> getSerializer() {
return ERecipeSerializers.COMPRESSED_SIEVE.get();
}
@Override
public RecipeType<?> getType() {
return ERecipeTypes.COMPRESSED_SIEVE.get();
}
public static class Serializer extends SieveRecipe.AbstractSerializer<CompressedSieveRecipe> {
@Override
protected CompressedSieveRecipe createSieveRecipe(ResourceLocation id, Ingredient ingredient, Item mesh, Item result, NumberProvider resultAmount, boolean byHandOnly) {
return new CompressedSieveRecipe(id, ingredient, mesh, result, resultAmount, byHandOnly);
}
}
}

View File

@ -0,0 +1,37 @@
/*
* Ex Deorum
* Copyright (c) 2024 thedarkcolour
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package thedarkcolour.exdeorum.recipe.sieve;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.storage.loot.providers.number.NumberProvider;
import thedarkcolour.exdeorum.registry.ERecipeSerializers;
public class FinishedCompressedSieveRecipe extends FinishedSieveRecipe{
public FinishedCompressedSieveRecipe(ResourceLocation id, Item mesh, Ingredient ingredient, Item result, NumberProvider resultAmount) {
super(id, mesh, ingredient, result, resultAmount);
}
@Override
public RecipeSerializer<?> getType() {
return ERecipeSerializers.COMPRESSED_SIEVE.get();
}
}

View File

@ -19,10 +19,8 @@
package thedarkcolour.exdeorum.recipe.sieve;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Either;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
@ -34,10 +32,10 @@ import thedarkcolour.exdeorum.registry.ERecipeSerializers;
public class FinishedSieveRecipe extends EFinishedRecipe {
private final Ingredient ingredient;
private final Item mesh;
private final Either<Item, TagKey<Item>> result;
private final Item result;
private final NumberProvider resultAmount;
public FinishedSieveRecipe(ResourceLocation id, Item mesh, Ingredient ingredient, Either<Item, TagKey<Item>> result, NumberProvider resultAmount) {
public FinishedSieveRecipe(ResourceLocation id, Item mesh, Ingredient ingredient, Item result, NumberProvider resultAmount) {
super(id);
this.mesh = mesh;
this.ingredient = ingredient;
@ -45,15 +43,12 @@ public class FinishedSieveRecipe extends EFinishedRecipe {
this.resultAmount = resultAmount;
}
@SuppressWarnings("deprecation")
@Override
public void serializeRecipeData(JsonObject object) {
object.add("ingredient", this.ingredient.toJson());
object.addProperty("mesh", BuiltInRegistries.ITEM.getKey(this.mesh).toString());
this.result.ifLeft(item -> {
object.addProperty("result", BuiltInRegistries.ITEM.getKey(item).toString());
}).ifRight(tag -> {
object.addProperty("result_tag", tag.location().toString());
});
object.addProperty("result", BuiltInRegistries.ITEM.getKey(this.result).toString());
object.add("result_amount", LootDataType.PREDICATE.parser().toJsonTree(this.resultAmount));
}

View File

@ -63,11 +63,11 @@ public class SieveRecipe extends ProbabilityRecipe {
return ERecipeTypes.SIEVE.get();
}
public static abstract class AbstractSerializer implements RecipeSerializer<SieveRecipe> {
protected abstract SieveRecipe createSieveRecipe(ResourceLocation id, Ingredient ingredient, Item mesh, Item result, NumberProvider resultAmount, boolean byHandOnly);
public static abstract class AbstractSerializer<T extends SieveRecipe> implements RecipeSerializer<T> {
protected abstract T createSieveRecipe(ResourceLocation id, Ingredient ingredient, Item mesh, Item result, NumberProvider resultAmount, boolean byHandOnly);
@Override
public SieveRecipe fromJson(ResourceLocation id, JsonObject json) {
public T fromJson(ResourceLocation id, JsonObject json) {
Ingredient ingredient = RecipeUtil.readIngredient(json, "ingredient");
Item mesh = RecipeUtil.readItem(json, "mesh");
Item result;
@ -93,7 +93,7 @@ public class SieveRecipe extends ProbabilityRecipe {
@SuppressWarnings("deprecation")
@Override
public @Nullable SieveRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
public @Nullable T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
Ingredient ingredient = Ingredient.fromNetwork(buffer);
Item mesh = Objects.requireNonNull(buffer.readById(BuiltInRegistries.ITEM));
Item result = Objects.requireNonNull(buffer.readById(BuiltInRegistries.ITEM));
@ -103,7 +103,7 @@ public class SieveRecipe extends ProbabilityRecipe {
@SuppressWarnings("deprecation")
@Override
public void toNetwork(FriendlyByteBuf buffer, SieveRecipe recipe) {
public void toNetwork(FriendlyByteBuf buffer, T recipe) {
recipe.getIngredient().toNetwork(buffer);
buffer.writeId(BuiltInRegistries.ITEM, recipe.mesh);
buffer.writeId(BuiltInRegistries.ITEM, recipe.result);
@ -112,7 +112,7 @@ public class SieveRecipe extends ProbabilityRecipe {
}
}
public static class Serializer extends AbstractSerializer {
public static class Serializer extends AbstractSerializer<SieveRecipe> {
@Override
protected SieveRecipe createSieveRecipe(ResourceLocation id, Ingredient ingredient, Item mesh, Item result, NumberProvider resultAmount, boolean byHandOnly) {
return new SieveRecipe(id, ingredient, mesh, result, resultAmount, byHandOnly);

View File

@ -34,6 +34,7 @@ public class EBlockEntities {
public static final RegistryObject<BlockEntityType<WaterCrucibleBlockEntity>> WATER_CRUCIBLE = BLOCK_ENTITIES.register("water_crucible", () -> DefaultMaterials.WATER_CRUCIBLES.createBlockEntityType(WaterCrucibleBlockEntity::new));
public static final RegistryObject<BlockEntityType<BarrelBlockEntity>> BARREL = BLOCK_ENTITIES.register("barrel", () -> DefaultMaterials.BARRELS.createBlockEntityType(BarrelBlockEntity::new));
public static final RegistryObject<BlockEntityType<SieveBlockEntity>> SIEVE = BLOCK_ENTITIES.register("sieve", () -> DefaultMaterials.SIEVES.createBlockEntityType(SieveBlockEntity::new));
public static final RegistryObject<BlockEntityType<CompressedSieveBlockEntity>> COMPRESSED_SIEVE = BLOCK_ENTITIES.register("compressed_sieve", () -> DefaultMaterials.COMPRESSED_SIEVES.createBlockEntityType(CompressedSieveBlockEntity::new));
public static final RegistryObject<BlockEntityType<MechanicalSieveBlockEntity>> MECHANICAL_SIEVE = BLOCK_ENTITIES.register("mechanical_sieve", () -> BlockEntityType.Builder.of(MechanicalSieveBlockEntity::new, EBlocks.MECHANICAL_SIEVE.get()).build(null));
public static final RegistryObject<BlockEntityType<MechanicalHammerBlockEntity>> MECHANICAL_HAMMER = BLOCK_ENTITIES.register("mechanical_hammer", () -> BlockEntityType.Builder.of(MechanicalHammerBlockEntity::new, EBlocks.MECHANICAL_HAMMER.get()).build(null));
}

View File

@ -32,6 +32,7 @@ import thedarkcolour.exdeorum.recipe.crook.CrookRecipe;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleHeatRecipe;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleRecipe;
import thedarkcolour.exdeorum.recipe.hammer.HammerRecipe;
import thedarkcolour.exdeorum.recipe.sieve.CompressedSieveRecipe;
import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe;
public class ERecipeSerializers {
@ -50,6 +51,7 @@ public class ERecipeSerializers {
public static final RegistryObject<RecipeSerializer<CrucibleRecipe>> WATER_CRUCIBLE = RECIPE_SERIALIZERS.register("water_crucible", () -> new CrucibleRecipe.Serializer(ERecipeTypes.WATER_CRUCIBLE.get()));
public static final RegistryObject<RecipeSerializer<SieveRecipe>> SIEVE = RECIPE_SERIALIZERS.register("sieve", SieveRecipe.Serializer::new);
public static final RegistryObject<RecipeSerializer<CompressedSieveRecipe>> COMPRESSED_SIEVE = RECIPE_SERIALIZERS.register("compressed_sieve", CompressedSieveRecipe.Serializer::new);
public static final RegistryObject<RecipeSerializer<?>> TAG_RESULT = RECIPE_SERIALIZERS.register("tag_result", TagResultRecipe.Serializer::new);
}

View File

@ -31,6 +31,7 @@ import thedarkcolour.exdeorum.recipe.crook.CrookRecipe;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleHeatRecipe;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleRecipe;
import thedarkcolour.exdeorum.recipe.hammer.HammerRecipe;
import thedarkcolour.exdeorum.recipe.sieve.CompressedSieveRecipe;
import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe;
public class ERecipeTypes {
@ -49,4 +50,5 @@ public class ERecipeTypes {
public static final RegistryObject<RecipeType<CrucibleHeatRecipe>> CRUCIBLE_HEAT_SOURCE = RECIPE_TYPES.register("crucible_heat_source", () -> RecipeType.simple(ERecipeTypes.CRUCIBLE_HEAT_SOURCE.getId()));
public static final RegistryObject<RecipeType<SieveRecipe>> SIEVE = RECIPE_TYPES.register("sieve", () -> RecipeType.simple(ERecipeTypes.SIEVE.getId()));
public static final RegistryObject<RecipeType<CompressedSieveRecipe>> COMPRESSED_SIEVE = RECIPE_TYPES.register("compressed_sieve", () -> RecipeType.simple(ERecipeTypes.COMPRESSED_SIEVE.getId()));
}