Add experimental dropper and dispenser movement behaviours.

WIP: (probably) unstable, definitely buggy
Unfinished: Bottles, maybe spawn eggs
This commit is contained in:
grimmauld 2020-09-01 22:41:17 +02:00
parent 53e0c61da7
commit 7e4ca0475e
8 changed files with 462 additions and 1 deletions

View file

@ -6,6 +6,8 @@ import javax.annotation.Nullable;
import com.simibubi.create.content.contraptions.components.actors.BellMovementBehaviour;
import com.simibubi.create.content.contraptions.components.actors.CampfireMovementBehaviour;
import com.simibubi.create.content.contraptions.components.actors.dispenser.DispenserMovementBehaviour;
import com.simibubi.create.content.contraptions.components.actors.dispenser.DropperMovementBehaviour;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
import com.tterrag.registrate.util.nullness.NonNullConsumer;
@ -48,5 +50,9 @@ public class AllMovementBehaviours {
static void register() {
addMovementBehaviour(Blocks.BELL, new BellMovementBehaviour());
addMovementBehaviour(Blocks.CAMPFIRE, new CampfireMovementBehaviour());
DispenserMovementBehaviour.gatherMovedDispenseItemBehaviours();
addMovementBehaviour(Blocks.DISPENSER, new DispenserMovementBehaviour());
addMovementBehaviour(Blocks.DROPPER, new DropperMovementBehaviour());
}
}

View file

@ -0,0 +1,68 @@
package com.simibubi.create.content.contraptions.components.actors.dispenser;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.block.BlockState;
import net.minecraft.dispenser.IBlockSource;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nullable;
@MethodsReturnNonnullByDefault
public class ContraptionBlockSource implements IBlockSource {
private final BlockPos pos;
private final MovementContext context;
private final Direction overrideFacing;
public ContraptionBlockSource(MovementContext context, BlockPos pos) {
this(context, pos, null);
}
public ContraptionBlockSource(MovementContext context, BlockPos pos, @Nullable Direction overrideFacing) {
this.pos = pos;
this.context = context;
this.overrideFacing = overrideFacing;
}
@Override
public double getX() {
return (double)this.pos.getX() + 0.5D;
}
@Override
public double getY() {
return (double)this.pos.getY() + 0.5D;
}
@Override
public double getZ() {
return (double)this.pos.getZ() + 0.5D;
}
@Override
public BlockPos getBlockPos() {
return pos;
}
@Override
public BlockState getBlockState() {
if(context.state.has(BlockStateProperties.FACING) && overrideFacing != null)
return context.state.with(BlockStateProperties.FACING, overrideFacing);
return context.state;
}
@Override
@Nullable
public <T extends TileEntity> T getBlockTileEntity() {
return null;
}
@Override
public World getWorld() {
return context.world;
}
}

View file

@ -0,0 +1,56 @@
package com.simibubi.create.content.contraptions.components.actors.dispenser;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.block.Blocks;
import net.minecraft.block.DispenserBlock;
import net.minecraft.dispenser.IDispenseItemBehavior;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import java.util.HashMap;
public class DispenserMovementBehaviour extends DropperMovementBehaviour {
private static final HashMap<Item, IMovedDispenseItemBehaviour> MOVED_DISPENSE_ITEM_BEHAVIOURS = new HashMap<>();
public static void gatherMovedDispenseItemBehaviours() {
IMovedDispenseItemBehaviour.init();
}
public static void registerMovedDispenseItemBehaviour(Item item, IMovedDispenseItemBehaviour movedDispenseItemBehaviour) {
MOVED_DISPENSE_ITEM_BEHAVIOURS.put(item, movedDispenseItemBehaviour);
}
@Override
protected void activate(MovementContext context, BlockPos pos) {
int i = getDispenseSlot(context);
if (i < 0) {
context.world.playEvent(1001, pos, 0);
} else {
ItemStack itemstack = getStacks(context).get(i);
if (MOVED_DISPENSE_ITEM_BEHAVIOURS.containsKey(itemstack.getItem())) {
MOVED_DISPENSE_ITEM_BEHAVIOURS.get(itemstack.getItem()).dispense(itemstack, context, pos);
return;
}
int count = itemstack.getCount();
// Try vanilla registry
try {
Vec3d facingVec = new Vec3d(context.state.get(DispenserBlock.FACING).getDirectionVec());
facingVec = VecHelper.rotate(facingVec, context.rotation.x, context.rotation.y, context.rotation.z);
facingVec.normalize();
Direction clostestFacing = Direction.getFacingFromVector(facingVec.x, facingVec.y, facingVec.z);
ContraptionBlockSource blockSource = new ContraptionBlockSource(context, pos, clostestFacing);
IDispenseItemBehavior idispenseitembehavior = ((DispenserBlock) Blocks.DISPENSER).getBehavior(itemstack);
idispenseitembehavior.dispense(blockSource, itemstack);
} catch (NullPointerException e) {
itemstack.setCount(count);
defaultBehaviour.dispense(itemstack, context, pos); // Something went wrong with the TE being null in ContraptionBlockSource, just drop the item
}
}
}
}

