diff --git a/src/main/java/com/simibubi/create/AllKeys.java b/src/main/java/com/simibubi/create/AllKeys.java index 7f30d4c8ce..bd96a798b9 100644 --- a/src/main/java/com/simibubi/create/AllKeys.java +++ b/src/main/java/com/simibubi/create/AllKeys.java @@ -27,6 +27,8 @@ public enum AllKeys { SHIFT_MODIFIER("shift_modifier", GLFW.GLFW_KEY_LEFT_SHIFT, "Shift Modifier", true), CTRL_MODIFIER("ctrl_modifier", GLFW.GLFW_KEY_LEFT_CONTROL, "Ctrl Modifier", true), ALT_MODIFIER("alt_modifier", GLFW.GLFW_KEY_LEFT_ALT, "Alt Modifier", true), + + TOGGLE_GOGGLES("toggle_goggles", GLFW.GLFW_KEY_UNKNOWN, "Toggle Engineer's Goggles"), ; private KeyMapping keybind; diff --git a/src/main/java/com/simibubi/create/content/contraptions/actors/roller/RollerBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/actors/roller/RollerBlockEntity.java index 2b0e816b2b..9d066c61ef 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/actors/roller/RollerBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/actors/roller/RollerBlockEntity.java @@ -3,6 +3,8 @@ import java.util.List; import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.ValueBoxTransform; @@ -17,9 +19,12 @@ import net.createmod.catnip.lang.Lang; import net.createmod.catnip.math.AngleHelper; import net.createmod.catnip.math.VecHelper; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Direction.Axis; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.EntityBlock; @@ -31,7 +36,7 @@ import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; -public class RollerBlockEntity extends SmartBlockEntity { +public class RollerBlockEntity extends SmartBlockEntity implements IHaveGoggleInformation { // For simulations such as Ponder private float manuallyAnimatedSpeed; @@ -144,6 +149,51 @@ public void shareValuesToAdjacent() { } } + + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + CreateLang.translate("tooltip.mechanical_roller.header") + .forGoggles(tooltip); + + boolean hasFilter = addFilterTooltip(tooltip); + if (!hasFilter) { + tooltip.remove(0); + return false; + } + return true; + } + + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filtering == null ? ItemStack.EMPTY : filtering.getFilter(); + // Verify if the roller has a filter + if (filterStack.isEmpty()) + return false; + + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } + return true; + } + static enum RollingMode implements INamedIconOptions { TUNNEL_PAVE(AllIcons.I_ROLLER_PAVE), diff --git a/src/main/java/com/simibubi/create/content/contraptions/bearing/WindmillBearingBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/bearing/WindmillBearingBlockEntity.java index 07b0ace603..dc1a261dc6 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/bearing/WindmillBearingBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/bearing/WindmillBearingBlockEntity.java @@ -14,6 +14,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; @@ -147,4 +148,10 @@ public String getTranslationKey() { } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + super.addToGoggleTooltip(tooltip, isPlayerSneaking); + addToGoggleRotationDirectionTooltip(tooltip); + return true; + } } diff --git a/src/main/java/com/simibubi/create/content/equipment/goggles/GoggleInputHandler.java b/src/main/java/com/simibubi/create/content/equipment/goggles/GoggleInputHandler.java new file mode 100644 index 0000000000..8aaf0e97b0 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/equipment/goggles/GoggleInputHandler.java @@ -0,0 +1,21 @@ +package com.simibubi.create.content.equipment.goggles; + +import com.simibubi.create.AllKeys; +import net.minecraft.client.Minecraft; + +public class GoggleInputHandler { + + public static void onKeyInput(int key, boolean pressed) { + if (!pressed) + return; + + if (AllKeys.TOGGLE_GOGGLES.doesModifierAndCodeMatch(key)) { + GoggleOverlayRenderer.toggleGoggleOverlay(); + Minecraft.getInstance().player.displayClientMessage( + net.minecraft.network.chat.Component.literal("Goggle overlay " + + (GoggleOverlayRenderer.goggleOverlayEnabled ? "enabled" : "disabled")), + true); + } + } + +} diff --git a/src/main/java/com/simibubi/create/content/equipment/goggles/GoggleOverlayRenderer.java b/src/main/java/com/simibubi/create/content/equipment/goggles/GoggleOverlayRenderer.java index 8a2a089ae2..a554611806 100644 --- a/src/main/java/com/simibubi/create/content/equipment/goggles/GoggleOverlayRenderer.java +++ b/src/main/java/com/simibubi/create/content/equipment/goggles/GoggleOverlayRenderer.java @@ -60,6 +60,14 @@ public class GoggleOverlayRenderer { public static int hoverTicks = 0; public static BlockPos lastHovered = null; + public static boolean goggleOverlayEnabled = true; + + public static void toggleGoggleOverlay() { + + if (GogglesItem.isWearingGoggles(Minecraft.getInstance().player)) + goggleOverlayEnabled = !goggleOverlayEnabled; + } + public static void renderOverlay(GuiGraphics guiGraphics, DeltaTracker deltaTracker) { Minecraft mc = Minecraft.getInstance(); if (mc.options.hideGui || mc.gameMode.getPlayerMode() == GameType.SPECTATOR) @@ -106,7 +114,10 @@ public static void renderOverlay(GuiGraphics guiGraphics, DeltaTracker deltaTrac if (be instanceof IHaveCustomOverlayIcon customOverlayIcon) item = customOverlayIcon.getIcon(isShifting); - if (hasGoggleInformation && wearingGoggles) { + + boolean showGoggleInfo = wearingGoggles && goggleOverlayEnabled; + + if (hasGoggleInformation && showGoggleInfo) { IHaveGoggleInformation gte = (IHaveGoggleInformation) be; goggleAddedInformation = gte.addToGoggleTooltip(tooltip, isShifting); } @@ -142,7 +153,7 @@ public static void renderOverlay(GuiGraphics guiGraphics, DeltaTracker deltaTrac // check for piston poles if goggles are worn BlockState state = world.getBlockState(pos); - if (wearingGoggles && AllBlocks.PISTON_EXTENSION_POLE.has(state)) { + if (showGoggleInfo && AllBlocks.PISTON_EXTENSION_POLE.has(state)) { Direction[] directions = Iterate.directionsInAxis(state.getValue(PistonExtensionPoleBlock.FACING) .getAxis()); int poles = 1; diff --git a/src/main/java/com/simibubi/create/content/fluids/pipes/SmartFluidPipeBlockEntity.java b/src/main/java/com/simibubi/create/content/fluids/pipes/SmartFluidPipeBlockEntity.java index bec868c0c1..4d5c5e3e94 100644 --- a/src/main/java/com/simibubi/create/content/fluids/pipes/SmartFluidPipeBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/fluids/pipes/SmartFluidPipeBlockEntity.java @@ -3,19 +3,25 @@ import java.util.List; import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; import com.simibubi.create.content.fluids.FluidPropagator; import com.simibubi.create.content.fluids.pipes.StraightPipeBlockEntity.StraightPipeFluidTransportBehaviour; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.ValueBoxTransform; import com.simibubi.create.foundation.blockEntity.behaviour.filtering.FilteringBehaviour; +import com.simibubi.create.foundation.utility.CreateLang; import dev.engine_room.flywheel.lib.transform.TransformStack; import net.createmod.catnip.math.AngleHelper; import net.createmod.catnip.math.VecHelper; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Direction.Axis; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import net.minecraft.world.Clearable; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.LevelAccessor; @@ -26,7 +32,7 @@ import net.neoforged.neoforge.fluids.FluidStack; -public class SmartFluidPipeBlockEntity extends SmartBlockEntity implements Clearable { +public class SmartFluidPipeBlockEntity extends SmartBlockEntity implements IHaveGoggleInformation, Clearable { private FilteringBehaviour filter; public SmartFluidPipeBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { @@ -51,6 +57,57 @@ private void onFilterChanged(ItemStack newFilter) { FluidPropagator.propagateChangedPipe(level, worldPosition, getBlockState()); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + CreateLang.translate("tooltip.smart_fluid_pipe.header") + .forGoggles(tooltip); + + boolean hasFilter = addFilterTooltip(tooltip); + if (!hasFilter) { + tooltip.remove(0); + return false; + } + return true; + } + + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filter == null ? ItemStack.EMPTY : filter.getFilter(); + // Verify if the smart fluid pipe has a filter + if (filterStack.isEmpty()) + return false; + + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + // If the filter is an item filter, use its summary, otherwise just show the item + if (filterStack.getItem() instanceof FilterItem filterItem) { + filterSummary = filterItem.makeSummary(filterStack); + } else { + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + } + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } else { + CreateLang.translate("gui.filter.empty") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip); + } + return true; + } + class SmartPipeBehaviour extends StraightPipeFluidTransportBehaviour { public SmartPipeBehaviour(SmartBlockEntity be) { super(be); diff --git a/src/main/java/com/simibubi/create/content/kinetics/base/KineticBlockEntity.java b/src/main/java/com/simibubi/create/content/kinetics/base/KineticBlockEntity.java index 6e9a6e5eda..28b370612d 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/base/KineticBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/kinetics/base/KineticBlockEntity.java @@ -33,6 +33,7 @@ import net.createmod.catnip.nbt.NBTHelper; import net.createmod.catnip.platform.CatnipServices; import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; import net.minecraft.client.resources.language.I18n; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -49,9 +50,10 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; - +import net.minecraft.world.phys.Vec3; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.fml.loading.FMLEnvironment; public class KineticBlockEntity extends SmartBlockEntity implements IHaveGoggleInformation, IHaveHoveringInformation { @@ -468,6 +470,54 @@ public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneak } + protected void addToGoggleRotationDirectionTooltip(List tooltip) { + float speed = getSpeed(); + // If the block isn't generating its own speed and is currently at 0, returns + if (speed == 0) speed = getGeneratedSpeed(); + if (speed == 0) return; + + // Used for GameTests to safely verify tooltip content on the server side + // Bypasses client-only formatting logic to prevent crashes in headless environments + // Note: Used Component.translatable directly to avoid issues with CreateLang in GameTests + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + tooltip.add(Component.translatable("create.gui.goggles.rotation_direction")); + if (speed > 0) { + tooltip.add(Component.translatable("create.gui.goggles.rotation_direction.clockwise")); + } else { + tooltip.add(Component.translatable("create.gui.goggles.rotation_direction.counter_clockwise")); + } + return; + } + + float apparentSpeed = speed; + BlockState state = getBlockState(); + if (state.getBlock() instanceof IRotate rotatingBlock) { + Axis rotationAxis = rotatingBlock.getRotationAxis(state); + Vec3 axisVector = switch (rotationAxis) { + case X -> new Vec3(1, 0, 0); + case Y -> new Vec3(0, 1, 0); + case Z -> new Vec3(0, 0, 1); + }; + + Vec3 lookVector = Minecraft.getInstance().player.getLookAngle(); + double dot = axisVector.dot(lookVector); + + // Invert apparent direction if player is looking against the rotation axis + if (Math.abs(dot) > 0.001) + apparentSpeed = (float) (speed * Math.signum(dot)); + } + + CreateLang.translate("gui.goggles.rotation_direction") + .style(GRAY) + .forGoggles(tooltip); + + CreateLang.translate(apparentSpeed > 0 + ? "gui.goggles.rotation_direction.clockwise" + : "gui.goggles.rotation_direction.counter_clockwise") + .style(apparentSpeed > 0 ? ChatFormatting.GREEN : ChatFormatting.BLUE) + .forGoggles(tooltip, 1); + } + protected void addStressImpactStats(List tooltip, float stressAtBase) { CreateLang.translate("tooltip.stressImpact") .style(GRAY) diff --git a/src/main/java/com/simibubi/create/content/kinetics/deployer/DeployerBlockEntity.java b/src/main/java/com/simibubi/create/content/kinetics/deployer/DeployerBlockEntity.java index abc810acd2..44a54e058a 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/deployer/DeployerBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/kinetics/deployer/DeployerBlockEntity.java @@ -19,7 +19,9 @@ import com.simibubi.create.content.kinetics.base.KineticBlockEntity; import com.simibubi.create.content.kinetics.belt.behaviour.BeltProcessingBehaviour; import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour; +import com.simibubi.create.content.processing.burner.BlazeBurnerBlockEntity.FuelType; import com.simibubi.create.content.processing.sequenced.SequencedAssemblyRecipe; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.filtering.FilteringBehaviour; @@ -64,6 +66,7 @@ import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.fml.loading.FMLEnvironment; import net.neoforged.neoforge.capabilities.Capabilities; import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; import net.neoforged.neoforge.common.NeoForge; @@ -505,25 +508,81 @@ public boolean addToTooltip(List tooltip, boolean isPlayerSneaking) { @Override public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { - CreateLang.translate("tooltip.deployer.header") - .forGoggles(tooltip); - - CreateLang.translate("tooltip.deployer." + (mode == Mode.USE ? "using" : "punching")) - .style(ChatFormatting.YELLOW) - .forGoggles(tooltip); + // Used for GameTests to safely verify tooltip content on the server side + // Bypasses client-only formatting logic to prevent crashes in headless environments + // Note: Used Component.translatable directly to avoid issues with CreateLang in GameTests + if (FMLEnvironment.dist != Dist.DEDICATED_SERVER) { + CreateLang.translate("tooltip.deployer.header") + .forGoggles(tooltip); - if (!heldItem.isEmpty()) - CreateLang.translate("tooltip.deployer.contains", Component.translatable(heldItem.getDescriptionId()) - .getString(), heldItem.getCount()) - .style(ChatFormatting.GREEN) + CreateLang.translate("tooltip.deployer." + (mode == Mode.USE ? "using" : "punching")) + .style(ChatFormatting.YELLOW) .forGoggles(tooltip); - float stressAtBase = calculateStressApplied(); - if (StressImpact.isEnabled() && !Mth.equal(stressAtBase, 0)) { - tooltip.add(CommonComponents.EMPTY); - addStressImpactStats(tooltip, stressAtBase); + if (!heldItem.isEmpty()) + CreateLang.translate("tooltip.deployer.contains", Component.translatable(heldItem.getDescriptionId()) + .getString(), heldItem.getCount()) + .style(ChatFormatting.GREEN) + .forGoggles(tooltip); + + float stressAtBase = calculateStressApplied(); + if (StressImpact.isEnabled() && !Mth.equal(stressAtBase, 0)) { + tooltip.add(CommonComponents.EMPTY); + addStressImpactStats(tooltip, stressAtBase); + } } + addFilterTooltip(tooltip); + + return true; + } + + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filtering == null ? ItemStack.EMPTY : filtering.getFilter(); + // Verify if the deployer has a filter + if (filterStack.isEmpty()) + return false; + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + // If the filter is an item filter, use its summary, otherwise just show the item + if (filterStack.getItem() instanceof FilterItem filterItem) { + filterSummary = filterItem.makeSummary(filterStack); + } else { + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + tooltip.add(Component.translatable("create.gui.filter.allow_item")); + return true; + } + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + } + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + tooltip.add(filterSummary.get(0)); + return true; + } + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } else { + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + tooltip.add(Component.translatable("create.gui.filter.empty")); + return true; + } + CreateLang.translate("gui.filter.empty") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip); + } return true; } diff --git a/src/main/java/com/simibubi/create/content/kinetics/fan/EncasedFanBlockEntity.java b/src/main/java/com/simibubi/create/content/kinetics/fan/EncasedFanBlockEntity.java index 39fdc003fb..5fde23bbfa 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/fan/EncasedFanBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/kinetics/fan/EncasedFanBlockEntity.java @@ -9,6 +9,9 @@ import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; import com.simibubi.create.infrastructure.config.AllConfigs; +import com.simibubi.create.foundation.utility.CreateLang; +import net.minecraft.network.chat.Component; +import net.minecraft.ChatFormatting; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; @@ -20,6 +23,8 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.loading.FMLEnvironment; @MethodsReturnNonnullByDefault public class EncasedFanBlockEntity extends KineticBlockEntity implements IAirCurrentSource { @@ -151,4 +156,63 @@ public void tick() { airCurrent.tick(); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + + Direction flowDirection = getAirFlowDirection(); + Direction facing = getBlockState().getValue(EncasedFanBlock.FACING); + boolean blowingOutward = false; + if (flowDirection != null) + blowingOutward = flowDirection == facing; + + // Used for GameTests to safely verify tooltip content on the server side + // Bypasses client-only formatting logic to prevent crashes in headless environments + // Note: Used Component.translatable directly to avoid issues with CreateLang in GameTests + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + tooltip.add(Component.translatable("create.tooltip.encased_fan.header")); + + if (flowDirection == null) { + tooltip.add(Component.translatable("create.tooltip.encased_fan.not_spinning")); + return true; + } + tooltip.add(Component.translatable("create.tooltip.encased_fan.direction")); + tooltip.add(Component.translatable(blowingOutward + ? "create.tooltip.encased_fan.outward" + : "create.tooltip.encased_fan.inward")); + tooltip.add(Component.translatable("create.tooltip.encased_fan.range")); + return true; + } + + super.addToGoggleTooltip(tooltip, isPlayerSneaking); + + CreateLang.translate("tooltip.encased_fan.header") + .forGoggles(tooltip); + + if (flowDirection == null) { + CreateLang.translate("tooltip.encased_fan.not_spinning") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip, 1); + return true; + } + + CreateLang.translate("tooltip.encased_fan.direction") + .style(ChatFormatting.GRAY) + .text(": ") + .add(CreateLang.translate(blowingOutward + ? "tooltip.encased_fan.outward" + : "tooltip.encased_fan.inward") + .style(blowingOutward ? ChatFormatting.GREEN : ChatFormatting.BLUE)) + .forGoggles(tooltip, 1); + + + CreateLang.translate("tooltip.encased_fan.range") + .style(ChatFormatting.GRAY) + .text(": ") + .add(CreateLang.text(String.format("%.1f", airCurrent.maxDistance)) + .style(ChatFormatting.AQUA)) + .forGoggles(tooltip, 1); + + return true; + } + } diff --git a/src/main/java/com/simibubi/create/content/kinetics/gantry/GantryShaftBlockEntity.java b/src/main/java/com/simibubi/create/content/kinetics/gantry/GantryShaftBlockEntity.java index 25e7619336..45eaa9d329 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/gantry/GantryShaftBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/kinetics/gantry/GantryShaftBlockEntity.java @@ -1,13 +1,17 @@ package com.simibubi.create.content.kinetics.gantry; +import java.util.List; + import com.simibubi.create.AllBlocks; import com.simibubi.create.content.contraptions.gantry.GantryCarriageBlock; import com.simibubi.create.content.contraptions.gantry.GantryCarriageBlockEntity; import com.simibubi.create.content.kinetics.base.KineticBlockEntity; +import com.simibubi.create.foundation.utility.CreateLang; import net.createmod.catnip.data.Iterate; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -107,6 +111,14 @@ public float getPinionMovementSpeed() { return Mth.clamp(convertToLinear(-getSpeed()), -.49f, .49f); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + CreateLang.translate("tooltip.gantry_shaft.header") + .forGoggles(tooltip); + addToGoggleRotationDirectionTooltip(tooltip); + return getSpeed() != 0; + } + @Override protected boolean isNoisy() { return false; diff --git a/src/main/java/com/simibubi/create/content/kinetics/motor/CreativeMotorBlockEntity.java b/src/main/java/com/simibubi/create/content/kinetics/motor/CreativeMotorBlockEntity.java index 6fd174bca4..b08c70abd2 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/motor/CreativeMotorBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/kinetics/motor/CreativeMotorBlockEntity.java @@ -21,11 +21,13 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Direction.Axis; +import net.minecraft.network.chat.Component; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; - +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.loading.FMLEnvironment; import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; public class CreativeMotorBlockEntity extends GeneratingKineticBlockEntity { @@ -112,6 +114,20 @@ protected boolean isSideActive(BlockState state, Direction direction) { } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + // Used for GameTests to safely verify tooltip content on the server side + // Bypasses client-only formatting logic to prevent crashes in headless environments + // Note: Used Component.translatable directly to avoid issues with CreateLang in GameTests + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + addToGoggleRotationDirectionTooltip(tooltip); + return true; + } + super.addToGoggleTooltip(tooltip, isPlayerSneaking); + addToGoggleRotationDirectionTooltip(tooltip); + return true; + } + @Override public void invalidate() { super.invalidate(); diff --git a/src/main/java/com/simibubi/create/content/kinetics/saw/SawBlockEntity.java b/src/main/java/com/simibubi/create/content/kinetics/saw/SawBlockEntity.java index 3b8bbd185f..8d3de8d201 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/saw/SawBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/kinetics/saw/SawBlockEntity.java @@ -18,6 +18,7 @@ import com.simibubi.create.content.kinetics.base.BlockBreakingKineticBlockEntity; import com.simibubi.create.content.kinetics.belt.behaviour.DirectBeltInputBehaviour; import com.simibubi.create.content.logistics.box.PackageItem; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.content.processing.recipe.ProcessingInventory; import com.simibubi.create.content.processing.sequenced.SequencedAssemblyRecipe; import com.simibubi.create.foundation.advancement.AllAdvancements; @@ -27,9 +28,11 @@ import com.simibubi.create.foundation.recipe.RecipeConditions; import com.simibubi.create.foundation.recipe.RecipeFinder; import com.simibubi.create.foundation.utility.AbstractBlockBreakQueue; +import com.simibubi.create.foundation.utility.CreateLang; import com.simibubi.create.infrastructure.config.AllConfigs; import net.createmod.catnip.math.VecHelper; +import net.minecraft.ChatFormatting; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -40,6 +43,8 @@ import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.BlockTags; import net.minecraft.util.Mth; @@ -136,6 +141,51 @@ protected void read(CompoundTag compound, HolderLookup.Provider registries, bool playEvent = ItemStack.parseOptional(registries, compound.getCompound("PlayEvent")); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + super.addToGoggleTooltip(tooltip, isPlayerSneaking); + addFilterTooltip(tooltip); + return true; + } + + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filtering == null ? ItemStack.EMPTY : filtering.getFilter(); + // Verify if the saw has a filter + if (filterStack.isEmpty()) + return false; + + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + // If the filter is an item filter, use its summary, otherwise just show the item + if (filterStack.getItem() instanceof FilterItem filterItem) { + filterSummary = filterItem.makeSummary(filterStack); + } else { + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + } + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } else { + CreateLang.translate("gui.filter.empty") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip); + } + return true; + } + @Override protected AABB createRenderBoundingBox() { return new AABB(getBlockPos()).inflate(.125f); diff --git a/src/main/java/com/simibubi/create/content/kinetics/simpleRelays/BracketedKineticBlockEntity.java b/src/main/java/com/simibubi/create/content/kinetics/simpleRelays/BracketedKineticBlockEntity.java index 9490c98105..e15b0ccefb 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/simpleRelays/BracketedKineticBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/kinetics/simpleRelays/BracketedKineticBlockEntity.java @@ -6,11 +6,16 @@ import com.simibubi.create.content.contraptions.StructureTransform; import com.simibubi.create.content.decoration.bracket.BracketedBlockEntityBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; +import com.simibubi.create.foundation.utility.CreateLang; import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.loading.FMLEnvironment; public class BracketedKineticBlockEntity extends SimpleKineticBlockEntity implements TransformableBlockEntity { @@ -33,4 +38,32 @@ public void transform(BlockEntity be, StructureTransform transform) { } } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + Block block = getBlockState().getBlock(); + boolean isCogwheel = ICogWheel.isSmallCog(block) || ICogWheel.isLargeCog(block); + + // Used for GameTests to safely verify tooltip content on the server side + // Bypasses client-only formatting logic to prevent crashes in headless environments + // Note: Used Component.translatable directly to avoid issues with CreateLang in GameTests + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + if (isCogwheel) { + tooltip.add(Component.translatable("create.tooltip.cogwheel.header")); + } else { + tooltip.add(Component.translatable("create.tooltip.shaft.header")); + } + addToGoggleRotationDirectionTooltip(tooltip); + return true; + } + + if (isCogwheel) { + CreateLang.translate("tooltip.cogwheel.header") + .forGoggles(tooltip); + } else { + CreateLang.translate("tooltip.shaft.header") + .forGoggles(tooltip); + } + addToGoggleRotationDirectionTooltip(tooltip); + return getSpeed() != 0; + } } diff --git a/src/main/java/com/simibubi/create/content/kinetics/waterwheel/WaterWheelBlockEntity.java b/src/main/java/com/simibubi/create/content/kinetics/waterwheel/WaterWheelBlockEntity.java index 8c484f064d..a503baef9e 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/waterwheel/WaterWheelBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/kinetics/waterwheel/WaterWheelBlockEntity.java @@ -21,6 +21,7 @@ import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderLookup.Provider; import net.minecraft.core.Vec3i; +import net.minecraft.network.chat.Component; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.tags.BlockTags; @@ -225,4 +226,10 @@ public float getGeneratedSpeed() { return Mth.clamp(flowScore, -1, 1) * 8 / getSize(); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + super.addToGoggleTooltip(tooltip, isPlayerSneaking); + addToGoggleRotationDirectionTooltip(tooltip); + return true; + } } diff --git a/src/main/java/com/simibubi/create/content/logistics/chute/SmartChuteBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/chute/SmartChuteBlockEntity.java index ba27ef7c5e..3c4caa36b1 100644 --- a/src/main/java/com/simibubi/create/content/logistics/chute/SmartChuteBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/chute/SmartChuteBlockEntity.java @@ -3,12 +3,17 @@ import java.util.List; import com.simibubi.create.AllBlockEntityTypes; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.filtering.FilteringBehaviour; import com.simibubi.create.foundation.item.ItemHelper.ExtractionCountMode; +import com.simibubi.create.foundation.utility.CreateLang; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import net.minecraft.world.Clearable; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -68,6 +73,51 @@ public void clearContent() { filtering.setFilter(ItemStack.EMPTY); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + super.addToGoggleTooltip(tooltip, isPlayerSneaking); + addFilterTooltip(tooltip); + return true; + } + + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filtering == null ? ItemStack.EMPTY : filtering.getFilter(); + // Verify if the smart chute has a filter + if (filterStack.isEmpty()) + return false; + + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + // If the filter is an item filter, use its summary, otherwise just show the item + if (filterStack.getItem() instanceof FilterItem filterItem) { + filterSummary = filterItem.makeSummary(filterStack); + } else { + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + } + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } else { + CreateLang.translate("gui.filter.empty") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip); + } + return true; + } + private boolean isExtracting() { boolean up = getItemMotion() < 0; BlockPos chutePos = worldPosition.relative(up ? Direction.UP : Direction.DOWN); diff --git a/src/main/java/com/simibubi/create/content/logistics/funnel/FunnelBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/funnel/FunnelBlockEntity.java index 0b33bc60a8..88093a8e24 100644 --- a/src/main/java/com/simibubi/create/content/logistics/funnel/FunnelBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/funnel/FunnelBlockEntity.java @@ -7,12 +7,14 @@ import com.simibubi.create.AllBlocks; import com.simibubi.create.AllSoundEvents; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; import com.simibubi.create.api.equipment.goggles.IHaveHoveringInformation; import com.simibubi.create.content.kinetics.belt.BeltBlockEntity; import com.simibubi.create.content.kinetics.belt.BeltHelper; import com.simibubi.create.content.kinetics.belt.behaviour.DirectBeltInputBehaviour; import com.simibubi.create.content.kinetics.belt.transport.TransportedItemStack; import com.simibubi.create.content.logistics.box.PackageEntity; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.content.logistics.funnel.BeltFunnelBlock.Shape; import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; @@ -21,6 +23,7 @@ import com.simibubi.create.foundation.blockEntity.behaviour.inventory.InvManipulationBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.inventory.VersionedInventoryTrackerBehaviour; import com.simibubi.create.foundation.item.ItemHelper.ExtractionCountMode; +import com.simibubi.create.foundation.utility.CreateLang; import com.simibubi.create.infrastructure.config.AllConfigs; import dev.engine_room.flywheel.lib.visualization.VisualizationHelper; @@ -29,10 +32,13 @@ import net.createmod.catnip.math.BlockFace; import net.createmod.catnip.math.VecHelper; import net.createmod.catnip.platform.CatnipServices; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.Clearable; import net.minecraft.world.entity.Entity; @@ -45,7 +51,7 @@ import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; -public class FunnelBlockEntity extends SmartBlockEntity implements IHaveHoveringInformation, Clearable { +public class FunnelBlockEntity extends SmartBlockEntity implements IHaveHoveringInformation, IHaveGoggleInformation, Clearable { private FilteringBehaviour filtering; private InvManipulationBehaviour invManipulation; private VersionedInventoryTrackerBehaviour invVersionTracker; @@ -367,6 +373,57 @@ public void onTransfer(ItemStack stack) { award(AllAdvancements.FUNNEL); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + CreateLang.translate("tooltip.brass_funnel.header") + .forGoggles(tooltip); + + boolean hasFilter = addFilterTooltip(tooltip); + if (!hasFilter) { + tooltip.remove(0); + return false; + } + return true; + } + + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filtering == null ? ItemStack.EMPTY : filtering.getFilter(); + // Verify if the funnel has a filter + if (filterStack.isEmpty()) + return false; + + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + // If the filter is an item filter, use its summary, otherwise just show the item + if (filterStack.getItem() instanceof FilterItem filterItem) { + filterSummary = filterItem.makeSummary(filterStack); + } else { + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + } + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } else { + CreateLang.translate("gui.filter.empty") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip); + } + return true; + } + private LerpedFloat createChasingFlap() { return LerpedFloat.linear() .startWithValue(.25f) diff --git a/src/main/java/com/simibubi/create/content/logistics/itemHatch/ItemHatchBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/itemHatch/ItemHatchBlockEntity.java index 3cd9eb767f..8ad7b7ccca 100644 --- a/src/main/java/com/simibubi/create/content/logistics/itemHatch/ItemHatchBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/itemHatch/ItemHatchBlockEntity.java @@ -2,17 +2,23 @@ import java.util.List; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.filtering.FilteringBehaviour; +import com.simibubi.create.foundation.utility.CreateLang; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import net.minecraft.world.Clearable; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -public class ItemHatchBlockEntity extends SmartBlockEntity implements Clearable { +public class ItemHatchBlockEntity extends SmartBlockEntity implements IHaveGoggleInformation, Clearable { public FilteringBehaviour filtering; public ItemHatchBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { @@ -28,4 +34,55 @@ public void addBehaviours(List behaviours) { public void clearContent() { filtering.setFilter(ItemStack.EMPTY); } + + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + CreateLang.translate("tooltip.item_hatch.header") + .forGoggles(tooltip); + + boolean hasFilter = addFilterTooltip(tooltip); + if (!hasFilter) { + tooltip.remove(0); + return false; + } + return true; + } + + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filtering == null ? ItemStack.EMPTY : filtering.getFilter(); + // Verify if the item hatch has a filter + if (filterStack.isEmpty()) + return false; + + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + // If the filter is an item filter, use its summary, otherwise just show the item + if (filterStack.getItem() instanceof FilterItem filterItem) { + filterSummary = filterItem.makeSummary(filterStack); + } else { + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + } + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } else { + CreateLang.translate("gui.filter.empty") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip); + } + return true; + } } diff --git a/src/main/java/com/simibubi/create/content/processing/basin/BasinBlockEntity.java b/src/main/java/com/simibubi/create/content/processing/basin/BasinBlockEntity.java index fe5a77e245..81f60b0308 100644 --- a/src/main/java/com/simibubi/create/content/processing/basin/BasinBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/processing/basin/BasinBlockEntity.java @@ -18,6 +18,7 @@ import com.simibubi.create.content.fluids.particle.FluidParticleData; import com.simibubi.create.content.kinetics.belt.behaviour.DirectBeltInputBehaviour; import com.simibubi.create.content.kinetics.mixer.MechanicalMixerBlockEntity; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.content.processing.burner.BlazeBurnerBlock; import com.simibubi.create.content.processing.burner.BlazeBurnerBlock.HeatLevel; import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; @@ -52,6 +53,7 @@ import net.minecraft.nbt.ListTag; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; @@ -786,12 +788,52 @@ public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneak isEmpty = false; } - if (isEmpty) + boolean hasFilter = addFilterTooltip(tooltip); + + if (isEmpty && !hasFilter) tooltip.remove(0); return true; } + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filtering == null ? ItemStack.EMPTY : filtering.getFilter(); + // Verify if the basin has a filter + if (filterStack.isEmpty()) + return false; + + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + // If the filter is an item filter, use its summary, otherwise just show the item + if (filterStack.getItem() instanceof FilterItem filterItem) { + filterSummary = filterItem.makeSummary(filterStack); + } else { + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + } + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } else { + CreateLang.translate("gui.filter.empty") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip); + } + return true; + } + @NotNull HeatLevel getHeatLevel() { if (cachedHeatLevel == null) { if (level == null) diff --git a/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerBlockEntity.java b/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerBlockEntity.java index 613e98a7d9..f56a3a2645 100644 --- a/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerBlockEntity.java @@ -8,6 +8,7 @@ import com.simibubi.create.AllItems; import com.simibubi.create.AllTags.AllItemTags; import com.simibubi.create.api.data.datamaps.BlazeBurnerFuel; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; import com.simibubi.create.api.registry.CreateDataMaps; import com.simibubi.create.content.fluids.tank.FluidTankBlock; import com.simibubi.create.content.logistics.stockTicker.StockTickerBlockEntity; @@ -15,6 +16,8 @@ import com.simibubi.create.content.processing.burner.BlazeBurnerBlock.HeatLevel; import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; +import com.simibubi.create.foundation.item.TooltipHelper; +import com.simibubi.create.foundation.utility.CreateLang; import dev.engine_room.flywheel.api.visualization.VisualizationManager; import net.createmod.catnip.animation.LerpedFloat; @@ -28,6 +31,8 @@ import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; +import net.minecraft.network.chat.Component; +import net.minecraft.ChatFormatting; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.sounds.SoundEvents; @@ -44,8 +49,9 @@ import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.fml.loading.FMLEnvironment; -public class BlazeBurnerBlockEntity extends SmartBlockEntity { +public class BlazeBurnerBlockEntity extends SmartBlockEntity implements IHaveGoggleInformation { public static final int MAX_HEAT_CAPACITY = 10000; public static final int INSERTION_THRESHOLD = 500; @@ -102,8 +108,11 @@ public void tick() { if (isCreative) return; - if (remainingBurnTime > 0) + if (remainingBurnTime > 0) { remainingBurnTime--; + if (remainingBurnTime % 20 == 0) + notifyUpdate(); + } if (activeFuel == FuelType.NORMAL) updateBlockState(); @@ -206,6 +215,85 @@ protected void read(CompoundTag compound, HolderLookup.Provider registries, bool super.read(compound, registries, clientPacket); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + // Used for GameTests to safely verify tooltip content on the server side + // Bypasses client-only formatting logic to prevent crashes in headless environments + // Note: Used Component.translatable directly to avoid issues with CreateLang in GameTests + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + tooltip.add(Component.translatable("create.tooltip.blaze_burner.header")); + tooltip.add(Component.translatable("create.tooltip.blaze_burner.fuel_capacity")); + + if (isCreative) { + tooltip.add(Component.translatable("create.tooltip.blaze_burner.infinite")); + return true; + } + + if (activeFuel == FuelType.NONE) { + tooltip.add(Component.translatable("create.tooltip.blaze_burner.empty")); + return true; + } + + tooltip.add(Component.translatable("create.tooltip.blaze_burner.remaining")); + tooltip.add(Component.literal(remainingBurnTime / 20 + " " + Component.translatable("create.generic.unit.seconds").getString())); + return true; + } + + CreateLang.translate("tooltip.blaze_burner.header") + .forGoggles(tooltip); + + CreateLang.translate("tooltip.blaze_burner.fuel_capacity") + .style(ChatFormatting.GRAY) + .forGoggles(tooltip); + + if (isCreative) { + CreateLang.text(TooltipHelper.makeProgressBar(3, 3)) + .add(CreateLang.translate("tooltip.blaze_burner.infinite")) + .style(ChatFormatting.GOLD) + .forGoggles(tooltip, 1); + return true; + } + + if (activeFuel == FuelType.NONE) { + CreateLang.text(TooltipHelper.makeProgressBar(3, 0)) + .add(CreateLang.translate("tooltip.blaze_burner.empty")) + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip, 1); + return true; + } + + // Divide by 20 to convert burn time to seconds + // Minecraft runs at 20 ticks per second (without changing tick speed) + int seconds = remainingBurnTime / 20; + int maxSeconds = MAX_HEAT_CAPACITY / 20; + int percent = (int) (remainingBurnTime / (float) MAX_HEAT_CAPACITY * 100); + + FuelDisplay display = FuelDisplay.of(percent); + + CreateLang.text(TooltipHelper.makeProgressBar(3, display.filled())) + .add(CreateLang.translate(display.fuel_level()) + .text(" (" + percent + "%)")) + .style(display.color()) + .forGoggles(tooltip, 1); + + CreateLang.translate("tooltip.blaze_burner.remaining") + .style(ChatFormatting.GRAY) + .forGoggles(tooltip); + + CreateLang.number(seconds) + .space() + .add(CreateLang.translate("generic.unit.seconds")) + .style(display.color()) + .text(ChatFormatting.GRAY, " / ") + .add(CreateLang.number(maxSeconds) + .space() + .add(CreateLang.translate("generic.unit.seconds")) + .style(ChatFormatting.DARK_GRAY)) + .forGoggles(tooltip, 1); + + return true; + } + public BlazeBurnerBlock.HeatLevel getHeatLevelFromBlock() { return BlazeBurnerBlock.getHeatLevelOf(getBlockState()); } @@ -404,6 +492,14 @@ public void spawnParticleBurst(boolean soulFlame) { } } + private record FuelDisplay(int filled, ChatFormatting color, String fuel_level) { + static FuelDisplay of(int percent) { + if (percent >= 75) return new FuelDisplay(3, ChatFormatting.GREEN, "tooltip.blaze_burner.fuel_level.high"); + if (percent >= 25) return new FuelDisplay(2, ChatFormatting.YELLOW, "tooltip.blaze_burner.fuel_level.medium"); + return new FuelDisplay(1, ChatFormatting.RED, "tooltip.blaze_burner.fuel_level.low"); + } + } + public enum FuelType { NONE, NORMAL, SPECIAL } diff --git a/src/main/java/com/simibubi/create/content/redstone/diodes/BrassDiodeBlockEntity.java b/src/main/java/com/simibubi/create/content/redstone/diodes/BrassDiodeBlockEntity.java index f7166bdc04..780f6563f9 100644 --- a/src/main/java/com/simibubi/create/content/redstone/diodes/BrassDiodeBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/redstone/diodes/BrassDiodeBlockEntity.java @@ -91,6 +91,15 @@ private String format(int value) { return (value / 20 / 60) + "m"; } + // User for showing state with the same unit as the max value in the goggles tooltip + protected String formatGoggleTooltip(int value, int max) { + if (max < 60) + return value + "t"; + if (max < 20 * 60) + return value / 20 + "s"; + return (value / 20 / 60) + "m"; + } + @Override public String getClipboardKey() { return "Block"; diff --git a/src/main/java/com/simibubi/create/content/redstone/diodes/PulseExtenderBlockEntity.java b/src/main/java/com/simibubi/create/content/redstone/diodes/PulseExtenderBlockEntity.java index 23dc3cf74c..c041ecadf5 100644 --- a/src/main/java/com/simibubi/create/content/redstone/diodes/PulseExtenderBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/redstone/diodes/PulseExtenderBlockEntity.java @@ -1,12 +1,20 @@ package com.simibubi.create.content.redstone.diodes; +import java.util.List; + import static com.simibubi.create.content.redstone.diodes.BrassDiodeBlock.POWERING; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; +import com.simibubi.create.foundation.utility.CreateLang; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.loading.FMLEnvironment; -public class PulseExtenderBlockEntity extends BrassDiodeBlockEntity { +public class PulseExtenderBlockEntity extends BrassDiodeBlockEntity implements IHaveGoggleInformation { public PulseExtenderBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); @@ -33,4 +41,35 @@ protected void updateState(boolean powered, boolean powering, boolean atMax, boo if (!powered) state--; } + + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + // Used for GameTests to safely verify tooltip content on the server side + // Bypasses client-only formatting logic to prevent crashes in headless environments + // Note: Used Component.translatable directly to avoid issues with CreateLang in GameTests + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + tooltip.add(Component.translatable("create.tooltip.pulse_extender.header")); + tooltip.add(Component.translatable("create.tooltip.pulse_extender.remaining")); + tooltip.add(Component.literal("" + state)); + return true; + } + + int maxTicks = maxState.getValue(); + + CreateLang.translate("tooltip.pulse_extender.header") + .forGoggles(tooltip); + + CreateLang.translate("tooltip.pulse_extender.remaining") + .style(ChatFormatting.GRAY) + .forGoggles(tooltip); + + CreateLang.text(formatGoggleTooltip(state, maxTicks)) + .style(ChatFormatting.AQUA) + .text(ChatFormatting.GRAY, " / ") + .add(CreateLang.text(formatGoggleTooltip(maxTicks, maxTicks)) + .style(ChatFormatting.DARK_GRAY)) + .forGoggles(tooltip, 1); + + return true; + } } diff --git a/src/main/java/com/simibubi/create/content/redstone/diodes/PulseRepeaterBlockEntity.java b/src/main/java/com/simibubi/create/content/redstone/diodes/PulseRepeaterBlockEntity.java index 76e9dc0040..b231207ebc 100644 --- a/src/main/java/com/simibubi/create/content/redstone/diodes/PulseRepeaterBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/redstone/diodes/PulseRepeaterBlockEntity.java @@ -1,12 +1,20 @@ package com.simibubi.create.content.redstone.diodes; +import java.util.List; + import static com.simibubi.create.content.redstone.diodes.BrassDiodeBlock.POWERING; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; +import com.simibubi.create.foundation.utility.CreateLang; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.loading.FMLEnvironment; -public class PulseRepeaterBlockEntity extends BrassDiodeBlockEntity { +public class PulseRepeaterBlockEntity extends BrassDiodeBlockEntity implements IHaveGoggleInformation { public PulseRepeaterBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); @@ -32,4 +40,34 @@ protected void updateState(boolean powered, boolean powering, boolean atMax, boo level.setBlockAndUpdate(worldPosition, getBlockState().cycle(POWERING)); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + // Used for GameTests to safely verify tooltip content on the server side + // Bypasses client-only formatting logic to prevent crashes in headless environments + // Note: Used Component.translatable directly to avoid issues with CreateLang in GameTests + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + tooltip.add(Component.translatable("create.tooltip.pulse_repeater.header")); + tooltip.add(Component.translatable("create.tooltip.pulse.until_next_pulse")); + tooltip.add(Component.literal("" + state)); + return true; + } + + int maxTicks = maxState.getValue(); + + CreateLang.translate("tooltip.pulse_repeater.header") + .forGoggles(tooltip); + + CreateLang.translate("tooltip.pulse.until_next_pulse") + .style(ChatFormatting.GRAY) + .forGoggles(tooltip); + + CreateLang.text(formatGoggleTooltip(state, maxTicks)) + .style(ChatFormatting.AQUA) + .text(ChatFormatting.GRAY, " / ") + .add(CreateLang.text(formatGoggleTooltip(maxTicks, maxTicks)) + .style(ChatFormatting.DARK_GRAY)) + .forGoggles(tooltip, 1); + + return true; + } } diff --git a/src/main/java/com/simibubi/create/content/redstone/diodes/PulseTimerBlockEntity.java b/src/main/java/com/simibubi/create/content/redstone/diodes/PulseTimerBlockEntity.java index 4b6ad09289..ed4d4d4fbf 100644 --- a/src/main/java/com/simibubi/create/content/redstone/diodes/PulseTimerBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/redstone/diodes/PulseTimerBlockEntity.java @@ -1,12 +1,21 @@ package com.simibubi.create.content.redstone.diodes; +import java.util.List; + import static com.simibubi.create.content.redstone.diodes.BrassDiodeBlock.POWERING; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; +import com.simibubi.create.content.processing.burner.BlazeBurnerBlockEntity.FuelType; +import com.simibubi.create.foundation.utility.CreateLang; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.loading.FMLEnvironment; -public class PulseTimerBlockEntity extends BrassDiodeBlockEntity { +public class PulseTimerBlockEntity extends BrassDiodeBlockEntity implements IHaveGoggleInformation { public PulseTimerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); @@ -33,4 +42,34 @@ protected void updateState(boolean powered, boolean powering, boolean atMax, boo level.setBlockAndUpdate(worldPosition, blockState.setValue(POWERING, shouldPower)); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + // Used for GameTests to safely verify tooltip content on the server side + // Bypasses client-only formatting logic to prevent crashes in headless environments + // Note: Used Component.translatable directly to avoid issues with CreateLang in GameTests + if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) { + tooltip.add(Component.translatable("create.tooltip.pulse_timer.header")); + tooltip.add(Component.translatable("create.tooltip.pulse.until_next_pulse")); + tooltip.add(Component.literal("" + state)); + return true; + } + + int maxTicks = maxState.getValue(); + + CreateLang.translate("tooltip.pulse_timer.header") + .forGoggles(tooltip); + + CreateLang.translate("tooltip.pulse.until_next_pulse") + .style(ChatFormatting.GRAY) + .forGoggles(tooltip); + + CreateLang.text(formatGoggleTooltip(state, maxTicks)) + .style(ChatFormatting.AQUA) + .text(ChatFormatting.GRAY, " / ") + .add(CreateLang.text(formatGoggleTooltip(maxTicks, maxTicks)) + .style(ChatFormatting.DARK_GRAY)) + .forGoggles(tooltip, 1); + + return true; + } } diff --git a/src/main/java/com/simibubi/create/content/redstone/smartObserver/SmartObserverBlockEntity.java b/src/main/java/com/simibubi/create/content/redstone/smartObserver/SmartObserverBlockEntity.java index c8f21f2044..d0f50d3e1b 100644 --- a/src/main/java/com/simibubi/create/content/redstone/smartObserver/SmartObserverBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/redstone/smartObserver/SmartObserverBlockEntity.java @@ -2,12 +2,14 @@ import java.util.List; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; import com.simibubi.create.content.fluids.FluidTransportBehaviour; import com.simibubi.create.content.fluids.PipeConnection.Flow; import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour; import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour.TransportedResult; import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorBlockEntity; import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorPackage; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.content.redstone.DirectedDirectionalBlock; import com.simibubi.create.content.redstone.FilteredDetectorFilterSlot; import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; @@ -17,20 +19,24 @@ import com.simibubi.create.foundation.blockEntity.behaviour.inventory.InvManipulationBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.inventory.TankManipulationBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.inventory.VersionedInventoryTrackerBehaviour; +import com.simibubi.create.foundation.utility.CreateLang; import net.createmod.catnip.data.Iterate; import net.createmod.catnip.math.BlockFace; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import net.minecraft.world.Clearable; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -public class SmartObserverBlockEntity extends SmartBlockEntity implements Clearable { +public class SmartObserverBlockEntity extends SmartBlockEntity implements IHaveGoggleInformation, Clearable { private static final int DEFAULT_DELAY = 6; private FilteringBehaviour filtering; private InvManipulationBehaviour observedInventory; @@ -181,6 +187,57 @@ protected void read(CompoundTag compound, HolderLookup.Provider registries, bool turnOffTicks = compound.getInt("TurnOff"); } + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + CreateLang.translate("tooltip.smart_observer.header") + .forGoggles(tooltip); + + boolean hasFilter = addFilterTooltip(tooltip); + if (!hasFilter) { + tooltip.remove(0); + return false; + } + return true; + } + + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filtering == null ? ItemStack.EMPTY : filtering.getFilter(); + // Verify if the smart observer has a filter + if (filterStack.isEmpty()) + return false; + + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + // If the filter is an item filter, use its summary, otherwise just show the item + if (filterStack.getItem() instanceof FilterItem filterItem) { + filterSummary = filterItem.makeSummary(filterStack); + } else { + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + } + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } else { + CreateLang.translate("gui.filter.empty") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip); + } + return true; + } + @Override public void clearContent() { filtering.setFilter(ItemStack.EMPTY); diff --git a/src/main/java/com/simibubi/create/content/redstone/thresholdSwitch/ThresholdSwitchBlockEntity.java b/src/main/java/com/simibubi/create/content/redstone/thresholdSwitch/ThresholdSwitchBlockEntity.java index 9056c3fa65..4610bbec09 100644 --- a/src/main/java/com/simibubi/create/content/redstone/thresholdSwitch/ThresholdSwitchBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/redstone/thresholdSwitch/ThresholdSwitchBlockEntity.java @@ -2,10 +2,12 @@ import java.util.List; +import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; import com.simibubi.create.compat.thresholdSwitch.FunctionalStorage; import com.simibubi.create.compat.thresholdSwitch.SophisticatedStorage; import com.simibubi.create.compat.thresholdSwitch.StorageDrawers; import com.simibubi.create.compat.thresholdSwitch.ThresholdSwitchCompat; +import com.simibubi.create.content.logistics.filter.FilterItem; import com.simibubi.create.content.logistics.stockTicker.StockTickerBlockEntity; import com.simibubi.create.content.processing.recipe.ProcessingInventory; import com.simibubi.create.content.redstone.DirectedDirectionalBlock; @@ -21,10 +23,13 @@ import com.simibubi.create.foundation.utility.CreateLang; import net.createmod.catnip.math.BlockFace; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.util.Mth; import net.minecraft.world.Clearable; @@ -40,7 +45,7 @@ import net.neoforged.neoforge.fluids.capability.IFluidHandler; import net.neoforged.neoforge.items.IItemHandler; -public class ThresholdSwitchBlockEntity extends SmartBlockEntity implements Clearable { +public class ThresholdSwitchBlockEntity extends SmartBlockEntity implements IHaveGoggleInformation, Clearable { public int onWhenAbove; public int offWhenBelow; @@ -353,4 +358,55 @@ public void setInverted(boolean inverted) { this.inverted = inverted; updatePowerAfterDelay(); } + + @Override + public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneaking) { + CreateLang.translate("tooltip.threshold_switch.header") + .forGoggles(tooltip); + + boolean hasFilter = addFilterTooltip(tooltip); + if (!hasFilter) { + tooltip.remove(0); + return false; + } + return true; + } + + private boolean addFilterTooltip(List tooltip) { + // Get filter blocks and items + ItemStack filterStack = filtering == null ? ItemStack.EMPTY : filtering.getFilter(); + // Verify if the threshold switch has a filter + if (filterStack.isEmpty()) + return false; + + tooltip.add(CommonComponents.EMPTY); + List filterSummary; + // If the filter is an item filter, use its summary, otherwise just show the item + if (filterStack.getItem() instanceof FilterItem filterItem) { + filterSummary = filterItem.makeSummary(filterStack); + } else { + CreateLang.translate("gui.filter.allow_item") + .style(ChatFormatting.GOLD) + .forGoggles(tooltip); + filterSummary = List.of(Component.literal("- ").append(filterStack.getHoverName()) + .withStyle(ChatFormatting.GRAY)); + } + // If the filter summary is not empty, add it to the tooltip + if (!filterSummary.isEmpty()) { + // Add the filter type (allow or deny) in the goggles tooltip format + CreateLang.builder() + .add(filterSummary.get(0)) + .forGoggles(tooltip); + // Add the filter blocks and items in the goggles tooltip format + for (int i = 1; i < filterSummary.size(); i++) + CreateLang.builder() + .add(filterSummary.get(i)) + .forGoggles(tooltip, 1); + } else { + CreateLang.translate("gui.filter.empty") + .style(ChatFormatting.DARK_GRAY) + .forGoggles(tooltip); + } + return true; + } } diff --git a/src/main/java/com/simibubi/create/foundation/events/InputEvents.java b/src/main/java/com/simibubi/create/foundation/events/InputEvents.java index 7adcc1138e..2042ee7ad9 100644 --- a/src/main/java/com/simibubi/create/foundation/events/InputEvents.java +++ b/src/main/java/com/simibubi/create/foundation/events/InputEvents.java @@ -4,6 +4,7 @@ import com.simibubi.create.CreateClient; import com.simibubi.create.content.contraptions.elevator.ElevatorControlsHandler; import com.simibubi.create.content.contraptions.wrench.RadialWrenchHandler; +import com.simibubi.create.content.equipment.goggles.GoggleInputHandler; import com.simibubi.create.content.equipment.toolbox.ToolboxHandlerClient; import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorConnectionHandler; import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorInteractionHandler; @@ -41,6 +42,7 @@ public static void onKeyInput(InputEvent.Key event) { CreateClient.SCHEMATIC_HANDLER.onKeyInput(key, pressed); ToolboxHandlerClient.onKeyInput(key, pressed); RadialWrenchHandler.onKeyInput(key, pressed); + GoggleInputHandler.onKeyInput(key, pressed); } @SubscribeEvent diff --git a/src/main/java/com/simibubi/create/infrastructure/gametest/CreateGameTests.java b/src/main/java/com/simibubi/create/infrastructure/gametest/CreateGameTests.java index 2c97ae1889..d621de75b8 100644 --- a/src/main/java/com/simibubi/create/infrastructure/gametest/CreateGameTests.java +++ b/src/main/java/com/simibubi/create/infrastructure/gametest/CreateGameTests.java @@ -4,6 +4,7 @@ import com.simibubi.create.infrastructure.gametest.tests.TestContraptions; import com.simibubi.create.infrastructure.gametest.tests.TestFluids; +import com.simibubi.create.infrastructure.gametest.tests.TestGogglesTooltip; import com.simibubi.create.infrastructure.gametest.tests.TestItems; import com.simibubi.create.infrastructure.gametest.tests.TestMisc; import com.simibubi.create.infrastructure.gametest.tests.TestProcessing; @@ -21,6 +22,7 @@ public class CreateGameTests { private static final Class[] testHolders = { TestContraptions.class, TestFluids.class, + TestGogglesTooltip.class, TestItems.class, TestMisc.class, TestProcessing.class, diff --git a/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestGogglesTooltip.java b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestGogglesTooltip.java new file mode 100644 index 0000000000..8242b5f528 --- /dev/null +++ b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestGogglesTooltip.java @@ -0,0 +1,600 @@ +package com.simibubi.create.infrastructure.gametest.tests; + +import java.util.List; + +import static com.simibubi.create.infrastructure.gametest.CreateGameTestHelper.TICKS_PER_SECOND; + +import com.simibubi.create.AllBlockEntityTypes; +import com.simibubi.create.content.processing.burner.BlazeBurnerBlock.HeatLevel; +import com.simibubi.create.content.processing.burner.BlazeBurnerBlockEntity; +import com.simibubi.create.content.processing.burner.BlazeBurnerBlockEntity.FuelType; +import com.simibubi.create.content.kinetics.deployer.DeployerBlockEntity; +import com.simibubi.create.content.kinetics.fan.EncasedFanBlockEntity; +import com.simibubi.create.content.kinetics.motor.CreativeMotorBlockEntity; +import com.simibubi.create.content.redstone.diodes.PulseTimerBlockEntity; +import com.simibubi.create.content.redstone.diodes.PulseRepeaterBlockEntity; +import com.simibubi.create.content.redstone.diodes.PulseExtenderBlockEntity; +import com.simibubi.create.content.kinetics.simpleRelays.BracketedKineticBlockEntity; +import com.simibubi.create.infrastructure.gametest.CreateGameTestHelper; +import com.simibubi.create.infrastructure.gametest.GameTestGroup; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.network.chat.Component; + +@GameTestGroup(path = "goggles_tooltip") +public class TestGogglesTooltip { + // ===== Test Variables ===== + + private static final int PROGRESSION_CHECK_SECONDS = 3; + private static final BlockPos BLOCK_POS = new BlockPos(1, 2, 1); + + // ===== Test Methods ===== + + // Blaze Burner Tooltip Tests + @GameTest(template = "blaze_burner_empty") + public static void noFuelTooltip(CreateGameTestHelper helper) { + BlazeBurnerBlockEntity burner = getBurner(helper); + String testType = "Blaze Burner Empty"; + + // Check if the blaze burner is in creative mode (it should not be) + if (burner.isCreative()) + helper.fail(testType + ": Should not be in creative mode for this test"); + // Check if the blaze burner has no fuel + if (burner.getHeatLevelFromBlock() != HeatLevel.SMOULDERING) + helper.fail(testType + ": Should not have fuel for this test. Expected heat level: SMOULDERING, got: " + burner.getHeatLevelFromBlock()); + // Check if active fuel is NONE + if (burner.getActiveFuel() != FuelType.NONE) + helper.fail(testType + ": Should not have fuel for this test. Expected fuel type: NONE, got: " + burner.getActiveFuel()); + + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = burner.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for an empty blaze burner + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.blaze_burner.header", + "create.tooltip.blaze_burner.fuel_capacity", + "create.tooltip.blaze_burner.empty"); + + // Assert that the tooltip does not contain the "Remaining Burn Time" text + String fullText = collectTooltipText(tooltip); + String remaining = Component.translatable("create.tooltip.blaze_burner.remaining").getString(); + if (fullText.contains(remaining)) + helper.fail(testType + ": Tooltip should not contain \"" + remaining + "\""); + + helper.succeed(); + } + + @GameTest(template = "blaze_burner_infinite") + public static void infiniteFuelTooltip(CreateGameTestHelper helper) { + BlazeBurnerBlockEntity burner = getBurner(helper); + String testType = "Blaze Burner Infinite"; + + // Check if the blaze burner is in creative mode + if (!burner.isCreative()) + helper.fail(testType + ": Should be in creative mode for this test"); + // Check if the remaining burn time is 0 for infinite fuel + if (burner.getRemainingBurnTime() != 0) + helper.fail(testType + ": Remaining burn time should be 0 for this test. Got: " + burner.getRemainingBurnTime()); + + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = burner.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for an infinite fuel blaze burner + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.blaze_burner.header", + "create.tooltip.blaze_burner.fuel_capacity", + "create.tooltip.blaze_burner.infinite"); + + // Assert that the tooltip does not contain the "Remaining Burn Time" text + String fullText = collectTooltipText(tooltip); + String remaining = Component.translatable("create.tooltip.blaze_burner.remaining").getString(); + if (fullText.contains(remaining)) + helper.fail(testType + ": Tooltip should not contain \"" + remaining + "\""); + + helper.succeed(); + } + + @GameTest(template = "blaze_burner_normal", timeoutTicks = (PROGRESSION_CHECK_SECONDS + 2) * TICKS_PER_SECOND) + public static void normalFuelTooltipAndDecay(CreateGameTestHelper helper) { + BlazeBurnerBlockEntity burner = getBurner(helper); + String testType = "Blaze Burner Normal"; + + // Check if the blaze burner is in creative mode (it should not be) + if (burner.isCreative()) + helper.fail(testType + ": Should not be in creative mode for this test"); + // Check if the blaze burner has normal fuel + if (burner.getActiveFuel() != FuelType.NORMAL) + helper.fail(testType + ": Should have normal fuel for this test. Expected fuel type: NORMAL, got: " + burner.getActiveFuel()); + if (burner.getRemainingBurnTime() <= 0) + helper.fail(testType + ": Remaining burn time should be greater than 0 for this test"); + + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = burner.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a normal fuel blaze burner + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.blaze_burner.header", + "create.tooltip.blaze_burner.fuel_capacity", + "create.tooltip.blaze_burner.remaining"); + + // Collect the initial burn time and tooltip text for later comparison + int initialBurnTime = burner.getRemainingBurnTime(); + + // Wait for a few seconds and check if the burn time has decayed appropriately + helper.whenSecondsPassed(PROGRESSION_CHECK_SECONDS, () -> { + // We expect the burn time to have lowered PROGRESSION_CHECK_SECONDS + // Note: Burn time decays at 20 ticks per second, but this can be affected by the game's tick rate + // Create a new tooltip after burn time decay and check if the text has updated accordingly + List updatedTooltip = new java.util.ArrayList<>(); + burner.addToGoggleTooltip(updatedTooltip, false); + + String expectedBurnTimeText = (initialBurnTime - PROGRESSION_CHECK_SECONDS * 20) / 20 + " " + Component.translatable("create.generic.unit.seconds").getString(); + + String updatedTooltipText = collectTooltipText(updatedTooltip); + + if (!updatedTooltipText.contains(expectedBurnTimeText)){ + helper.fail(testType + ": Tooltip text did not update properly after delay. Expected: \"" + + expectedBurnTimeText + "\", Got: \"" + updatedTooltipText + "\""); + } + + helper.succeed(); + }); + } + + // Pulse Mechanism Tooltip Tests + @GameTest(template = "pulse_timer", timeoutTicks = (PROGRESSION_CHECK_SECONDS + 2) * TICKS_PER_SECOND) + public static void pulseTimerTooltip(CreateGameTestHelper helper) { + PulseTimerBlockEntity pulseTimer = helper.getBlockEntity(AllBlockEntityTypes.PULSE_TIMER.get(), BLOCK_POS); + String testType = "Pulse Timer"; + + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = pulseTimer.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a pulse timer + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.pulse_timer.header", + "create.tooltip.pulse.until_next_pulse"); + + // Collect the initial tooltip text for later comparison + String initialTooltipText = collectTooltipText(tooltip); + + // Wait for a few seconds and check if the tooltip has updated to reflect the pulse timer's progression + helper.whenSecondsPassed(PROGRESSION_CHECK_SECONDS, () -> { + // Create a new tooltip after time has passed and check if the text has updated accordingly + List updatedTooltip = new java.util.ArrayList<>(); + pulseTimer.addToGoggleTooltip(updatedTooltip, false); + String updatedTooltipText = collectTooltipText(updatedTooltip); + + if (initialTooltipText.equals(updatedTooltipText)) + helper.fail(testType + ": Tooltip text did not update after pulse timer progression. Initial: \"" + + initialTooltipText + "\", Updated: \"" + updatedTooltipText + "\""); + + helper.succeed(); + }); + } + + @GameTest(template = "pulse_repeater", timeoutTicks = (PROGRESSION_CHECK_SECONDS + 2) * TICKS_PER_SECOND) + public static void pulseRepeaterTooltip(CreateGameTestHelper helper) { + PulseRepeaterBlockEntity pulseRepeater = helper.getBlockEntity(AllBlockEntityTypes.PULSE_REPEATER.get(), BLOCK_POS); + String testType = "Pulse Repeater"; + + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = pulseRepeater.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a pulse repeater + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.pulse_repeater.header", + "create.tooltip.pulse.until_next_pulse"); + + // Collect the initial tooltip text for later comparison + String initialTooltipText = collectTooltipText(tooltip); + + // Wait for a few seconds and check if the tooltip has updated to reflect the pulse repeater's progression + helper.whenSecondsPassed(PROGRESSION_CHECK_SECONDS, () -> { + // Create a new tooltip after time has passed and check if the text has updated accordingly + List updatedTooltip = new java.util.ArrayList<>(); + pulseRepeater.addToGoggleTooltip(updatedTooltip, false); + String updatedTooltipText = collectTooltipText(updatedTooltip); + + if (initialTooltipText.equals(updatedTooltipText)) + helper.fail(testType + ": Tooltip text did not update after pulse repeater progression. Initial: \"" + + initialTooltipText + "\", Updated: \"" + updatedTooltipText + "\""); + + helper.succeed(); + }); + } + + @GameTest(template = "pulse_extender", timeoutTicks = (PROGRESSION_CHECK_SECONDS + 2) * TICKS_PER_SECOND) + public static void pulseExtenderTooltip(CreateGameTestHelper helper) { + PulseExtenderBlockEntity pulseExtender = helper.getBlockEntity(AllBlockEntityTypes.PULSE_EXTENDER.get(), BLOCK_POS); + String testType = "Pulse Extender"; + + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = pulseExtender.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a pulse extender + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.pulse_extender.header", + "create.tooltip.pulse_extender.remaining"); + + // Collect the initial tooltip text for later comparison + String initialTooltipText = collectTooltipText(tooltip); + + // Wait for a few seconds and check if the tooltip has updated to reflect the pulse extender's progression + helper.whenSecondsPassed(PROGRESSION_CHECK_SECONDS, () -> { + // Create a new tooltip after time has passed and check if the text has updated accordingly + List updatedTooltip = new java.util.ArrayList<>(); + pulseExtender.addToGoggleTooltip(updatedTooltip, false); + String updatedTooltipText = collectTooltipText(updatedTooltip); + + if (initialTooltipText.equals(updatedTooltipText)) + helper.fail(testType + ": Tooltip text did not update after pulse extender progression. Initial: \"" + + initialTooltipText + "\", Updated: \"" + updatedTooltipText + "\""); + + helper.succeed(); + }); + } + + // Kinetic Blocks Tooltip Tests + // Note: This tests don't cover all affected kinetic blocks, because many of them share the same tooltip logic + + @GameTest(template = "creative_motor_clockwise") + public static void creativeMotorClockwiseTooltip(CreateGameTestHelper helper) { + CreativeMotorBlockEntity motor = helper.getBlockEntity(AllBlockEntityTypes.MOTOR.get(), BLOCK_POS); + String testType = "Creative Motor Clockwise"; + + // Wait 1 second to ensure the motor has started generating speed before checking the tooltip + helper.whenSecondsPassed(1, () -> { + if (motor.getGeneratedSpeed() <= 0) + helper.fail(testType + ": Motor should be generating clockwise speed for this test. Got speed: " + motor.getGeneratedSpeed()); + }); + + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = motor.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a clockwise creative motor + assertTooltipContains(helper, tooltip, result, testType, + "create.gui.goggles.rotation_direction", + "create.gui.goggles.rotation_direction.clockwise"); + + helper.succeed(); + } + + @GameTest(template = "creative_motor_counter_clockwise") + public static void creativeMotorCounterClockwiseTooltip(CreateGameTestHelper helper) { + CreativeMotorBlockEntity motor = helper.getBlockEntity(AllBlockEntityTypes.MOTOR.get(), BLOCK_POS); + String testType = "Creative Motor Counter-Clockwise"; + + // Wait 1 second to ensure the motor has started generating speed before checking the tooltip + helper.whenSecondsPassed(1, () -> { + if (motor.getGeneratedSpeed() >= 0) + helper.fail(testType + ": Motor should be rotating counter-clockwise for this test. Got speed: " + motor.getGeneratedSpeed()); + }); + + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = motor.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a counter-clockwise creative motor + assertTooltipContains(helper, tooltip, result, testType, + "create.gui.goggles.rotation_direction", + "create.gui.goggles.rotation_direction.counter_clockwise"); + + helper.succeed(); + } + + @GameTest(template = "shaft_clockwise") + public static void shaftClockwiseTooltip(CreateGameTestHelper helper) { + BracketedKineticBlockEntity shaft = helper.getBlockEntity(AllBlockEntityTypes.BRACKETED_KINETIC.get(), BLOCK_POS); + String testType = "Shaft Clockwise"; + + // Wait 1 second to ensure the shaft has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = shaft.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a clockwise shaft + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.shaft.header", + "create.gui.goggles.rotation_direction", + "create.gui.goggles.rotation_direction.clockwise"); + + helper.succeed(); + }); + } + + @GameTest(template = "shaft_counter_clockwise") + public static void shaftCounterClockwiseTooltip(CreateGameTestHelper helper) { + BracketedKineticBlockEntity shaft = helper.getBlockEntity(AllBlockEntityTypes.BRACKETED_KINETIC.get(), BLOCK_POS); + String testType = "Shaft Counter-Clockwise"; + + // Wait 1 second to ensure the shaft has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = shaft.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a counter-clockwise shaft + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.shaft.header", + "create.gui.goggles.rotation_direction", + "create.gui.goggles.rotation_direction.counter_clockwise"); + + helper.succeed(); + }); + } + + @GameTest(template = "shaft_stop") + public static void shaftStopTooltip(CreateGameTestHelper helper) { + BracketedKineticBlockEntity shaft = helper.getBlockEntity(AllBlockEntityTypes.BRACKETED_KINETIC.get(), BLOCK_POS); + String testType = "Shaft Stop"; + + // Wait 1 second to ensure the shaft is not rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + shaft.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a stopped shaft + String fullText = collectTooltipText(tooltip); + String rotation = Component.translatable("create.gui.goggles.rotation_direction").getString(); + if (fullText.contains(rotation)) + helper.fail(testType + ": Tooltip should not contain rotation direction when shaft is stopped. Got tooltip: " + fullText); + + helper.succeed(); + }); + } + + @GameTest(template = "cogwheel_clockwise") + public static void cogwheelClockwiseTooltip(CreateGameTestHelper helper) { + BracketedKineticBlockEntity cogwheel = helper.getBlockEntity(AllBlockEntityTypes.BRACKETED_KINETIC.get(), BLOCK_POS); + String testType = "Cogwheel Clockwise"; + + // Wait 1 second to ensure the cogwheel has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = cogwheel.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a clockwise cogwheel + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.cogwheel.header", + "create.gui.goggles.rotation_direction", + "create.gui.goggles.rotation_direction.clockwise"); + + helper.succeed(); + }); + } + + @GameTest(template = "cogwheel_counter_clockwise") + public static void cogwheelCounterClockwiseTooltip(CreateGameTestHelper helper) { + BracketedKineticBlockEntity cogwheel = helper.getBlockEntity(AllBlockEntityTypes.BRACKETED_KINETIC.get(), BLOCK_POS); + String testType = "Cogwheel Counter-Clockwise"; + + // Wait 1 second to ensure the cogwheel has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = cogwheel.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a counter-clockwise cogwheel + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.cogwheel.header", + "create.gui.goggles.rotation_direction", + "create.gui.goggles.rotation_direction.counter_clockwise"); + + helper.succeed(); + }); + } + + @GameTest(template = "cogwheel_stop") + public static void cogwheelStopTooltip(CreateGameTestHelper helper) { + BracketedKineticBlockEntity cogwheel = helper.getBlockEntity(AllBlockEntityTypes.BRACKETED_KINETIC.get(), BLOCK_POS); + String testType = "Cogwheel Stop"; + + // Wait 1 second to ensure the cogwheel is not rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + cogwheel.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a stopped cogwheel + String fullText = collectTooltipText(tooltip); + String rotation = Component.translatable("create.gui.goggles.rotation_direction").getString(); + if (fullText.contains(rotation)) + helper.fail(testType + ": Tooltip should not contain rotation direction when cogwheel is stopped. Got tooltip: " + fullText); + + helper.succeed(); + }); + } + + // Encased Fan Tests + @GameTest(template = "encased_fan_stop") + public static void encasedFanStopToolTip(CreateGameTestHelper helper) { + EncasedFanBlockEntity fan = helper.getBlockEntity(AllBlockEntityTypes.ENCASED_FAN.get(), BLOCK_POS); + String testType = "Encased Fan Stop"; + + // Wait 1 second to ensure the shaft has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = fan.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for a stopped encased fan + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.encased_fan.header", + "create.tooltip.encased_fan.not_spinning"); + + helper.succeed(); + }); + } + + @GameTest(template = "encased_fan_inward") + public static void encasedFanInwardToolTip(CreateGameTestHelper helper) { + EncasedFanBlockEntity fan = helper.getBlockEntity(AllBlockEntityTypes.ENCASED_FAN.get(), BLOCK_POS); + String testType = "Encased Fan Inward"; + + // Wait 1 second to ensure the shaft has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = fan.addToGoggleTooltip(tooltip, false); + + //Assert that the tooltip contains the expected information for an encased fan that is blowing inwards + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.encased_fan.header", + "create.tooltip.encased_fan.direction", + "create.tooltip.encased_fan.inward", + "create.tooltip.encased_fan.range" + ); + + helper.succeed(); + }); + } + + @GameTest(template = "encased_fan_outward") + public static void encasedFanOutwardToolTip(CreateGameTestHelper helper) { + EncasedFanBlockEntity fan = helper.getBlockEntity(AllBlockEntityTypes.ENCASED_FAN.get(), BLOCK_POS); + String testType = "Encased Fan Outward"; + + // Wait 1 second to ensure the shaft has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = fan.addToGoggleTooltip(tooltip, false); + + // Assert that the tooltip contains the expected information for an encased fan that is blowing outwards + assertTooltipContains(helper, tooltip, result, testType, + "create.tooltip.encased_fan.header", + "create.tooltip.encased_fan.direction", + "create.tooltip.encased_fan.outward", + "create.tooltip.encased_fan.range" + ); + + helper.succeed(); + }); + } + + // Deployer Filter Tests + @GameTest(template = "deployer_no_filter") + public static void DeployerNoFilter(CreateGameTestHelper helper) { + DeployerBlockEntity deployer = helper.getBlockEntity(AllBlockEntityTypes.DEPLOYER.get(), BLOCK_POS); + String testType = "Deployer No Filter"; + + // Wait 1 second to ensure the shaft has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = deployer.addToGoggleTooltip(tooltip, false); + + if (!result) + helper.fail(testType + ": addToGoggleTooltip() returned false"); + if (!tooltip.isEmpty()) + helper.fail(testType + ": tooltip should be empty"); + helper.succeed(); + }); + } + + @GameTest(template = "deployer_single_item_filter") + public static void DeployerSingleItemFilter(CreateGameTestHelper helper) { + DeployerBlockEntity deployer = helper.getBlockEntity(AllBlockEntityTypes.DEPLOYER.get(), BLOCK_POS); + String testType = "Deployer Single Item Filter"; + + // Wait 1 second to ensure the shaft has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = deployer.addToGoggleTooltip(tooltip, false); + + assertTooltipContains(helper, tooltip, result, testType, + "create.gui.filter.allow_item"); + + helper.succeed(); + }); + } + + @GameTest(template = "deployer_empty_list_filter") + public static void DeployerEmptyListFilter(CreateGameTestHelper helper) { + DeployerBlockEntity deployer = helper.getBlockEntity(AllBlockEntityTypes.DEPLOYER.get(), BLOCK_POS); + String testType = "Deployer Empty List Filter"; + + // Wait 1 second to ensure the shaft has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = deployer.addToGoggleTooltip(tooltip, false); + + assertTooltipContains(helper, tooltip, result, testType, + "create.gui.filter.empty"); + + helper.succeed(); + }); + } + + @GameTest(template = "deployer_allow_item_list_filter") + public static void DeployerAllowItemListFilter(CreateGameTestHelper helper) { + DeployerBlockEntity deployer = helper.getBlockEntity(AllBlockEntityTypes.DEPLOYER.get(), BLOCK_POS); + String testType = "Deployer Allow Item List Filter"; + + // Wait 1 second to ensure the shaft has started rotating before checking the tooltip + helper.whenSecondsPassed(1, () -> { + // Create the tooltip and call the method addToGoggleTooltip to populate it + List tooltip = new java.util.ArrayList<>(); + boolean result = deployer.addToGoggleTooltip(tooltip, false); + + assertTooltipContains(helper, tooltip, result, testType, + "create.gui.filter.allow_list"); + + helper.succeed(); + }); + } + + + // ===== Helper Methods ===== + + // Helper method to collect tooltip text into a single string for easier searching + private static String collectTooltipText(List tooltip) { + StringBuilder sb = new StringBuilder(); + // Concatenate all tooltip components into a single string + for (Component component : tooltip) + sb.append(component.getString()).append(" "); + return sb.toString(); + } + + // Helper method to assert that the tooltip contains the expected text based on the translation key + private static void assertTooltipContains(CreateGameTestHelper helper, List tooltip, + boolean result, String testType, String... translationKeys) { + + // Check if addToGoggleTooltip returned true and that the tooltip is not empty + if (!result) + helper.fail(testType + ": addToGoggleTooltip() returned false"); + if (tooltip.isEmpty()) + helper.fail(testType + ": tooltip is empty"); + + // Collect the full tooltip text for easier searching + String fullText = collectTooltipText(tooltip); + + // Check that each expected message is present in the tooltip + // Note: Used Component.translatable to get the expected text based on the translation key + for (String key : translationKeys) { + String expected = Component.translatable(key).getString(); + + if (!fullText.contains(expected)) + helper.fail(testType + ": tooltip does not contain \"" + expected + + "\". Content: " + fullText); + } + } + + private static BlazeBurnerBlockEntity getBurner(CreateGameTestHelper helper) { + return helper.getBlockEntity(AllBlockEntityTypes.HEATER.get(), BLOCK_POS); + } +} diff --git a/src/main/resources/assets/create/lang/default/interface.json b/src/main/resources/assets/create/lang/default/interface.json index 88e1b863c1..c6e2e35233 100644 --- a/src/main/resources/assets/create/lang/default/interface.json +++ b/src/main/resources/assets/create/lang/default/interface.json @@ -251,6 +251,9 @@ "create.gui.goggles.basin_contents": "Basin Contents:", "create.gui.goggles.fluid_container": "Fluid Container Info:", "create.gui.goggles.fluid_container.capacity": "Capacity: ", + "create.gui.goggles.rotation_direction": "Rotation Direction: ", + "create.gui.goggles.rotation_direction.clockwise": "\u27f3 Clockwise", + "create.gui.goggles.rotation_direction.counter_clockwise": "\u27f2 Counter-Clockwise", "create.gui.assembly.exception": "This Contraption was unable to assemble:", "create.gui.assembly.exception.unmovableBlock": "Unmovable Block (%4$s) at [%1$s,%2$s,%3$s]", @@ -519,6 +522,8 @@ "create.materialChecklist": "Material Checklist", "create.materialChecklist.blocksNotLoaded": "* Disclaimer *\n\nMaterial List may be inaccurate due to relevant chunks not being loaded.", + "create.gui.filter.empty": "Empty Filter", + "create.gui.filter.allow_item": "Allow Item", "create.gui.filter.deny_list": "Deny-List", "create.gui.filter.deny_list.description": "Items pass if they do NOT match any of the above. An empty Deny-List accepts everything.", "create.gui.filter.allow_list": "Allow-List", @@ -628,6 +633,32 @@ "create.tooltip.keyShift": "Shift", "create.tooltip.keyCtrl": "Ctrl", + "create.tooltip.blaze_burner.empty": "No fuel", + "create.tooltip.blaze_burner.fuel_capacity": "Fuel Capacity", + "create.tooltip.blaze_burner.fuel_level.high": "High", + "create.tooltip.blaze_burner.fuel_level.medium": "Medium", + "create.tooltip.blaze_burner.fuel_level.low": "Low", + "create.tooltip.blaze_burner.header": "Blaze Burner Information", + "create.tooltip.blaze_burner.infinite": "Infinite", + "create.tooltip.blaze_burner.remaining": "Remaining Burn Time", + + "create.tooltip.cogwheel.header": "Cogwheel Information", + "create.tooltip.gantry_shaft.header": "Gantry Shaft Information", + "create.tooltip.shaft.header": "Shaft Information", + + "create.tooltip.pulse.until_next_pulse": "Time Until Next Pulse", + "create.tooltip.pulse_timer.header": "Pulse Timer Information", + "create.tooltip.pulse_repeater.header": "Pulse Repeater Information", + "create.tooltip.pulse_extender.header": "Pulse Extender Information", + "create.tooltip.pulse_extender.remaining": "Remaining Pulse Time", + + "create.tooltip.encased_fan.header": "Encased Fan:", + "create.tooltip.encased_fan.not_spinning": "Not spinning", + "create.tooltip.encased_fan.direction": "Direction", + "create.tooltip.encased_fan.outward": "Outward", + "create.tooltip.encased_fan.inward": "Inward", + "create.tooltip.encased_fan.range": "Range", + "create.tooltip.speedRequirement": "Speed Requirement:", "create.tooltip.speedRequirement.none": "None", "create.tooltip.speedRequirement.slow": "Slow", @@ -689,6 +720,13 @@ "create.tooltip.deployer.punching": "Mode: Attack", "create.tooltip.deployer.contains": "Item: %1$s x%2$s", + "create.tooltip.brass_funnel.header": "Brass Funel Information", + "create.tooltip.item_hatch.header": "Item Hatch Information", + "create.tooltip.mechanical_roller.header": "Mechanical Roller Information", + "create.tooltip.smart_fluid_pipe.header": "Smart Fluid Pipe Information", + "create.tooltip.smart_observer.header": "Smart Observer Information", + "create.tooltip.threshold_switch.header": "Threshold Switch Information", + "create.tooltip.brass_tunnel.contains": "Currently distributing:", "create.tooltip.brass_tunnel.contains_entry": "> %1$s x%2$s", "create.tooltip.brass_tunnel.retrieve": "Right-Click to retrieve", diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/blaze_burner_empty.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/blaze_burner_empty.nbt new file mode 100644 index 0000000000..0fdc184e23 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/blaze_burner_empty.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/blaze_burner_infinite.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/blaze_burner_infinite.nbt new file mode 100644 index 0000000000..0fdab5fc3b Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/blaze_burner_infinite.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/blaze_burner_normal.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/blaze_burner_normal.nbt new file mode 100644 index 0000000000..54ae579059 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/blaze_burner_normal.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/cogwheel_clockwise.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/cogwheel_clockwise.nbt new file mode 100644 index 0000000000..7f85af9d32 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/cogwheel_clockwise.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/cogwheel_counter_clockwise.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/cogwheel_counter_clockwise.nbt new file mode 100644 index 0000000000..10c9976654 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/cogwheel_counter_clockwise.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/cogwheel_stop.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/cogwheel_stop.nbt new file mode 100644 index 0000000000..cc83f08119 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/cogwheel_stop.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/creative_motor_clockwise.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/creative_motor_clockwise.nbt new file mode 100644 index 0000000000..4ac05b2dfe Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/creative_motor_clockwise.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/creative_motor_counter_clockwise.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/creative_motor_counter_clockwise.nbt new file mode 100644 index 0000000000..2c2b88b90c Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/creative_motor_counter_clockwise.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_allow_item_list_filter.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_allow_item_list_filter.nbt new file mode 100644 index 0000000000..b169d47fbf Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_allow_item_list_filter.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_empty_list_filter.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_empty_list_filter.nbt new file mode 100644 index 0000000000..cd28f27165 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_empty_list_filter.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_no_filter.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_no_filter.nbt new file mode 100644 index 0000000000..64bfae9296 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_no_filter.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_single_item_filter.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_single_item_filter.nbt new file mode 100644 index 0000000000..409bf07e7b Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/deployer_single_item_filter.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/encased_fan_inward.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/encased_fan_inward.nbt new file mode 100644 index 0000000000..87d3a6b604 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/encased_fan_inward.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/encased_fan_outward.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/encased_fan_outward.nbt new file mode 100644 index 0000000000..54aed49420 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/encased_fan_outward.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/encased_fan_stop.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/encased_fan_stop.nbt new file mode 100644 index 0000000000..d0bf239783 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/encased_fan_stop.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/pulse_extender.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/pulse_extender.nbt new file mode 100644 index 0000000000..8cff489711 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/pulse_extender.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/pulse_repeater.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/pulse_repeater.nbt new file mode 100644 index 0000000000..9d07106379 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/pulse_repeater.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/pulse_timer.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/pulse_timer.nbt new file mode 100644 index 0000000000..f23d1e99e7 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/pulse_timer.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/shaft_clockwise.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/shaft_clockwise.nbt new file mode 100644 index 0000000000..5f1965f4ed Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/shaft_clockwise.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/shaft_counter_clockwise.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/shaft_counter_clockwise.nbt new file mode 100644 index 0000000000..0333a0f58a Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/shaft_counter_clockwise.nbt differ diff --git a/src/main/resources/data/create/structure/gametest/goggles_tooltip/shaft_stop.nbt b/src/main/resources/data/create/structure/gametest/goggles_tooltip/shaft_stop.nbt new file mode 100644 index 0000000000..6a27d3ebc3 Binary files /dev/null and b/src/main/resources/data/create/structure/gametest/goggles_tooltip/shaft_stop.nbt differ