diff --git a/src/main/java/com/moguang/ctnhbio/api/entity/LivingMetaMachineEntity.java b/src/main/java/com/moguang/ctnhbio/api/entity/LivingMetaMachineEntity.java index 2dafadc..0b5a5ae 100644 --- a/src/main/java/com/moguang/ctnhbio/api/entity/LivingMetaMachineEntity.java +++ b/src/main/java/com/moguang/ctnhbio/api/entity/LivingMetaMachineEntity.java @@ -1,5 +1,7 @@ package com.moguang.ctnhbio.api.entity; +import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMultiController; + import net.createmod.catnip.math.VecHelper; import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleTypes; @@ -255,7 +257,9 @@ public void tick() { if (tickCount % 40 == 0) if (getHealth() < getMaxHealth()) { if (getHost() != null && getHost().getHostMachine() instanceof ILivingMachine livingMachine) { - if (livingMachine.getNutrientAmount() >= 2) { + boolean structureOperational = !(livingMachine instanceof IMultiController controller) || + controller.isStructureOperational(); + if (structureOperational && livingMachine.getNutrientAmount() >= 2) { livingMachine.extractNutrient(2); heal(1); var pos = getHost().getHostPos().getCenter(); diff --git a/src/main/java/com/moguang/ctnhbio/api/machine/multiblock/WorkableLivingMultiblockMachine.java b/src/main/java/com/moguang/ctnhbio/api/machine/multiblock/WorkableLivingMultiblockMachine.java index bd5e063..94d83a1 100644 --- a/src/main/java/com/moguang/ctnhbio/api/machine/multiblock/WorkableLivingMultiblockMachine.java +++ b/src/main/java/com/moguang/ctnhbio/api/machine/multiblock/WorkableLivingMultiblockMachine.java @@ -48,6 +48,7 @@ public class WorkableLivingMultiblockMachine extends RecipeElectricMultiblockMac protected LivingMetaMachineEntity machineEntity; protected TickableSubscription entityBindingSubscription; + protected TickableSubscription growthSubscription; public WorkableLivingMultiblockMachine(IMachineBlockEntity holder, Object... args) { super(holder, args); @@ -58,13 +59,17 @@ public WorkableLivingMultiblockMachine(IMachineBlockEntity holder, Object... arg @Override public LivingMetaMachineEntity getMachineEntity() { - if (machineEntity == null || !machineEntity.isAlive() || machineEntity.isRemoved()) { + if (!isStructureRevalidationPending() && + (machineEntity == null || !machineEntity.isAlive() || machineEntity.isRemoved())) { refreshMachineEntityBinding(); } return machineEntity; } protected void refreshMachineEntityBinding() { + if (isStructureRevalidationPending()) { + return; + } if (holder instanceof LivingMetaMachineBlockEntity blockEntity) { blockEntity.refreshHostedEntityBinding(true); machineEntity = blockEntity.getHostedEntity(); @@ -83,7 +88,9 @@ public float getNutrientCapacity() { @Override public void extractNutrient(float amount) { - nutrientHandler.extract(amount); + if (isStructureOperational()) { + nutrientHandler.extract(amount); + } } @Override @@ -97,7 +104,7 @@ public InteractionResult tryToOpenUI(Player player, InteractionHand hand, BlockH // 判断是否是食物 if (stack.isEdible()) { - if (!getLevel().isClientSide) { + if (!getLevel().isClientSide && !isStructureRevalidationPending()) { // 消耗一个物品 if (!player.getAbilities().instabuild) { stack.shrink(1); @@ -109,7 +116,7 @@ public InteractionResult tryToOpenUI(Player player, InteractionHand hand, BlockH getLevel().playSound(null, getPos().getX(), getPos().getY(), getPos().getZ(), SoundEvents.GENERIC_EAT, SoundSource.PLAYERS, 1.0f, 1.0f); - if (!isFormed()) checkGrow(); + if (!isStructureFormedSnapshot()) checkGrow(); } return InteractionResult.sidedSuccess(getLevel().isClientSide); @@ -150,9 +157,9 @@ public void onLoad() { super.onLoad(); refreshMachineEntityBinding(); entityBindingSubscription = subscribeServerTick(entityBindingSubscription, this::refreshMachineEntityBinding); - // subscribeServerTick(this::checkGrow); - checkGrow(); - subscribeServerTick(this::tickGrow); + // super.onLoad() has already queued the authoritative structure check. Growth waits for that check to + // distinguish a definitive mismatch from a temporarily unloaded structure. + growthSubscription = subscribeServerTick(growthSubscription, this::tickGrow); } @Override @@ -171,32 +178,37 @@ public void onMachinePlaced(LivingEntity player, ItemStack stack) { } public boolean shouldTick(int interval) { - return (!isFormed() && getOffsetTimer() % interval == 0) || getOffsetTimer() % 10 * interval == 0; + return getOffsetTimer() % interval == 0; } - public void checkGrow() { - if (true || shouldTick(20)) { - isFormed = false; - checkPattern(); - if (!isFormed()) { - if (growingBlockPattern == null) - growingBlockPattern = GrowingBlockPattern.getGrowingBlockPattern(getPattern()); - - if (growingBlockPattern.growPlan.isCompleted()) - growingBlockPattern.generateGrowPlan(this, new GrowingBlockPattern.GrowSetting()); - } + private boolean canGrow() { + return !isStructureRevalidationPending() && !isStructureFormedSnapshot(); + } + public void checkGrow() { + if (!canGrow() || isRemote()) { + return; + } + if (growingBlockPattern == null) { + growingBlockPattern = GrowingBlockPattern.getGrowingBlockPattern(getPattern()); + } + if (growingBlockPattern != null && growingBlockPattern.growPlan.isCompleted()) { + growingBlockPattern.generateGrowPlan(this, new GrowingBlockPattern.GrowSetting()); } } public void tickGrow() { + if (!canGrow()) { + return; + } + if (shouldTick(20)) { + checkGrow(); + } if (shouldTick(2) && getNutrientAmount() >= NUTRIENT_NEEDED_FOR_GROWTH && growingBlockPattern != null && growingBlockPattern.growPlan.tick()) { nutrientHandler.extract(NUTRIENT_NEEDED_FOR_GROWTH); - if (growingBlockPattern.growPlan.isCompleted()) checkPattern(); } - // updatePartPositions(); } } diff --git a/src/main/java/com/moguang/ctnhbio/api/pattern/GrowingBlockPattern.java b/src/main/java/com/moguang/ctnhbio/api/pattern/GrowingBlockPattern.java index 1f57cb7..9f05a94 100644 --- a/src/main/java/com/moguang/ctnhbio/api/pattern/GrowingBlockPattern.java +++ b/src/main/java/com/moguang/ctnhbio/api/pattern/GrowingBlockPattern.java @@ -22,7 +22,6 @@ import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.*; import java.util.stream.Collectors; @@ -139,7 +138,9 @@ public BuildTask(BlockPos pos, TraceabilityPredicate predicate) { } public boolean execute() { - updateWorldState(context.worldState, pos, predicate); + if (!context.worldState.update(pos, predicate)) { + return false; + } if (!canReplaceExistingBlock(pos, predicate)) return false; @@ -176,13 +177,18 @@ public void add(BuildTask task) { } public boolean tick() { - if (isCompleted()) return false; + if (isCompleted() || context.controller.isStructureRevalidationPending()) return false; // 每tick处理一定数量的方块 try { for (int i = 0; i < BLOCKS_PER_TICK && !isCompleted(); i++) { BuildTask task = buildQueue.poll(); while (task != null) { + if (!context.world.isLoaded(task.pos)) { + buildQueue.add(task); + context.controller.setStructureRevalidationPending(true); + return false; + } if (task.isFluid()) { fluidQueue.add(task); } else if (task.execute()) return true; @@ -190,6 +196,11 @@ public boolean tick() { } task = fluidQueue.poll(); if (task != null) { + if (!context.world.isLoaded(task.pos)) { + fluidQueue.add(task); + context.controller.setStructureRevalidationPending(true); + return false; + } return task.execute(); } } @@ -273,17 +284,17 @@ private void initializeBuildContext(@NotNull IMultiController controller, GrowSe context.world = controller.self().getLevel(); - context.worldState = controller.getMultiblockState(); context.settings = setting; context.controller = controller; context.centerPos = controller.self().getPos(); + // Growth predicates need scratch counters and match context, but must never clean or mutate the controller's + // authoritative state owned by GTM's queued validator. + context.worldState = new MultiblockState(context.world, context.centerPos); + context.worldState.clean(); context.facing = controller.self().getFrontFacing(); context.upwardsFacing = controller.self().getUpwardsFacing(); context.isFlipped = controller.self().isFlipped(); context.minZ = -centerOffset[4]; - - clearWorldState(context.worldState); - // context.blocks.put(context.centerPos, controller); } private int[] calculateLayerRepeatCounts(GrowSetting settings) { @@ -470,25 +481,6 @@ public static class BuildContext { boolean inFluid; } - private void clearWorldState(MultiblockState worldState) { - try { - Class clazz = Class.forName("com.gregtechceu.gtceu.api.pattern.MultiblockState"); - // Object obj = clazz.newInstance(); - Method method = clazz.getDeclaredMethod("clean"); - method.setAccessible(true); - method.invoke(worldState); - } catch (Exception ignored) {} - } - - private void updateWorldState(MultiblockState worldState, BlockPos posIn, TraceabilityPredicate predicate) { - try { - Class clazz = Class.forName("com.gregtechceu.gtceu.api.pattern.MultiblockState"); - Method method = clazz.getDeclaredMethod("update", BlockPos.class, TraceabilityPredicate.class); - method.setAccessible(true); - method.invoke(worldState, posIn, predicate); - } catch (Exception ignored) {} - } - private BlockPos setActualRelativeOffset(int x, int y, int z, Direction facing, Direction upwardsFacing, boolean isFlipped) { int[] c0 = new int[] { x, y, z }, c1 = new int[3]; diff --git a/src/main/java/com/moguang/ctnhbio/machine/greatflesh/GreatFleshMachine.java b/src/main/java/com/moguang/ctnhbio/machine/greatflesh/GreatFleshMachine.java index 94a476c..a31e084 100644 --- a/src/main/java/com/moguang/ctnhbio/machine/greatflesh/GreatFleshMachine.java +++ b/src/main/java/com/moguang/ctnhbio/machine/greatflesh/GreatFleshMachine.java @@ -24,7 +24,7 @@ public GreatFleshMachine(IMachineBlockEntity holder, Object... args) { @Override public void afterWorking() { super.afterWorking(); - if (!isRemote()) { + if (!isRemote() && isStructureOperational()) { tryDifferentiate(); } } @@ -46,6 +46,9 @@ public void tryDifferentiate() { // 2. 下一tick销毁所有机器 level.getServer().submit(() -> { + if (!isStructureOperational() || MetaMachine.getMachine(level, getPos()) != this) { + return; + } for (BlockPos pos : positionsToDestroy) { level.destroyBlock(pos, true); } diff --git a/src/main/java/com/moguang/ctnhbio/machine/multiblock/part/NeuralModelAccessorMachine.java b/src/main/java/com/moguang/ctnhbio/machine/multiblock/part/NeuralModelAccessorMachine.java index 3d76f06..4d51023 100644 --- a/src/main/java/com/moguang/ctnhbio/machine/multiblock/part/NeuralModelAccessorMachine.java +++ b/src/main/java/com/moguang/ctnhbio/machine/multiblock/part/NeuralModelAccessorMachine.java @@ -9,7 +9,6 @@ import com.gregtechceu.gtceu.api.gui.widget.IntInputWidget; import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; import com.gregtechceu.gtceu.api.machine.feature.IMachineLife; -import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMultiController; import com.gregtechceu.gtceu.api.machine.feature.multiblock.IWorkableMultiController; import com.gregtechceu.gtceu.api.machine.multiblock.RecipeMultiblockMachine; import com.gregtechceu.gtceu.api.machine.multiblock.part.MultiblockPartMachine; @@ -26,6 +25,7 @@ import com.lowdragmc.lowdraglib.syncdata.annotation.Persisted; import com.lowdragmc.lowdraglib.utils.Position; +import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -36,8 +36,6 @@ import com.moguang.ctnhbio.api.machine.trait.NeuralModelContainer; import lombok.Getter; import lombok.Setter; -import org.jetbrains.annotations.MustBeInvokedByOverriders; -import org.jetbrains.annotations.NotNull; import tech.vixhentx.mcmod.ctnhlib.langprovider.Lang; import java.util.List; @@ -77,12 +75,11 @@ public NeuralModelAccessorMachine(IMachineBlockEntity holder, boolean isAdvanced this.isAdvanced = isAdvanced; } - @MustBeInvokedByOverriders @Override - public void removedFromController(@NotNull IMultiController controller) { - super.removedFromController(controller); - if (controllers.isEmpty()) + public void onControllerBindingRetired(BlockPos controllerPos, long instanceId) { + if (!isFormed()) { setLocked(false); + } } @Override @@ -130,7 +127,7 @@ public boolean canShared() { private void tryOutputModel() { var machine = getControllers().stream().findFirst().orElse(null); - if (machine instanceof RecipeMultiblockMachine r && r.isFormed()) { + if (machine instanceof RecipeMultiblockMachine r && r.isStructureOperational()) { if (r.isActive()) tick = 0; else if (isOutputModel() && !modelHolder.getStackInSlot(0).isEmpty()) tick++; if (tick >= ticksPerCycle) {