View file

@ -0,0 +1,76 @@
package com.simibubi.create.content.contraptions.components.actors.dispenser;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import com.simibubi.create.content.logistics.item.filter.FilterItem;
import com.simibubi.create.foundation.item.ItemHelper;
import net.minecraft.inventory.ItemStackHelper;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.server.ServerWorld;
import java.util.Random;
public class DropperMovementBehaviour extends MovementBehaviour {
protected static final MovedDefaultDispenseItemBehaviour defaultBehaviour = new MovedDefaultDispenseItemBehaviour();
private static final Random RNG = new Random();
protected void activate(MovementContext context, BlockPos pos) {
int i = getDispenseSlot(context);
if (i < 0) {
context.world.playEvent(1001, pos, 0);
} else {
defaultBehaviour.dispense(getStacks(context).get(i), context, pos);
}
}
@Override
public void visitNewPosition(MovementContext context, BlockPos pos) {
if (context.world.isRemote)
return;
collectItems(context);
activate(context, pos);
}
private void collectItems(MovementContext context) {
getStacks(context).stream().filter(itemStack -> !itemStack.isEmpty() && itemStack.getItem() != Items.AIR && itemStack.getMaxStackSize() > itemStack.getCount()).forEach(itemStack -> itemStack.grow(
ItemHelper.extract(context.contraption.inventory, stack -> FilterItem.test(context.world, stack, itemStack), ItemHelper.ExtractionCountMode.UPTO, itemStack.getMaxStackSize() - itemStack.getCount(), false).getCount()));
}
protected NonNullList<ItemStack> getStacks(MovementContext context) {
if (!(context.temporaryData instanceof NonNullList) && context.world instanceof ServerWorld) {
NonNullList<ItemStack> stacks = NonNullList.withSize(9, ItemStack.EMPTY);
ItemStackHelper.loadAllItems(context.tileData, stacks);
context.temporaryData = stacks;
}
return (NonNullList<ItemStack>) context.temporaryData;
}
@Override
public void writeExtraData(MovementContext context) {
NonNullList<ItemStack> stacks = getStacks(context);
if (stacks == null)
return;
ItemStackHelper.saveAllItems(context.tileData, stacks);
}
@Override
public void stopMoving(MovementContext context) {
super.stopMoving(context);
writeExtraData(context);
}
protected int getDispenseSlot(MovementContext context) {
int i = -1;
int j = 1;
NonNullList<ItemStack> stacks = getStacks(context);
for (int k = 0; k < stacks.size(); ++k) {
if (!stacks.get(k).isEmpty() && RNG.nextInt(j++) == 0 && stacks.get(k).getCount() >= 2) {
i = k;
}
}
return i;
}
}

View file

