Allow registration of custom fan processing types

- Fix crash on startup
This commit is contained in:
PepperCode1 2023-08-20 19:52:41 -07:00
parent 8f5031c330
commit 2a1c6e6916
18 changed files with 844 additions and 595 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<TransportedItemStack> {
public float prevBeltPosition;
public float prevSideOffset;
public FanProcessing.Type processedBy;
public FanProcessingType processedBy;
public int processingTime;
public TransportedItemStack(ItemStack stack) {

View File

@ -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<Pair<TransportedItemStackHandlerBehaviour, FanProcessing.Type>> affectedItemHandlers =
protected List<Pair<TransportedItemStackHandlerBehaviour, FanProcessingType>> affectedItemHandlers =
new ArrayList<>();
protected List<Entity> 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<TransportedItemStackHandlerBehaviour, Type> pair : affectedItemHandlers) {
for (Pair<TransportedItemStackHandlerBehaviour, FanProcessingType> 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;
}

View File

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

View File

@ -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<SplashingRecipe> recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, world);
return recipe.isPresent();
}
public static boolean isHauntable(ItemStack stack, Level world) {
HAUNTING_WRAPPER.setItem(0, stack);
Optional<HauntingRecipe> 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<ItemStack> 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<ItemStack> stacks = process(transported.stack, type, world);
if (stacks == null)
return ignore;
List<TransportedItemStack> 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<ItemStack> process(ItemStack stack, Type type, Level world) {
if (type == Type.SPLASHING) {
SPLASHING_WRAPPER.setItem(0, stack);
Optional<SplashingRecipe> 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<HauntingRecipe> 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> smokingRecipe = world.getRecipeManager()
.getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, world);
if (type == Type.BLASTING) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<? extends AbstractCookingRecipe> 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<SmokingRecipe> 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> smeltingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, level);
if (smeltingRecipe.isPresent())
return true;
RECIPE_WRAPPER.setItem(0, stack);
Optional<BlastingRecipe> 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));
}
}
}

View File

@ -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<String, FanProcessingType> LEGACY_NAME_MAP;
static {
Object2ReferenceOpenHashMap<String, FanProcessingType> 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 extends FanProcessingType> 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<ItemStack> 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> smeltingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMELTING, RECIPE_WRAPPER, level);
if (smeltingRecipe.isPresent())
return true;
RECIPE_WRAPPER.setItem(0, stack);
Optional<BlastingRecipe> blastingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.BLASTING, RECIPE_WRAPPER, level);
if (blastingRecipe.isPresent())
return true;
return !stack.getItem()
.isFireResistant();
}
@Override
@Nullable
public List<ItemStack> process(ItemStack stack, Level level) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<SmokingRecipe> smokingRecipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level);
RECIPE_WRAPPER.setItem(0, stack);
Optional<? extends AbstractCookingRecipe> 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<HauntingRecipe> recipe = AllRecipeTypes.HAUNTING.find(HAUNTING_WRAPPER, level);
return recipe.isPresent();
}
@Override
@Nullable
public List<ItemStack> process(ItemStack stack, Level level) {
HAUNTING_WRAPPER.setItem(0, stack);
Optional<HauntingRecipe> 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<SmokingRecipe> recipe = level.getRecipeManager()
.getRecipeFor(RecipeType.SMOKING, RECIPE_WRAPPER, level);
return recipe.isPresent();
}
@Override
@Nullable
public List<ItemStack> process(ItemStack stack, Level level) {
RECIPE_WRAPPER.setItem(0, stack);
Optional<SmokingRecipe> 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<SplashingRecipe> recipe = AllRecipeTypes.SPLASHING.find(SPLASHING_WRAPPER, level);
return recipe.isPresent();
}
@Override
@Nullable
public List<ItemStack> process(ItemStack stack, Level level) {
SPLASHING_WRAPPER.setItem(0, stack);
Optional<SplashingRecipe> 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);
}
}
}
}

View File

@ -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<ItemStack> 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<ItemStack> stacks = type.process(transported.stack, world);
if (stacks == null)
return ignore;
List<TransportedItemStack> 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;
}
}

View File

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

View File

@ -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<ResourceLocation, FanProcessingType> TYPES = new Object2ReferenceOpenHashMap<>();
private static final Map<FanProcessingType, ResourceLocation> IDS = new Reference2ObjectOpenHashMap<>();
private static final List<FanProcessingType> SORTED_TYPES = new ReferenceArrayList<>();
private static final List<FanProcessingType> 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<FanProcessingType> getSortedTypesView() {
return SORTED_TYPES_VIEW;
}
}

View File

@ -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<FanProcessing.HauntingWrapper> {
public class HauntingRecipe extends ProcessingRecipe<HauntingWrapper> {
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<FanProcessing.HauntingWrapp
return 12;
}
public static class HauntingWrapper extends RecipeWrapper {
public HauntingWrapper() {
super(new ItemStackHandler(1));
}
}
}

View File

@ -1,16 +1,18 @@
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.FanProcessing.SplashingWrapper;
import com.simibubi.create.content.kinetics.fan.processing.SplashingRecipe.SplashingWrapper;
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 SplashingRecipe extends ProcessingRecipe<FanProcessing.SplashingWrapper> {
public class SplashingRecipe extends ProcessingRecipe<SplashingWrapper> {
public SplashingRecipe(ProcessingRecipeParams params) {
super(AllRecipeTypes.SPLASHING, params);
@ -34,4 +36,10 @@ public class SplashingRecipe extends ProcessingRecipe<FanProcessing.SplashingWra
return 12;
}
public static class SplashingWrapper extends RecipeWrapper {
public SplashingWrapper() {
super(new ItemStackHandler(1));
}
}
}

View File

@ -13,7 +13,7 @@ import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.AllRecipeTypes;
import com.simibubi.create.content.kinetics.fan.FanProcessing;
import com.simibubi.create.content.kinetics.fan.processing.AllFanProcessingTypes;
import com.simibubi.create.content.logistics.filter.attribute.BookAuthorAttribute;
import com.simibubi.create.content.logistics.filter.attribute.BookCopyAttribute;
import com.simibubi.create.content.logistics.filter.attribute.ColorAttribute;
@ -145,8 +145,8 @@ public interface ItemAttribute {
EQUIPABLE(s -> 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)),

View File

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