Add ComputerCraft integration to Nixie Tubes

- Makes computer-controlled Nixie Tubes unable to be changed by external
  factors (but can still be used as a Display Link source)
- Add CC setText(text[, colour]) function
- Add CC setTextColour(colour) function
- Add CC setSignal(first, second) function taking 2 tables describing
  the appearance of the first and second tube as custom train signals
This commit is contained in:
Céleste Wouters 2024-08-23 18:54:21 +02:00
parent 522dbc7f37
commit 96a325c03b
Failed to generate hash of commit
13 changed files with 621 additions and 76 deletions

View file

@ -156,6 +156,10 @@ public class AllPartialModels {
SIGNAL_RED_CUBE = block("track_signal/red_cube"), SIGNAL_RED_GLOW = block("track_signal/red_glow"), SIGNAL_RED_CUBE = block("track_signal/red_cube"), SIGNAL_RED_GLOW = block("track_signal/red_glow"),
SIGNAL_RED = block("track_signal/red_tube"), SIGNAL_YELLOW_CUBE = block("track_signal/yellow_cube"), SIGNAL_RED = block("track_signal/red_tube"), SIGNAL_YELLOW_CUBE = block("track_signal/yellow_cube"),
SIGNAL_YELLOW_GLOW = block("track_signal/yellow_glow"), SIGNAL_YELLOW = block("track_signal/yellow_tube"), SIGNAL_YELLOW_GLOW = block("track_signal/yellow_glow"), SIGNAL_YELLOW = block("track_signal/yellow_tube"),
SIGNAL_COMPUTER_WHITE_CUBE = block("track_signal/computer_white_cube"),
SIGNAL_COMPUTER_WHITE_GLOW = block("track_signal/computer_white_glow"),
SIGNAL_COMPUTER_WHITE = block("track_signal/computer_white_tube"),
SIGNAL_COMPUTER_WHITE_BASE = block("track_signal/computer_white_tube_base"),
BLAZE_INERT = block("blaze_burner/blaze/inert"), BLAZE_SUPER_ACTIVE = block("blaze_burner/blaze/super_active"), BLAZE_INERT = block("blaze_burner/blaze/inert"), BLAZE_SUPER_ACTIVE = block("blaze_burner/blaze/super_active"),
BLAZE_GOGGLES = block("blaze_burner/goggles"), BLAZE_GOGGLES_SMALL = block("blaze_burner/goggles_small"), BLAZE_GOGGLES = block("blaze_burner/goggles"), BLAZE_GOGGLES_SMALL = block("blaze_burner/goggles_small"),

View file

@ -2,6 +2,7 @@ package com.simibubi.create.compat.computercraft.implementation;
import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour; import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.implementation.peripherals.DisplayLinkPeripheral; import com.simibubi.create.compat.computercraft.implementation.peripherals.DisplayLinkPeripheral;
import com.simibubi.create.compat.computercraft.implementation.peripherals.NixieTubePeripheral;
import com.simibubi.create.compat.computercraft.implementation.peripherals.SequencedGearshiftPeripheral; import com.simibubi.create.compat.computercraft.implementation.peripherals.SequencedGearshiftPeripheral;
import com.simibubi.create.compat.computercraft.implementation.peripherals.SpeedControllerPeripheral; import com.simibubi.create.compat.computercraft.implementation.peripherals.SpeedControllerPeripheral;
import com.simibubi.create.compat.computercraft.implementation.peripherals.SpeedGaugePeripheral; import com.simibubi.create.compat.computercraft.implementation.peripherals.SpeedGaugePeripheral;
@ -12,6 +13,7 @@ import com.simibubi.create.content.kinetics.gauge.StressGaugeBlockEntity;
import com.simibubi.create.content.kinetics.speedController.SpeedControllerBlockEntity; import com.simibubi.create.content.kinetics.speedController.SpeedControllerBlockEntity;
import com.simibubi.create.content.kinetics.transmission.sequencer.SequencedGearshiftBlockEntity; import com.simibubi.create.content.kinetics.transmission.sequencer.SequencedGearshiftBlockEntity;
import com.simibubi.create.content.redstone.displayLink.DisplayLinkBlockEntity; import com.simibubi.create.content.redstone.displayLink.DisplayLinkBlockEntity;
import com.simibubi.create.content.redstone.nixieTube.NixieTubeBlockEntity;
import com.simibubi.create.content.trains.station.StationBlockEntity; import com.simibubi.create.content.trains.station.StationBlockEntity;
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
@ -40,6 +42,8 @@ public class ComputerBehaviour extends AbstractComputerBehaviour {
return () -> new SpeedControllerPeripheral(scbe, scbe.targetSpeed); return () -> new SpeedControllerPeripheral(scbe, scbe.targetSpeed);
if (be instanceof DisplayLinkBlockEntity dlbe) if (be instanceof DisplayLinkBlockEntity dlbe)
return () -> new DisplayLinkPeripheral(dlbe); return () -> new DisplayLinkPeripheral(dlbe);
if (be instanceof NixieTubeBlockEntity ntbe)
return () -> new NixieTubePeripheral(ntbe);
if (be instanceof SequencedGearshiftBlockEntity sgbe) if (be instanceof SequencedGearshiftBlockEntity sgbe)
return () -> new SequencedGearshiftPeripheral(sgbe); return () -> new SequencedGearshiftPeripheral(sgbe);
if (be instanceof SpeedGaugeBlockEntity sgbe) if (be instanceof SpeedGaugeBlockEntity sgbe)

View file

@ -0,0 +1,169 @@
package com.simibubi.create.compat.computercraft.implementation.peripherals;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.content.redstone.nixieTube.NixieTubeBlock;
import com.simibubi.create.content.redstone.nixieTube.NixieTubeBlockEntity;
import com.simibubi.create.foundation.utility.Components;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.lua.LuaValues;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
public class NixieTubePeripheral extends SyncedPeripheral<NixieTubeBlockEntity> {
private static final String EMPTY_COMPONENT_JSON = Component.Serializer.toJson(Components.literal(""));
public NixieTubePeripheral(NixieTubeBlockEntity blockEntity) {
super(blockEntity);
}
@Override
protected void onFirstAttach() {
// When first attaching to a computer, clear out the entire nixie tube row.
super.onFirstAttach();
Level world = blockEntity.getLevel();
if (world == null)
return;
NixieTubeBlock.walkNixies(world, blockEntity.getBlockPos(), true,
(currentPos, rowPosition) -> {
if (world.getBlockEntity(currentPos) instanceof NixieTubeBlockEntity ntbe)
ntbe.displayCustomText(EMPTY_COMPONENT_JSON, rowPosition);
});
}
@Override
protected void onLastDetach() {
// When detaching from the last computer, reset the entire nixie tube row back to redstone display,
// except if it's still being controlled from some other tube. onLastDetach runs after the
// hasAttachedComputer flag is reset, so we can use walkNixies()'s computer control rejection for that.
super.onLastDetach();
Level world = blockEntity.getLevel();
if (world == null)
return;
// Check if the nixie tube block is still there; if it isn't then the nixie was removed/destroyed
// and the row reset is handled in NixieTubeBlock::remove.
BlockState state = world.getBlockState(blockEntity.getBlockPos());
if (!(state.getBlock() instanceof NixieTubeBlock))
return;
NixieTubeBlock.walkNixies(world, blockEntity.getBlockPos(), false,
(currentPos, rowPosition) -> {
if (world.getBlockEntity(currentPos) instanceof NixieTubeBlockEntity ntbe) {
NixieTubeBlock.updateDisplayedRedstoneValue(ntbe, true);
}
});
}
@LuaFunction(mainThread = true)
public void setText(IArguments arguments) throws LuaException {
Level world = blockEntity.getLevel();
if (world == null)
return;
blockEntity.computerSignal = null;
String tagElement = Component.Serializer.toJson(Components.literal(arguments.getString(0)));
@Nullable String colour = arguments.optString(1, null);
BlockState state = null;
DyeColor dye = null;
if (colour != null) {
state = blockEntity.getLevel().getBlockState(blockEntity.getBlockPos());
dye = LuaValues.checkEnum(1, DyeColor.class, colour.equals("grey") ? "gray" : colour);
}
changeTextNixie(tagElement, state, dye);
}
@LuaFunction(mainThread = true)
public void setTextColour(String colour) throws LuaException {
Level world = blockEntity.getLevel();
if (world == null)
return;
BlockState state = blockEntity.getLevel().getBlockState(blockEntity.getBlockPos());
DyeColor dye = LuaValues.checkEnum(1, DyeColor.class, colour.equals("grey") ? "gray" : colour);
changeTextNixie(null, state, dye);
}
@LuaFunction(mainThread = true)
public void setTextColor(String color) throws LuaException {
setTextColour(color);
}
private void changeTextNixie(@Nullable String tagElement, @Nullable BlockState state, @Nullable DyeColor dye) {
Level world = blockEntity.getLevel();
if (world == null)
return;
NixieTubeBlock.walkNixies(world, blockEntity.getBlockPos(), true, (currentPos, rowPosition) -> {
if (tagElement != null)
((NixieTubeBlock) blockEntity.getBlockState().getBlock()).withBlockEntityDo(
world, currentPos, be -> be.displayCustomText(tagElement, rowPosition));
if (state != null && dye != null)
world.setBlockAndUpdate(currentPos, NixieTubeBlock.withColor(state, dye));
});
}
@LuaFunction(mainThread = true)
public void setSignal(IArguments arguments) throws LuaException {
if (arguments.optTable(0).isPresent())
setSignal(signal().first, arguments.getTable(0));
if (arguments.optTable(1).isPresent())
setSignal(signal().second, arguments.getTable(1));
}
private void setSignal(NixieTubeBlockEntity.ComputerSignal.TubeDisplay display, @NotNull Map<?, ?> attrs)
throws LuaException {
if (attrs.containsKey("r"))
display.r = constrainByte("r", 0, 255, attrs.get("r"));
if (attrs.containsKey("g"))
display.g = constrainByte("g", 0, 255, attrs.get("g"));
if (attrs.containsKey("b"))
display.b = constrainByte("r", 0, 255, attrs.get("b"));
if (attrs.containsKey("glowWidth"))
display.glowWidth = constrainByte("glowWidth", 1, 4, attrs.get("glowWidth"));
if (attrs.containsKey("glowHeight"))
display.glowHeight = constrainByte("glowHeight", 1, 4, attrs.get("glowHeight"));
if (attrs.containsKey("blinkPeriod"))
display.blinkPeriod = constrainByte("blinkPeriod", 0, 255, attrs.get("blinkPeriod"));
if (attrs.containsKey("blinkOffTime"))
display.blinkOffTime = constrainByte("blinkOffTime", 0, 255, attrs.get("blinkOffTime"));
if (display.r == 0 && display.g == 0 && display.b == 0) {
display.blinkPeriod = 0;
display.blinkOffTime = 0;
} else if (display.blinkPeriod == 0) {
display.blinkPeriod = 1;
display.blinkOffTime = 0;
}
blockEntity.notifyUpdate();
}
private byte constrainByte(String name, int min, int max, Object rawValue) throws LuaException {
if (!(rawValue instanceof Number))
throw LuaValues.badField(name, "number", LuaValues.getType(rawValue));
int value = ((Number) rawValue).intValue();
if (value < min || value > max)
throw new LuaException("field " + name + " must be in range " + min + "-" + max);
return (byte) value;
}
private NixieTubeBlockEntity.ComputerSignal signal() {
if (blockEntity.computerSignal == null)
blockEntity.computerSignal = new NixieTubeBlockEntity.ComputerSignal();
return blockEntity.computerSignal;
}
@NotNull
@Override
public String getType() {
return "Create_NixieTube";
}
}

View file

@ -1,6 +1,9 @@
package com.simibubi.create.compat.computercraft.implementation.peripherals; package com.simibubi.create.compat.computercraft.implementation.peripherals;
import java.util.concurrent.atomic.AtomicInteger; import java.util.ArrayList;
import java.util.List;
import com.simibubi.create.compat.computercraft.events.ComputerEvent;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -17,7 +20,7 @@ import net.minecraftforge.network.PacketDistributor;
public abstract class SyncedPeripheral<T extends SmartBlockEntity> implements IPeripheral { public abstract class SyncedPeripheral<T extends SmartBlockEntity> implements IPeripheral {
protected final T blockEntity; protected final T blockEntity;
private final AtomicInteger computers = new AtomicInteger(); private final List<@NotNull IComputerAccess> computers = new ArrayList<>();
public SyncedPeripheral(T blockEntity) { public SyncedPeripheral(T blockEntity) {
this.blockEntity = blockEntity; this.blockEntity = blockEntity;
@ -25,21 +28,34 @@ public abstract class SyncedPeripheral<T extends SmartBlockEntity> implements IP
@Override @Override
public void attach(@NotNull IComputerAccess computer) { public void attach(@NotNull IComputerAccess computer) {
computers.incrementAndGet(); synchronized (computers) {
computers.add(computer);
if (computers.size() == 1)
onFirstAttach();
updateBlockEntity(); updateBlockEntity();
} }
}
protected void onFirstAttach() {}
@Override @Override
public void detach(@NotNull IComputerAccess computer) { public void detach(@NotNull IComputerAccess computer) {
computers.decrementAndGet(); synchronized (computers) {
computers.remove(computer);
updateBlockEntity(); updateBlockEntity();
if (computers.isEmpty())
onLastDetach();
}
} }
protected void onLastDetach() {}
private void updateBlockEntity() { private void updateBlockEntity() {
boolean hasAttachedComputer = computers.get() > 0; boolean hasAttachedComputer = !computers.isEmpty();
blockEntity.getBehaviour(ComputerBehaviour.TYPE).setHasAttachedComputer(hasAttachedComputer); blockEntity.getBehaviour(ComputerBehaviour.TYPE).setHasAttachedComputer(hasAttachedComputer);
AllPackets.getChannel().send(PacketDistributor.ALL.noArg(), new AttachedComputerPacket(blockEntity.getBlockPos(), hasAttachedComputer)); AllPackets.getChannel().send(PacketDistributor.ALL.noArg(),
new AttachedComputerPacket(blockEntity.getBlockPos(), hasAttachedComputer));
} }
@Override @Override

View file

@ -22,7 +22,7 @@ public class NixieTubeDisplayTarget extends SingleLineDisplayTarget {
@Override @Override
protected void acceptLine(MutableComponent text, DisplayLinkContext context) { protected void acceptLine(MutableComponent text, DisplayLinkContext context) {
String tagElement = Component.Serializer.toJson(text); String tagElement = Component.Serializer.toJson(text);
NixieTubeBlock.walkNixies(context.level(), context.getTargetPos(), (currentPos, rowPosition) -> { NixieTubeBlock.walkNixies(context.level(), context.getTargetPos(), false, (currentPos, rowPosition) -> {
BlockEntity blockEntity = context.level() BlockEntity blockEntity = context.level()
.getBlockEntity(currentPos); .getBlockEntity(currentPos);
if (blockEntity instanceof NixieTubeBlockEntity nixie) if (blockEntity instanceof NixieTubeBlockEntity nixie)
@ -33,7 +33,8 @@ public class NixieTubeDisplayTarget extends SingleLineDisplayTarget {
@Override @Override
protected int getWidth(DisplayLinkContext context) { protected int getWidth(DisplayLinkContext context) {
MutableInt count = new MutableInt(0); MutableInt count = new MutableInt(0);
NixieTubeBlock.walkNixies(context.level(), context.getTargetPos(), (currentPos, rowPosition) -> count.add(2)); NixieTubeBlock.walkNixies(context.level(), context.getTargetPos(), false,
(currentPos, rowPosition) -> count.add(2));
return count.intValue(); return count.intValue();
} }
@ -42,7 +43,7 @@ public class NixieTubeDisplayTarget extends SingleLineDisplayTarget {
public AABB getMultiblockBounds(LevelAccessor level, BlockPos pos) { public AABB getMultiblockBounds(LevelAccessor level, BlockPos pos) {
MutableObject<BlockPos> start = new MutableObject<>(null); MutableObject<BlockPos> start = new MutableObject<>(null);
MutableObject<BlockPos> end = new MutableObject<>(null); MutableObject<BlockPos> end = new MutableObject<>(null);
NixieTubeBlock.walkNixies(level, pos, (currentPos, rowPosition) -> { NixieTubeBlock.walkNixies(level, pos, true, (currentPos, rowPosition) -> {
end.setValue(currentPos); end.setValue(currentPos);
if (start.getValue() == null) if (start.getValue() == null)
start.setValue(currentPos); start.setValue(currentPos);

View file

@ -6,9 +6,14 @@ import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.AllBlockEntityTypes; import com.simibubi.create.AllBlockEntityTypes;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllShapes; import com.simibubi.create.AllShapes;
import com.simibubi.create.compat.Mods;
import com.simibubi.create.content.equipment.clipboard.ClipboardEntry; import com.simibubi.create.content.equipment.clipboard.ClipboardEntry;
import com.simibubi.create.content.equipment.wrench.IWrenchable; import com.simibubi.create.content.equipment.wrench.IWrenchable;
import com.simibubi.create.content.schematics.requirement.ISpecialBlockItemRequirement; import com.simibubi.create.content.schematics.requirement.ISpecialBlockItemRequirement;
@ -71,6 +76,11 @@ public class NixieTubeBlock extends DoubleFaceAttachedBlock
if (nixie == null) if (nixie == null)
return InteractionResult.PASS; return InteractionResult.PASS;
// Refuse interaction if nixie tube is in a computer-controlled row
if (isInComputerControlledRow(world, pos))
return InteractionResult.PASS;
if (heldItem.isEmpty()) { if (heldItem.isEmpty()) {
if (nixie.reactsToRedstone()) if (nixie.reactsToRedstone())
return InteractionResult.PASS; return InteractionResult.PASS;
@ -101,7 +111,8 @@ public class NixieTubeBlock extends DoubleFaceAttachedBlock
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS;
String tagUsed = tagElement; String tagUsed = tagElement;
walkNixies(world, pos, (currentPos, rowPosition) -> { // Skip computer check in this walk since it was already performed at the start.
walkNixies(world, pos, true, (currentPos, rowPosition) -> {
if (display) if (display)
withBlockEntityDo(world, currentPos, be -> be.displayCustomText(tagUsed, rowPosition)); withBlockEntityDo(world, currentPos, be -> be.displayCustomText(tagUsed, rowPosition));
if (dye != null) if (dye != null)
@ -111,33 +122,89 @@ public class NixieTubeBlock extends DoubleFaceAttachedBlock
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS;
} }
public static void walkNixies(LevelAccessor world, BlockPos start, BiConsumer<BlockPos, Integer> callback) { public static Direction getLeftNixieDirection(@NotNull BlockState state) {
BlockState state = world.getBlockState(start); Direction left = state.getValue(FACING).getOpposite();
if (!(state.getBlock() instanceof NixieTubeBlock))
return;
BlockPos currentPos = start;
Direction left = state.getValue(FACING)
.getOpposite();
if (state.getValue(FACE) == DoubleAttachFace.WALL) if (state.getValue(FACE) == DoubleAttachFace.WALL)
left = Direction.UP; left = Direction.UP;
if (state.getValue(FACE) == DoubleAttachFace.WALL_REVERSED) if (state.getValue(FACE) == DoubleAttachFace.WALL_REVERSED)
left = Direction.DOWN; left = Direction.DOWN;
return left;
}
public static Direction getRightNixieDirection(@NotNull BlockState state) {
return getLeftNixieDirection(state).getOpposite();
}
public static boolean isInComputerControlledRow(@NotNull LevelAccessor world, @NotNull BlockPos pos) {
return Mods.COMPUTERCRAFT.isLoaded() && !walkNixies(world, pos, false, null);
}
/**
* Walk down a nixie tube row and execute a callback on each tube in said row.
* @param world The world the tubes are in.
* @param start Start position for the walk.
* @param allowComputerControlled Allow or disallow running callbacks if the row is computer-controlled.
* @param callback Callback to run for each tube.
* @return True if the row was walked, false if the walk was aborted because it is computer-controlled.
*/
public static boolean walkNixies(@NotNull LevelAccessor world, @NotNull BlockPos start,
boolean allowComputerControlled,
@Nullable BiConsumer<BlockPos, Integer> callback) {
BlockState state = world.getBlockState(start);
if (!(state.getBlock() instanceof NixieTubeBlock))
return false;
// If ComputerCraft is not installed, ignore allowComputerControlled since
// nixies can't be computer-controlled
if (!Mods.COMPUTERCRAFT.isLoaded())
allowComputerControlled = true;
BlockPos currentPos = start;
Direction left = getLeftNixieDirection(state);
Direction right = left.getOpposite(); Direction right = left.getOpposite();
while (true) { while (true) {
BlockPos nextPos = currentPos.relative(left); BlockPos nextPos = currentPos.relative(left);
if (!areNixieBlocksEqual(world.getBlockState(nextPos), state)) if (!areNixieBlocksEqual(world.getBlockState(nextPos), state))
break; break;
// If computer-controlled nixie walking is disallowed, presence of any (same-color)
// controlled nixies aborts the entire nixie walk.
if (!allowComputerControlled && world.getBlockEntity(nextPos) instanceof NixieTubeBlockEntity ntbe &&
ntbe.computerBehaviour.hasAttachedComputer()) {
return false;
}
currentPos = nextPos; currentPos = nextPos;
} }
// As explained above, a controlled nixie in the row aborts the walk if they are disallowed,
// and that includes those down the chain too.
if (!allowComputerControlled) {
// Check the start block itself
if (world.getBlockEntity(start) instanceof NixieTubeBlockEntity ntbe &&
ntbe.computerBehaviour.hasAttachedComputer()) {
return false;
}
BlockPos leftmostPos = currentPos;
// No need to iterate over the nixies to the left again
currentPos = start;
while (true) {
BlockPos nextPos = currentPos.relative(right);
if (!areNixieBlocksEqual(world.getBlockState(nextPos), state))
break;
if (world.getBlockEntity(nextPos) instanceof NixieTubeBlockEntity ntbe &&
ntbe.computerBehaviour.hasAttachedComputer()) {
return false;
}
currentPos = nextPos;
}
currentPos = leftmostPos;
}
int index = 0; int index = 0;
while (true) { while (true) {
final int rowPosition = index; final int rowPosition = index;
if (callback != null)
callback.accept(currentPos, rowPosition); callback.accept(currentPos, rowPosition);
BlockPos nextPos = currentPos.relative(right); BlockPos nextPos = currentPos.relative(right);
if (!areNixieBlocksEqual(world.getBlockState(nextPos), state)) if (!areNixieBlocksEqual(world.getBlockState(nextPos), state))
@ -145,6 +212,8 @@ public class NixieTubeBlock extends DoubleFaceAttachedBlock
currentPos = nextPos; currentPos = nextPos;
index++; index++;
} }
return true;
} }
@Override @Override
@ -153,10 +222,41 @@ public class NixieTubeBlock extends DoubleFaceAttachedBlock
} }
@Override @Override
public void onRemove(BlockState p_196243_1_, Level p_196243_2_, BlockPos p_196243_3_, BlockState p_196243_4_, public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean isMoving) {
boolean p_196243_5_) { if (newState.getBlock() instanceof NixieTubeBlock)
if (!(p_196243_4_.getBlock() instanceof NixieTubeBlock)) return;
p_196243_2_.removeBlockEntity(p_196243_3_); world.removeBlockEntity(pos);
if (Mods.COMPUTERCRAFT.isLoaded()) {
// A computer-controlled nixie tube row may have been broken in the middle.
Direction left = getLeftNixieDirection(state);
BlockPos leftPos = pos.relative(left);
if (areNixieBlocksEqual(world.getBlockState(leftPos), state)) {
boolean leftRowComputerControlled = isInComputerControlledRow(world, leftPos);
walkNixies(world, leftPos, true, leftRowComputerControlled ?
(currentPos, rowPosition) -> {
if (world.getBlockEntity(currentPos) instanceof NixieTubeBlockEntity ntbe)
ntbe.displayCustomText("{\"text\":\"\"}", rowPosition);
} :
(currentPos, rowPosition) -> {
if (world.getBlockEntity(currentPos) instanceof NixieTubeBlockEntity ntbe)
NixieTubeBlock.updateDisplayedRedstoneValue(ntbe, true);
});
}
Direction right = left.getOpposite();
BlockPos rightPos = pos.relative(right);
if (areNixieBlocksEqual(world.getBlockState(rightPos), state)) {
boolean rightRowComputerControlled = isInComputerControlledRow(world, rightPos);
walkNixies(world, rightPos, true, rightRowComputerControlled ?
(currentPos, rowPosition) -> {
if (world.getBlockEntity(currentPos) instanceof NixieTubeBlockEntity ntbe)
ntbe.displayCustomText("{\"text\":\"\"}", rowPosition);
} :
(currentPos, rowPosition) -> {
if (world.getBlockEntity(currentPos) instanceof NixieTubeBlockEntity ntbe)
NixieTubeBlock.updateDisplayedRedstoneValue(ntbe, true);
});
}
}
} }
@Override @Override
@ -237,18 +337,30 @@ public class NixieTubeBlock extends DoubleFaceAttachedBlock
@Override @Override
public void onPlace(BlockState state, Level worldIn, BlockPos pos, BlockState oldState, boolean isMoving) { public void onPlace(BlockState state, Level worldIn, BlockPos pos, BlockState oldState, boolean isMoving) {
if (state.getBlock() == oldState.getBlock() || isMoving) if (state.getBlock() == oldState.getBlock() || isMoving || oldState.getBlock() instanceof NixieTubeBlock)
return; return;
if (Mods.COMPUTERCRAFT.isLoaded() && isInComputerControlledRow(worldIn, pos)) {
// The nixie tube has been placed in a computer-controlled row.
walkNixies(worldIn, pos, true, (currentPos, rowPosition) -> {
if (worldIn.getBlockEntity(currentPos) instanceof NixieTubeBlockEntity ntbe)
ntbe.displayCustomText("{\"text\":\"\"}", rowPosition);
});
return;
}
updateDisplayedRedstoneValue(state, worldIn, pos); updateDisplayedRedstoneValue(state, worldIn, pos);
} }
public static void updateDisplayedRedstoneValue(NixieTubeBlockEntity be, boolean force) {
if (be.getLevel() == null || be.getLevel().isClientSide)
return;
if (be.reactsToRedstone() || force)
be.updateRedstoneStrength(getPower(be.getLevel(), be.getBlockPos()));
}
private void updateDisplayedRedstoneValue(BlockState state, Level worldIn, BlockPos pos) { private void updateDisplayedRedstoneValue(BlockState state, Level worldIn, BlockPos pos) {
if (worldIn.isClientSide) if (worldIn.isClientSide)
return; return;
withBlockEntityDo(worldIn, pos, be -> { withBlockEntityDo(worldIn, pos, be -> NixieTubeBlock.updateDisplayedRedstoneValue(be, false));
if (be.reactsToRedstone())
be.updateRedstoneStrength(getPower(worldIn, pos));
});
} }
static boolean isValidBlock(BlockGetter world, BlockPos pos, boolean above) { static boolean isValidBlock(BlockGetter world, BlockPos pos, boolean above) {
@ -257,7 +369,7 @@ public class NixieTubeBlock extends DoubleFaceAttachedBlock
.isEmpty(); .isEmpty();
} }
private int getPower(Level worldIn, BlockPos pos) { private static int getPower(Level worldIn, BlockPos pos) {
int power = 0; int power = 0;
for (Direction direction : Iterate.directions) for (Direction direction : Iterate.directions)
power = Math.max(worldIn.getSignal(pos.relative(direction), direction), power); power = Math.max(worldIn.getSignal(pos.relative(direction), direction), power);

View file

@ -4,6 +4,14 @@ import java.lang.ref.WeakReference;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import com.simibubi.create.Create;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.ComputerCraftProxy;
import com.simibubi.create.content.redstone.displayLink.DisplayLinkBlock; import com.simibubi.create.content.redstone.displayLink.DisplayLinkBlock;
import com.simibubi.create.content.trains.signal.SignalBlockEntity; import com.simibubi.create.content.trains.signal.SignalBlockEntity;
import com.simibubi.create.content.trains.signal.SignalBlockEntity.SignalState; import com.simibubi.create.content.trains.signal.SignalBlockEntity.SignalState;
@ -13,6 +21,8 @@ import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.DynamicComponent; import com.simibubi.create.foundation.utility.DynamicComponent;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@ -23,15 +33,62 @@ import net.minecraft.world.level.block.state.BlockState;
public class NixieTubeBlockEntity extends SmartBlockEntity { public class NixieTubeBlockEntity extends SmartBlockEntity {
public static final class ComputerSignal {
public static final class TubeDisplay {
public static final int ENCODED_SIZE = 7;
public byte r = 63, g = 63, b = 63;
public byte blinkPeriod = 0, blinkOffTime = 0;
public byte glowWidth = 1, glowHeight = 1;
public void decode(byte[] data, int offset) {
r = data[offset];
g = data[offset + 1];
b = data[offset + 2];
blinkPeriod = data[offset + 3];
blinkOffTime = data[offset + 4];
glowWidth = data[offset + 5];
glowHeight = data[offset + 6];
}
public void encode(byte[] data, int offset) {
data[offset] = r;
data[offset + 1] = g;
data[offset + 2] = b;
data[offset + 3] = blinkPeriod;
data[offset + 4] = blinkOffTime;
data[offset + 5] = glowWidth;
data[offset + 6] = glowHeight;
}
}
public @NotNull TubeDisplay first = new TubeDisplay();
public @NotNull TubeDisplay second = new TubeDisplay();
public void decode(byte[] encoded) {
first.decode(encoded, 0);
second.decode(encoded, TubeDisplay.ENCODED_SIZE);
}
public byte[] encode() {
byte[] encoded = new byte[TubeDisplay.ENCODED_SIZE * 2];
first.encode(encoded, 0);
second.encode(encoded, TubeDisplay.ENCODED_SIZE);
return encoded;
}
}
private static final Couple<String> EMPTY = Couple.create("", ""); private static final Couple<String> EMPTY = Couple.create("", "");
private int redstoneStrength; private int redstoneStrength;
private Optional<DynamicComponent> customText; private Optional<DynamicComponent> customText;
private int nixieIndex; private int nixieIndex;
private Couple<String> displayedStrings; private Couple<String> displayedStrings;
public AbstractComputerBehaviour computerBehaviour;
private WeakReference<SignalBlockEntity> cachedSignalTE; private WeakReference<SignalBlockEntity> cachedSignalTE;
public SignalState signalState; public @Nullable SignalState signalState;
public @Nullable ComputerSignal computerSignal;
public NixieTubeBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) { public NixieTubeBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state); super(type, pos, state);
@ -47,6 +104,13 @@ public class NixieTubeBlockEntity extends SmartBlockEntity {
return; return;
signalState = null; signalState = null;
if (computerBehaviour.hasAttachedComputer()) {
if (level.isClientSide && cachedSignalTE.get() != null) {
cachedSignalTE = new WeakReference<>(null);
}
return;
}
SignalBlockEntity signalBlockEntity = cachedSignalTE.get(); SignalBlockEntity signalBlockEntity = cachedSignalTE.get();
if (signalBlockEntity == null || signalBlockEntity.isRemoved()) { if (signalBlockEntity == null || signalBlockEntity.isRemoved()) {
@ -71,7 +135,7 @@ public class NixieTubeBlockEntity extends SmartBlockEntity {
// //
public boolean reactsToRedstone() { public boolean reactsToRedstone() {
return customText.isEmpty(); return !computerBehaviour.hasAttachedComputer() && customText.isEmpty();
} }
public Couple<String> getDisplayedStrings() { public Couple<String> getDisplayedStrings() {
@ -108,7 +172,7 @@ public class NixieTubeBlockEntity extends SmartBlockEntity {
} }
public void updateDisplayedStrings() { public void updateDisplayedStrings() {
if (signalState != null) if (signalState != null || computerSignal != null)
return; return;
customText.map(DynamicComponent::resolve) customText.map(DynamicComponent::resolve)
.ifPresentOrElse( .ifPresentOrElse(
@ -144,13 +208,26 @@ public class NixieTubeBlockEntity extends SmartBlockEntity {
customText = Optional.empty(); customText = Optional.empty();
nixieIndex = 0; nixieIndex = 0;
} }
} else {
customText = Optional.empty();
nixieIndex = 0;
} }
if (customText.isEmpty()) if (customText.isEmpty())
redstoneStrength = nbt.getInt("RedstoneStrength"); redstoneStrength = nbt.getInt("RedstoneStrength");
if (clientPacket) if (clientPacket) {
if (nbt.contains("ComputerSignal")) {
byte[] encodedComputerSignal = nbt.getByteArray("ComputerSignal");
if (computerSignal == null)
computerSignal = new ComputerSignal();
computerSignal.decode(encodedComputerSignal);
} else {
computerSignal = null;
}
updateDisplayedStrings(); updateDisplayedStrings();
} }
}
@Override @Override
protected void write(CompoundTag nbt, boolean clientPacket) { protected void write(CompoundTag nbt, boolean clientPacket) {
@ -162,6 +239,8 @@ public class NixieTubeBlockEntity extends SmartBlockEntity {
.write(nbt); .write(nbt);
} else } else
nbt.putInt("RedstoneStrength", redstoneStrength); nbt.putInt("RedstoneStrength", redstoneStrength);
if (clientPacket && computerSignal != null)
nbt.putByteArray("ComputerSignal", computerSignal.encode());
} }
private String charOrEmpty(String string, int index) { private String charOrEmpty(String string, int index) {
@ -169,6 +248,21 @@ public class NixieTubeBlockEntity extends SmartBlockEntity {
} }
@Override @Override
public void addBehaviours(List<BlockEntityBehaviour> behaviours) {} public void addBehaviours(List<BlockEntityBehaviour> behaviours) {
behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this));
}
@Override
public <T> @NotNull LazyOptional<T> getCapability(@NotNull Capability<T> cap, Direction side) {
if (computerBehaviour.isPeripheralCap(cap))
return computerBehaviour.getPeripheralCapability();
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
computerBehaviour.removePeripheral();
}
} }

View file

@ -32,6 +32,7 @@ import net.minecraft.world.phys.Vec3;
public class NixieTubeRenderer extends SafeBlockEntityRenderer<NixieTubeBlockEntity> { public class NixieTubeRenderer extends SafeBlockEntityRenderer<NixieTubeBlockEntity> {
private static final int GLOW_VIEW_DISTANCE = 96;
private static Random r = new Random(); private static Random r = new Random();
public NixieTubeRenderer(BlockEntityRendererProvider.Context context) {} public NixieTubeRenderer(BlockEntityRendererProvider.Context context) {}
@ -52,7 +53,7 @@ public class NixieTubeRenderer extends SafeBlockEntityRenderer<NixieTubeBlockEnt
.rotateZ(xRot) .rotateZ(xRot)
.unCentre(); .unCentre();
if (be.signalState != null) { if (be.signalState != null || be.computerSignal != null) {
renderAsSignal(be, partialTicks, ms, buffer, light, overlay); renderAsSignal(be, partialTicks, ms, buffer, light, overlay);
ms.popPose(); ms.popPose();
return; return;
@ -144,11 +145,11 @@ public class NixieTubeRenderer extends SafeBlockEntityRenderer<NixieTubeBlockEnt
ms.pushPose(); ms.pushPose();
ms.translate(1 / 2f, 7.5f / 16f, 1 / 2f); ms.translate(1 / 2f, 7.5f / 16f, 1 / 2f);
float renderTime = AnimationTickHolder.getRenderTime(be.getLevel()); float renderTime = AnimationTickHolder.getRenderTime(be.getLevel());
for (boolean first : Iterate.trueAndFalse) {
Vec3 lampVec = Vec3.atCenterOf(be.getBlockPos()); Vec3 lampVec = Vec3.atCenterOf(be.getBlockPos());
Vec3 diff = lampVec.subtract(observerVec); Vec3 diff = lampVec.subtract(observerVec);
if (be.signalState != null) {
for (boolean first : Iterate.trueAndFalse) {
if (first && !be.signalState.isRedLight(renderTime)) if (first && !be.signalState.isRedLight(renderTime))
continue; continue;
if (!first && !be.signalState.isGreenLight(renderTime) && !be.signalState.isYellowLight(renderTime)) if (!first && !be.signalState.isGreenLight(renderTime) && !be.signalState.isYellowLight(renderTime))
@ -160,7 +161,7 @@ public class NixieTubeRenderer extends SafeBlockEntityRenderer<NixieTubeBlockEnt
ms.pushPose(); ms.pushPose();
ms.translate(flip ? 4 / 16f : -4 / 16f, 0, 0); ms.translate(flip ? 4 / 16f : -4 / 16f, 0, 0);
if (diff.lengthSqr() < 96 * 96) { if (diff.lengthSqr() < GLOW_VIEW_DISTANCE * GLOW_VIEW_DISTANCE) {
boolean vert = first ^ facing.getAxis() boolean vert = first ^ facing.getAxis()
.isHorizontal(); .isHorizontal();
float longSide = yellow ? 1 : 4; float longSide = yellow ? 1 : 4;
@ -193,6 +194,62 @@ public class NixieTubeRenderer extends SafeBlockEntityRenderer<NixieTubeBlockEnt
ms.popPose(); ms.popPose();
} }
} else if (be.computerSignal != null) {
for (boolean first : Iterate.trueAndFalse) {
NixieTubeBlockEntity.ComputerSignal.TubeDisplay tubeDisplay = first ?
be.computerSignal.first : be.computerSignal.second;
if (tubeDisplay.blinkPeriod == 0 || tubeDisplay.blinkPeriod > 1 && renderTime % tubeDisplay.blinkPeriod < tubeDisplay.blinkOffTime)
continue;
boolean flip = first == invertTubes;
ms.pushPose();
ms.translate(flip ? 4 / 16f : -4 / 16f, 0, 0);
if (diff.lengthSqr() < GLOW_VIEW_DISTANCE * GLOW_VIEW_DISTANCE) {
boolean horiz = facing.getAxis().isHorizontal();
float width = horiz ? tubeDisplay.glowWidth : tubeDisplay.glowHeight;
float height = horiz ? tubeDisplay.glowHeight : tubeDisplay.glowWidth;
CachedBufferer.partial(AllPartialModels.SIGNAL_COMPUTER_WHITE_CUBE, blockState)
.light(0xf000f0)
.disableDiffuse()
.scale(width, height, 1)
.renderInto(ms, buffer.getBuffer(RenderType.translucent()));
CachedBufferer
.partial(AllPartialModels.SIGNAL_COMPUTER_WHITE_GLOW, blockState)
.light(0xf000f0)
.color(
Math.min(((tubeDisplay.r & 0xFF) * 6 + 256) >> 3, 255),
Math.min(((tubeDisplay.g & 0xFF) * 6 + 256) >> 3, 255),
Math.min(((tubeDisplay.b & 0xFF) * 6 + 256) >> 3, 255),
255)
.disableDiffuse()
.scale(width + 1.125f, height + 1.125f, 2)
.renderInto(ms, buffer.getBuffer(RenderTypes.getAdditive()));
}
CachedBufferer
.partial(AllPartialModels.SIGNAL_COMPUTER_WHITE_BASE, blockState)
.light(0xF000F0)
.color(12, 12, 12, 255)
.disableDiffuse()
.scale(1 + 1.25f / 16f)
.renderInto(ms, buffer.getBuffer(RenderTypes.getAdditive()));
CachedBufferer
.partial(AllPartialModels.SIGNAL_COMPUTER_WHITE, blockState)
.light(0xF000F0)
.color(tubeDisplay.r, tubeDisplay.g, tubeDisplay.b, 255)
.disableDiffuse()
.scale(1 + 1 / 16f)
.renderInto(ms, buffer.getBuffer(RenderTypes.getAdditive()));
ms.popPose();
}
}
ms.popPose(); ms.popPose();
} }

View file

@ -0,0 +1,20 @@
{
"textures": {
"0": "create:block/signal_glow_3",
"particle": "create:block/signal_glow_3"
},
"elements": [
{
"from": [-0.5, -0.5, -0.5],
"to": [0.5, 0.5, 0.5],
"faces": {
"north": {"uv": [1, 1, 2, 2], "texture": "#0"},
"east": {"uv": [1, 1, 2, 2], "texture": "#0"},
"south": {"uv": [1, 1, 2, 2], "texture": "#0"},
"west": {"uv": [1, 1, 2, 2], "texture": "#0"},
"up": {"uv": [1, 1, 2, 2], "texture": "#0"},
"down": {"uv": [1, 1, 2, 2], "texture": "#0"}
}
}
]
}

View file

@ -0,0 +1,20 @@
{
"textures": {
"0": "create:block/signal_glow_3",
"particle": "create:block/signal_glow_3"
},
"elements": [
{
"from": [-0.5, -0.5, -0.5],
"to": [0.5, 0.5, 0.5],
"faces": {
"north": {"uv": [1, 0, 2, 1], "texture": "#0"},
"east": {"uv": [1, 0, 2, 1], "texture": "#0"},
"south": {"uv": [1, 0, 2, 1], "texture": "#0"},
"west": {"uv": [1, 0, 2, 1], "texture": "#0"},
"up": {"uv": [1, 0, 2, 1], "texture": "#0"},
"down": {"uv": [1, 0, 2, 1], "texture": "#0"}
}
}
]
}

View file

@ -0,0 +1,24 @@
{
"parent": "block/block",
"ambientocclusion": false,
"textures": {
"1": "create:block/signal_glow_3",
"particle": "create:block/signal_glow_3"
},
"elements": [
{
"name": "tube3",
"from": [-3, -4.5, -3],
"to": [3, 4.5, 3],
"rotation": {"angle": 0, "axis": "z", "origin": [8, 8, 8]},
"faces": {
"north": {"uv": [10, 7, 16, 16], "texture": "#1"},
"east": {"uv": [10, 7, 16, 16], "texture": "#1"},
"south": {"uv": [10, 7, 16, 16], "texture": "#1"},
"west": {"uv": [10, 7, 16, 16], "texture": "#1"},
"up": {"uv": [10, 0, 16, 6], "rotation": 90, "texture": "#1"},
"down": {"uv": [10, 0, 16, 6], "rotation": 90, "texture": "#1"}
}
}
]
}

View file

@ -0,0 +1,24 @@
{
"parent": "block/block",
"ambientocclusion": false,
"textures": {
"1": "create:block/signal_glow_3",
"particle": "create:block/signal_glow_3"
},
"elements": [
{
"name": "tube3",
"from": [-3, -4.5, -3],
"to": [3, 4.5, 3],
"rotation": {"angle": 0, "axis": "z", "origin": [8, 8, 8]},
"faces": {
"north": {"uv": [3, 7, 9, 16], "texture": "#1"},
"east": {"uv": [3, 7, 9, 16], "texture": "#1"},
"south": {"uv": [3, 7, 9, 16], "texture": "#1"},
"west": {"uv": [3, 7, 9, 16], "texture": "#1"},
"up": {"uv": [3, 0, 9, 6], "rotation": 90, "texture": "#1"},
"down": {"uv": [3, 0, 9, 6], "rotation": 90, "texture": "#1"}
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B