CC-Tweaked/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemFullBlockEntity.java

306 lines
11 KiB
Java

// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.peripheral.modem.wired;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.util.*;
import static dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlock.MODEM_ON;
import static dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlock.PERIPHERAL_ON;
public class WiredModemFullBlockEntity extends BlockEntity {
private static final String NBT_PERIPHERAL_ENABLED = "PeripheralAccess";
private static final class FullElement extends WiredModemElement {
private final WiredModemFullBlockEntity entity;
private FullElement(WiredModemFullBlockEntity entity) {
this.entity = entity;
}
@Override
protected void attachPeripheral(String name, IPeripheral peripheral) {
for (var i = 0; i < 6; i++) {
var modem = entity.modems[i];
if (modem != null) modem.attachPeripheral(name, peripheral);
}
}
@Override
protected void detachPeripheral(String name) {
for (var i = 0; i < 6; i++) {
var modem = entity.modems[i];
if (modem != null) modem.detachPeripheral(name);
}
}
@Override
public Level getLevel() {
return entity.getLevel();
}
@Override
public Vec3 getPosition() {
return Vec3.atCenterOf(entity.getBlockPos());
}
}
private final WiredModemPeripheral[] modems = new WiredModemPeripheral[6];
private boolean peripheralAccessAllowed = false;
private final WiredModemLocalPeripheral[] peripherals = new WiredModemLocalPeripheral[6];
private boolean connectionsFormed = false;
private boolean connectionsChanged = false;
private final TickScheduler.Token tickToken = new TickScheduler.Token(this);
private final ModemState modemState = new ModemState(() -> TickScheduler.schedule(tickToken));
private final WiredModemElement element = new FullElement(this);
private final WiredNode node = element.getNode();
private final ComponentAccess<WiredElement> connectedElements = PlatformHelper.get().createWiredElementAccess(this, x -> scheduleConnectionsChanged());
private int invalidSides = 0;
public WiredModemFullBlockEntity(BlockEntityType<WiredModemFullBlockEntity> type, BlockPos pos, BlockState state) {
super(type, pos, state);
var peripheralAccess = PlatformHelper.get().createPeripheralAccess(this, this::queueRefreshPeripheral);
for (var i = 0; i < peripherals.length; i++) {
peripherals[i] = new WiredModemLocalPeripheral(peripheralAccess);
}
}
@Override
public void setRemoved() {
super.setRemoved();
if (level == null || !level.isClientSide) {
node.remove();
connectionsFormed = false;
}
}
void neighborChanged(BlockPos neighbour) {
if (!level.isClientSide && peripheralAccessAllowed) {
for (var facing : DirectionUtil.FACINGS) {
if (getBlockPos().relative(facing).equals(neighbour)) queueRefreshPeripheral(facing);
}
}
}
private void queueRefreshPeripheral(Direction facing) {
if (invalidSides == 0) TickScheduler.schedule(tickToken);
invalidSides |= 1 << facing.ordinal();
}
private void refreshPeripheral(Direction facing) {
invalidSides &= ~(1 << facing.ordinal());
var peripheral = peripherals[facing.ordinal()];
if (level != null && !isRemoved() && peripheral.attach(level, getBlockPos(), facing)) {
updateConnectedPeripherals();
}
}
public InteractionResult use(Player player) {
if (player.isCrouching() || !player.mayBuild()) return InteractionResult.PASS;
if (getLevel().isClientSide) return InteractionResult.SUCCESS;
// On server, we interacted if a peripheral was found
var oldPeriphNames = getConnectedPeripheralNames();
togglePeripheralAccess();
var periphNames = getConnectedPeripheralNames();
if (!Objects.equals(periphNames, oldPeriphNames)) {
sendPeripheralChanges(player, "chat.computercraft.wired_modem.peripheral_disconnected", oldPeriphNames);
sendPeripheralChanges(player, "chat.computercraft.wired_modem.peripheral_connected", periphNames);
}
return InteractionResult.CONSUME;
}
private static void sendPeripheralChanges(Player player, String kind, Collection<String> peripherals) {
if (peripherals.isEmpty()) return;
List<String> names = new ArrayList<>(peripherals);
names.sort(Comparator.naturalOrder());
var base = Component.literal("");
for (var i = 0; i < names.size(); i++) {
if (i > 0) base.append(", ");
base.append(ChatHelpers.copy(names.get(i)));
}
player.displayClientMessage(Component.translatable(kind, base), false);
}
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
peripheralAccessAllowed = nbt.getBoolean(NBT_PERIPHERAL_ENABLED);
for (var i = 0; i < peripherals.length; i++) peripherals[i].read(nbt, Integer.toString(i));
}
@Override
public void saveAdditional(CompoundTag nbt) {
nbt.putBoolean(NBT_PERIPHERAL_ENABLED, peripheralAccessAllowed);
for (var i = 0; i < peripherals.length; i++) peripherals[i].write(nbt, Integer.toString(i));
super.saveAdditional(nbt);
}
private void updateBlockState() {
var state = getBlockState();
boolean modemOn = modemState.isOpen(), peripheralOn = peripheralAccessAllowed;
if (state.getValue(MODEM_ON) == modemOn && state.getValue(PERIPHERAL_ON) == peripheralOn) return;
getLevel().setBlockAndUpdate(getBlockPos(), state.setValue(MODEM_ON, modemOn).setValue(PERIPHERAL_ON, peripheralOn));
}
@Override
public void clearRemoved() {
super.clearRemoved();
TickScheduler.schedule(tickToken);
}
void blockTick() {
if (getLevel().isClientSide) return;
if (invalidSides != 0) {
for (var direction : DirectionUtil.FACINGS) {
if ((invalidSides & (1 << direction.ordinal())) != 0) refreshPeripheral(direction);
}
}
if (modemState.pollChanged()) updateBlockState();
if (!connectionsFormed) {
connectionsFormed = true;
connectionsChanged();
if (peripheralAccessAllowed) {
for (var facing : DirectionUtil.FACINGS) {
peripherals[facing.ordinal()].attach(level, getBlockPos(), facing);
}
updateConnectedPeripherals();
}
}
if (connectionsChanged) connectionsChanged();
}
private void scheduleConnectionsChanged() {
connectionsChanged = true;
TickScheduler.schedule(tickToken);
}
private void connectionsChanged() {
if (getLevel().isClientSide) return;
connectionsChanged = false;
var world = getLevel();
var current = getBlockPos();
for (var facing : DirectionUtil.FACINGS) {
var offset = current.relative(facing);
if (!world.isLoaded(offset)) continue;
var element = connectedElements.get(facing);
if (element == null) continue;
node.connectTo(element.getNode());
}
}
private void togglePeripheralAccess() {
if (!peripheralAccessAllowed) {
var hasAny = false;
for (var facing : DirectionUtil.FACINGS) {
var peripheral = peripherals[facing.ordinal()];
peripheral.attach(level, getBlockPos(), facing);
hasAny |= peripheral.hasPeripheral();
}
if (!hasAny) return;
peripheralAccessAllowed = true;
node.updatePeripherals(getConnectedPeripherals());
} else {
peripheralAccessAllowed = false;
for (var peripheral : peripherals) peripheral.detach();
node.updatePeripherals(Map.of());
}
updateBlockState();
}
private Set<String> getConnectedPeripheralNames() {
if (!peripheralAccessAllowed) return Set.of();
Set<String> peripherals = new HashSet<>(6);
for (var peripheral : this.peripherals) {
var name = peripheral.getConnectedName();
if (name != null) peripherals.add(name);
}
return peripherals;
}
private Map<String, IPeripheral> getConnectedPeripherals() {
if (!peripheralAccessAllowed) return Map.of();
Map<String, IPeripheral> peripherals = new HashMap<>(6);
for (var peripheral : this.peripherals) peripheral.extendMap(peripherals);
return Collections.unmodifiableMap(peripherals);
}
private void updateConnectedPeripherals() {
var peripherals = getConnectedPeripherals();
if (peripherals.isEmpty()) {
// If there are no peripherals then disable access and update the display state.
peripheralAccessAllowed = false;
updateBlockState();
}
node.updatePeripherals(peripherals);
}
public WiredElement getElement() {
return element;
}
@Nullable
public WiredModemPeripheral getPeripheral(@Nullable Direction side) {
if (side == null) return null;
var peripheral = modems[side.ordinal()];
if (peripheral != null) return peripheral;
return modems[side.ordinal()] = new WiredModemPeripheral(modemState, element, peripherals[side.ordinal()], this) {
@Override
public Vec3 getPosition() {
return Vec3.atCenterOf(getBlockPos().relative(side));
}
};
}
}