@ -0,0 +1,146 @@
package com.simibubi.create.content.contraptions.components.actors.dispenser;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import net.minecraft.entity.IProjectile;
import net.minecraft.entity.item.ExperienceBottleEntity;
import net.minecraft.entity.item.FireworkRocketEntity;
import net.minecraft.entity.item.TNTEntity;
import net.minecraft.entity.projectile.*;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvents;
import net.minecraft.util.Util;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import java.util.Random;
public interface IMovedDispenseItemBehaviour {
static void init() {
DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.ARROW, new MovedProjectileDispenserBehaviour() {
@Override
protected IProjectile getProjectileEntity(World world, double x, double y, double z, ItemStack itemStack) {
ArrowEntity arrowEntity = new ArrowEntity(world, x, y, z);
arrowEntity.pickupStatus = AbstractArrowEntity.PickupStatus.ALLOWED;
return arrowEntity;
}
});
DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.TIPPED_ARROW, new MovedProjectileDispenserBehaviour() {
@Override
protected IProjectile getProjectileEntity(World world, double x, double y, double z, ItemStack itemStack) {
ArrowEntity arrowEntity = new ArrowEntity(world, x, y, z);
arrowEntity.setPotionEffect(itemStack);
arrowEntity.pickupStatus = AbstractArrowEntity.PickupStatus.ALLOWED;
return arrowEntity;
}
});
DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.SPECTRAL_ARROW, new MovedProjectileDispenserBehaviour() {
@Override
protected IProjectile getProjectileEntity(World world, double x, double y, double z, ItemStack itemStack) {
AbstractArrowEntity arrowEntity = new SpectralArrowEntity(world, x, y, z);
arrowEntity.pickupStatus = AbstractArrowEntity.PickupStatus.ALLOWED;
return arrowEntity;
}
});
DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.EGG, new MovedProjectileDispenserBehaviour() {
@Override
protected IProjectile getProjectileEntity(World world, double x, double y, double z, ItemStack itemStack) {
return Util.make(new EggEntity(world, x, y, z), p_218408_1_ -> p_218408_1_.setItem(itemStack));
}
});
DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.SNOWBALL, new MovedProjectileDispenserBehaviour() {
@Override
protected IProjectile getProjectileEntity(World world, double x, double y, double z, ItemStack itemStack) {
return Util.make(new SnowballEntity(world, x, y, z), p_218409_1_ -> p_218409_1_.setItem(itemStack));
}
});
DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.EXPERIENCE_BOTTLE, new MovedProjectileDispenserBehaviour() {
@Override
protected IProjectile getProjectileEntity(World world, double x, double y, double z, ItemStack itemStack) {
return Util.make(new ExperienceBottleEntity(world, x, y, z), p_218409_1_ -> p_218409_1_.setItem(itemStack));
}
@Override
protected float getProjectileInaccuracy() {
return super.getProjectileInaccuracy() * 0.5F;
}
@Override
protected float getProjectileVelocity() {
return super.getProjectileVelocity() * 1.25F;
}
});
DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.TNT, new MovedDefaultDispenseItemBehaviour() {
@Override
protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, Vec3d facing) {
double x = pos.getX() + facing.x * .7 + .5;
double y = pos.getY() + facing.y * .7 + .5;
double z = pos.getZ() + facing.z * .7 + .5;
TNTEntity tntentity = new TNTEntity(context.world, x, y, z, null);
tntentity.addVelocity(context.motion.x, context.motion.y, context.motion.z);
context.world.addEntity(tntentity);
context.world.playSound(null, tntentity.getX(), tntentity.getY(), tntentity.getZ(), SoundEvents.ENTITY_TNT_PRIMED, SoundCategory.BLOCKS, 1.0F, 1.0F);
itemStack.shrink(1);
return itemStack;
}
});
DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.FIREWORK_ROCKET, new MovedDefaultDispenseItemBehaviour() {
@Override
protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, Vec3d facing) {
double x = pos.getX() + facing.x * .7 + .5;
double y = pos.getY() + facing.y * .7 + .5;
double z = pos.getZ() + facing.z * .7 + .5;
FireworkRocketEntity fireworkrocketentity = new FireworkRocketEntity(context.world, itemStack, x, y, z, true);
fireworkrocketentity.shoot(facing.x, facing.y, facing.z, 0.5F, 1.0F);
context.world.addEntity(fireworkrocketentity);
itemStack.shrink(1);
return itemStack;
}
@Override
protected void playDispenseSound(IWorld world, BlockPos pos) {
world.playEvent(1004, pos, 0);
}
});
DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.FIRE_CHARGE, new MovedDefaultDispenseItemBehaviour() {
@Override
protected void playDispenseSound(IWorld world, BlockPos pos) {
world.playEvent(1018, pos, 0);
}
@Override
protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, Vec3d facing) {
Random random = context.world.rand;
double x = pos.getX() + facing.x * .7 + .5;
double y = pos.getY() + facing.y * .7 + .5;
double z = pos.getZ() + facing.z * .7 + .5;
context.world.addEntity(Util.make(new SmallFireballEntity(context.world, x, y, z,
random.nextGaussian() * 0.05D + facing.x + context.motion.x, random.nextGaussian() * 0.05D + facing.y + context.motion.y, random.nextGaussian() * 0.05D + facing.z + context.motion.z), (p_229425_1_) -> p_229425_1_.setStack(itemStack)));
itemStack.shrink(1);
return itemStack;
}
});
}
ItemStack dispense(ItemStack itemStack, MovementContext context, BlockPos pos);
}

View file

