More WIP porting messing around with Codex pt. 2

This commit is contained in:
thedarkcolour 2026-04-06 19:20:22 -07:00
parent ff6776a660
commit 2fc91263ee
33 changed files with 1107 additions and 518 deletions

View File

@ -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

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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<InfestedLeavesBlock> CODEC = simpleCodec(InfestedLeavesBlock::new);
public InfestedLeavesBlock(Properties properties) {
super(properties);
super(0.005f, properties);
registerDefaultState(defaultBlockState().setValue(FULLY_INFESTED, false));
}
@Override
public MapCodec<? extends LeavesBlock> 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;
}
}

View File

@ -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<Component> 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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 extends Mob> T attemptToConvertEntity(Level level, Entity entity, EntityType<T> 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 <T extends LivingEntity, V> void setVariant(T entity, String methodName, Class<V> 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);
}
}
}

View File

@ -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) {
}
}

View File

@ -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<BarrelBlockEntity> {
@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<ItemStack> 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

View File

@ -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);
}

View File

@ -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));

View File

@ -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<String, List<Item>> 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<String> 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<String>();
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<Item> 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());
}
}

View File

@ -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<Block, RenderFace> 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<BlockStateModelPart> parts = new ArrayList<>();
model.collectParts(BlockAndTintGetter.EMPTY, BlockPos.ZERO, state, random, parts);
var layers = new LinkedHashMap<String, RenderFace.CompositeLayer>();
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();
}

View File

@ -43,10 +43,7 @@ public class MechanicalHammerScreen extends AbstractContainerScreen<MechanicalHa
private RedstoneControlWidget redstoneControlWidget;
public MechanicalHammerScreen(MechanicalHammerMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
this.imageWidth = 176;
this.imageHeight = 166;
super(menu, playerInventory, title, 176, 166);
}
@Override

View File

@ -44,10 +44,8 @@ public class MechanicalSieveScreen extends AbstractContainerScreen<MechanicalSie
private RedstoneControlWidget redstoneControlWidget;
public MechanicalSieveScreen(MechanicalSieveMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
super(menu, playerInventory, title, 176, 173);
this.imageWidth = 176;
this.imageHeight = 173;
this.inventoryLabelY += 7;
}

View File

@ -20,6 +20,7 @@ package thedarkcolour.exdeorum.client.screen;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.components.events.GuiEventListener;
@ -128,7 +129,11 @@ public class RedstoneControlWidget implements GuiEventListener, NarratableEntry,
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
public boolean mouseClicked(MouseButtonEvent event, boolean focused) {
return handleMouseClicked(event.x(), event.y(), event.button());
}
private boolean handleMouseClicked(double mouseX, double mouseY, int button) {
// relative xy
int mx = (int) mouseX;
int my = (int) mouseY;

View File

@ -19,26 +19,174 @@
package thedarkcolour.exdeorum.client.ter;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.state.level.CameraRenderState;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.block.BlockModelRenderState;
import net.minecraft.client.renderer.block.model.BlockDisplayContext;
import net.minecraft.client.renderer.state.level.CameraRenderState;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
import net.minecraft.client.renderer.item.ItemModelResolver;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.renderer.rendertype.RenderTypes;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.Identifier;
import net.minecraft.util.Mth;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.level.material.Fluid;
import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.block.BarrelBlock;
import thedarkcolour.exdeorum.blockentity.BarrelBlockEntity;
import thedarkcolour.exdeorum.client.RenderUtil;
import thedarkcolour.exdeorum.config.EConfig;
// TODO: port BarrelRenderer to MC 26.x rendering API (BlockEntityRenderer changed to extract/submit pattern,
// BlockRenderDispatcher/ItemRenderer removed, fluid color/texture APIs changed)
public class BarrelRenderer implements BlockEntityRenderer<BarrelBlockEntity, BlockEntityRenderState> {
public class BarrelRenderer implements BlockEntityRenderer<BarrelBlockEntity, BarrelRenderer.BarrelRenderState> {
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;
}
}

View File

@ -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<AbstractCrucibleBlockEntity, BlockEntityRenderState> {
public class CrucibleRenderer implements BlockEntityRenderer<AbstractCrucibleBlockEntity, CrucibleRenderer.CrucibleRenderState> {
@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;
}
}

View File

@ -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<T extends EBlockEntity & SieveLogic.Owner> implements BlockEntityRenderer<T, BlockEntityRenderState> {
public class SieveRenderer<T extends EBlockEntity & SieveLogic.Owner> implements BlockEntityRenderer<T, SieveRenderer.SieveRenderState> {
public static final Map<Item, TextureAtlasSprite> MESH_TEXTURES = new HashMap<>();
private final float meshHeight;
@ -46,16 +54,84 @@ public class SieveRenderer<T extends EBlockEntity & SieveLogic.Owner> 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;
}
}

View File

@ -91,7 +91,7 @@ public class CompatUtil {
public static void addEnchantmentsTooltip(ItemStack mesh, Level level, Consumer<Component> 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);
}
}
}

