[Feature] Furniture API

This commit is contained in:
Frank 2023-06-10 23:35:39 +02:00
parent 1e153c452d
commit fd7fbee43f
15 changed files with 1492 additions and 0 deletions

View file

@ -0,0 +1,152 @@
package org.betterx.bclib.furniture.block;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.blocks.BaseBlockNotFull;
import org.betterx.bclib.furniture.entity.EntityChair;
import org.betterx.bclib.registry.BaseBlockEntities;
import org.betterx.bclib.util.BlocksHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import java.util.List;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
public abstract class AbstractChair extends BaseBlockNotFull {
public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING;
private final float height;
public AbstractChair(Block block, int height) {
super(FabricBlockSettings.copyOf(block).noOcclusion());
this.height = (height - 3F) / 16F;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
stateManager.add(FACING);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
return this.defaultBlockState().setValue(FACING, ctx.getHorizontalDirection().getOpposite());
}
@Override
public InteractionResult use(
BlockState state,
Level world,
BlockPos pos,
Player player,
InteractionHand hand,
BlockHitResult hit
) {
if (world.isClientSide) {
return InteractionResult.FAIL;
} else {
if (player.isPassenger() || player.isSpectator())
return InteractionResult.FAIL;
Optional<EntityChair> active = getEntity(world, pos);
EntityChair entity;
if (active.isEmpty()) {
entity = createEntity(state, world, pos);
} else {
entity = active.get();
if (entity.isVehicle())
return InteractionResult.FAIL;
}
if (entity != null) {
float yaw = state.getValue(FACING).getOpposite().toYRot();
player.startRiding(entity, true);
player.setYBodyRot(yaw);
player.setYHeadRot(yaw);
return InteractionResult.SUCCESS;
}
return InteractionResult.FAIL;
}
}
@Nullable
private EntityChair createEntity(BlockState state, Level world, BlockPos pos) {
BCLib.LOGGER.info("Creating Chair at " + pos + ", " + state);
EntityChair entity;
double px = pos.getX() + 0.5;
double py = pos.getY() + height;
double pz = pos.getZ() + 0.5;
float yaw = state.getValue(FACING).getOpposite().toYRot();
entity = BaseBlockEntities.CHAIR.create(world);
entity.moveTo(px, py, pz, yaw, 0);
entity.setNoGravity(true);
entity.setSilent(true);
entity.setInvisible(true);
entity.setYHeadRot(yaw);
entity.setYBodyRot(yaw);
if (!world.addFreshEntity(entity)) {
entity = null;
}
return entity;
}
private Optional<EntityChair> getEntity(Level level, BlockPos pos) {
List<EntityChair> list = level.getEntitiesOfClass(
EntityChair.class,
new AABB(pos),
entity -> true
);
if (list.isEmpty()) return Optional.empty();
return Optional.of(list.get(0));
}
@Override
public BlockState rotate(BlockState state, Rotation rotation) {
return BlocksHelper.rotateHorizontal(state, rotation, FACING);
}
@Override
public BlockState mirror(BlockState state, Mirror mirror) {
return BlocksHelper.mirrorHorizontal(state, mirror, FACING);
}
@Override
public void onPlace(BlockState blockState, Level level, BlockPos blockPos, BlockState blockState2, boolean bl) {
super.onPlace(blockState, level, blockPos, blockState2, bl);
BCLib.LOGGER.info("Created at " + blockPos + ", " + blockState + ", " + blockState2);
if (blockState.hasProperty(BaseChair.TOP)) {
if (blockState.getValue(BaseChair.TOP))
return;
}
createEntity(blockState, level, blockPos);
}
@Override
public void onRemove(BlockState blockState, Level level, BlockPos blockPos, BlockState blockState2, boolean bl) {
super.onRemove(blockState, level, blockPos, blockState2, bl);
// Optional<EntityChair> e = getEntity(level, blockPos);
//
// if (e.isPresent()) {
// BCLib.LOGGER.info("Discarding Chair at " + blockPos);
// e.get().discard();
// }
}
}

View file

@ -0,0 +1,21 @@
package org.betterx.bclib.furniture.block;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class BaseBarStool extends AbstractChair {
private static final VoxelShape SHAPE = Block.box(4, 0, 4, 12, 16, 12);
public BaseBarStool(Block block) {
super(block, 15);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
return SHAPE;
}
}

View file