@ -0,0 +1,66 @@
package com.simibubi.create.content.contraptions.components.actors.dispenser;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.block.DispenserBlock;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
public class MovedDefaultDispenseItemBehaviour implements IMovedDispenseItemBehaviour {
public static void doDispense(World p_82486_0_, ItemStack p_82486_1_, int p_82486_2_, Vec3d facing, BlockPos p_82486_4_) {
double d0 = p_82486_4_.getX() + facing.x + .5;
double d1 = p_82486_4_.getY() + facing.y + .5;
double d2 = p_82486_4_.getZ() + facing.z + .5;
if (Direction.getFacingFromVector(facing.x, facing.y, facing.z).getAxis() == Direction.Axis.Y) {
d1 = d1 - 0.125D;
} else {
d1 = d1 - 0.15625D;
}
ItemEntity itementity = new ItemEntity(p_82486_0_, d0, d1, d2, p_82486_1_);
double d3 = p_82486_0_.rand.nextDouble() * 0.1D + 0.2D;
itementity.setMotion(p_82486_0_.rand.nextGaussian() * (double) 0.0075F * (double) p_82486_2_ + facing.getX() * d3, p_82486_0_.rand.nextGaussian() * (double) 0.0075F * (double) p_82486_2_ + facing.getY() * d3, p_82486_0_.rand.nextGaussian() * (double) 0.0075F * (double) p_82486_2_ + facing.getZ() * d3);
p_82486_0_.addEntity(itementity);
}
@Override
public ItemStack dispense(ItemStack itemStack, MovementContext context, BlockPos pos) {
Vec3d facingVec = new Vec3d(context.state.get(DispenserBlock.FACING).getDirectionVec());
facingVec = VecHelper.rotate(facingVec, context.rotation.x, context.rotation.y, context.rotation.z);
facingVec.normalize();
ItemStack itemstack = this.dispenseStack(itemStack, context, pos, facingVec);
this.playDispenseSound(context.world, pos);
this.spawnDispenseParticles(context.world, pos, facingVec);
return itemstack;
}
/**
* Dispense the specified stack, play the dispense sound and spawn particles.
*/
protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, Vec3d facing) {
ItemStack itemstack = itemStack.split(1);
doDispense(context.world, itemstack, 6, facing, pos);
return itemStack;
}
/**
* Play the dispense sound from the specified block.
*/
protected void playDispenseSound(IWorld world, BlockPos pos) {
world.playEvent(1000, pos, 0);
}
/**
* Order clients to display dispense particles from the specified block and facing.
*/
protected void spawnDispenseParticles(IWorld world, BlockPos pos, Vec3d facing) {
world.playEvent(2000, pos, Direction.getFacingFromVector(facing.x, facing.y, facing.z).getIndex());
}
}

View file

@ -0,0 +1,40 @@
package com.simibubi.create.content.contraptions.components.actors.dispenser;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import net.minecraft.entity.Entity;
import net.minecraft.entity.IProjectile;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
public abstract class MovedProjectileDispenserBehaviour extends MovedDefaultDispenseItemBehaviour {
@Override
protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, Vec3d facing) {
double x = pos.getX() + facing.x * .7 + .5;
double y = pos.getY() + facing.y * .7 + .5;
double z = pos.getZ() + facing.z * .7 + .5;
IProjectile iprojectile = this.getProjectileEntity(context.world, x, y, z, itemStack);
Vec3d effectiveMovementVec = facing.scale(getProjectileVelocity()).add(context.motion);
iprojectile.shoot(effectiveMovementVec.x, effectiveMovementVec.y, effectiveMovementVec.z, (float) effectiveMovementVec.length(), this.getProjectileInaccuracy());
context.world.addEntity((Entity) iprojectile);
itemStack.shrink(1);
return itemStack;
}
@Override
protected void playDispenseSound(IWorld world, BlockPos pos) {
world.playEvent(1002, pos, 0);
}
protected abstract IProjectile getProjectileEntity(World world, double x, double y, double z, ItemStack itemStack);
protected float getProjectileInaccuracy() {
return 6.0F;
}
protected float getProjectileVelocity() {
return 1.1F;
}
}

View file

@ -1,4 +1,7 @@
public net.minecraft.network.play.ServerPlayNetHandler field_147365_f # floatingTickCount
# CubeParticle
protected net.minecraft.client.particle.Particle field_228343_B_ # collidedY
protected net.minecraft.client.particle.Particle field_228343_B_ # collidedY
# Dispenser movement behaviour default
public net.minecraft.block.DispenserBlock func_149940_a(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/dispenser/IDispenseItemBehavior; # getBehavior