Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,8 @@ if (System.getenv("MODRINTH_TOKEN")) {
}
}
}

tasks.named('compileJava') {
dependsOn tasks.named('compileKotlin')
classpath += files(tasks.named('compileKotlin').flatMap { it.destinationDirectory })
}
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
org.gradle.java.home=C:/Program Files/Java/jdk-21.0.11
org.gradle.jvmargs=-Xmx2G
org.gradle.daemon=true
org.gradle.parallel=true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Small Java interop boundary for NeoForge capability APIs.
*
* NeoForge 1.21.1 uses TYPE_USE nullability in capability context generics,
* such as BlockCapability<IItemHandler, @Nullable Direction>. Kotlin 2.0.0
* reports false JAVA_TYPE_MISMATCH diagnostics for those signatures. Keeping
* only the affected calls in Java lets both compilers type-check the official
* API without suppressions, unchecked casts, or changed capability behavior.
*/
package de.devin.cbbees.compat;

import net.minecraft.core.BlockPos;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
import net.neoforged.neoforge.fluids.capability.IFluidHandlerItem;
import net.neoforged.neoforge.items.IItemHandler;
import org.jetbrains.annotations.Nullable;

public final class NeoForgeCapabilityInterop {
private NeoForgeCapabilityInterop() {}

@FunctionalInterface
public interface BlockItemHandlerProvider<BE extends BlockEntity> {
@Nullable
IItemHandler get(BE blockEntity);
}

@FunctionalInterface
public interface FluidItemHandlerProvider {
@Nullable
IFluidHandlerItem get(ItemStack stack);
}

/**
* Registers the standard sided block item capability with a provider that
* intentionally exposes the same handler from every side.
*/
public static <BE extends BlockEntity> void registerUnsidedBlockItemHandler(
RegisterCapabilitiesEvent event,
BlockEntityType<BE> blockEntityType,
BlockItemHandlerProvider<BE> provider
) {
event.registerBlockEntity(
Capabilities.ItemHandler.BLOCK,
blockEntityType,
(blockEntity, side) -> provider.get(blockEntity)
);
}

/** Registers the standard context-free item fluid capability. */
public static void registerFluidItemHandler(
RegisterCapabilitiesEvent event,
ItemLike item,
FluidItemHandlerProvider provider
) {
event.registerItem(
Capabilities.FluidHandler.ITEM,
(stack, context) -> provider.get(stack),
item
);
}

/**
* Queries the standard sided item capability without selecting a side.
* A null direction is NeoForge's documented unsided-query behavior.
*/
@Nullable
public static IItemHandler getUnsidedItemHandler(Level level, BlockPos pos) {
return level.getCapability(Capabilities.ItemHandler.BLOCK, pos, null);
}
}
80 changes: 74 additions & 6 deletions src/main/java/de/devin/cbbees/mixin/SchematicHandlerHudMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import com.simibubi.create.AllDataComponents;
import com.simibubi.create.CreateClient;
import com.simibubi.create.content.schematics.SchematicInstances;
import com.simibubi.create.content.schematics.client.SchematicHandler;
import de.devin.cbbees.content.drone.client.DroneViewClientState;
import de.devin.cbbees.content.schematics.ConstructionPlannerItem;
import de.devin.cbbees.content.schematics.client.ConstructionToolState;
import de.devin.cbbees.items.AllItems;
import de.devin.cbbees.content.deployer.SchematicProgram;
import de.devin.cbbees.network.PlannerTransformSyncPacket;
import de.devin.cbbees.network.ProgramSchematicPacket;
import de.devin.cbbees.network.StartConstructionPacket;
import de.devin.cbbees.network.StopTasksPacket;
Expand Down Expand Up @@ -44,18 +46,53 @@
public abstract class SchematicHandlerHudMixin {

@Shadow private boolean active;
@Shadow private int activeHotbarSlot;
@Shadow private int syncCooldown;
@Shadow private ItemStack activeSchematicItem;
@Shadow public abstract boolean isDeployed();

/* ------------------------------------------------------------------ */
/* Construction packet helper */
/* ------------------------------------------------------------------ */

/**
* Reads placement data from the SchematicHandler's transformation and sends a
* StartConstructionPacket. Only used in state 3 (deployed).
* Copies Create's live transformation into the planner ItemStack and mirrors
* it to the server. Create's own SchematicSyncPacket ignores this custom
* item, so without this packet a slot change reloads the original anchor.
*/
@Unique
private boolean ccr$persistPlannerTransformation() {
ItemStack stack = activeSchematicItem;
if (stack == null || stack.isEmpty()) return false;
if (!AllItems.INSTANCE.getCONSTRUCTION_PLANNER().isIn(stack)) return false;
if (!stack.has(AllDataComponents.SCHEMATIC_FILE)) return false;

var transform = CreateClient.SCHEMATIC_HANDLER.getTransformation();
if (transform == null) return false;

var settings = transform.toSettings();
BlockPos anchor = transform.getAnchor();
Rotation rotation = settings.getRotation();
Mirror mirror = settings.getMirror();
boolean deployed = stack.getOrDefault(AllDataComponents.SCHEMATIC_DEPLOYED, false);

stack.set(AllDataComponents.SCHEMATIC_ANCHOR, anchor);
stack.set(AllDataComponents.SCHEMATIC_ROTATION, rotation);
stack.set(AllDataComponents.SCHEMATIC_MIRROR, mirror);
SchematicInstances.clearHash(stack);

PacketDistributor.sendToServer(new PlannerTransformSyncPacket(
activeHotbarSlot, anchor, rotation, mirror, deployed
));
return true;
}

/**
* Reads placement data from the live transformation and starts construction.
*/
@Unique
private void ccr$sendConstructionPacket(ItemStack stack) {
ccr$persistPlannerTransformation();
var transform = CreateClient.SCHEMATIC_HANDLER.getTransformation();
var settings = transform.toSettings();
BlockPos anchor = transform.getAnchor();
Expand All @@ -64,6 +101,29 @@ public abstract class SchematicHandlerHudMixin {
PacketDistributor.sendToServer(new StartConstructionPacket(anchor, rotation, mirror));
}

/**
* Every Move XZ / Move Y / Rotate / Mirror operation ends in markDirty().
* Persist immediately instead of waiting for Create's delayed sync packet,
* because that packet only accepts Create's own Schematic item server-side.
*/
@Inject(method = "markDirty", at = @At("TAIL"))
private void ccr$persistPlannerTransformOnDirty(CallbackInfo ci) {
if (ccr$persistPlannerTransformation()) {
// Create's delayed SchematicSyncPacket rejects custom planner items.
// Cancel that redundant delayed send after our own sync succeeds.
syncCooldown = 0;
}
}

/**
* Creative Print copies the ItemStack itself into SchematicPlacePacket. Make
* sure that copy contains the fine-tuned live position first.
*/
@Inject(method = "printInstantly", at = @At("HEAD"))
private void ccr$persistPlannerTransformBeforePrint(CallbackInfo ci) {
ccr$persistPlannerTransformation();
}

/* ------------------------------------------------------------------ */
/* RMB — custom tool actions (state 3 only) */
/* ------------------------------------------------------------------ */
Expand Down Expand Up @@ -111,14 +171,22 @@ public abstract class SchematicHandlerHudMixin {
ConstructionToolState.setActiveTool(ConstructionToolState.CustomTool.NONE);
cir.setReturnValue(true);
} else if (tool == ConstructionToolState.CustomTool.PROGRAM) {
// Read placement data and send a ProgramSchematicPacket
// Read live placement data from Create's SchematicTransformation.
// The ItemStack components only store the first deploy anchor and are not
// updated immediately by Move XZ / Move Y / Rotate / Mirror tools. Those
// tools update the transformation and only sync later via Create's normal
// SchematicSyncPacket. Programming from the ItemStack therefore rolled the
// schematic back to its first clicked position. This must match Construct.
String schematicFile = mainHand.get(AllDataComponents.SCHEMATIC_FILE);
String owner = mainHand.get(AllDataComponents.SCHEMATIC_OWNER);
if (schematicFile == null || owner == null) return;

BlockPos anchor = mainHand.getOrDefault(AllDataComponents.SCHEMATIC_ANCHOR, BlockPos.ZERO);
Rotation rotation = mainHand.getOrDefault(AllDataComponents.SCHEMATIC_ROTATION, Rotation.NONE);
Mirror mirror = mainHand.getOrDefault(AllDataComponents.SCHEMATIC_MIRROR, Mirror.NONE);
ccr$persistPlannerTransformation();
var transform = CreateClient.SCHEMATIC_HANDLER.getTransformation();
var settings = transform.toSettings();
BlockPos anchor = transform.getAnchor();
Rotation rotation = settings.getRotation();
Mirror mirror = settings.getMirror();

SchematicProgram program = new SchematicProgram.Construction(
schematicFile, anchor, rotation, mirror, owner
Expand Down
41 changes: 20 additions & 21 deletions src/main/kotlin/de/devin/cbbees/CreateBuzzyBeez.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.simibubi.create.foundation.item.TooltipModifier
import de.devin.cbbees.blocks.AllBlocks
import de.devin.cbbees.config.CBBeesClientConfig
import de.devin.cbbees.config.CBBeesConfig
import de.devin.cbbees.compat.NeoForgeCapabilityInterop
import de.devin.cbbees.content.backpack.client.CCRClientEvents
import de.devin.cbbees.content.backpack.client.BeeNetworkClientEvents
import de.devin.cbbees.content.bee.brain.BeeMemoryModules
Expand Down Expand Up @@ -39,7 +40,6 @@ import de.devin.cbbees.registry.AllEntityTypes
import de.devin.cbbees.registry.AllKeys
import de.devin.cbbees.registry.AllMenuTypes
import de.devin.cbbees.tabs.AllCreativeModeTabs
import net.neoforged.neoforge.capabilities.Capabilities
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
import net.createmod.catnip.lang.FontHelper
import net.createmod.ponder.foundation.PonderIndex
Expand Down Expand Up @@ -153,26 +153,25 @@ object CreateBuzzyBeez {
MOD_BUS.addListener<FMLCommonSetupEvent> { onCommonSetup(it) }

MOD_BUS.addListener<RegisterCapabilitiesEvent> { event ->
event.registerBlockEntity(
Capabilities.ItemHandler.BLOCK,
AllBlockEntityTypes.MECHANICAL_BEEHIVE.get(),
{ be, side -> be.inventory }
)
event.registerBlockEntity(
Capabilities.ItemHandler.BLOCK,
AllBlockEntityTypes.LOGISTICS_PORT.get(),
{ be, side -> be.getItemHandler(be.world) }
)
event.registerBlockEntity(
Capabilities.ItemHandler.BLOCK,
AllBlockEntityTypes.SCHEMATIC_DEPLOYER.get(),
{ be, _ -> de.devin.cbbees.content.deployer.SchematicDeployerItemHandler(be) }
)
event.registerItem(
Capabilities.FluidHandler.ITEM,
{ stack, _ -> de.devin.cbbees.content.backpack.PortableBeehiveFluidHandler(stack) },
de.devin.cbbees.items.AllItems.PORTABLE_BEEHIVE.get()
)
NeoForgeCapabilityInterop.registerUnsidedBlockItemHandler(
event,
AllBlockEntityTypes.MECHANICAL_BEEHIVE.get()
) { be -> be.inventory }

NeoForgeCapabilityInterop.registerUnsidedBlockItemHandler(
event,
AllBlockEntityTypes.LOGISTICS_PORT.get()
) { be -> be.getItemHandler(be.world) }

NeoForgeCapabilityInterop.registerUnsidedBlockItemHandler(
event,
AllBlockEntityTypes.SCHEMATIC_DEPLOYER.get()
) { be -> de.devin.cbbees.content.deployer.SchematicDeployerItemHandler(be) }

NeoForgeCapabilityInterop.registerFluidItemHandler(
event,
AllItems.PORTABLE_BEEHIVE.get()
) { stack -> de.devin.cbbees.content.backpack.PortableBeehiveFluidHandler(stack) }
}

NeoForge.EVENT_BUS.addListener<RegisterCommandsEvent> {
Expand Down
11 changes: 8 additions & 3 deletions src/main/kotlin/de/devin/cbbees/compat/CuriosCompat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@ object CuriosCompat {
*/
private object CuriosCompatImpl {
fun findFirstCurio(player: Player, predicate: (ItemStack) -> Boolean): ItemStack {
val result = top.theillusivec4.curios.api.CuriosApi.getCuriosHelper()
.findFirstCurio(player, predicate)
return if (result.isPresent) result.get().stack() else ItemStack.EMPTY
val inventory = top.theillusivec4.curios.api.CuriosApi
.getCuriosInventory(player)
.orElse(null)
?: return ItemStack.EMPTY

return inventory.findFirstCurio(predicate)
.map { it.stack() }
.orElse(ItemStack.EMPTY)
}
}
Loading