mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-12 11:10:29 +00:00
Add support for libmultipart to wireless modems
This commit is contained in:
parent
0b2bb5e7b5
commit
4d3d8e6a8e
@ -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")
|
||||
|
@ -49,6 +49,7 @@ fun JavaExec.copyToFull(spec: JavaExec) {
|
||||
* 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
|
||||
|
@ -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
|
||||
|
@ -11,7 +11,7 @@ import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
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 class ModemState {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import net.minecraft.world.phys.Vec3;
|
||||
|
||||
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 @@ public class WirelessModemBlockEntity extends BlockEntity {
|
||||
}
|
||||
}
|
||||
|
||||
public ModemState getModemState() {
|
||||
return modem.getModemState();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IPeripheral getPeripheral(@Nullable Direction direction) {
|
||||
return direction == null || getDirection() == direction ? modem : null;
|
||||
|
@ -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"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.networking.v1.ClientPlayNetworking;
|
||||
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 class ComputerCraftClient {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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()));
|
||||
});
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@ import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||
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.itemgroup.v1.FabricItemGroup;
|
||||
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 class ComputerCraft {
|
||||
|
||||
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)
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
},
|
||||
"mixins": [
|
||||
"ArgumentTypeInfosAccessor",
|
||||
"BlockItemMixin",
|
||||
"ChunkMapMixin",
|
||||
"EntityMixin",
|
||||
"ExplosionDamageCalculatorMixin",
|
||||
|
Loading…
Reference in New Issue
Block a user