View File

@ -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<Item> tag, ModConfigSpec.ConfigValue<String> 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;

View File

@ -79,8 +79,8 @@ public record XeiSieveRecipe(Ingredient ingredient, ItemStack mesh, List<Result>
// 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);
}
}

View File

@ -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,

View File

@ -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<? extends Item> 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<Component> 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);
}
}

View File

@ -50,9 +50,9 @@ public class CrookLootModifier extends LootModifier {
@Override
protected @NotNull ObjectArrayList<ItemStack> doApply(ObjectArrayList<ItemStack> 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));

View File

@ -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) {

View File

@ -29,12 +29,12 @@ import thedarkcolour.exdeorum.material.DefaultMaterials;
public class EBlockEntities {
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, ExDeorum.ID);
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<InfestedLeavesBlockEntity>> INFESTED_LEAVES = BLOCK_ENTITIES.register("infested_leaves", () -> BlockEntityType.Builder.of(InfestedLeavesBlockEntity::new, EBlocks.INFESTED_LEAVES.get()).build(null));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<InfestedLeavesBlockEntity>> INFESTED_LEAVES = BLOCK_ENTITIES.register("infested_leaves", () -> new BlockEntityType<>(InfestedLeavesBlockEntity::new, java.util.Set.of(EBlocks.INFESTED_LEAVES.get())));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<LavaCrucibleBlockEntity>> LAVA_CRUCIBLE = BLOCK_ENTITIES.register("lava_crucible", () -> DefaultMaterials.LAVA_CRUCIBLES.createBlockEntityType(LavaCrucibleBlockEntity::new));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<WaterCrucibleBlockEntity>> WATER_CRUCIBLE = BLOCK_ENTITIES.register("water_crucible", () -> DefaultMaterials.WATER_CRUCIBLES.createBlockEntityType(WaterCrucibleBlockEntity::new));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<BarrelBlockEntity>> BARREL = BLOCK_ENTITIES.register("barrel", () -> DefaultMaterials.BARRELS.createBlockEntityType(BarrelBlockEntity::new));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<SieveBlockEntity>> SIEVE = BLOCK_ENTITIES.register("sieve", () -> DefaultMaterials.SIEVES.createBlockEntityType(SieveBlockEntity::new));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<CompressedSieveBlockEntity>> COMPRESSED_SIEVE = BLOCK_ENTITIES.register("compressed_sieve", () -> DefaultMaterials.COMPRESSED_SIEVES.createBlockEntityType(CompressedSieveBlockEntity::new));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MechanicalSieveBlockEntity>> MECHANICAL_SIEVE = BLOCK_ENTITIES.register("mechanical_sieve", () -> BlockEntityType.Builder.of(MechanicalSieveBlockEntity::new, EBlocks.MECHANICAL_SIEVE.get()).build(null));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MechanicalHammerBlockEntity>> MECHANICAL_HAMMER = BLOCK_ENTITIES.register("mechanical_hammer", () -> BlockEntityType.Builder.of(MechanicalHammerBlockEntity::new, EBlocks.MECHANICAL_HAMMER.get()).build(null));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MechanicalSieveBlockEntity>> MECHANICAL_SIEVE = BLOCK_ENTITIES.register("mechanical_sieve", () -> new BlockEntityType<>(MechanicalSieveBlockEntity::new, java.util.Set.of(EBlocks.MECHANICAL_SIEVE.get())));
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MechanicalHammerBlockEntity>> MECHANICAL_HAMMER = BLOCK_ENTITIES.register("mechanical_hammer", () -> new BlockEntityType<>(MechanicalHammerBlockEntity::new, java.util.Set.of(EBlocks.MECHANICAL_HAMMER.get())));
}

View File

@ -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<Integer> 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);
}
}

View File

@ -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<FluidResource> {
private final Function<ItemStack, IFluidHandlerItem> factory;
public LegacyFluidItemAccessTransfer(ItemAccess itemAccess, Function<ItemStack, IFluidHandlerItem> 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)));
}
}

View File

@ -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<FluidStack> implements ResourceHandler<FluidResource> {
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());
}
}

View File

@ -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<NonNullList<ItemStack>> implements ResourceHandler<ItemResource> {
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<ItemStack> 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<ItemStack> snapshot) {
for (int i = 0; i < snapshot.size(); i++) {
this.handler.setStackInSlot(i, snapshot.get(i).copy());
}
}
}