Add support for libmultipart to wireless modems

This commit is contained in:
Jonathan Coates 2023-07-02 17:25:58 +01:00
parent 0b2bb5e7b5
commit 4d3d8e6a8e
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
16 changed files with 513 additions and 6 deletions

View File

@ -59,6 +59,7 @@ repositories {
includeGroup("org.squiddev")
includeGroup("cc.tweaked")
// Things we mirror
includeGroup("alexiil.mc.lib")
includeGroup("dev.architectury")
includeGroup("maven.modrinth")
includeGroup("me.shedaniel")

View File

@ -49,6 +49,7 @@
* Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
*/
fun BaseExecSpec.copyToExec(spec: BaseExecSpec) {
spec.workingDir = workingDir
spec.isIgnoreExitValue = isIgnoreExitValue
if (standardInput != null) spec.standardInput = standardInput
if (standardOutput != null) spec.standardOutput = standardOutput

View File

@ -35,6 +35,7 @@ slf4j = "1.7.36"
# Minecraft mods
iris = "1.5.2+1.19.4"
jei = "13.1.0.11"
libmultipart = "0.10.0"
modmenu = "6.1.0-rc.1"
oculus = "1.2.5"
rei = "10.0.578"
@ -96,6 +97,7 @@ iris = { module = "maven.modrinth:iris", version.ref = "iris" }
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.19.4-forge", version.ref = "jei" }
libmultipart = { module = "alexiil.mc.lib:libmultipart-all", version.ref = "libmultipart" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
@ -152,7 +154,7 @@ externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["oculus", "jei-api"]
externalMods-forge-runtime = ["jei-forge"]
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin", "libmultipart"]
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
# Testing

View File

@ -11,7 +11,7 @@
import javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
public class ModemState {
public final class ModemState {
private final @Nullable Runnable onChanged;
private final AtomicBoolean changed = new AtomicBoolean(true);
@ -69,4 +69,30 @@ public void closeAll() {
setOpen(false);
}
}
/**
* Copy this modem state, returning a new instance. The new instance will have the same set of open channels but no
* on-change listener.
*
* @return The new modem state.
*/
public ModemState copy() {
return copy(null);
}
/**
* Copy this modem state, returning a new instance. The new instance will have the same set of open channels and a
* different on-change listener.
*
* @param onChanged The on-change listener.
* @return The new modem state.
*/
public ModemState copy(@Nullable Runnable onChanged) {
synchronized (channels) {
var clone = onChanged == null ? new ModemState() : new ModemState(onChanged);
clone.channels.addAll(channels);
clone.open = open;
return clone;
}
}
}

View File

@ -18,7 +18,7 @@
import javax.annotation.Nullable;
public class WirelessModemBlockEntity extends BlockEntity {
public final class WirelessModemBlockEntity extends BlockEntity {
private static class Peripheral extends WirelessModemPeripheral {
private final WirelessModemBlockEntity entity;
@ -96,6 +96,10 @@ private void updateBlockState() {
}
}
public ModemState getModemState() {
return modem.getModemState();
}
@Nullable
public IPeripheral getPeripheral(@Nullable Direction direction) {
return direction == null || getDirection() == direction ? modem : null;

View File

@ -28,6 +28,7 @@ fun addRemappedConfiguration(name: String) {
val ourSourceSet = sourceSets.register(name) {
// Try to make this source set as much of a non-entity as possible.
listOf(allSource, java, resources, kotlin).forEach { it.setSrcDirs(emptyList<File>()) }
runtimeClasspath += sourceSets["client"].runtimeClasspath
}
val capitalName = name.replaceFirstChar { it.titlecase(Locale.ROOT) }
loom.addRemapConfiguration("mod$capitalName") {
@ -45,6 +46,7 @@ fun addRemappedConfiguration(name: String) {
addRemappedConfiguration("testWithSodium")
addRemappedConfiguration("testWithIris")
addRemappedConfiguration("integrations")
dependencies {
modImplementation(libs.bundles.externalMods.fabric)
@ -60,6 +62,7 @@ dependencies {
"modTestWithSodium"(libs.sodium)
"modTestWithIris"(libs.iris)
"modTestWithIris"(libs.sodium)
"modIntegrations"(libs.libmultipart)
include(libs.cobalt)
include(libs.jzlib)
@ -166,6 +169,14 @@ loom {
property("fabric-api.gametest.report-file", project.buildDir.resolve("test-results/runGametest.xml").absolutePath)
runDir("run/gametest")
}
register("clientWithIntegrations") {
configName = "Client (+integrations)"
runDir("run/integration")
client()
source(sourceSets["integrations"])
}
}
}

View File

@ -4,11 +4,16 @@
package dan200.computercraft.client;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.integration.libmultipart.LibMultiPartIntegrationClient;
import dan200.computercraft.client.model.EmissiveComputerModel;
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.integration.LoadedMods;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.platform.FabricConfigFile;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
@ -17,6 +22,7 @@
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.client.player.ClientPickBlockGatherCallback;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.item.ItemStack;
@ -68,5 +74,10 @@ public static void init() {
return cable.getCloneItemStack(state, hit, level, pos, player);
});
// Load config file
((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml"));
if (LoadedMods.LIB_MULTI_PART) LibMultiPartIntegrationClient.init();
}
}

View File

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.integration.libmultipart;
import alexiil.mc.lib.multipart.api.render.PartStaticModelRegisterEvent;
import dan200.computercraft.shared.integration.libmultipart.BlockStateModelKey;
import dan200.computercraft.shared.integration.libmultipart.LibMultiPartIntegration;
import net.minecraft.client.Minecraft;
/**
* Client-side support for LibMultiPart.
*
* @see LibMultiPartIntegration
*/
public class LibMultiPartIntegrationClient {
public static void init() {
PartStaticModelRegisterEvent.EVENT.register(renderer -> {
var baker = Minecraft.getInstance().getBlockRenderer();
renderer.register(BlockStateModelKey.class, (key, ctx) ->
ctx.bakedModelConsumer().accept(baker.getBlockModel(key.state()), key.state()));
});
}
}

View File

@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin;
import dan200.computercraft.shared.integration.LoadedMods;
import dan200.computercraft.shared.integration.libmultipart.LibMultiPartIntegration;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.context.BlockPlaceContext;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Adds multipart support to {@link BlockItem}.
*/
@Mixin(BlockItem.class)
class BlockItemMixin extends Item {
BlockItemMixin(Properties properties) {
super(properties);
}
@Inject(method = "place", at = @At(value = "HEAD"), cancellable = true)
private void placeMultipart(BlockPlaceContext context, CallbackInfoReturnable<InteractionResult> cir) {
if (!LoadedMods.LIB_MULTI_PART) return;
// If we have custom handling for this item, and the default logic would not work, then we run our libmultipart
// hook.
var factory = LibMultiPartIntegration.getCreatorForItem(this);
if (factory != null && !context.canPlace()) cir.setReturnValue(factory.placePart(context));
}
}

View File

@ -12,6 +12,8 @@
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.details.FluidDetails;
import dan200.computercraft.shared.integration.LoadedMods;
import dan200.computercraft.shared.integration.libmultipart.LibMultiPartIntegration;
import dan200.computercraft.shared.network.client.UpgradesLoadedMessage;
import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral;
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
@ -30,7 +32,6 @@
import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.resources.PreparableReloadListener;
@ -100,11 +101,11 @@ public static void init() {
CommonHooks.onDatapackReload((name, listener) -> ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new ReloadListener(name, listener)));
((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml"));
FabricDetailRegistries.FLUID_VARIANT.addProvider(FluidDetails::fill);
ComputerCraftAPI.registerGenericSource(new InventoryMethods());
if (LoadedMods.LIB_MULTI_PART) LibMultiPartIntegration.init();
}
private record ReloadListener(String name, PreparableReloadListener listener)

View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.integration;
import dan200.computercraft.shared.integration.libmultipart.LibMultiPartIntegration;
import net.fabricmc.loader.api.FabricLoader;
/**
* Constants indicating whether various mods are loaded or not. These are stored as static final fields, to avoid
* repeated lookups and allow the JIT to inline them to constants.
*/
public final class LoadedMods {
/**
* Whether LibMultiPart is loaded.
*
* @see LibMultiPartIntegration
*/
public static final boolean LIB_MULTI_PART = FabricLoader.getInstance().isModLoaded(LibMultiPartIntegration.MOD_ID);
private LoadedMods() {
}
}

View File

@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.integration.libmultipart;
import alexiil.mc.lib.multipart.api.render.PartModelKey;
import net.minecraft.world.level.block.state.BlockState;
/**
* A {@link PartModelKey} which just renders a basic {@link BlockState}.
*/
public final class BlockStateModelKey extends PartModelKey {
private final BlockState state;
public BlockStateModelKey(BlockState state) {
this.state = state;
}
public BlockState state() {
return state;
}
@Override
public boolean equals(Object o) {
return o instanceof BlockStateModelKey other && state == other.state;
}
@Override
public int hashCode() {
return state.hashCode();
}
}

View File

@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.integration.libmultipart;
import alexiil.mc.lib.attributes.*;
import alexiil.mc.lib.multipart.api.NativeMultipart;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.node.wired.WiredElementLookup;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.PeripheralLookup;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.integration.libmultipart.parts.WirelessModemPart;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Integration for <a href="https://github.com/AlexIIL/LibMultiPart/">LibMultiPart</a>.
* <p>
* This adds multipart versions of modems and cables.
*/
public final class LibMultiPartIntegration {
public static final String MOD_ID = "libmultipart";
public static final Attribute<IPeripheral> PERIPHERAL = Attributes.create(IPeripheral.class);
public static final Attribute<WiredElement> WIRED_ELEMENT = Attributes.create(WiredElement.class);
private static final Map<Item, PlacementMultipartCreator> itemPlacers = new HashMap<>();
private LibMultiPartIntegration() {
}
public static void init() {
// Register an adapter from Fabric block lookup to attributes. This would be very inefficient by default, so
// we only do it for blocks which explicitly implement the attribute interfaces.
PeripheralLookup.get().registerFallback((world, pos, state, blockEntity, context) ->
state.getBlock() instanceof AttributeProvider || blockEntity instanceof AttributeProviderBlockEntity
? PERIPHERAL.getFirstOrNull(world, pos, SearchOptions.inDirection(context.getOpposite()))
: null);
WiredElementLookup.get().registerFallback((world, pos, state, blockEntity, context) ->
state.getBlock() instanceof AttributeProvider || blockEntity instanceof AttributeProviderBlockEntity
? WIRED_ELEMENT.getFirstOrNull(world, pos, SearchOptions.inDirection(context.getOpposite()))
: null);
registerWirelessModem(WirelessModemPart.makeDefinition(ModRegistry.Blocks.WIRELESS_MODEM_NORMAL, false));
registerWirelessModem(WirelessModemPart.makeDefinition(ModRegistry.Blocks.WIRELESS_MODEM_ADVANCED, true));
}
private static void registerWirelessModem(WirelessModemPart.Definition definition) {
definition.register();
NativeMultipart.LOOKUP.registerForBlocks((world, pos, state, blockEntity, context) -> (level, blockPos, blockState) ->
List.of(holder -> definition.convert(holder, state, blockEntity)), definition.block());
itemPlacers.put(definition.block().asItem(), definition);
}
/**
* Get the corresponding {@link PlacementMultipartCreator} for an item.
*
* @param item The item we're trying to place.
* @return The placement-aware multipart creator, or {@code null}.
*/
public static @Nullable PlacementMultipartCreator getCreatorForItem(Item item) {
return itemPlacers.get(item);
}
}

View File

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.integration.libmultipart;
import alexiil.mc.lib.multipart.api.AbstractPart;
import alexiil.mc.lib.multipart.api.MultipartContainer.MultipartCreator;
import alexiil.mc.lib.multipart.api.MultipartHolder;
import alexiil.mc.lib.multipart.api.MultipartUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.GameEvent;
/**
* Creates a {@linkplain AbstractPart multipart} based on a {@link BlockPlaceContext}.
*
* @see MultipartCreator
*/
public interface PlacementMultipartCreator {
/**
* Create a new part.
*
* @param holder The holder which is creating this part.
* @param context The current block placement context.
* @return The newly created part.
*/
AbstractPart create(MultipartHolder holder, BlockPlaceContext context);
/**
* Attempt to place this part into the world.
* <p>
* This largely mirrors the logic in {@link BlockItem#place(BlockPlaceContext)}, but using
* {@link MultipartUtil#offerNewPart(Level, BlockPos, MultipartCreator)} to place the new part instead.
*
* @param context The current placement context.
* @return Whether the part was placed or not.
*/
default InteractionResult placePart(BlockPlaceContext context) {
var level = context.getLevel();
var position = context.getClickedPos();
var offer = MultipartUtil.offerNewPart(level, position, holder -> create(holder, context));
if (offer == null) return InteractionResult.PASS;
// Be careful to only apply this server-side. Multiparts send a bunch of network packets when created, which we
// obviously don't want to do on the server!
if (!level.isClientSide) offer.apply();
// Approximate the block state from the placed part, and then fire all the appropriate events.
var stack = context.getItemInHand();
var blockState = ((BlockItem) stack.getItem()).getBlock().defaultBlockState();
var player = context.getPlayer();
var sound = blockState.getSoundType();
level.playSound(player, position, sound.getPlaceSound(), SoundSource.BLOCKS, (sound.getVolume() + 1f) / 2f, sound.getPitch() * 0.8f);
level.gameEvent(GameEvent.BLOCK_PLACE, position, GameEvent.Context.of(player, blockState));
if (player == null || !player.getAbilities().instabuild) stack.shrink(1);
return InteractionResult.sidedSuccess(level.isClientSide);
}
}

View File

@ -0,0 +1,194 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.integration.libmultipart.parts;
import alexiil.mc.lib.attributes.AttributeList;
import alexiil.mc.lib.multipart.api.AbstractPart;
import alexiil.mc.lib.multipart.api.MultipartEventBus;
import alexiil.mc.lib.multipart.api.MultipartHolder;
import alexiil.mc.lib.multipart.api.PartDefinition;
import alexiil.mc.lib.multipart.api.event.PartTickEvent;
import alexiil.mc.lib.multipart.api.render.PartModelKey;
import alexiil.mc.lib.net.IMsgReadCtx;
import alexiil.mc.lib.net.IMsgWriteCtx;
import alexiil.mc.lib.net.InvalidInputDataException;
import alexiil.mc.lib.net.NetByteBuf;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.integration.libmultipart.BlockStateModelKey;
import dan200.computercraft.shared.integration.libmultipart.LibMultiPartIntegration;
import dan200.computercraft.shared.integration.libmultipart.PlacementMultipartCreator;
import dan200.computercraft.shared.peripheral.modem.ModemShapes;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
import dan200.computercraft.shared.platform.RegistryEntry;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
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.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.VoxelShape;
import javax.annotation.Nullable;
/**
* A {@linkplain AbstractPart multipart} for wireless modems.
*
* @see WirelessModemBlock
* @see WirelessModemBlockEntity
*/
public final class WirelessModemPart extends AbstractPart {
private final WirelessModemBlock modemBlock;
private final boolean advanced;
private final Direction direction;
private final Peripheral modem;
private boolean on;
private WirelessModemPart(
PartDefinition definition, MultipartHolder holder, WirelessModemBlock modemBlock, boolean advanced,
Direction direction, @Nullable ModemState state
) {
super(definition, holder);
this.modemBlock = modemBlock;
this.advanced = advanced;
this.direction = direction;
modem = new Peripheral(this, state);
}
public static Definition makeDefinition(RegistryEntry<WirelessModemBlock> modem, boolean advanced) {
return new Definition(modem, advanced);
}
@Override
public void onAdded(MultipartEventBus bus) {
if (container.getMultipartWorld().isClientSide) return;
bus.addListener(this, PartTickEvent.class, event -> {
if (modem.getModemState().pollChanged()) sendNetworkUpdate(this, NET_RENDER_DATA);
});
}
@Override
public void addAllAttributes(AttributeList<?> list) {
super.addAllAttributes(list);
if (list.attribute == LibMultiPartIntegration.PERIPHERAL && list.getSearchDirection() == direction.getOpposite()) {
list.offer(modem);
}
}
@Override
public void writeCreationData(NetByteBuf buffer, IMsgWriteCtx ctx) {
super.writeCreationData(buffer, ctx);
buffer.writeEnum(direction);
}
@Override
public CompoundTag toTag() {
var tag = super.toTag();
tag.putString("direction", direction.getSerializedName());
return tag;
}
@Override
public void writeRenderData(NetByteBuf buffer, IMsgWriteCtx ctx) {
super.writeRenderData(buffer, ctx);
buffer.writeBoolean(on = modem.getModemState().isOpen());
}
@Override
public void readRenderData(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException {
super.readRenderData(buffer, ctx);
on = buffer.readBoolean();
redrawIfChanged();
}
@Override
public VoxelShape getShape() {
return ModemShapes.getBounds(direction);
}
@Nullable
@Override
public PartModelKey getModelKey() {
return new BlockStateModelKey(
modemBlock.defaultBlockState()
.setValue(WirelessModemBlock.FACING, direction)
.setValue(WirelessModemBlock.ON, on)
);
}
private static class Peripheral extends WirelessModemPeripheral {
private final WirelessModemPart part;
Peripheral(WirelessModemPart part, @Nullable ModemState state) {
// state will be non-null when converting an existing modem. This allows us to preserve the open channels.
super(state == null ? new ModemState() : state.copy(), part.advanced);
this.part = part;
}
@Override
public Level getLevel() {
return part.container.getMultipartWorld();
}
@Override
public Vec3 getPosition() {
return Vec3.atLowerCornerOf(part.container.getMultipartPos().relative(part.direction));
}
@Override
public boolean equals(@Nullable IPeripheral other) {
return this == other || (other instanceof Peripheral && part == ((Peripheral) other).part);
}
@Override
public Object getTarget() {
return part;
}
}
public static final class Definition extends PartDefinition implements PlacementMultipartCreator {
private final RegistryEntry<WirelessModemBlock> modem;
private final boolean advanced;
private Definition(RegistryEntry<WirelessModemBlock> modem, boolean advanced) {
super(
modem.id(),
(def, holder, tag) -> {
var direction = Direction.CODEC.byName(tag.getString("direction"), Direction.NORTH);
return new WirelessModemPart(def, holder, modem.get(), advanced, direction, null);
},
(def, holder, buffer, context) -> {
var direction = buffer.readEnum(Direction.class);
return new WirelessModemPart(def, holder, modem.get(), advanced, direction, null);
}
);
this.modem = modem;
this.advanced = advanced;
}
public Block block() {
return modem.get();
}
public AbstractPart convert(MultipartHolder holder, BlockState state, @Nullable BlockEntity blockEntity) {
return new WirelessModemPart(
this, holder, modem.get(), advanced,
state.getValue(WirelessModemBlock.FACING),
blockEntity instanceof WirelessModemBlockEntity modemBlockEntity ? modemBlockEntity.getModemState() : null
);
}
@Override
public AbstractPart create(MultipartHolder holder, BlockPlaceContext context) {
return new WirelessModemPart(this, holder, modem.get(), advanced, context.getClickedFace().getOpposite(), null);
}
}
}

View File

@ -8,6 +8,7 @@
},
"mixins": [
"ArgumentTypeInfosAccessor",
"BlockItemMixin",
"ChunkMapMixin",
"EntityMixin",
"ExplosionDamageCalculatorMixin",