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("org.squiddev")
|
||||||
includeGroup("cc.tweaked")
|
includeGroup("cc.tweaked")
|
||||||
// Things we mirror
|
// Things we mirror
|
||||||
|
includeGroup("alexiil.mc.lib")
|
||||||
includeGroup("dev.architectury")
|
includeGroup("dev.architectury")
|
||||||
includeGroup("maven.modrinth")
|
includeGroup("maven.modrinth")
|
||||||
includeGroup("me.shedaniel")
|
includeGroup("me.shedaniel")
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
* Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
|
* Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
|
||||||
*/
|
*/
|
||||||
fun BaseExecSpec.copyToExec(spec: BaseExecSpec) {
|
fun BaseExecSpec.copyToExec(spec: BaseExecSpec) {
|
||||||
|
spec.workingDir = workingDir
|
||||||
spec.isIgnoreExitValue = isIgnoreExitValue
|
spec.isIgnoreExitValue = isIgnoreExitValue
|
||||||
if (standardInput != null) spec.standardInput = standardInput
|
if (standardInput != null) spec.standardInput = standardInput
|
||||||
if (standardOutput != null) spec.standardOutput = standardOutput
|
if (standardOutput != null) spec.standardOutput = standardOutput
|
||||||
|
|
|
@ -35,6 +35,7 @@ slf4j = "1.7.36"
|
||||||
# Minecraft mods
|
# Minecraft mods
|
||||||
iris = "1.5.2+1.19.4"
|
iris = "1.5.2+1.19.4"
|
||||||
jei = "13.1.0.11"
|
jei = "13.1.0.11"
|
||||||
|
libmultipart = "0.10.0"
|
||||||
modmenu = "6.1.0-rc.1"
|
modmenu = "6.1.0-rc.1"
|
||||||
oculus = "1.2.5"
|
oculus = "1.2.5"
|
||||||
rei = "10.0.578"
|
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-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-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" }
|
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" }
|
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
|
||||||
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
|
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
|
||||||
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
|
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-compile = ["oculus", "jei-api"]
|
||||||
externalMods-forge-runtime = ["jei-forge"]
|
externalMods-forge-runtime = ["jei-forge"]
|
||||||
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
|
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"]
|
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class ModemState {
|
public final class ModemState {
|
||||||
private final @Nullable Runnable onChanged;
|
private final @Nullable Runnable onChanged;
|
||||||
private final AtomicBoolean changed = new AtomicBoolean(true);
|
private final AtomicBoolean changed = new AtomicBoolean(true);
|
||||||
|
|
||||||
|
@ -69,4 +69,30 @@ public void closeAll() {
|
||||||
setOpen(false);
|
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 javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public class WirelessModemBlockEntity extends BlockEntity {
|
public final class WirelessModemBlockEntity extends BlockEntity {
|
||||||
private static class Peripheral extends WirelessModemPeripheral {
|
private static class Peripheral extends WirelessModemPeripheral {
|
||||||
private final WirelessModemBlockEntity entity;
|
private final WirelessModemBlockEntity entity;
|
||||||
|
|
||||||
|
@ -96,6 +96,10 @@ private void updateBlockState() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ModemState getModemState() {
|
||||||
|
return modem.getModemState();
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public IPeripheral getPeripheral(@Nullable Direction direction) {
|
public IPeripheral getPeripheral(@Nullable Direction direction) {
|
||||||
return direction == null || getDirection() == direction ? modem : null;
|
return direction == null || getDirection() == direction ? modem : null;
|
||||||
|
|
|
@ -28,6 +28,7 @@ fun addRemappedConfiguration(name: String) {
|
||||||
val ourSourceSet = sourceSets.register(name) {
|
val ourSourceSet = sourceSets.register(name) {
|
||||||
// Try to make this source set as much of a non-entity as possible.
|
// Try to make this source set as much of a non-entity as possible.
|
||||||
listOf(allSource, java, resources, kotlin).forEach { it.setSrcDirs(emptyList<File>()) }
|
listOf(allSource, java, resources, kotlin).forEach { it.setSrcDirs(emptyList<File>()) }
|
||||||
|
runtimeClasspath += sourceSets["client"].runtimeClasspath
|
||||||
}
|
}
|
||||||
val capitalName = name.replaceFirstChar { it.titlecase(Locale.ROOT) }
|
val capitalName = name.replaceFirstChar { it.titlecase(Locale.ROOT) }
|
||||||
loom.addRemapConfiguration("mod$capitalName") {
|
loom.addRemapConfiguration("mod$capitalName") {
|
||||||
|
@ -45,6 +46,7 @@ fun addRemappedConfiguration(name: String) {
|
||||||
|
|
||||||
addRemappedConfiguration("testWithSodium")
|
addRemappedConfiguration("testWithSodium")
|
||||||
addRemappedConfiguration("testWithIris")
|
addRemappedConfiguration("testWithIris")
|
||||||
|
addRemappedConfiguration("integrations")
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
modImplementation(libs.bundles.externalMods.fabric)
|
modImplementation(libs.bundles.externalMods.fabric)
|
||||||
|
@ -60,6 +62,7 @@ dependencies {
|
||||||
"modTestWithSodium"(libs.sodium)
|
"modTestWithSodium"(libs.sodium)
|
||||||
"modTestWithIris"(libs.iris)
|
"modTestWithIris"(libs.iris)
|
||||||
"modTestWithIris"(libs.sodium)
|
"modTestWithIris"(libs.sodium)
|
||||||
|
"modIntegrations"(libs.libmultipart)
|
||||||
|
|
||||||
include(libs.cobalt)
|
include(libs.cobalt)
|
||||||
include(libs.jzlib)
|
include(libs.jzlib)
|
||||||
|
@ -166,6 +169,14 @@ loom {
|
||||||
property("fabric-api.gametest.report-file", project.buildDir.resolve("test-results/runGametest.xml").absolutePath)
|
property("fabric-api.gametest.report-file", project.buildDir.resolve("test-results/runGametest.xml").absolutePath)
|
||||||
runDir("run/gametest")
|
runDir("run/gametest")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register("clientWithIntegrations") {
|
||||||
|
configName = "Client (+integrations)"
|
||||||
|
runDir("run/integration")
|
||||||
|
client()
|
||||||
|
|
||||||
|
source(sourceSets["integrations"])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,16 @@
|
||||||
|
|
||||||
package dan200.computercraft.client;
|
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.EmissiveComputerModel;
|
||||||
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
|
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
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.network.client.ClientNetworkContext;
|
||||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
|
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
|
||||||
|
import dan200.computercraft.shared.platform.FabricConfigFile;
|
||||||
import dan200.computercraft.shared.platform.NetworkHandler;
|
import dan200.computercraft.shared.platform.NetworkHandler;
|
||||||
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
|
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
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.ColorProviderRegistry;
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
|
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
|
||||||
import net.fabricmc.fabric.api.event.client.player.ClientPickBlockGatherCallback;
|
import net.fabricmc.fabric.api.event.client.player.ClientPickBlockGatherCallback;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.renderer.RenderType;
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
@ -68,5 +74,10 @@ public static void init() {
|
||||||
|
|
||||||
return cable.getCloneItemStack(state, hit, level, pos, player);
|
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.config.Config;
|
import dan200.computercraft.shared.config.Config;
|
||||||
import dan200.computercraft.shared.config.ConfigSpec;
|
import dan200.computercraft.shared.config.ConfigSpec;
|
||||||
import dan200.computercraft.shared.details.FluidDetails;
|
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.network.client.UpgradesLoadedMessage;
|
||||||
import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral;
|
import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral;
|
||||||
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
|
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.loot.v2.LootTableEvents;
|
||||||
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
|
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
|
||||||
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.packs.PackType;
|
import net.minecraft.server.packs.PackType;
|
||||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
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)));
|
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);
|
FabricDetailRegistries.FLUID_VARIANT.addProvider(FluidDetails::fill);
|
||||||
|
|
||||||
ComputerCraftAPI.registerGenericSource(new InventoryMethods());
|
ComputerCraftAPI.registerGenericSource(new InventoryMethods());
|
||||||
|
|
||||||
|
if (LoadedMods.LIB_MULTI_PART) LibMultiPartIntegration.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ReloadListener(String name, PreparableReloadListener listener)
|
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": [
|
"mixins": [
|
||||||
"ArgumentTypeInfosAccessor",
|
"ArgumentTypeInfosAccessor",
|
||||||
|
"BlockItemMixin",
|
||||||
"ChunkMapMixin",
|
"ChunkMapMixin",
|
||||||
"EntityMixin",
|
"EntityMixin",
|
||||||
"ExplosionDamageCalculatorMixin",
|
"ExplosionDamageCalculatorMixin",
|
||||||
|
|
Loading…
Reference in New Issue