Add facade support, allows covering bus cables with other blocks.

This commit is contained in:
Florian Nücke
2021-07-17 19:51:03 +02:00
parent b97bdfea69
commit 501732794d
9 changed files with 224 additions and 55 deletions

View File

@@ -2,12 +2,17 @@ package li.cil.oc2.client.model;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.block.BusCableBlock;
import li.cil.oc2.common.tileentity.BusCableTileEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelShapes;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ItemOverrideList;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.state.EnumProperty;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
@@ -16,12 +21,17 @@ import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.data.ModelDataMap;
import net.minecraftforge.client.model.data.ModelProperty;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public final class BusCableBakedModel implements IDynamicBakedModel {
private static final ModelProperty<BusCableSupportSide> BUS_CABLE_SUPPORT_PROPERTY = new ModelProperty<>();
private static final ModelProperty<BusCableFacade> BUS_CABLE_FACADE_PROPERTY = new ModelProperty<>();
private final IBakedModel proxy;
private final IBakedModel[] straightModelByAxis;
private final IBakedModel[] supportModelByFace;
@@ -37,9 +47,15 @@ public final class BusCableBakedModel implements IDynamicBakedModel {
///////////////////////////////////////////////////////////////////
@Override
@Nonnull
public List<BakedQuad> getQuads(@Nullable final BlockState state, @Nullable final Direction side, final Random rand, final IModelData extraData) {
if (extraData.hasProperty(BUS_CABLE_FACADE_PROPERTY)) {
final BusCableFacade facade = extraData.getData(BUS_CABLE_FACADE_PROPERTY);
return facade.model.getQuads(facade.blockState, side, rand, facade.data);
}
if (state == null || !state.getValue(BusCableBlock.HAS_CABLE)) {
return proxy.getQuads(null, side, rand, extraData);
return Collections.emptyList();
}
for (int i = 0; i < Constants.AXES.length; i++) {
@@ -51,9 +67,9 @@ public final class BusCableBakedModel implements IDynamicBakedModel {
final ArrayList<BakedQuad> quads = new ArrayList<>(proxy.getQuads(state, side, rand, extraData));
final BusCableSupportSide supportSide = extraData.getData(BusCableSupportSide.BUS_CABLE_SUPPORT_PROPERTY);
final BusCableSupportSide supportSide = extraData.getData(BUS_CABLE_SUPPORT_PROPERTY);
if (supportSide != null) {
quads.addAll(supportModelByFace[supportSide.get().get3DDataValue()].getQuads(state, side, rand, extraData));
quads.addAll(supportModelByFace[supportSide.value.get3DDataValue()].getQuads(state, side, rand, extraData));
}
return quads;
@@ -69,7 +85,6 @@ public final class BusCableBakedModel implements IDynamicBakedModel {
return proxy.isGui3d();
}
@Override
public boolean usesBlockLight() {
return proxy.usesBlockLight();
@@ -80,6 +95,7 @@ public final class BusCableBakedModel implements IDynamicBakedModel {
return proxy.isCustomRenderer();
}
@SuppressWarnings("deprecation")
@Override
public TextureAtlasSprite getParticleIcon() {
return proxy.getParticleIcon();
@@ -91,7 +107,26 @@ public final class BusCableBakedModel implements IDynamicBakedModel {
}
@Override
@Nonnull
public IModelData getModelData(final IBlockDisplayReader world, final BlockPos pos, final BlockState state, final IModelData tileData) {
if (state.hasProperty(BusCableBlock.HAS_FACADE) && state.getValue(BusCableBlock.HAS_FACADE)) {
final TileEntity tileEntity = world.getBlockEntity(pos);
final BlockState facadeState;
if (tileEntity instanceof BusCableTileEntity && ((BusCableTileEntity) tileEntity).hasFacade()) {
facadeState = ((BusCableTileEntity) tileEntity).getFacade();
} else {
facadeState = Blocks.IRON_BLOCK.defaultBlockState();
}
final BlockModelShapes shapes = Minecraft.getInstance().getBlockRenderer().getBlockModelShaper();
final IBakedModel model = shapes.getBlockModel(facadeState);
final IModelData data = model.getModelData(world, pos, facadeState, tileData);
return new ModelDataMap.Builder()
.withInitial(BUS_CABLE_FACADE_PROPERTY, new BusCableFacade(facadeState, model, data))
.build();
}
Direction supportSide = null;
for (final Direction direction : Constants.DIRECTIONS) {
if (isNeighborInDirectionSolid(world, pos, direction)) {
@@ -108,7 +143,7 @@ public final class BusCableBakedModel implements IDynamicBakedModel {
if (supportSide != null) {
return new ModelDataMap.Builder()
.withInitial(BusCableSupportSide.BUS_CABLE_SUPPORT_PROPERTY, new BusCableSupportSide(supportSide))
.withInitial(BUS_CABLE_SUPPORT_PROPERTY, new BusCableSupportSide(supportSide))
.build();
}
@@ -141,17 +176,23 @@ public final class BusCableBakedModel implements IDynamicBakedModel {
///////////////////////////////////////////////////////////////////
public static final class BusCableSupportSide {
public static final ModelProperty<BusCableSupportSide> BUS_CABLE_SUPPORT_PROPERTY = new ModelProperty<>();
private final Direction value;
private static final class BusCableSupportSide {
public final Direction value;
private BusCableSupportSide(final Direction value) {
this.value = value;
}
}
public Direction get() {
return value;
private static final class BusCableFacade {
public final BlockState blockState;
public final IBakedModel model;
public final IModelData data;
public BusCableFacade(final BlockState blockState, final IBakedModel model, final IModelData data) {
this.blockState = blockState;
this.model = model;
this.data = data;
}
}
}

View File

@@ -45,6 +45,8 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static li.cil.oc2.common.util.TranslationUtils.text;
public final class BusCableBlock extends Block {
public enum ConnectionType implements IStringSerializable {
NONE,
@@ -69,6 +71,7 @@ public final class BusCableBlock extends Block {
///////////////////////////////////////////////////////////////////
public static final BooleanProperty HAS_CABLE = BooleanProperty.create("has_cable");
public static final BooleanProperty HAS_FACADE = BooleanProperty.create("has_facade");
public static final EnumProperty<ConnectionType> CONNECTION_NORTH = EnumProperty.create("connection_north", ConnectionType.class);
public static final EnumProperty<ConnectionType> CONNECTION_EAST = EnumProperty.create("connection_east", ConnectionType.class);
public static final EnumProperty<ConnectionType> CONNECTION_SOUTH = EnumProperty.create("connection_south", ConnectionType.class);
@@ -125,6 +128,7 @@ public final class BusCableBlock extends Block {
defaultState = defaultState.setValue(property, ConnectionType.NONE);
}
defaultState = defaultState.setValue(HAS_CABLE, true);
defaultState = defaultState.setValue(HAS_FACADE, false);
registerDefaultState(defaultState);
shapes = makeShapes();
@@ -132,7 +136,15 @@ public final class BusCableBlock extends Block {
///////////////////////////////////////////////////////////////////
public boolean addInterface(final World world, final BlockPos pos, final BlockState state, final Direction side) {
public static boolean addInterface(final World world, final BlockPos pos, final BlockState state, final Direction side) {
if (state.getBlock() != Blocks.BUS_CABLE.get()) {
return false;
}
if (state.getValue(HAS_FACADE)) {
return false;
}
final EnumProperty<BusCableBlock.ConnectionType> property = FACING_TO_CONNECTION_MAP.get(side);
if (state.getValue(property) != ConnectionType.NONE) {
return false;
@@ -145,7 +157,11 @@ public final class BusCableBlock extends Block {
return true;
}
public boolean addCable(final World world, final BlockPos pos, final BlockState state) {
public static boolean addCable(final World world, final BlockPos pos, final BlockState state) {
if (state.getBlock() != Blocks.BUS_CABLE.get()) {
return false;
}
if (state.getValue(HAS_CABLE)) {
return false;
}
@@ -157,6 +173,16 @@ public final class BusCableBlock extends Block {
return true;
}
public static void setHasFacade(final World world, final BlockPos pos, final BlockState state, final boolean value) {
if (state.getValue(HAS_FACADE) == value) {
return;
}
world.setBlock(pos, state.setValue(HAS_FACADE, value), BlockFlags.DEFAULT_AND_RERENDER);
WorldUtils.playSound(world, pos, state.getSoundType(), value ? SoundType::getPlaceSound : SoundType::getBreakSound);
}
@Override
public boolean hasTileEntity(final BlockState state) {
return true;
@@ -180,26 +206,48 @@ public final class BusCableBlock extends Block {
@SuppressWarnings("deprecation")
@Override
public ActionResultType use(final BlockState state, final World world, final BlockPos pos, final PlayerEntity player, final Hand hand, final BlockRayTraceResult hit) {
if (Wrenches.isWrench(player.getItemInHand(hand))) {
if (player.isShiftKeyDown()) {
// NB: leave wrenching logic up to wrench when the to-be-removed interface is the last
// part of this bus. This ensures we properly remove the block itself without having
// to duplicate the logic needed for that.
if (getPartCount(state) > 1)
if (tryRemovePlug(state, world, pos, player, hit) || tryRemoveCable(state, world, pos, player)) {
return ActionResultType.sidedSuccess(world.isClientSide);
}
} else {
final TileEntity tileEntity = world.getBlockEntity(pos);
if (tileEntity instanceof BusCableTileEntity) {
final BusCableTileEntity busCableTileEntity = (BusCableTileEntity) tileEntity;
final ItemStack heldItem = player.getItemInHand(hand);
if (heldItem.getItem() == Items.BUS_CABLE.get() ||
heldItem.getItem() == Items.BUS_INTERFACE.get()) {
return ActionResultType.PASS;
}
final Direction side = getHitSide(pos, hit);
if (getConnectionType(state, side) == ConnectionType.INTERFACE) {
openBusInterfaceScreen(busCableTileEntity, side);
final TileEntity tileEntity = world.getBlockEntity(pos);
if (!(tileEntity instanceof BusCableTileEntity)) {
return super.use(state, world, pos, player, hand, hit);
}
final BusCableTileEntity busCableTileEntity = (BusCableTileEntity) tileEntity;
if (Wrenches.isWrench(heldItem)) {
if (player.isShiftKeyDown()) {
if (busCableTileEntity.hasFacade()) {
busCableTileEntity.removeFacade();
return ActionResultType.sidedSuccess(world.isClientSide);
} else {
// NB: leave wrenching logic up to wrench when the to-be-removed interface is the last
// part of this bus. This ensures we properly remove the block itself without having
// to duplicate the logic needed for that.
if (getPartCount(state) > 1 && (tryRemovePlug(state, world, pos, player, hit) || tryRemoveCable(state, world, pos, player))) {
return ActionResultType.sidedSuccess(world.isClientSide);
}
}
} else {
final Direction side = getHitSide(pos, hit);
if (getConnectionType(state, side) == ConnectionType.INTERFACE) {
openBusInterfaceScreen(busCableTileEntity, side);
return ActionResultType.sidedSuccess(world.isClientSide);
}
}
} else if (getInterfaceCount(state) == 0 && !player.isShiftKeyDown()) {
final BlockState blockState = ItemStackUtils.getBlockState(heldItem);
if (blockState != null) {
if (!busCableTileEntity.trySetFacade(blockState) && !world.isClientSide()) {
player.displayClientMessage(text("message.{mod}.invalid_facade_block"), true);
}
// Always return success (even on failure) to avoid accidentally placing blocks.
return ActionResultType.sidedSuccess(world.isClientSide);
}
}
@@ -269,6 +317,10 @@ public final class BusCableBlock extends Block {
@SuppressWarnings("deprecation")
@Override
public VoxelShape getShape(final BlockState state, final IBlockReader world, final BlockPos pos, final ISelectionContext context) {
if (state.getValue(HAS_FACADE)) {
return VoxelShapes.block();
}
return shapes[getShapeIndex(state)];
}
@@ -279,6 +331,7 @@ public final class BusCableBlock extends Block {
super.createBlockStateDefinition(builder);
FACING_TO_CONNECTION_MAP.values().forEach(builder::add);
builder.add(HAS_CABLE);
builder.add(HAS_FACADE);
}
///////////////////////////////////////////////////////////////////

View File

@@ -1,7 +1,6 @@
package li.cil.oc2.common.item;
import li.cil.oc2.common.Config;
import li.cil.oc2.common.block.Blocks;
import li.cil.oc2.common.block.BusCableBlock;
import li.cil.oc2.common.util.TooltipUtils;
import li.cil.oc2.common.util.WorldUtils;
@@ -53,14 +52,12 @@ public final class BusCableItem extends ModBlockItem {
///////////////////////////////////////////////////////////////////
private ActionResultType tryAddToBlock(final ItemUseContext context) {
final BusCableBlock busCableBlock = Blocks.BUS_CABLE.get();
private static ActionResultType tryAddToBlock(final ItemUseContext context) {
final World world = context.getLevel();
final BlockPos pos = context.getClickedPos();
final BlockState state = world.getBlockState(pos);
if (state.getBlock() == busCableBlock && busCableBlock.addCable(world, pos, state)) {
if (BusCableBlock.addCable(world, pos, state)) {
final PlayerEntity player = context.getPlayer();
final ItemStack stack = context.getItemInHand();

View File

@@ -93,14 +93,12 @@ public final class BusInterfaceItem extends ModBlockItem {
///////////////////////////////////////////////////////////////////
private ActionResultType tryAddToBlock(final ItemUseContext context, final Direction side) {
final BusCableBlock busCableBlock = Blocks.BUS_CABLE.get();
private static ActionResultType tryAddToBlock(final ItemUseContext context, final Direction side) {
final World world = context.getLevel();
final BlockPos pos = context.getClickedPos();
final BlockState state = world.getBlockState(pos);
if (state.getBlock() == busCableBlock && busCableBlock.addInterface(world, pos, state, side)) {
if (BusCableBlock.addInterface(world, pos, state, side)) {
final PlayerEntity player = context.getPlayer();
final ItemStack stack = context.getItemInHand();

View File

@@ -10,9 +10,12 @@ import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.network.Network;
import li.cil.oc2.common.network.message.BusInterfaceNameMessage;
import li.cil.oc2.common.util.NBTTagIds;
import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.nbt.StringNBT;
import net.minecraft.util.Direction;
import net.minecraft.util.StringUtils;
@@ -26,11 +29,13 @@ import java.util.Objects;
public final class BusCableTileEntity extends AbstractTileEntity {
private static final String BUS_ELEMENT_TAG_NAME = "busElement";
private static final String INTERFACE_NAMES_TAG_NAME = "interfaceNames";
private static final String FACADE_TAG_NAME = "facade";
///////////////////////////////////////////////////////////////////
private final TileEntityDeviceBusElement busElement = new BusCableBusElement();
private final String[] interfaceNames = new String[Constants.BLOCK_FACE_COUNT];
private BlockState facade = Blocks.AIR.defaultBlockState();
///////////////////////////////////////////////////////////////////
@@ -59,6 +64,38 @@ public final class BusCableTileEntity extends AbstractTileEntity {
}
}
public boolean hasFacade() {
return getFacade() != Blocks.AIR.defaultBlockState();
}
public BlockState getFacade() {
return facade;
}
public boolean trySetFacade(final BlockState state) {
if (!trySetFacadeWithoutUpdate(state)) {
return false;
}
if (!getLevel().isClientSide()) {
setChanged();
}
BusCableBlock.setHasFacade(getLevel(), getBlockPos(), getBlockState(), true);
return true;
}
public void removeFacade() {
facade = Blocks.AIR.defaultBlockState();
if (!getLevel().isClientSide()) {
setChanged();
}
BusCableBlock.setHasFacade(getLevel(), getBlockPos(), getBlockState(), false);
}
public void handleNeighborChanged(final BlockPos pos) {
busElement.handleNeighborChanged(pos);
}
@@ -92,6 +129,7 @@ public final class BusCableTileEntity extends AbstractTileEntity {
final CompoundNBT tag = super.getUpdateTag();
tag.put(INTERFACE_NAMES_TAG_NAME, serializeInterfaceNames());
tag.put(FACADE_TAG_NAME, NBTUtil.writeBlockState(facade));
return tag;
}
@@ -99,6 +137,7 @@ public final class BusCableTileEntity extends AbstractTileEntity {
@Override
public void handleUpdateTag(final BlockState state, final CompoundNBT tag) {
deserializeInterfaceNames(tag.getList(INTERFACE_NAMES_TAG_NAME, NBTTagIds.TAG_STRING));
trySetFacade(NBTUtil.readBlockState(tag.getCompound(FACADE_TAG_NAME)));
}
@Override
@@ -106,6 +145,7 @@ public final class BusCableTileEntity extends AbstractTileEntity {
tag = super.save(tag);
tag.put(BUS_ELEMENT_TAG_NAME, busElement.save());
tag.put(INTERFACE_NAMES_TAG_NAME, serializeInterfaceNames());
tag.put(FACADE_TAG_NAME, NBTUtil.writeBlockState(facade));
return tag;
}
@@ -115,6 +155,7 @@ public final class BusCableTileEntity extends AbstractTileEntity {
super.load(state, tag);
busElement.load(tag.getList(BUS_ELEMENT_TAG_NAME, NBTTagIds.TAG_COMPOUND));
deserializeInterfaceNames(tag.getList(INTERFACE_NAMES_TAG_NAME, NBTTagIds.TAG_STRING));
trySetFacadeWithoutUpdate(NBTUtil.readBlockState(tag.getCompound(FACADE_TAG_NAME)));
}
///////////////////////////////////////////////////////////////////
@@ -135,6 +176,18 @@ public final class BusCableTileEntity extends AbstractTileEntity {
///////////////////////////////////////////////////////////////////
private boolean trySetFacadeWithoutUpdate(final BlockState state) {
if (state.getRenderShape() != BlockRenderType.MODEL ||
!state.isSolidRender(getLevel(), getBlockPos()) ||
state.getBlock().hasTileEntity(state)) {
return false;
}
facade = state;
return true;
}
private ListNBT serializeInterfaceNames() {
final ListNBT tag = new ListNBT();
for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) {

View File

@@ -1,5 +1,7 @@
package li.cil.oc2.common.util;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
@@ -23,6 +25,20 @@ public final class ItemStackUtils {
return NBTUtils.getOrCreateChildTag(stack.getOrCreateTag(), MOD_TAG_NAME);
}
@Nullable
public static BlockState getBlockState(final ItemStack stack) {
if (stack.isEmpty()) {
return null;
}
final Block block = Block.byItem(stack.getItem());
if (block == null || block == net.minecraft.block.Blocks.AIR) {
return null;
}
return block.defaultBlockState();
}
public static Optional<ItemEntity> spawnAsEntity(final World world, final BlockPos pos, final ItemStack stack) {
return spawnAsEntity(world, Vector3d.atCenterOf(pos), stack);
}

View File

@@ -63,12 +63,11 @@ public class ModBlockStateProvider extends BlockStateProvider {
final MultiPartBlockStateBuilder builder = getMultipartBuilder(Blocks.BUS_CABLE.get());
// NB: We use a custom model loader + baked model to replace the base part with straight parts and
// insert supports where appropriate.
// insert supports where appropriate, as well as for replacing it with a facade block model.
builder.part()
.modelFile(baseModel)
.addModel()
.condition(BusCableBlock.HAS_CABLE, true)
.end();
BusCableBlock.FACING_TO_CONNECTION_MAP.forEach((direction, connectionType) -> {
@@ -88,6 +87,7 @@ public class ModBlockStateProvider extends BlockStateProvider {
.rotationX(rotationX)
.addModel()
.condition(connectionType, BusCableBlock.ConnectionType.CABLE)
.condition(BusCableBlock.HAS_FACADE, false)
.end();
builder.part()
@@ -96,6 +96,7 @@ public class ModBlockStateProvider extends BlockStateProvider {
.rotationX(rotationX)
.addModel()
.condition(connectionType, BusCableBlock.ConnectionType.INTERFACE)
.condition(BusCableBlock.HAS_FACADE, false)
.end();
});

View File

@@ -1,16 +1,14 @@
{
"multipart": [
{
"when": {
"has_cable": "true"
},
"apply": {
"model": "oc2:block/cable_base"
}
},
{
"when": {
"connection_down": "cable"
"connection_down": "cable",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_link",
@@ -20,7 +18,8 @@
},
{
"when": {
"connection_down": "interface"
"connection_down": "interface",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_plug",
@@ -30,7 +29,8 @@
},
{
"when": {
"connection_up": "cable"
"connection_up": "cable",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_link",
@@ -40,7 +40,8 @@
},
{
"when": {
"connection_up": "interface"
"connection_up": "interface",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_plug",
@@ -50,7 +51,8 @@
},
{
"when": {
"connection_north": "cable"
"connection_north": "cable",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_link",
@@ -59,7 +61,8 @@
},
{
"when": {
"connection_north": "interface"
"connection_north": "interface",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_plug",
@@ -68,7 +71,8 @@
},
{
"when": {
"connection_south": "cable"
"connection_south": "cable",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_link"
@@ -76,7 +80,8 @@
},
{
"when": {
"connection_south": "interface"
"connection_south": "interface",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_plug"
@@ -84,7 +89,8 @@
},
{
"when": {
"connection_west": "cable"
"connection_west": "cable",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_link",
@@ -93,7 +99,8 @@
},
{
"when": {
"connection_west": "interface"
"connection_west": "interface",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_plug",
@@ -102,7 +109,8 @@
},
{
"when": {
"connection_east": "cable"
"connection_east": "cable",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_link",
@@ -111,7 +119,8 @@
},
{
"when": {
"connection_east": "interface"
"connection_east": "interface",
"has_facade": "false"
},
"apply": {
"model": "oc2:block/cable_plug",

View File

@@ -75,6 +75,7 @@
"message.oc2.connector.error.too_far": "Distance between connectors is too large.",
"message.oc2.connector.error.obstructed": "No clear line of sight between connectors.",
"message.oc2.import_file.file_too_large": "File is too large.",
"message.oc2.invalid_facade_block": "This block cannot be used as a facade.",
"tooltip.oc2.device_needs_reboot": "Requires reboot",
"tooltip.oc2.flash_memory_missing": "A flash memory containing a firmware is required to boot.",