diff --git a/src/main/java/com/simibubi/create/AllRecipeTypes.java b/src/main/java/com/simibubi/create/AllRecipeTypes.java index 6479b6036..f39371e53 100644 --- a/src/main/java/com/simibubi/create/AllRecipeTypes.java +++ b/src/main/java/com/simibubi/create/AllRecipeTypes.java @@ -14,8 +14,8 @@ import com.simibubi.create.content.kinetics.crafter.MechanicalCraftingRecipe; import com.simibubi.create.content.kinetics.crusher.CrushingRecipe; import com.simibubi.create.content.kinetics.deployer.DeployerApplicationRecipe; import com.simibubi.create.content.kinetics.deployer.ManualApplicationRecipe; -import com.simibubi.create.content.kinetics.fan.HauntingRecipe; -import com.simibubi.create.content.kinetics.fan.SplashingRecipe; +import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe; +import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe; import com.simibubi.create.content.kinetics.millstone.MillingRecipe; import com.simibubi.create.content.kinetics.mixer.CompactingRecipe; import com.simibubi.create.content.kinetics.mixer.MixingRecipe; diff --git a/src/main/java/com/simibubi/create/Create.java b/src/main/java/com/simibubi/create/Create.java index e4747e5e8..523dffa64 100644 --- a/src/main/java/com/simibubi/create/Create.java +++ b/src/main/java/com/simibubi/create/Create.java @@ -17,6 +17,7 @@ import com.simibubi.create.content.decoration.slidingDoor.SlidingDoorBlock; import com.simibubi.create.content.equipment.potatoCannon.BuiltinPotatoProjectileTypes; import com.simibubi.create.content.fluids.tank.BoilerHeaters; import com.simibubi.create.content.kinetics.TorquePropagator; +import com.simibubi.create.content.kinetics.fan.processing.AllFanProcessingTypes; import com.simibubi.create.content.kinetics.mechanicalArm.AllArmInteractionPointTypes; import com.simibubi.create.content.redstone.displayLink.AllDisplayBehaviours; import com.simibubi.create.content.redstone.link.RedstoneLinkNetworkHandler; @@ -138,9 +139,8 @@ public class Create { AllDisplayBehaviours.registerDefaults(); ContraptionMovementSetting.registerDefaults(); AllArmInteractionPointTypes.register(); + AllFanProcessingTypes.register(); BlockSpoutingBehaviour.registerDefaults(); - BoilerHeaters.registerDefaults(); - BuiltinPotatoProjectileTypes.register(); BogeySizes.init(); AllBogeyStyles.register(); // ---- @@ -164,6 +164,13 @@ public class Create { public static void init(final FMLCommonSetupEvent event) { event.enqueueWork(() -> { + // TODO: custom registration should all happen in one place + // Most registration happens in the constructor. + // These registrations use Create's registered objects directly so they must run after registration has finished. + BuiltinPotatoProjectileTypes.register(); + BoilerHeaters.registerDefaults(); + // -- + AttachedRegistry.unwrapAll(); AllAdvancements.register(); AllTriggers.register(); diff --git a/src/main/java/com/simibubi/create/compat/jei/CreateJEI.java b/src/main/java/com/simibubi/create/compat/jei/CreateJEI.java index ec4fa01b6..fc41d0205 100644 --- a/src/main/java/com/simibubi/create/compat/jei/CreateJEI.java +++ b/src/main/java/com/simibubi/create/compat/jei/CreateJEI.java @@ -51,8 +51,8 @@ import com.simibubi.create.content.kinetics.crusher.AbstractCrushingRecipe; import com.simibubi.create.content.kinetics.deployer.DeployerApplicationRecipe; import com.simibubi.create.content.kinetics.deployer.ItemApplicationRecipe; import com.simibubi.create.content.kinetics.deployer.ManualApplicationRecipe; -import com.simibubi.create.content.kinetics.fan.HauntingRecipe; -import com.simibubi.create.content.kinetics.fan.SplashingRecipe; +import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe; +import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe; import com.simibubi.create.content.kinetics.press.MechanicalPressBlockEntity; import com.simibubi.create.content.kinetics.press.PressingRecipe; import com.simibubi.create.content.kinetics.saw.CuttingRecipe; diff --git a/src/main/java/com/simibubi/create/compat/jei/category/FanHauntingCategory.java b/src/main/java/com/simibubi/create/compat/jei/category/FanHauntingCategory.java index c574a5922..630837c6a 100644 --- a/src/main/java/com/simibubi/create/compat/jei/category/FanHauntingCategory.java +++ b/src/main/java/com/simibubi/create/compat/jei/category/FanHauntingCategory.java @@ -4,7 +4,7 @@ import org.jetbrains.annotations.NotNull; import com.mojang.blaze3d.vertex.PoseStack; import com.simibubi.create.compat.jei.category.animations.AnimatedKinetics; -import com.simibubi.create.content.kinetics.fan.HauntingRecipe; +import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe; import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.element.GuiGameElement; diff --git a/src/main/java/com/simibubi/create/compat/jei/category/FanWashingCategory.java b/src/main/java/com/simibubi/create/compat/jei/category/FanWashingCategory.java index aa41ec84f..701902b5d 100644 --- a/src/main/java/com/simibubi/create/compat/jei/category/FanWashingCategory.java +++ b/src/main/java/com/simibubi/create/compat/jei/category/FanWashingCategory.java @@ -4,7 +4,7 @@ import org.jetbrains.annotations.NotNull; import com.mojang.blaze3d.vertex.PoseStack; import com.simibubi.create.compat.jei.category.animations.AnimatedKinetics; -import com.simibubi.create.content.kinetics.fan.SplashingRecipe; +import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe; import com.simibubi.create.foundation.gui.element.GuiGameElement; import net.minecraft.world.level.material.Fluids; diff --git a/src/main/java/com/simibubi/create/content/contraptions/BlockMovementChecks.java b/src/main/java/com/simibubi/create/content/contraptions/BlockMovementChecks.java index 0a223944d..63d2a5cf9 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/BlockMovementChecks.java +++ b/src/main/java/com/simibubi/create/content/contraptions/BlockMovementChecks.java @@ -337,7 +337,7 @@ public class BlockMovementChecks { return direction == state.getValue(StickerBlock.FACING) && !isNotSupportive(world.getBlockState(pos.relative(direction)), direction.getOpposite()); } - if (block instanceof AbstractBogeyBlock bogey) + if (block instanceof AbstractBogeyBlock bogey) return bogey.getStickySurfaces(world, pos, state) .contains(direction); if (block instanceof WhistleBlock) diff --git a/src/main/java/com/simibubi/create/content/kinetics/belt/transport/TransportedItemStack.java b/src/main/java/com/simibubi/create/content/kinetics/belt/transport/TransportedItemStack.java index aec7c61f8..aa5500260 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/belt/transport/TransportedItemStack.java +++ b/src/main/java/com/simibubi/create/content/kinetics/belt/transport/TransportedItemStack.java @@ -3,7 +3,7 @@ package com.simibubi.create.content.kinetics.belt.transport; import java.util.Random; import com.simibubi.create.content.kinetics.belt.BeltHelper; -import com.simibubi.create.content.kinetics.fan.FanProcessing; +import com.simibubi.create.content.kinetics.fan.processing.FanProcessingType; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -25,7 +25,7 @@ public class TransportedItemStack implements Comparable { public float prevBeltPosition; public float prevSideOffset; - public FanProcessing.Type processedBy; + public FanProcessingType processedBy; public int processingTime; public TransportedItemStack(ItemStack stack) { diff --git a/src/main/java/com/simibubi/create/content/kinetics/fan/AirCurrent.java b/src/main/java/com/simibubi/create/content/kinetics/fan/AirCurrent.java index 672945f07..00260e943 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/fan/AirCurrent.java +++ b/src/main/java/com/simibubi/create/content/kinetics/fan/AirCurrent.java @@ -10,7 +10,9 @@ import com.simibubi.create.AllTags; import com.simibubi.create.content.decoration.copycat.CopycatBlock; 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.fan.FanProcessing.Type; +import com.simibubi.create.content.kinetics.fan.processing.AllFanProcessingTypes; +import com.simibubi.create.content.kinetics.fan.processing.FanProcessing; +import com.simibubi.create.content.kinetics.fan.processing.FanProcessingType; import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; import com.simibubi.create.foundation.utility.Iterate; @@ -29,7 +31,6 @@ import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.Fluids; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; @@ -49,7 +50,7 @@ public class AirCurrent { public boolean pushing; public float maxDistance; - protected List> affectedItemHandlers = + protected List> affectedItemHandlers = new ArrayList<>(); protected List caughtEntities = new ArrayList<>(); @@ -110,14 +111,14 @@ public class AirCurrent { ((ServerPlayer) entity).connection.aboveGroundTickCount = 0; entityDistance -= .5f; - FanProcessing.Type processingType = getSegmentAt((float) entityDistance); + FanProcessingType processingType = getSegmentAt((float) entityDistance); - if (processingType == null || processingType == Type.NONE) + if (processingType == AllFanProcessingTypes.NONE) continue; if (entity instanceof ItemEntity itemEntity) { - if (world.isClientSide) { - processingType.spawnParticlesForProcessing(world, entity.position()); + if (world != null && world.isClientSide) { + processingType.spawnProcessingParticles(world, entity.position()); continue; } if (FanProcessing.canProcess(itemEntity, processingType)) @@ -127,7 +128,8 @@ public class AirCurrent { continue; } - processingType.affectEntity(entity, world); + if (world != null) + processingType.affectEntity(entity, world); } } @@ -155,7 +157,7 @@ public class AirCurrent { AirCurrentSegment currentSegment = new AirCurrentSegment(); segments.clear(); currentSegment.startOffset = 0; - FanProcessing.Type type = Type.NONE; + FanProcessingType type = AllFanProcessingTypes.NONE; int limit = (int) (maxDistance + .5f); int searchStart = pushing ? 0 : limit; @@ -164,8 +166,8 @@ public class AirCurrent { for (int i = searchStart; i * searchStep <= searchEnd * searchStep; i += searchStep) { BlockPos currentPos = start.relative(direction, i); - FanProcessing.Type newType = FanProcessing.Type.byBlock(world, currentPos); - if (newType != Type.NONE) + FanProcessingType newType = FanProcessingType.getAt(world, currentPos); + if (newType != AllFanProcessingTypes.NONE) type = newType; if (currentSegment.type != type || currentSegment.startOffset == 0) { currentSegment.endOffset = i; @@ -258,21 +260,18 @@ public class AirCurrent { BlockPos start = source.getAirCurrentPos(); affectedItemHandlers.clear(); for (int i = 0; i < maxDistance + 1; i++) { - Type type = getSegmentAt(i); - if (type == null) - continue; - + FanProcessingType segmentType = getSegmentAt(i); for (int offset : Iterate.zeroAndOne) { BlockPos pos = start.relative(direction, i) .below(offset); TransportedItemStackHandlerBehaviour behaviour = BlockEntityBehaviour.get(world, pos, TransportedItemStackHandlerBehaviour.TYPE); - FanProcessing.Type typeAtHandler = type; - if (world.getFluidState(pos) - .is(Fluids.WATER)) - typeAtHandler = Type.SPLASHING; - if (behaviour != null) - affectedItemHandlers.add(Pair.of(behaviour, typeAtHandler)); + if (behaviour == null) + continue; + FanProcessingType type = FanProcessingType.getAt(world, pos); + if (type == AllFanProcessingTypes.NONE) + type = segmentType; + affectedItemHandlers.add(Pair.of(behaviour, type)); if (direction.getAxis() .isVertical()) break; @@ -281,15 +280,14 @@ public class AirCurrent { } public void tickAffectedHandlers() { - for (Pair pair : affectedItemHandlers) { + for (Pair pair : affectedItemHandlers) { TransportedItemStackHandlerBehaviour handler = pair.getKey(); Level world = handler.getWorld(); - FanProcessing.Type processingType = pair.getRight(); + FanProcessingType processingType = pair.getRight(); - handler.handleProcessingOnAllItems((transported) -> { + handler.handleProcessingOnAllItems(transported -> { if (world.isClientSide) { - if (world != null) - processingType.spawnParticlesForProcessing(world, handler.getWorldPositionOf(transported)); + processingType.spawnProcessingParticles(world, handler.getWorldPositionOf(transported)); return TransportedResult.doNothing(); } TransportedResult applyProcessing = FanProcessing.applyProcessing(transported, world, processingType); @@ -304,7 +302,7 @@ public class AirCurrent { return AllTags.AllBlockTags.FAN_TRANSPARENT.matches(state); } - public FanProcessing.Type getSegmentAt(float offset) { + public FanProcessingType getSegmentAt(float offset) { for (AirCurrentSegment airCurrentSegment : segments) { if (offset > airCurrentSegment.endOffset && pushing) continue; @@ -312,11 +310,11 @@ public class AirCurrent { continue; return airCurrentSegment.type; } - return FanProcessing.Type.NONE; + return AllFanProcessingTypes.NONE; } public static class AirCurrentSegment { - FanProcessing.Type type; + FanProcessingType type; int startOffset; int endOffset; } diff --git a/src/main/java/com/simibubi/create/content/kinetics/fan/AirFlowParticle.java b/src/main/java/com/simibubi/create/content/kinetics/fan/AirFlowParticle.java index 81d4c764e..2afb18468 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/fan/AirFlowParticle.java +++ b/src/main/java/com/simibubi/create/content/kinetics/fan/AirFlowParticle.java @@ -2,8 +2,8 @@ package com.simibubi.create.content.kinetics.fan; import javax.annotation.Nonnull; -import com.simibubi.create.Create; -import com.simibubi.create.foundation.utility.Color; +import com.simibubi.create.content.kinetics.fan.processing.AllFanProcessingTypes; +import com.simibubi.create.content.kinetics.fan.processing.FanProcessingType; import com.simibubi.create.foundation.utility.VecHelper; import net.minecraft.client.multiplayer.ClientLevel; @@ -14,16 +14,15 @@ import net.minecraft.client.particle.SimpleAnimatedParticle; import net.minecraft.client.particle.SpriteSet; import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.core.BlockPos; -import net.minecraft.core.particles.BlockParticleOption; -import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.core.particles.ParticleOptions; import net.minecraft.util.Mth; -import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.phys.Vec3; public class AirFlowParticle extends SimpleAnimatedParticle { private final IAirCurrentSource source; + private final Access access = new Access(); protected AirFlowParticle(ClientLevel world, IAirCurrentSource source, double x, double y, double z, SpriteSet sprite) { @@ -33,11 +32,12 @@ public class AirFlowParticle extends SimpleAnimatedParticle { this.lifetime = 40; hasPhysics = false; selectSprite(7); - Vec3 offset = VecHelper.offsetRandomly(Vec3.ZERO, Create.RANDOM, .25f); + Vec3 offset = VecHelper.offsetRandomly(Vec3.ZERO, random, .25f); this.setPos(x + offset.x, y + offset.y, z + offset.z); this.xo = x; this.yo = y; this.zo = z; + setColor(0xEEEEEE); setAlpha(.25f); } @@ -49,36 +49,44 @@ public class AirFlowParticle extends SimpleAnimatedParticle { @Override public void tick() { if (source == null || source.isSourceRemoved()) { - dissipate(); + remove(); return; } this.xo = this.x; this.yo = this.y; this.zo = this.z; if (this.age++ >= this.lifetime) { - this.remove(); + remove(); } else { - if (source.getAirCurrent() == null || !source.getAirCurrent().bounds.inflate(.25f).contains(x, y, z)) { - dissipate(); + AirCurrent airCurrent = source.getAirCurrent(); + if (airCurrent == null || !airCurrent.bounds.inflate(.25f).contains(x, y, z)) { + remove(); return; } - Vec3 directionVec = Vec3.atLowerCornerOf(source.getAirCurrent().direction.getNormal()); + Vec3 directionVec = Vec3.atLowerCornerOf(airCurrent.direction.getNormal()); Vec3 motion = directionVec.scale(1 / 8f); if (!source.getAirCurrent().pushing) motion = motion.scale(-1); double distance = new Vec3(x, y, z).subtract(VecHelper.getCenterOf(source.getAirCurrentPos())) .multiply(directionVec).length() - .5f; - if (distance > source.getAirCurrent().maxDistance + 1 || distance < -.25f) { - dissipate(); + if (distance > airCurrent.maxDistance + 1 || distance < -.25f) { + remove(); return; } - motion = motion.scale(source.getAirCurrent().maxDistance - (distance - 1f)).scale(.5f); - selectSprite((int) Mth.clamp((distance / source.getAirCurrent().maxDistance) * 8 + level.random.nextInt(4), - 0, 7)); + motion = motion.scale(airCurrent.maxDistance - (distance - 1f)).scale(.5f); - morphType(distance); + FanProcessingType type = getType(distance); + if (type == AllFanProcessingTypes.NONE) { + setColor(0xEEEEEE); + setAlpha(.25f); + selectSprite((int) Mth.clamp((distance / airCurrent.maxDistance) * 8 + random.nextInt(4), + 0, 7)); + } else { + type.morphAirFlow(access, random); + selectSprite(random.nextInt(3)); + } xd = motion.x; yd = motion.y; @@ -94,68 +102,10 @@ public class AirFlowParticle extends SimpleAnimatedParticle { } - public void morphType(double distance) { + private FanProcessingType getType(double distance) { if (source.getAirCurrent() == null) - return; - FanProcessing.Type type = source.getAirCurrent().getSegmentAt((float) distance); - - if (type == FanProcessing.Type.SPLASHING) { - setColor(Color.mixColors(0x4499FF, 0x2277FF, level.random.nextFloat())); - setAlpha(1f); - selectSprite(level.random.nextInt(3)); - if (level.random.nextFloat() < 1 / 32f) - level.addParticle(ParticleTypes.BUBBLE, x, y, z, xd * .125f, yd * .125f, - zd * .125f); - if (level.random.nextFloat() < 1 / 32f) - level.addParticle(ParticleTypes.BUBBLE_POP, x, y, z, xd * .125f, yd * .125f, - zd * .125f); - } - - if (type == FanProcessing.Type.SMOKING) { - setColor(Color.mixColors(0x0, 0x555555, level.random.nextFloat())); - setAlpha(1f); - selectSprite(level.random.nextInt(3)); - if (level.random.nextFloat() < 1 / 32f) - level.addParticle(ParticleTypes.SMOKE, x, y, z, xd * .125f, yd * .125f, - zd * .125f); - if (level.random.nextFloat() < 1 / 32f) - level.addParticle(ParticleTypes.LARGE_SMOKE, x, y, z, xd * .125f, yd * .125f, - zd * .125f); - } - - if (type == FanProcessing.Type.HAUNTING) { - setColor(Color.mixColors(0x0, 0x126568, level.random.nextFloat())); - setAlpha(1f); - selectSprite(level.random.nextInt(3)); - if (level.random.nextFloat() < 1 / 128f) - level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, x, y, z, xd * .125f, yd * .125f, - zd * .125f); - if (level.random.nextFloat() < 1 / 32f) - level.addParticle(ParticleTypes.SMOKE, x, y, z, xd * .125f, yd * .125f, - zd * .125f); - } - - if (type == FanProcessing.Type.BLASTING) { - setColor(Color.mixColors(0xFF4400, 0xFF8855, level.random.nextFloat())); - setAlpha(.5f); - selectSprite(level.random.nextInt(3)); - if (level.random.nextFloat() < 1 / 32f) - level.addParticle(ParticleTypes.FLAME, x, y, z, xd * .25f, yd * .25f, - zd * .25f); - if (level.random.nextFloat() < 1 / 16f) - level.addParticle(new BlockParticleOption(ParticleTypes.BLOCK, Blocks.LAVA.defaultBlockState()), x, y, - z, xd * .25f, yd * .25f, zd * .25f); - } - - if (type == null) { - setColor(0xEEEEEE); - setAlpha(.25f); - setSize(.2f, .2f); - } - } - - private void dissipate() { - remove(); + return AllFanProcessingTypes.NONE; + return source.getAirCurrent().getSegmentAt((float) distance); } public int getLightColor(float partialTick) { @@ -183,4 +133,21 @@ public class AirFlowParticle extends SimpleAnimatedParticle { } } + private class Access implements FanProcessingType.AirFlowParticleAccess { + @Override + public void setColor(int color) { + AirFlowParticle.this.setColor(color); + } + + @Override + public void setAlpha(float alpha) { + AirFlowParticle.this.setAlpha(alpha); + } + + @Override + public void spawnExtraParticle(ParticleOptions options, float speedMultiplier) { + level.addParticle(options, x, y, z, xd * speedMultiplier, yd * speedMultiplier, zd * speedMultiplier); + } + } + } diff --git a/src/main/java/com/simibubi/create/content/kinetics/fan/FanProcessing.java b/src/main/java/com/simibubi/create/content/kinetics/fan/FanProcessing.java deleted file mode 100644 index 0c02646d2..000000000 --- a/src/main/java/com/simibubi/create/content/kinetics/fan/FanProcessing.java +++ /dev/null @@ -1,452 +0,0 @@ -package com.simibubi.create.content.kinetics.fan; - -import static com.simibubi.create.content.processing.burner.BlazeBurnerBlock.getHeatLevelOf; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import com.mojang.math.Vector3f; -import com.simibubi.create.AllBlocks; -import com.simibubi.create.AllRecipeTypes; -import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour.TransportedResult; -import com.simibubi.create.content.kinetics.belt.transport.TransportedItemStack; -import com.simibubi.create.content.processing.burner.BlazeBurnerBlock; -import com.simibubi.create.content.processing.burner.LitBlazeBurnerBlock; -import com.simibubi.create.foundation.recipe.RecipeApplier; -import com.simibubi.create.foundation.utility.Color; -import com.simibubi.create.foundation.utility.VecHelper; -import com.simibubi.create.infrastructure.config.AllConfigs; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.particles.DustParticleOptions; -import net.minecraft.core.particles.ParticleTypes; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.tags.BlockTags; -import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.effect.MobEffects; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.animal.horse.Horse; -import net.minecraft.world.entity.animal.horse.SkeletonHorse; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.entity.monster.EnderMan; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.AbstractCookingRecipe; -import net.minecraft.world.item.crafting.BlastingRecipe; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraft.world.item.crafting.SmeltingRecipe; -import net.minecraft.world.item.crafting.SmokingRecipe; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.CampfireBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.phys.Vec3; -import net.minecraftforge.items.ItemStackHandler; -import net.minecraftforge.items.wrapper.RecipeWrapper; - -public class FanProcessing { - - private static final DamageSource FIRE_DAMAGE_SOURCE = new DamageSource("create.fan_fire").setScalesWithDifficulty() - .setIsFire(); - private static final DamageSource LAVA_DAMAGE_SOURCE = new DamageSource("create.fan_lava").setScalesWithDifficulty() - .setIsFire(); - - private static final RecipeWrapper RECIPE_WRAPPER = new RecipeWrapper(new ItemStackHandler(1)); - private static final SplashingWrapper SPLASHING_WRAPPER = new SplashingWrapper(); - private static final HauntingWrapper HAUNTING_WRAPPER = new HauntingWrapper(); - - public static boolean canProcess(ItemEntity entity, Type type) { - if (entity.getPersistentData() - .contains("CreateData")) { - CompoundTag compound = entity.getPersistentData() - .getCompound("CreateData"); - if (compound.contains("Processing")) { - CompoundTag processing = compound.getCompound("Processing"); - - if (Type.valueOf(processing.getString("Type")) != type) - return type.canProcess(entity.getItem(), entity.level); - else if (processing.getInt("Time") >= 0) - return true; - else if (processing.getInt("Time") == -1) - return false; - } - } - return type.canProcess(entity.getItem(), entity.level); - } - - public static boolean isWashable(ItemStack stack, Level world) { - SPLASHING_WRAPPER.setItem(0, stack); - Optional recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, world); - return recipe.isPresent(); - } - - public static boolean isHauntable(ItemStack stack, Level world) { - HAUNTING_WRAPPER.setItem(0, stack); - Optional recipe = AllRecipeTypes.HAUNTING.find(HAUNTING_WRAPPER, world); - return recipe.isPresent(); - } - - public static boolean applyProcessing(ItemEntity entity, Type type) { - if (decrementProcessingTime(entity, type) != 0) - return false; - List stacks = process(entity.getItem(), type, entity.level); - if (stacks == null) - return false; - if (stacks.isEmpty()) { - entity.discard(); - return false; - } - entity.setItem(stacks.remove(0)); - for (ItemStack additional : stacks) { - ItemEntity entityIn = new ItemEntity(entity.level, entity.getX(), entity.getY(), entity.getZ(), additional); - entityIn.setDeltaMovement(entity.getDeltaMovement()); - entity.level.addFreshEntity(entityIn); - } - return true; - } - - public static TransportedResult applyProcessing(TransportedItemStack transported, Level world, Type type) { - TransportedResult ignore = TransportedResult.doNothing(); - if (transported.processedBy != type) { - transported.processedBy = type; - int timeModifierForStackSize = ((transported.stack.getCount() - 1) / 16) + 1; - int processingTime = - (int) (AllConfigs.server().kinetics.fanProcessingTime.get() * timeModifierForStackSize) + 1; - transported.processingTime = processingTime; - if (!type.canProcess(transported.stack, world)) - transported.processingTime = -1; - return ignore; - } - if (transported.processingTime == -1) - return ignore; - if (transported.processingTime-- > 0) - return ignore; - - List stacks = process(transported.stack, type, world); - if (stacks == null) - return ignore; - - List transportedStacks = new ArrayList<>(); - for (ItemStack additional : stacks) { - TransportedItemStack newTransported = transported.getSimilar(); - newTransported.stack = additional.copy(); - transportedStacks.add(newTransported); - } - return TransportedResult.convertTo(transportedStacks); - } - - private static List process(ItemStack stack, Type type, Level world) { - if (type == Type.SPLASHING) { - SPLASHING_WRAPPER.setItem(0, stack); - Optional recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, world); - if (recipe.isPresent()) - return RecipeApplier.applyRecipeOn(stack, recipe.get()); - return null; - } - if (type == Type.HAUNTING) { - HAUNTING_WRAPPER.setItem(0, stack); - Optional recipe = AllRecipeTypes.HAUNTING.find(HAUNTING_WRAPPER, world); - if (recipe.isPresent()) - return RecipeApplier.applyRecipeOn(stack, recipe.get()); - return null; - } - - RECIPE_WRAPPER.setItem(0, stack); - Optional smokingRecipe = world.getRecipeManager() - .getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, world); - - if (type == Type.BLASTING) { - RECIPE_WRAPPER.setItem(0, stack); - Optional smeltingRecipe = world.getRecipeManager() - .getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, world); - if (!smeltingRecipe.isPresent()) { - RECIPE_WRAPPER.setItem(0, stack); - smeltingRecipe = world.getRecipeManager() - .getRecipeFor(RecipeType.BLASTING, RECIPE_WRAPPER, world); - } - - if (smeltingRecipe.isPresent()) { - if (!smokingRecipe.isPresent() || !ItemStack.isSame(smokingRecipe.get() - .getResultItem(), - smeltingRecipe.get() - .getResultItem())) { - return RecipeApplier.applyRecipeOn(stack, smeltingRecipe.get()); - } - } - - return Collections.emptyList(); - } - - if (type == Type.SMOKING && smokingRecipe.isPresent()) - return RecipeApplier.applyRecipeOn(stack, smokingRecipe.get()); - - return null; - } - - private static int decrementProcessingTime(ItemEntity entity, Type type) { - CompoundTag nbt = entity.getPersistentData(); - - if (!nbt.contains("CreateData")) - nbt.put("CreateData", new CompoundTag()); - CompoundTag createData = nbt.getCompound("CreateData"); - - if (!createData.contains("Processing")) - createData.put("Processing", new CompoundTag()); - CompoundTag processing = createData.getCompound("Processing"); - - if (!processing.contains("Type") || Type.valueOf(processing.getString("Type")) != type) { - processing.putString("Type", type.name()); - int timeModifierForStackSize = ((entity.getItem() - .getCount() - 1) / 16) + 1; - int processingTime = - (int) (AllConfigs.server().kinetics.fanProcessingTime.get() * timeModifierForStackSize) + 1; - processing.putInt("Time", processingTime); - } - - int value = processing.getInt("Time") - 1; - processing.putInt("Time", value); - return value; - } - - public enum Type { - SPLASHING { - @Override - public void spawnParticlesForProcessing(Level level, Vec3 pos) { - if (level.random.nextInt(8) != 0) - return; - Vector3f color = new Color(0x0055FF).asVectorF(); - level.addParticle(new DustParticleOptions(color, 1), pos.x + (level.random.nextFloat() - .5f) * .5f, - pos.y + .5f, pos.z + (level.random.nextFloat() - .5f) * .5f, 0, 1 / 8f, 0); - level.addParticle(ParticleTypes.SPIT, pos.x + (level.random.nextFloat() - .5f) * .5f, pos.y + .5f, - pos.z + (level.random.nextFloat() - .5f) * .5f, 0, 1 / 8f, 0); - } - - @Override - public void affectEntity(Entity entity, Level level) { - if (level.isClientSide) - return; - - if (entity instanceof EnderMan || entity.getType() == EntityType.SNOW_GOLEM - || entity.getType() == EntityType.BLAZE) { - entity.hurt(DamageSource.DROWN, 2); - } - if (entity.isOnFire()) { - entity.clearFire(); - level.playSound(null, entity.blockPosition(), SoundEvents.GENERIC_EXTINGUISH_FIRE, - SoundSource.NEUTRAL, 0.7F, 1.6F + (level.random.nextFloat() - level.random.nextFloat()) * 0.4F); - } - } - - @Override - public boolean canProcess(ItemStack stack, Level level) { - return isWashable(stack, level); - } - }, - SMOKING { - @Override - public void spawnParticlesForProcessing(Level level, Vec3 pos) { - if (level.random.nextInt(8) != 0) - return; - level.addParticle(ParticleTypes.POOF, pos.x, pos.y + .25f, pos.z, 0, 1 / 16f, 0); - } - - @Override - public void affectEntity(Entity entity, Level level) { - if (level.isClientSide) - return; - - if (!entity.fireImmune()) { - entity.setSecondsOnFire(2); - entity.hurt(FIRE_DAMAGE_SOURCE, 2); - } - } - - @Override - public boolean canProcess(ItemStack stack, Level level) { - RECIPE_WRAPPER.setItem(0, stack); - Optional recipe = level.getRecipeManager() - .getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level); - return recipe.isPresent(); - } - }, - HAUNTING { - @Override - public void spawnParticlesForProcessing(Level level, Vec3 pos) { - if (level.random.nextInt(8) != 0) - return; - pos = pos.add(VecHelper.offsetRandomly(Vec3.ZERO, level.random, 1) - .multiply(1, 0.05f, 1) - .normalize() - .scale(0.15f)); - level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, pos.x, pos.y + .45f, pos.z, 0, 0, 0); - if (level.random.nextInt(2) == 0) - level.addParticle(ParticleTypes.SMOKE, pos.x, pos.y + .25f, pos.z, 0, 0, 0); - } - - @Override - public void affectEntity(Entity entity, Level level) { - if (level.isClientSide) { - if (entity instanceof Horse) { - Vec3 p = entity.getPosition(0); - Vec3 v = p.add(0, 0.5f, 0) - .add(VecHelper.offsetRandomly(Vec3.ZERO, level.random, 1) - .multiply(1, 0.2f, 1) - .normalize() - .scale(1f)); - level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, v.x, v.y, v.z, 0, 0.1f, 0); - if (level.random.nextInt(3) == 0) - level.addParticle(ParticleTypes.LARGE_SMOKE, p.x, p.y + .5f, p.z, - (level.random.nextFloat() - .5f) * .5f, 0.1f, (level.random.nextFloat() - .5f) * .5f); - } - return; - } - - if (entity instanceof LivingEntity livingEntity) { - livingEntity.addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 30, 0, false, false)); - livingEntity.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 20, 1, false, false)); - } - if (entity instanceof Horse horse) { - int progress = horse.getPersistentData() - .getInt("CreateHaunting"); - if (progress < 100) { - if (progress % 10 == 0) { - level.playSound(null, entity.blockPosition(), SoundEvents.SOUL_ESCAPE, SoundSource.NEUTRAL, - 1f, 1.5f * progress / 100f); - } - horse.getPersistentData() - .putInt("CreateHaunting", progress + 1); - return; - } - - level.playSound(null, entity.blockPosition(), SoundEvents.GENERIC_EXTINGUISH_FIRE, - SoundSource.NEUTRAL, 1.25f, 0.65f); - - SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(level); - CompoundTag serializeNBT = horse.saveWithoutId(new CompoundTag()); - serializeNBT.remove("UUID"); - if (!horse.getArmor() - .isEmpty()) - horse.spawnAtLocation(horse.getArmor()); - - skeletonHorse.deserializeNBT(serializeNBT); - skeletonHorse.setPos(horse.getPosition(0)); - level.addFreshEntity(skeletonHorse); - horse.discard(); - } - } - - @Override - public boolean canProcess(ItemStack stack, Level level) { - return isHauntable(stack, level); - } - }, - BLASTING { - @Override - public void spawnParticlesForProcessing(Level level, Vec3 pos) { - if (level.random.nextInt(8) != 0) - return; - level.addParticle(ParticleTypes.LARGE_SMOKE, pos.x, pos.y + .25f, pos.z, 0, 1 / 16f, 0); - } - - @Override - public void affectEntity(Entity entity, Level level) { - if (level.isClientSide) - return; - - if (!entity.fireImmune()) { - entity.setSecondsOnFire(10); - entity.hurt(LAVA_DAMAGE_SOURCE, 4); - } - } - - @Override - public boolean canProcess(ItemStack stack, Level level) { - RECIPE_WRAPPER.setItem(0, stack); - Optional smeltingRecipe = level.getRecipeManager() - .getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, level); - - if (smeltingRecipe.isPresent()) - return true; - - RECIPE_WRAPPER.setItem(0, stack); - Optional blastingRecipe = level.getRecipeManager() - .getRecipeFor(RecipeType.BLASTING, RECIPE_WRAPPER, level); - - if (blastingRecipe.isPresent()) - return true; - - return !stack.getItem() - .isFireResistant(); - } - }, - NONE { - @Override - public void spawnParticlesForProcessing(Level level, Vec3 pos) {} - - @Override - public void affectEntity(Entity entity, Level level) {} - - @Override - public boolean canProcess(ItemStack stack, Level level) { - return false; - } - }; - - public abstract boolean canProcess(ItemStack stack, Level level); - - public abstract void spawnParticlesForProcessing(Level level, Vec3 pos); - - public abstract void affectEntity(Entity entity, Level level); - - public static Type byBlock(BlockGetter reader, BlockPos pos) { - FluidState fluidState = reader.getFluidState(pos); - if (fluidState.getType() == Fluids.WATER || fluidState.getType() == Fluids.FLOWING_WATER) - return Type.SPLASHING; - BlockState blockState = reader.getBlockState(pos); - Block block = blockState.getBlock(); - if (block == Blocks.SOUL_FIRE - || block == Blocks.SOUL_CAMPFIRE && blockState.getOptionalValue(CampfireBlock.LIT) - .orElse(false) - || AllBlocks.LIT_BLAZE_BURNER.has(blockState) - && blockState.getOptionalValue(LitBlazeBurnerBlock.FLAME_TYPE) - .map(flame -> flame == LitBlazeBurnerBlock.FlameType.SOUL) - .orElse(false)) - return Type.HAUNTING; - if (block == Blocks.FIRE - || blockState.is(BlockTags.CAMPFIRES) && blockState.getOptionalValue(CampfireBlock.LIT) - .orElse(false) - || AllBlocks.LIT_BLAZE_BURNER.has(blockState) - && blockState.getOptionalValue(LitBlazeBurnerBlock.FLAME_TYPE) - .map(flame -> flame == LitBlazeBurnerBlock.FlameType.REGULAR) - .orElse(false) - || getHeatLevelOf(blockState) == BlazeBurnerBlock.HeatLevel.SMOULDERING) - return Type.SMOKING; - if (block == Blocks.LAVA || getHeatLevelOf(blockState).isAtLeast(BlazeBurnerBlock.HeatLevel.FADING)) - return Type.BLASTING; - return Type.NONE; - } - } - - public static class SplashingWrapper extends RecipeWrapper { - public SplashingWrapper() { - super(new ItemStackHandler(1)); - } - } - - public static class HauntingWrapper extends RecipeWrapper { - public HauntingWrapper() { - super(new ItemStackHandler(1)); - } - } - -} diff --git a/src/main/java/com/simibubi/create/content/kinetics/fan/processing/AllFanProcessingTypes.java b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/AllFanProcessingTypes.java new file mode 100644 index 000000000..d45084968 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/AllFanProcessingTypes.java @@ -0,0 +1,488 @@ +package com.simibubi.create.content.kinetics.fan.processing; + +import static com.simibubi.create.content.processing.burner.BlazeBurnerBlock.getHeatLevelOf; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; + +import org.jetbrains.annotations.Nullable; + +import com.mojang.math.Vector3f; +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllRecipeTypes; +import com.simibubi.create.Create; +import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe.HauntingWrapper; +import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe.SplashingWrapper; +import com.simibubi.create.content.processing.burner.BlazeBurnerBlock; +import com.simibubi.create.content.processing.burner.LitBlazeBurnerBlock; +import com.simibubi.create.foundation.recipe.RecipeApplier; +import com.simibubi.create.foundation.utility.Color; +import com.simibubi.create.foundation.utility.VecHelper; + +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.BlockParticleOption; +import net.minecraft.core.particles.DustParticleOptions; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.animal.horse.Horse; +import net.minecraft.world.entity.animal.horse.SkeletonHorse; +import net.minecraft.world.entity.monster.EnderMan; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.AbstractCookingRecipe; +import net.minecraft.world.item.crafting.BlastingRecipe; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.item.crafting.SmeltingRecipe; +import net.minecraft.world.item.crafting.SmokingRecipe; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.CampfireBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.items.ItemStackHandler; +import net.minecraftforge.items.wrapper.RecipeWrapper; + +public class AllFanProcessingTypes { + public static final NoneType NONE = register("none", new NoneType()); + public static final BlastingType BLASTING = register("blasting", new BlastingType()); + public static final HauntingType HAUNTING = register("haunting", new HauntingType()); + public static final SmokingType SMOKING = register("smoking", new SmokingType()); + public static final SplashingType SPLASHING = register("splashing", new SplashingType()); + + private static final Map LEGACY_NAME_MAP; + + static { + Object2ReferenceOpenHashMap map = new Object2ReferenceOpenHashMap<>(); + map.put("NONE", NONE); + map.put("BLASTING", BLASTING); + map.put("HAUNTING", HAUNTING); + map.put("SMOKING", SMOKING); + map.put("SPLASHING", SPLASHING); + map.trim(); + LEGACY_NAME_MAP = map; + } + + private static T register(String id, T type) { + FanProcessingTypeRegistry.register(Create.asResource(id), type); + return type; + } + + @Nullable + public static FanProcessingType ofLegacyName(String name) { + return LEGACY_NAME_MAP.get(name); + } + + public static void register() { + } + + public static FanProcessingType parseLegacy(String str) { + FanProcessingType type = ofLegacyName(str); + if (type != null) { + return type; + } + return FanProcessingType.parse(str); + } + + public static class NoneType implements FanProcessingType { + @Override + public boolean isValidAt(Level level, BlockPos pos) { + return true; + } + + @Override + public int getPriority() { + return -1000000; + } + + @Override + public boolean canProcess(ItemStack stack, Level level) { + return false; + } + + @Override + @Nullable + public List process(ItemStack stack, Level level) { + return null; + } + + @Override + public void spawnProcessingParticles(Level level, Vec3 pos) { + } + + @Override + public void morphAirFlow(AirFlowParticleAccess particleAccess, Random random) { + } + + @Override + public void affectEntity(Entity entity, Level level) { + } + } + + public static class BlastingType implements FanProcessingType { + private static final RecipeWrapper RECIPE_WRAPPER = new RecipeWrapper(new ItemStackHandler(1)); + private static final DamageSource LAVA_DAMAGE_SOURCE = new DamageSource("create.fan_lava").setScalesWithDifficulty() + .setIsFire(); + + @Override + public boolean isValidAt(Level level, BlockPos pos) { + BlockState blockState = level.getBlockState(pos); + Block block = blockState.getBlock(); + return block == Blocks.LAVA || getHeatLevelOf(blockState).isAtLeast(BlazeBurnerBlock.HeatLevel.FADING); + } + + @Override + public int getPriority() { + return 100; + } + + @Override + public boolean canProcess(ItemStack stack, Level level) { + RECIPE_WRAPPER.setItem(0, stack); + Optional smeltingRecipe = level.getRecipeManager() + .getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, level); + + if (smeltingRecipe.isPresent()) + return true; + + RECIPE_WRAPPER.setItem(0, stack); + Optional blastingRecipe = level.getRecipeManager() + .getRecipeFor(RecipeType.BLASTING, RECIPE_WRAPPER, level); + + if (blastingRecipe.isPresent()) + return true; + + return !stack.getItem() + .isFireResistant(); + } + + @Override + @Nullable + public List process(ItemStack stack, Level level) { + RECIPE_WRAPPER.setItem(0, stack); + Optional smokingRecipe = level.getRecipeManager() + .getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level); + + RECIPE_WRAPPER.setItem(0, stack); + Optional smeltingRecipe = level.getRecipeManager() + .getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, level); + if (!smeltingRecipe.isPresent()) { + RECIPE_WRAPPER.setItem(0, stack); + smeltingRecipe = level.getRecipeManager() + .getRecipeFor(RecipeType.BLASTING, RECIPE_WRAPPER, level); + } + + if (smeltingRecipe.isPresent()) { + if (!smokingRecipe.isPresent() || !ItemStack.isSame(smokingRecipe.get() + .getResultItem(), + smeltingRecipe.get() + .getResultItem())) { + return RecipeApplier.applyRecipeOn(stack, smeltingRecipe.get()); + } + } + + return Collections.emptyList(); + } + + @Override + public void spawnProcessingParticles(Level level, Vec3 pos) { + if (level.random.nextInt(8) != 0) + return; + level.addParticle(ParticleTypes.LARGE_SMOKE, pos.x, pos.y + .25f, pos.z, 0, 1 / 16f, 0); + } + + @Override + public void morphAirFlow(AirFlowParticleAccess particleAccess, Random random) { + particleAccess.setColor(Color.mixColors(0xFF4400, 0xFF8855, random.nextFloat())); + particleAccess.setAlpha(.5f); + if (random.nextFloat() < 1 / 32f) + particleAccess.spawnExtraParticle(ParticleTypes.FLAME, .25f); + if (random.nextFloat() < 1 / 16f) + particleAccess.spawnExtraParticle(new BlockParticleOption(ParticleTypes.BLOCK, Blocks.LAVA.defaultBlockState()), .25f); + } + + @Override + public void affectEntity(Entity entity, Level level) { + if (level.isClientSide) + return; + + if (!entity.fireImmune()) { + entity.setSecondsOnFire(10); + entity.hurt(LAVA_DAMAGE_SOURCE, 4); + } + } + } + + public static class HauntingType implements FanProcessingType { + private static final HauntingWrapper HAUNTING_WRAPPER = new HauntingWrapper(); + + @Override + public boolean isValidAt(Level level, BlockPos pos) { + BlockState blockState = level.getBlockState(pos); + Block block = blockState.getBlock(); + return block == Blocks.SOUL_FIRE + || block == Blocks.SOUL_CAMPFIRE && blockState.getOptionalValue(CampfireBlock.LIT) + .orElse(false) + || AllBlocks.LIT_BLAZE_BURNER.has(blockState) + && blockState.getOptionalValue(LitBlazeBurnerBlock.FLAME_TYPE) + .map(flame -> flame == LitBlazeBurnerBlock.FlameType.SOUL) + .orElse(false); + } + + @Override + public int getPriority() { + return 300; + } + + @Override + public boolean canProcess(ItemStack stack, Level level) { + HAUNTING_WRAPPER.setItem(0, stack); + Optional recipe = AllRecipeTypes.HAUNTING.find(HAUNTING_WRAPPER, level); + return recipe.isPresent(); + } + + @Override + @Nullable + public List process(ItemStack stack, Level level) { + HAUNTING_WRAPPER.setItem(0, stack); + Optional recipe = AllRecipeTypes.HAUNTING.find(HAUNTING_WRAPPER, level); + if (recipe.isPresent()) + return RecipeApplier.applyRecipeOn(stack, recipe.get()); + return null; + } + + @Override + public void spawnProcessingParticles(Level level, Vec3 pos) { + if (level.random.nextInt(8) != 0) + return; + pos = pos.add(VecHelper.offsetRandomly(Vec3.ZERO, level.random, 1) + .multiply(1, 0.05f, 1) + .normalize() + .scale(0.15f)); + level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, pos.x, pos.y + .45f, pos.z, 0, 0, 0); + if (level.random.nextInt(2) == 0) + level.addParticle(ParticleTypes.SMOKE, pos.x, pos.y + .25f, pos.z, 0, 0, 0); + } + + @Override + public void morphAirFlow(AirFlowParticleAccess particleAccess, Random random) { + particleAccess.setColor(Color.mixColors(0x0, 0x126568, random.nextFloat())); + particleAccess.setAlpha(1f); + if (random.nextFloat() < 1 / 128f) + particleAccess.spawnExtraParticle(ParticleTypes.SOUL_FIRE_FLAME, .125f); + if (random.nextFloat() < 1 / 32f) + particleAccess.spawnExtraParticle(ParticleTypes.SMOKE, .125f); + } + + @Override + public void affectEntity(Entity entity, Level level) { + if (level.isClientSide) { + if (entity instanceof Horse) { + Vec3 p = entity.getPosition(0); + Vec3 v = p.add(0, 0.5f, 0) + .add(VecHelper.offsetRandomly(Vec3.ZERO, level.random, 1) + .multiply(1, 0.2f, 1) + .normalize() + .scale(1f)); + level.addParticle(ParticleTypes.SOUL_FIRE_FLAME, v.x, v.y, v.z, 0, 0.1f, 0); + if (level.random.nextInt(3) == 0) + level.addParticle(ParticleTypes.LARGE_SMOKE, p.x, p.y + .5f, p.z, + (level.random.nextFloat() - .5f) * .5f, 0.1f, (level.random.nextFloat() - .5f) * .5f); + } + return; + } + + if (entity instanceof LivingEntity livingEntity) { + livingEntity.addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 30, 0, false, false)); + livingEntity.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 20, 1, false, false)); + } + if (entity instanceof Horse horse) { + int progress = horse.getPersistentData() + .getInt("CreateHaunting"); + if (progress < 100) { + if (progress % 10 == 0) { + level.playSound(null, entity.blockPosition(), SoundEvents.SOUL_ESCAPE, SoundSource.NEUTRAL, + 1f, 1.5f * progress / 100f); + } + horse.getPersistentData() + .putInt("CreateHaunting", progress + 1); + return; + } + + level.playSound(null, entity.blockPosition(), SoundEvents.GENERIC_EXTINGUISH_FIRE, + SoundSource.NEUTRAL, 1.25f, 0.65f); + + SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(level); + CompoundTag serializeNBT = horse.saveWithoutId(new CompoundTag()); + serializeNBT.remove("UUID"); + if (!horse.getArmor() + .isEmpty()) + horse.spawnAtLocation(horse.getArmor()); + + skeletonHorse.deserializeNBT(serializeNBT); + skeletonHorse.setPos(horse.getPosition(0)); + level.addFreshEntity(skeletonHorse); + horse.discard(); + } + } + } + + public static class SmokingType implements FanProcessingType { + private static final RecipeWrapper RECIPE_WRAPPER = new RecipeWrapper(new ItemStackHandler(1)); + private static final DamageSource FIRE_DAMAGE_SOURCE = new DamageSource("create.fan_fire").setScalesWithDifficulty() + .setIsFire(); + + @Override + public boolean isValidAt(Level level, BlockPos pos) { + BlockState blockState = level.getBlockState(pos); + Block block = blockState.getBlock(); + return block == Blocks.FIRE + || blockState.is(BlockTags.CAMPFIRES) && blockState.getOptionalValue(CampfireBlock.LIT) + .orElse(false) + || AllBlocks.LIT_BLAZE_BURNER.has(blockState) + && blockState.getOptionalValue(LitBlazeBurnerBlock.FLAME_TYPE) + .map(flame -> flame == LitBlazeBurnerBlock.FlameType.REGULAR) + .orElse(false) + || getHeatLevelOf(blockState) == BlazeBurnerBlock.HeatLevel.SMOULDERING; + } + + @Override + public int getPriority() { + return 200; + } + + @Override + public boolean canProcess(ItemStack stack, Level level) { + RECIPE_WRAPPER.setItem(0, stack); + Optional recipe = level.getRecipeManager() + .getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level); + return recipe.isPresent(); + } + + @Override + @Nullable + public List process(ItemStack stack, Level level) { + RECIPE_WRAPPER.setItem(0, stack); + Optional smokingRecipe = level.getRecipeManager() + .getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level); + + if (smokingRecipe.isPresent()) + return RecipeApplier.applyRecipeOn(stack, smokingRecipe.get()); + + return null; + } + + @Override + public void spawnProcessingParticles(Level level, Vec3 pos) { + if (level.random.nextInt(8) != 0) + return; + level.addParticle(ParticleTypes.POOF, pos.x, pos.y + .25f, pos.z, 0, 1 / 16f, 0); + } + + @Override + public void morphAirFlow(AirFlowParticleAccess particleAccess, Random random) { + particleAccess.setColor(Color.mixColors(0x0, 0x555555, random.nextFloat())); + particleAccess.setAlpha(1f); + if (random.nextFloat() < 1 / 32f) + particleAccess.spawnExtraParticle(ParticleTypes.SMOKE, .125f); + if (random.nextFloat() < 1 / 32f) + particleAccess.spawnExtraParticle(ParticleTypes.LARGE_SMOKE, .125f); + } + + @Override + public void affectEntity(Entity entity, Level level) { + if (level.isClientSide) + return; + + if (!entity.fireImmune()) { + entity.setSecondsOnFire(2); + entity.hurt(FIRE_DAMAGE_SOURCE, 2); + } + } + } + + public static class SplashingType implements FanProcessingType { + private static final SplashingWrapper SPLASHING_WRAPPER = new SplashingWrapper(); + + @Override + public boolean isValidAt(Level level, BlockPos pos) { + FluidState fluidState = level.getFluidState(pos); + Fluid fluid = fluidState.getType(); + return fluid == Fluids.WATER || fluid == Fluids.FLOWING_WATER; + } + + @Override + public int getPriority() { + return 400; + } + + @Override + public boolean canProcess(ItemStack stack, Level level) { + SPLASHING_WRAPPER.setItem(0, stack); + Optional recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, level); + return recipe.isPresent(); + } + + @Override + @Nullable + public List process(ItemStack stack, Level level) { + SPLASHING_WRAPPER.setItem(0, stack); + Optional recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, level); + if (recipe.isPresent()) + return RecipeApplier.applyRecipeOn(stack, recipe.get()); + return null; + } + + @Override + public void spawnProcessingParticles(Level level, Vec3 pos) { + if (level.random.nextInt(8) != 0) + return; + Vector3f color = new Color(0x0055FF).asVectorF(); + level.addParticle(new DustParticleOptions(color, 1), pos.x + (level.random.nextFloat() - .5f) * .5f, + pos.y + .5f, pos.z + (level.random.nextFloat() - .5f) * .5f, 0, 1 / 8f, 0); + level.addParticle(ParticleTypes.SPIT, pos.x + (level.random.nextFloat() - .5f) * .5f, pos.y + .5f, + pos.z + (level.random.nextFloat() - .5f) * .5f, 0, 1 / 8f, 0); + } + + @Override + public void morphAirFlow(AirFlowParticleAccess particleAccess, Random random) { + particleAccess.setColor(Color.mixColors(0x4499FF, 0x2277FF, random.nextFloat())); + particleAccess.setAlpha(1f); + if (random.nextFloat() < 1 / 32f) + particleAccess.spawnExtraParticle(ParticleTypes.BUBBLE, .125f); + if (random.nextFloat() < 1 / 32f) + particleAccess.spawnExtraParticle(ParticleTypes.BUBBLE_POP, .125f); + } + + @Override + public void affectEntity(Entity entity, Level level) { + if (level.isClientSide) + return; + + if (entity instanceof EnderMan || entity.getType() == EntityType.SNOW_GOLEM + || entity.getType() == EntityType.BLAZE) { + entity.hurt(DamageSource.DROWN, 2); + } + if (entity.isOnFire()) { + entity.clearFire(); + level.playSound(null, entity.blockPosition(), SoundEvents.GENERIC_EXTINGUISH_FIRE, + SoundSource.NEUTRAL, 0.7F, 1.6F + (level.random.nextFloat() - level.random.nextFloat()) * 0.4F); + } + } + } +} diff --git a/src/main/java/com/simibubi/create/content/kinetics/fan/processing/FanProcessing.java b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/FanProcessing.java new file mode 100644 index 000000000..8ea07366c --- /dev/null +++ b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/FanProcessing.java @@ -0,0 +1,108 @@ +package com.simibubi.create.content.kinetics.fan.processing; + +import java.util.ArrayList; +import java.util.List; + +import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour.TransportedResult; +import com.simibubi.create.content.kinetics.belt.transport.TransportedItemStack; +import com.simibubi.create.infrastructure.config.AllConfigs; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; + +public class FanProcessing { + public static boolean canProcess(ItemEntity entity, FanProcessingType type) { + if (entity.getPersistentData() + .contains("CreateData")) { + CompoundTag compound = entity.getPersistentData() + .getCompound("CreateData"); + if (compound.contains("Processing")) { + CompoundTag processing = compound.getCompound("Processing"); + + if (AllFanProcessingTypes.parseLegacy(processing.getString("Type")) != type) + return type.canProcess(entity.getItem(), entity.level); + else if (processing.getInt("Time") >= 0) + return true; + else if (processing.getInt("Time") == -1) + return false; + } + } + return type.canProcess(entity.getItem(), entity.level); + } + + public static boolean applyProcessing(ItemEntity entity, FanProcessingType type) { + if (decrementProcessingTime(entity, type) != 0) + return false; + List stacks = type.process(entity.getItem(), entity.level); + if (stacks == null) + return false; + if (stacks.isEmpty()) { + entity.discard(); + return false; + } + entity.setItem(stacks.remove(0)); + for (ItemStack additional : stacks) { + ItemEntity entityIn = new ItemEntity(entity.level, entity.getX(), entity.getY(), entity.getZ(), additional); + entityIn.setDeltaMovement(entity.getDeltaMovement()); + entity.level.addFreshEntity(entityIn); + } + return true; + } + + public static TransportedResult applyProcessing(TransportedItemStack transported, Level world, FanProcessingType type) { + TransportedResult ignore = TransportedResult.doNothing(); + if (transported.processedBy != type) { + transported.processedBy = type; + int timeModifierForStackSize = ((transported.stack.getCount() - 1) / 16) + 1; + int processingTime = + (int) (AllConfigs.server().kinetics.fanProcessingTime.get() * timeModifierForStackSize) + 1; + transported.processingTime = processingTime; + if (!type.canProcess(transported.stack, world)) + transported.processingTime = -1; + return ignore; + } + if (transported.processingTime == -1) + return ignore; + if (transported.processingTime-- > 0) + return ignore; + + List stacks = type.process(transported.stack, world); + if (stacks == null) + return ignore; + + List transportedStacks = new ArrayList<>(); + for (ItemStack additional : stacks) { + TransportedItemStack newTransported = transported.getSimilar(); + newTransported.stack = additional.copy(); + transportedStacks.add(newTransported); + } + return TransportedResult.convertTo(transportedStacks); + } + + private static int decrementProcessingTime(ItemEntity entity, FanProcessingType type) { + CompoundTag nbt = entity.getPersistentData(); + + if (!nbt.contains("CreateData")) + nbt.put("CreateData", new CompoundTag()); + CompoundTag createData = nbt.getCompound("CreateData"); + + if (!createData.contains("Processing")) + createData.put("Processing", new CompoundTag()); + CompoundTag processing = createData.getCompound("Processing"); + + if (!processing.contains("Type") || AllFanProcessingTypes.parseLegacy(processing.getString("Type")) != type) { + processing.putString("Type", FanProcessingTypeRegistry.getIdOrThrow(type).toString()); + int timeModifierForStackSize = ((entity.getItem() + .getCount() - 1) / 16) + 1; + int processingTime = + (int) (AllConfigs.server().kinetics.fanProcessingTime.get() * timeModifierForStackSize) + 1; + processing.putInt("Time", processingTime); + } + + int value = processing.getInt("Time") - 1; + processing.putInt("Time", value); + return value; + } +} diff --git a/src/main/java/com/simibubi/create/content/kinetics/fan/processing/FanProcessingType.java b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/FanProcessingType.java new file mode 100644 index 000000000..1c7faa188 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/FanProcessingType.java @@ -0,0 +1,60 @@ +package com.simibubi.create.content.kinetics.fan.processing; + +import java.util.List; +import java.util.Random; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; + +public interface FanProcessingType { + boolean isValidAt(Level level, BlockPos pos); + + int getPriority(); + + boolean canProcess(ItemStack stack, Level level); + + @Nullable + List process(ItemStack stack, Level level); + + void spawnProcessingParticles(Level level, Vec3 pos); + + void morphAirFlow(AirFlowParticleAccess particleAccess, Random random); + + void affectEntity(Entity entity, Level level); + + static FanProcessingType parse(String str) { + ResourceLocation id = ResourceLocation.tryParse(str); + if (id == null) { + return AllFanProcessingTypes.NONE; + } + FanProcessingType type = FanProcessingTypeRegistry.getType(id); + if (type == null) { + return AllFanProcessingTypes.NONE; + } + return type; + } + + static FanProcessingType getAt(Level level, BlockPos pos) { + for (FanProcessingType type : FanProcessingTypeRegistry.getSortedTypesView()) { + if (type.isValidAt(level, pos)) { + return type; + } + } + return AllFanProcessingTypes.NONE; + } + + interface AirFlowParticleAccess { + void setColor(int color); + + void setAlpha(float alpha); + + void spawnExtraParticle(ParticleOptions options, float speedMultiplier); + } +} diff --git a/src/main/java/com/simibubi/create/content/kinetics/fan/processing/FanProcessingTypeRegistry.java b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/FanProcessingTypeRegistry.java new file mode 100644 index 000000000..0e177a012 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/FanProcessingTypeRegistry.java @@ -0,0 +1,68 @@ +package com.simibubi.create.content.kinetics.fan.processing; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import net.minecraft.resources.ResourceLocation; + +public class FanProcessingTypeRegistry { + private static final Map TYPES = new Object2ReferenceOpenHashMap<>(); + private static final Map IDS = new Reference2ObjectOpenHashMap<>(); + private static final List SORTED_TYPES = new ReferenceArrayList<>(); + private static final List SORTED_TYPES_VIEW = Collections.unmodifiableList(SORTED_TYPES); + + public static void register(ResourceLocation id, FanProcessingType type) { + if (TYPES.put(id, type) != null) { + throw new IllegalArgumentException("Tried to override FanProcessingType registration for id '" + id + "'. This is not supported!"); + } + ResourceLocation prevId = IDS.put(type, id); + if (prevId != null) { + throw new IllegalArgumentException("Tried to register same FanProcessingType instance for multiple ids '" + prevId + "' and '" + id + "'. This is not supported!"); + } + insertSortedType(type, id); + } + + private static void insertSortedType(FanProcessingType type, ResourceLocation id) { + int index = Collections.binarySearch(SORTED_TYPES, type, (type1, type2) -> type2.getPriority() - type1.getPriority()); + if (index >= 0) { + throw new IllegalStateException(); + } + SORTED_TYPES.add(-index - 1, type); + } + + @Nullable + public static FanProcessingType getType(ResourceLocation id) { + return TYPES.get(id); + } + + public static FanProcessingType getTypeOrThrow(ResourceLocation id) { + FanProcessingType type = getType(id); + if (type == null) { + throw new IllegalArgumentException("Could not get FanProcessingType for id '" + id + "'!"); + } + return type; + } + + @Nullable + public static ResourceLocation getId(FanProcessingType type) { + return IDS.get(type); + } + + public static ResourceLocation getIdOrThrow(FanProcessingType type) { + ResourceLocation id = getId(type); + if (id == null) { + throw new IllegalArgumentException("Could not get id for FanProcessingType " + type + "!"); + } + return id; + } + + public static List getSortedTypesView() { + return SORTED_TYPES_VIEW; + } +} diff --git a/src/main/java/com/simibubi/create/content/kinetics/fan/HauntingRecipe.java b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/HauntingRecipe.java similarity index 56% rename from src/main/java/com/simibubi/create/content/kinetics/fan/HauntingRecipe.java rename to src/main/java/com/simibubi/create/content/kinetics/fan/processing/HauntingRecipe.java index 43dbc81d0..b936db96f 100644 --- a/src/main/java/com/simibubi/create/content/kinetics/fan/HauntingRecipe.java +++ b/src/main/java/com/simibubi/create/content/kinetics/fan/processing/HauntingRecipe.java @@ -1,22 +1,25 @@ -package com.simibubi.create.content.kinetics.fan; +package com.simibubi.create.content.kinetics.fan.processing; import javax.annotation.ParametersAreNonnullByDefault; import com.simibubi.create.AllRecipeTypes; +import com.simibubi.create.content.kinetics.fan.processing.HauntingRecipe.HauntingWrapper; import com.simibubi.create.content.processing.recipe.ProcessingRecipe; import com.simibubi.create.content.processing.recipe.ProcessingRecipeBuilder.ProcessingRecipeParams; import net.minecraft.world.level.Level; +import net.minecraftforge.items.ItemStackHandler; +import net.minecraftforge.items.wrapper.RecipeWrapper; @ParametersAreNonnullByDefault -public class HauntingRecipe extends ProcessingRecipe { +public class HauntingRecipe extends ProcessingRecipe { public HauntingRecipe(ProcessingRecipeParams params) { super(AllRecipeTypes.HAUNTING, params); } @Override - public boolean matches(FanProcessing.HauntingWrapper inv, Level worldIn) { + public boolean matches(HauntingWrapper inv, Level worldIn) { if (inv.isEmpty()) return false; return ingredients.get(0) @@ -33,4 +36,10 @@ public class HauntingRecipe extends ProcessingRecipe { +public class SplashingRecipe extends ProcessingRecipe { public SplashingRecipe(ProcessingRecipeParams params) { super(AllRecipeTypes.SPLASHING, params); @@ -34,4 +36,10 @@ public class SplashingRecipe extends ProcessingRecipe LivingEntity.getEquipmentSlotForItem(s) .getType() != EquipmentSlot.Type.HAND), FURNACE_FUEL(AbstractFurnaceBlockEntity::isFuel), - WASHABLE(FanProcessing::isWashable), - HAUNTABLE(FanProcessing::isHauntable), + WASHABLE(AllFanProcessingTypes.SPLASHING::canProcess), + HAUNTABLE(AllFanProcessingTypes.HAUNTING::canProcess), CRUSHABLE((s, w) -> testRecipe(s, w, AllRecipeTypes.CRUSHING.getType()) || testRecipe(s, w, AllRecipeTypes.MILLING.getType())), SMELTABLE((s, w) -> testRecipe(s, w, RecipeType.SMELTING)), diff --git a/src/main/java/com/simibubi/create/content/trains/track/TrackPlacement.java b/src/main/java/com/simibubi/create/content/trains/track/TrackPlacement.java index 7c73f9da3..d5870a04b 100644 --- a/src/main/java/com/simibubi/create/content/trains/track/TrackPlacement.java +++ b/src/main/java/com/simibubi/create/content/trains/track/TrackPlacement.java @@ -13,6 +13,7 @@ import com.simibubi.create.CreateClient; import com.simibubi.create.content.equipment.blueprint.BlueprintOverlayRenderer; import com.simibubi.create.foundation.block.ProperWaterloggedBlock; import com.simibubi.create.foundation.utility.AngleHelper; +import com.simibubi.create.foundation.utility.BlockHelper; import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Lang; @@ -44,7 +45,6 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult.Type; @@ -478,18 +478,6 @@ public class TrackPlacement { info.requiredPavement += TrackPaver.paveCurve(level, info.curve, block, simulate, visited); } - private static BlockState copyProperties(BlockState from, BlockState onto) { - for (Property property : onto.getProperties()) { - if (from.hasProperty(property)) - onto = onto.setValue(property, from.getValue(property)); - } - return onto; - } - - private static BlockState copyProperties(BlockState from, BlockState onto, boolean keepFrom) { - return keepFrom ? from : copyProperties(from, onto); - } - private static PlacementInfo placeTracks(Level level, PlacementInfo info, BlockState state1, BlockState state2, BlockPos targetPos1, BlockPos targetPos2, boolean simulate) { info.requiredTracks = 0; @@ -518,7 +506,7 @@ public class TrackPlacement { BlockPos offsetPos = pos.offset(offset.x, offset.y, offset.z); BlockState stateAtPos = level.getBlockState(offsetPos); // copy over all shared properties from the shaped state to the correct track material block - BlockState toPlace = copyProperties(state, info.trackMaterial.getBlock().defaultBlockState()); + BlockState toPlace = BlockHelper.copyProperties(state, info.trackMaterial.getBlock().defaultBlockState()); boolean canPlace = stateAtPos.getMaterial() .isReplaceable(); @@ -544,12 +532,12 @@ public class TrackPlacement { BlockState onto = info.trackMaterial.getBlock().defaultBlockState(); BlockState stateAtPos = level.getBlockState(targetPos1); level.setBlock(targetPos1, ProperWaterloggedBlock.withWater(level, - (AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : copyProperties(state1, onto)) + (AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : BlockHelper.copyProperties(state1, onto)) .setValue(TrackBlock.HAS_BE, true), targetPos1), 3); stateAtPos = level.getBlockState(targetPos2); level.setBlock(targetPos2, ProperWaterloggedBlock.withWater(level, - (AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : copyProperties(state2, onto)) + (AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : BlockHelper.copyProperties(state2, onto)) .setValue(TrackBlock.HAS_BE, true), targetPos2), 3); }