Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
da67d23
fix: The GPS data card correctly stores the dimension and vector in i…
Fred-Redstone-2 May 26, 2026
f49f660
feat: All three building scepters work
Fred-Redstone-2 May 28, 2026
f0716be
feat: All three mattocks work
Fred-Redstone-2 May 28, 2026
4820032
fix: The Auto Smelt enchantment is only applied when the tool is actu…
Fred-Redstone-2 May 28, 2026
6767333
fix: Use the EnchantUtil from FLib instead of manually checking the M…
Fred-Redstone-2 May 28, 2026
86b34d5
Merge remote-tracking branch 'upstream/trunk/1.21.1' into trunk/1.21.1
Fred-Redstone-2 May 30, 2026
3706ee6
feat: Addition of every outline and shadow alongside the use of modif…
Fred-Redstone-2 May 30, 2026
3dc7d7c
fix: All the enchantments work as intended
Fred-Redstone-2 Jun 1, 2026
088dbba
Merge remote-tracking branch 'upstream/trunk/1.21.1' into trunk/1.21.1
Fred-Redstone-2 Jun 2, 2026
1fbd180
fix: Block randomizer and Ice wand work as intended
Fred-Redstone-2 Jun 2, 2026
b67a0ba
fix: Laser and energy-related items work as intended
Fred-Redstone-2 Jun 2, 2026
7b6af79
Merge branch 'trunk/1.21.1' into trunk/1.21.1
Fred-Redstone-2 Jun 2, 2026
24efc0b
Merge remote-tracking branch 'upstream/trunk/1.21.1' into trunk/1.21.1
Fred-Redstone-2 Jun 2, 2026
e5f7396
Merge branch 'trunk/1.21.1' of https://github.com/Fred-Redstone-2/Cyc…
Fred-Redstone-2 Jun 2, 2026
771957c
Merge remote-tracking branch 'upstream/trunk/1.21.1' into trunk/1.21.1
Fred-Redstone-2 Jun 2, 2026
8fe7aa5
fix: Re-add the tooltip for energy items
Fred-Redstone-2 Jun 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed libs/flib-1.21.1-0.1.2-SNAPSHOT.jar
Binary file not shown.
21 changes: 21 additions & 0 deletions src/main/java/com/lothrazar/cyclic/enchant/AutoSmeltEnchant.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import com.lothrazar.cyclic.registry.EnchantRegistry;
import com.lothrazar.library.util.EnchantUtil;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.core.Holder;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.item.crafting.SingleRecipeInput;
import net.minecraft.world.item.crafting.SmeltingRecipe;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.neoforged.neoforge.common.ModConfigSpec.BooleanValue;
import net.neoforged.neoforge.common.loot.IGlobalLootModifier;
Expand All @@ -37,6 +44,20 @@ public EnchantAutoSmeltModifier(LootItemCondition[] conditionsIn) {

@Override
protected ObjectArrayList<ItemStack> doApply(ObjectArrayList<ItemStack> originalLoot, LootContext context) {
/// First, know if the enchantment is really applied and enabled
if (!isEnabled()) return originalLoot;

ItemStack tool = context.getParamOrNull(LootContextParams.TOOL);
if (tool == null || tool.isEmpty()) return originalLoot;

Entity entity = context.getParamOrNull(LootContextParams.THIS_ENTITY);
if (!(entity instanceof Player player)) return originalLoot;

Holder<Enchantment> holder = EnchantUtil.holder(EnchantRegistry.AUTO_SMELT, player);
int level = EnchantUtil.getCurrentLevelTool(holder, tool);
if (level <= 0) return originalLoot;

/// The enchantment is applied and enabled, convert the loot
ObjectArrayList<ItemStack> newLoot = new ObjectArrayList<>();
originalLoot.forEach((stack) -> {
Optional<RecipeHolder<SmeltingRecipe>> optional = context.getLevel().getRecipeManager()
Expand Down
50 changes: 22 additions & 28 deletions src/main/java/com/lothrazar/cyclic/enchant/MultiJumpEnchant.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
package com.lothrazar.cyclic.enchant;

import com.lothrazar.cyclic.registry.EnchantRegistry;
import com.lothrazar.cyclic.registry.PacketRegistry;
import com.lothrazar.library.core.Const;
import com.lothrazar.library.util.EnchantUtil;
import com.lothrazar.library.packet.PacketPlayerFalldamage;
import com.lothrazar.library.util.EntityUtil;
import com.lothrazar.library.util.ParticleUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.item.ItemStack;
Expand All @@ -22,6 +16,8 @@
import net.neoforged.neoforge.event.tick.EntityTickEvent;
import net.neoforged.neoforge.network.PacketDistributor;

import static com.lothrazar.cyclic.registry.AttachmentRegistry.LAUNCH_USES;

public class MultiJumpEnchant {

public static final String ID = "launch";
Expand All @@ -38,40 +34,38 @@ public static boolean isEnabled() {
@SubscribeEvent
public void onEntityUpdate(EntityTickEvent.Pre event) {
if (!(event.getEntity() instanceof Player p)) { return; }

Holder<Enchantment> h = EnchantUtil.holder(EnchantRegistry.LAUNCH, p);
ItemStack armorStack = EnchantUtil.getFirstArmorStackWithEnchant(h, p);
if (armorStack.isEmpty()) { return; }
if ((p.hasImpulse == false || p.onGround()) && armorStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag().getInt(NBT_USES) > 0) {
armorStack.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, d -> { CompoundTag t = d.copyTag(); t.putInt(NBT_USES, 0); return CustomData.of(t); });

if (p.onGround()) {
p.setData(LAUNCH_USES, 0);
}
}

// Called from ClientInputEventHandler. Static because the caller can't go through the registry anymore
// (in 1.21 EnchantRegistry.LAUNCH is a ResourceKey<Enchantment>, not a DeferredHolder<…, MultiJumpEnchant>).
public static void onKeyInput(Player player) {
if (player == null || player.getVehicle() instanceof Boat) { return; }
if (player.onGround() || player.isCrouching() || player.isInWater()) return;
if (Minecraft.getInstance().screen != null) return;

Holder<Enchantment> h = EnchantUtil.holder(EnchantRegistry.LAUNCH, player);
ItemStack feet = EnchantUtil.getFirstArmorStackWithEnchant(h, player);
if (feet.isEmpty() || player.isCrouching()) { return; }
int level = EnchantUtil.getCurrentLevelTool(h, feet);
if (level <= 0) { return; }
if (player.getCooldowns().isOnCooldown(feet.getItem())) { return; }
if (Minecraft.getInstance().options.keyJump.isDown()
&& player.getY() < player.yOld && player.hasImpulse && !player.isInWater()) {
int uses = feet.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag().getInt(NBT_USES);
player.fallDistance = 0;
float angle = (player.getDeltaMovement().x == 0 && player.getDeltaMovement().z == 0) ? 90 : ROTATIONPITCH;
EntityUtil.launch(player, angle, POWER);
ParticleUtil.spawnParticle(player.getCommandSenderWorld(), ParticleTypes.CRIT, player.blockPosition(), 7);
uses++;
if (uses >= level) {
if (!feet.isEmpty()) EntityUtil.setCooldownItem(player, feet.getItem(), COOLDOWN);
uses = 0;
}
final int finalUses = uses;
feet.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, d -> { CompoundTag t = d.copyTag(); t.putInt(NBT_USES, finalUses); return CustomData.of(t); });
player.fallDistance = 0;
PacketDistributor.sendToServer(new PacketPlayerFalldamage());
int level = feet.getEnchantmentLevel(h);
if (level <= 0 || player.getCooldowns().isOnCooldown(feet.getItem())) return;

int jumpsUsed = player.getData(LAUNCH_USES) + 1;
if (jumpsUsed >= level) {
if (!feet.isEmpty()) EntityUtil.setCooldownItem(player, feet.getItem(), COOLDOWN);
}

float angle = (player.getDeltaMovement().x == 0 && player.getDeltaMovement().z == 0) ? 90 : ROTATIONPITCH;
EntityUtil.launch(player, angle, POWER);
player.setData(LAUNCH_USES, jumpsUsed);

player.fallDistance = 0;
PacketDistributor.sendToServer(new PacketPlayerFalldamage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@
import net.neoforged.neoforge.client.event.InputEvent;
import net.neoforged.neoforge.client.event.ScreenEvent;
import net.neoforged.neoforge.network.PacketDistributor;
import org.lwjgl.glfw.GLFW;

public class ClientInputEventHandler {

@SubscribeEvent
public void onKeyInput(InputEvent.Key event) {
MultiJumpEnchant.onKeyInput(Minecraft.getInstance().player);
if (event.getKey() == Minecraft.getInstance().options.keyJump.getKey().getValue() && event.getAction() == GLFW.GLFW_PRESS) {
MultiJumpEnchant.onKeyInput(Minecraft.getInstance().player);
}
if (ClientRegistryCyclic.CAKE.consumeClick()) {
ItemCakeInventory.onKeyInput(Minecraft.getInstance().player);
}
Expand Down
73 changes: 73 additions & 0 deletions src/main/java/com/lothrazar/cyclic/event/LaserBeamHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.lothrazar.cyclic.event;

import com.lothrazar.cyclic.config.ConfigRegistry;
import com.lothrazar.cyclic.item.LaserItem;
import com.lothrazar.library.render.RenderEntityToBlockLaser;
import net.minecraft.client.Minecraft;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.ProjectileUtil;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.phys.*;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
import net.neoforged.neoforge.energy.IEnergyStorage;

public class LaserBeamHandler {
@SubscribeEvent
public void onRenderBeam(RenderLevelStageEvent event) {
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) {
return;
}

Minecraft mc = Minecraft.getInstance();
Player player = mc.player;
if (player == null) {
return;
}

ItemStack stack = LaserItem.getIfHeld(player);
if (!stack.isEmpty() && player.isUsingItem()) {
// Check energy
IEnergyStorage storage = stack.getCapability(Capabilities.EnergyStorage.ITEM);
if (storage == null || storage.getEnergyStored() < ConfigRegistry.LaserItemEnergy.get()) {
return;
}

float partialTick = event.getPartialTick().getGameTimeDeltaTicks();

// Close-range
if (mc.crosshairPickEntity != null) {
RenderEntityToBlockLaser.renderLaser(event, player, partialTick, stack, InteractionHand.MAIN_HAND, 18, -0.02F);
}
// Long-range
else {
boolean shouldRenderMiss = ConfigRegistry.LaserRenderMisses.get();
boolean hitLongRange = false;

// Determine where the laser should hit
double laserRange = ConfigRegistry.LaserItemRange.get();
Vec3 eyePos = player.getEyePosition(partialTick);
Vec3 view = player.getViewVector(partialTick);
Vec3 end = eyePos.add(view.scale(laserRange));
AABB aabb = player.getBoundingBox().expandTowards(view.scale(laserRange)).inflate(1.0D);

EntityHitResult ehr = ProjectileUtil.getEntityHitResult(player, eyePos, end, aabb, ent -> ent.isAttackable() && ent.isAlive(), 0);

if (ehr != null) {
Vec3 hitLoc = ehr.getLocation();
BlockHitResult miss = player.level().clip(new ClipContext(eyePos, hitLoc, ClipContext.Block.VISUAL, ClipContext.Fluid.NONE, player));
if (miss.getType() != HitResult.Type.BLOCK) {
hitLongRange = true;
}
}

if (hitLongRange || shouldRenderMiss) {
RenderEntityToBlockLaser.renderLaser(event, player, partialTick, stack, InteractionHand.MAIN_HAND);
}
}
}
}
}
158 changes: 158 additions & 0 deletions src/main/java/com/lothrazar/cyclic/event/OutlineRenderHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package com.lothrazar.cyclic.event;

import com.lothrazar.cyclic.config.ClientConfigCyclic;
import com.lothrazar.cyclic.item.OreProspector;
import com.lothrazar.cyclic.item.builder.BuildStyle;
import com.lothrazar.cyclic.item.builder.BuilderItem;
import com.lothrazar.cyclic.item.builder.PacketSwapBlock;
import com.lothrazar.cyclic.item.datacard.LocationGpsCard;
import com.lothrazar.cyclic.item.datacard.ShapeCard;
import com.lothrazar.cyclic.item.random.RandomizerItem;
import com.lothrazar.library.data.BlockPosDim;
import com.lothrazar.library.data.RelativeShape;
import com.lothrazar.library.util.LevelWorldUtil;
import com.lothrazar.library.util.RenderBlockUtils;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;

import java.awt.Color;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@OnlyIn(Dist.CLIENT)
public class OutlineRenderHandler {
@SubscribeEvent
public void onRenderOutline(RenderLevelStageEvent event) {
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) {
return;
}

Minecraft mc = Minecraft.getInstance();
Player player = mc.player;
if (player == null) {
return;
}

PoseStack poseStack = event.getPoseStack();
Vec3 cameraPosition = event.getCamera().getPosition();
Level world = player.level();
ItemStack stack;

// Normally, each condition should be mutually exclusive, so the overhead of render delay seems minimal
/// Cubes: outlines and shadows
stack = OreProspector.getIfHeld(player);
if (stack.getItem() instanceof OreProspector) {
handleOreProspector(poseStack, cameraPosition, world, stack);
}

stack = BuilderItem.getIfHeld(player);
if (stack.getItem() instanceof BuilderItem) {
HitResult hitResult = mc.hitResult;
handleBuilderItem(poseStack, cameraPosition, world, hitResult, stack);
}

stack = RandomizerItem.getIfHeld(player);
if (stack.getItem() instanceof RandomizerItem) {
handleRandomizerItem(poseStack, cameraPosition, world, player);
}

stack = player.getMainHandItem();
if (stack.getItem() instanceof LocationGpsCard) {
handleLocationGpsCard(poseStack, cameraPosition, world, stack);
}

if (stack.getItem() instanceof ShapeCard) {
handleShapeCard(poseStack, cameraPosition, player, stack);
}
}

private void handleOreProspector(PoseStack poseStack, Vec3 camPos, Level world, ItemStack stack) {
List<BlockPosDim> coords = OreProspector.getPosition(stack);
for (BlockPosDim loc : coords) {
if (loc != null) {
if (loc.getDimension() == null ||
loc.getDimension().equalsIgnoreCase(LevelWorldUtil.dimensionToString(world))) {
RenderBlockUtils.createBox(poseStack, loc.getPos(), camPos);
}
}
}
}

private void handleBuilderItem(PoseStack poseStack, Vec3 camPos, Level world, HitResult hitResult, ItemStack stack) {
if (hitResult != null && hitResult.getType() == HitResult.Type.BLOCK) {
BlockHitResult lookingAt = (BlockHitResult) hitResult;
BlockPos pos = lookingAt.getBlockPos();
BuildStyle buildStyle = ((BuilderItem) stack.getItem()).style;
if (buildStyle.isOffset()) {
pos = pos.relative(lookingAt.getDirection());
}

List<BlockPos> coordinates = PacketSwapBlock.getSelectedBlocks(world, pos, BuilderItem.getActionType(stack), lookingAt.getDirection(), buildStyle);

for (BlockPos outline : coordinates) {
RenderBlockUtils.createBox(poseStack, outline, camPos);
}
}
}

private void handleRandomizerItem(PoseStack poseStack, Vec3 camPos, Level world, Player player) {
int range = 6;
BlockHitResult lookingAt = RenderBlockUtils.getLookingAt(player, range);
if (world.getBlockState(lookingAt.getBlockPos()).isAir()) {
return;
}

List<BlockPos> coords = RandomizerItem.getPlaces(lookingAt.getBlockPos(), lookingAt.getDirection());
Map<BlockPos, Color> colourMap = new HashMap<>();
for (BlockPos e : coords) {
BlockState stHere = world.getBlockState(e);
if (!RandomizerItem.canMove(stHere, world, e) && !stHere.isAir()) {
colourMap.put(e, Color.RED);
}
else if (!stHere.isAir()) {
RenderBlockUtils.createBox(poseStack, e, camPos);
}
}

if (!colourMap.isEmpty()) {
final float scale = 1, alpha = 1;
RenderBlockUtils.renderColourCubes(poseStack, camPos, colourMap, scale, alpha);
}
}

private void handleLocationGpsCard(PoseStack poseStack, Vec3 camPos, Level world, ItemStack stack) {
BlockPosDim loc = LocationGpsCard.getPosition(stack);
if (loc != null) {
if (loc.getDimension() == null ||
loc.getDimension().equalsIgnoreCase(LevelWorldUtil.dimensionToString(world))) {
final float scale = 1, alpha = 1;
Map<BlockPos, Color> colourMap = new HashMap<>();
colourMap.put(loc.getPos(), ClientConfigCyclic.getColor(stack));
RenderBlockUtils.renderColourCubes(poseStack, camPos, colourMap, scale, alpha);
}
}
}

private void handleShapeCard(PoseStack poseStack, Vec3 camPos, Player player, ItemStack stack) {
RelativeShape shape = RelativeShape.read(stack);
if (shape != null) {
BlockPos here = player.blockPosition();
for (BlockPos s : shape.getShape()) {
RenderBlockUtils.createBox(poseStack, here.offset(s), camPos);
}
}
}
}
4 changes: 3 additions & 1 deletion src/main/java/com/lothrazar/cyclic/item/ItemHasEnergy.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
import java.util.List;

public class ItemHasEnergy extends ItemFlib {
public static final int MAX_ENERGY = 16000;
public static final String NBT_TAG = "energy";

public ItemHasEnergy(Properties properties) {
super(properties);
super(properties, new Settings().tooltip());
}
public ItemHasEnergy(Properties properties, Settings settings) {
super(properties, settings);
Expand Down
Loading