@ -0,0 +1,117 @@
package org.betterx.bclib.furniture.block;
import org.betterx.bclib.util.BlocksHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import java.util.Collections;
import java.util.List;
public class BaseChair extends AbstractChair {
private static final VoxelShape SHAPE_BOTTOM = box(3, 0, 3, 13, 16, 13);
private static final VoxelShape SHAPE_TOP = box(3, 0, 3, 13, 6, 13);
private static final VoxelShape COLLIDER = box(3, 0, 3, 13, 10, 13);
public static final BooleanProperty TOP = BooleanProperty.create("top");
public BaseChair(Block block) {
super(block, 10);
this.registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.NORTH).setValue(TOP, false));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
stateManager.add(FACING, TOP);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
return state.getValue(TOP) ? SHAPE_TOP : SHAPE_BOTTOM;
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
return state.getValue(TOP) ? Shapes.empty() : COLLIDER;
}
@Override
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
if (state.getValue(TOP))
return true;
BlockState up = world.getBlockState(pos.above());
return up.isAir() || (up.getBlock() == this && up.getValue(TOP));
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) {
if (!world.isClientSide())
BlocksHelper.setWithUpdate(world, pos.above(), state.setValue(TOP, true));
}
@Override
public BlockState updateShape(
BlockState state,
Direction facing,
BlockState neighborState,
LevelAccessor world,
BlockPos pos,
BlockPos neighborPos
) {
if (state.getValue(TOP)) {
return world.getBlockState(pos.below()).getBlock() == this ? state : Blocks.AIR.defaultBlockState();
} else {
return world.getBlockState(pos.above()).getBlock() == this ? state : Blocks.AIR.defaultBlockState();
}
}
@Override
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
if (!state.getValue(TOP))
return Collections.singletonList(new ItemStack(this.asItem()));
else
return Collections.emptyList();
}
@Override
public InteractionResult use(
BlockState state,
Level world,
BlockPos pos,
Player player,
InteractionHand hand,
BlockHitResult hit
) {
if (state.getValue(TOP)) {
pos = pos.below();
state = world.getBlockState(pos);
}
return super.use(state, world, pos, player, hand, hit);
}
@Override
public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
if (player.isCreative() && state.getValue(TOP) && world.getBlockState(pos.below()).getBlock() == this) {
world.setBlockAndUpdate(pos.below(), Blocks.AIR.defaultBlockState());
}
super.playerWillDestroy(world, pos, state, player);
}
}

View file

@ -0,0 +1,21 @@
package org.betterx.bclib.furniture.block;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class BaseTaburet extends AbstractChair {
private static final VoxelShape SHAPE = Block.box(2, 0, 2, 14, 10, 14);
public BaseTaburet(Block block) {
super(block, 9);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
return SHAPE;
}
}

View file

@ -0,0 +1,147 @@
package org.betterx.bclib.furniture.entity;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.furniture.block.AbstractChair;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySelector;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.animal.WaterAnimal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import java.util.List;
import org.jetbrains.annotations.Nullable;
public class EntityChair extends Entity {
public EntityChair(EntityType<? extends EntityChair> type, Level world) {
super(type, world);
}
@Override
protected void defineSynchedData() {
}
protected int getMaxPassengers() {
return 1;
}
@Override
public void tick() {
if (this.level.getBlockState(this.blockPosition()).getBlock() instanceof AbstractChair)
localTick();
else {
BCLib.LOGGER.info("Chair Block was deleted -> ejecting");
this.ejectPassengers();
this.discard();
}
}
protected void localTick() {
super.tick();
List<Entity> pushableEntities = this.level.getEntities(
this,
this.getBoundingBox().inflate(0.7f, -0.01f, 0.7f),
EntitySelector.pushableBy(this)
);
if (!pushableEntities.isEmpty()) {
boolean free = !this.level.isClientSide && !(this.getControllingPassenger() instanceof Player);
for (int j = 0; j < pushableEntities.size(); ++j) {
Entity entity = pushableEntities.get(j);
if (entity.hasPassenger(this)) continue;
if (free
&& this.getPassengers().size() < this.getMaxPassengers()
&& !entity.isPassenger()
//&& entity.getBbWidth() < this.getBbWidth()
&& entity instanceof LivingEntity
&& !(entity instanceof WaterAnimal)
&& !(entity instanceof Player)
) {
entity.startRiding(this);
continue;
}
this.push(entity);
}
}
}
@Override
protected void readAdditionalSaveData(CompoundTag compoundTag) {
}
@Override
protected void addAdditionalSaveData(CompoundTag compoundTag) {
}
@Override
public boolean isAlive() {
return !this.isRemoved();
}
@Override
public Packet<ClientGamePacketListener> getAddEntityPacket() {
return new ClientboundAddEntityPacket(this);
}
@Override
protected boolean canAddPassenger(Entity entity) {
return this.getPassengers().size() < this.getMaxPassengers();
}
@Nullable
@Override
public LivingEntity getControllingPassenger() {
for (Entity e : getPassengers()) {
if (e instanceof LivingEntity le) return le;
}
return null;
}
@Override
public void push(Entity entity) {
//Do nothing. Should not be pushable
}
@Override
public double getPassengersRidingOffset() {
return 0.0;
}
@Override
public double getMyRidingOffset() {
return 0.0;
}
@Override
public InteractionResult interact(Player player, InteractionHand interactionHand) {
if (player.isSecondaryUseActive()) {
return InteractionResult.PASS;
}
if (!this.level.isClientSide) {
return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS;
}
return InteractionResult.SUCCESS;
}
public static AttributeSupplier getAttributeContainer() {
return AttributeSupplier.builder().build();
}
@Override
public boolean isPickable() {
return !this.isRemoved();
}
}

View file

@ -0,0 +1,24 @@
package org.betterx.bclib.furniture.renderer;
import org.betterx.bclib.furniture.entity.EntityChair;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@Environment(value = EnvType.CLIENT)
public class RenderChair extends EntityRenderer<EntityChair> {
private static final ResourceLocation TEXTURE = new ResourceLocation("minecraft:textures/block/stone.png");
public RenderChair(EntityRendererProvider.Context context) {
super(context);
}
@Override
public ResourceLocation getTextureLocation(EntityChair entity) {
return TEXTURE;
}
}