状态同步+模型同步

This commit is contained in:
GaLi 2026-03-20 17:39:37 +08:00
parent e438021e84
commit dc7fc6d0b1
12 changed files with 568 additions and 9 deletions

View File

@ -145,7 +145,11 @@ public class ExtendedAEPlus {
MirrorPatternProviderBlockEntity.class,
ModBlockEntities.MIRROR_PATTERN_PROVIDER_BE.get(),
null,
null
(level, pos, state, blockEntity) -> MirrorPatternProviderBlockEntity.serverTick(
level,
pos,
state,
(MirrorPatternProviderBlockEntity) blockEntity)
);
LOGGER.info("Bound UploadCoreBlockEntity to assembler matrix upload core block.");
@ -187,4 +191,4 @@ public class ExtendedAEPlus {
public void onServerStarting(ServerStartingEvent event) {
LOGGER.info("HELLO from server starting");
}
}
}

View File

@ -1,10 +1,101 @@
package com.extendedae_plus.content.ae2;
import appeng.api.implementations.items.IMemoryCard;
import appeng.block.crafting.PatternProviderBlock;
import appeng.util.InteractionUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.Nullable;
public class MirrorPatternProviderBlock extends PatternProviderBlock {
public MirrorPatternProviderBlock() {
super();
}
@Override
public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
super.setPlacedBy(level, pos, state, placer, stack);
if (level.isClientSide) {
return;
}
var mirror = this.getMirror(level, pos);
if (mirror == null) {
return;
}
if (mirror.tryBindToAdjacentMaster()) {
this.notifyPlayer(placer, mirror.createBoundMessage());
} else {
this.notifyPlayer(placer, Component.translatable("extendedae_plus.message.mirror_pattern_provider.missing_master"));
}
}
@Override
protected ItemInteractionResult useItemOn(ItemStack heldItem, BlockState state, Level level, BlockPos pos,
Player player, InteractionHand hand, BlockHitResult hit) {
var mirror = this.getMirror(level, pos);
if (mirror == null) {
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
}
if (InteractionUtil.canWrenchRotate(heldItem) || heldItem.getItem() instanceof IMemoryCard) {
if (!level.isClientSide) {
player.displayClientMessage(
Component.translatable("extendedae_plus.message.mirror_pattern_provider.readonly"),
true);
}
return ItemInteractionResult.sidedSuccess(level.isClientSide);
}
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
}
@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player,
BlockHitResult hitResult) {
var mirror = this.getMirror(level, pos);
if (mirror == null) {
return InteractionResult.PASS;
}
if (!level.isClientSide) {
if (player.isShiftKeyDown()) {
if (mirror.tryBindToAdjacentMaster()) {
player.displayClientMessage(mirror.createBoundMessage(), true);
} else {
player.displayClientMessage(
Component.translatable("extendedae_plus.message.mirror_pattern_provider.missing_master"),
true);
}
} else {
player.displayClientMessage(mirror.getStatusMessage(), true);
}
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
@Nullable
private MirrorPatternProviderBlockEntity getMirror(Level level, BlockPos pos) {
var blockEntity = this.getBlockEntity(level, pos);
return blockEntity instanceof MirrorPatternProviderBlockEntity mirror ? mirror : null;
}
private void notifyPlayer(@Nullable LivingEntity entity, Component message) {
if (entity instanceof Player player) {
player.displayClientMessage(message, true);
}
}
}

View File

@ -1,12 +1,429 @@
package com.extendedae_plus.content.ae2;
import appeng.api.config.LockCraftingMode;
import appeng.api.config.Settings;
import appeng.api.ids.AEComponents;
import appeng.api.networking.IManagedGridNode;
import appeng.api.stacks.GenericStack;
import appeng.block.crafting.PatternProviderBlock;
import appeng.blockentity.crafting.PatternProviderBlockEntity;
import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.util.SettingsFrom;
import appeng.util.inv.AppEngInternalInventory;
import com.extendedae_plus.init.ModBlockEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.ItemContainerContents;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
public class MirrorPatternProviderBlockEntity extends PatternProviderBlockEntity {
private static final String TAG_MASTER = "mirrorMaster";
private static final String TAG_MASTER_DIMENSION = "dimension";
private static final String TAG_MASTER_POS = "pos";
private static final int SYNC_INTERVAL = 2;
@Nullable
private ResourceKey<Level> masterDimension;
@Nullable
private BlockPos masterPos;
public MirrorPatternProviderBlockEntity(BlockPos pos, BlockState blockState) {
super(ModBlockEntities.MIRROR_PATTERN_PROVIDER_BE.get(), pos, blockState);
}
@Override
protected PatternProviderLogic createLogic() {
return new MirrorLogic(this.getMainNode(), this);
}
public static void serverTick(Level level, BlockPos pos, BlockState state, MirrorPatternProviderBlockEntity blockEntity) {
if (level instanceof ServerLevel serverLevel) {
blockEntity.serverTick(serverLevel);
}
}
private void serverTick(ServerLevel level) {
if (Math.floorMod(level.getGameTime() + this.getBlockPos().asLong(), SYNC_INTERVAL) != 0) {
return;
}
this.syncOrRebind(false);
}
@Override
public void onReady() {
super.onReady();
if (this.getLevel() instanceof ServerLevel serverLevel) {
this.syncOrRebind(true);
}
}
@Override
public void saveAdditional(CompoundTag data, HolderLookup.Provider registries) {
super.saveAdditional(data, registries);
if (this.masterDimension != null && this.masterPos != null) {
var masterTag = new CompoundTag();
masterTag.putString(TAG_MASTER_DIMENSION, this.masterDimension.location().toString());
masterTag.putLong(TAG_MASTER_POS, this.masterPos.asLong());
data.put(TAG_MASTER, masterTag);
} else {
data.remove(TAG_MASTER);
}
}
@Override
public void loadTag(CompoundTag data, HolderLookup.Provider registries) {
super.loadTag(data, registries);
this.masterDimension = null;
this.masterPos = null;
if (data.contains(TAG_MASTER, Tag.TAG_COMPOUND)) {
var masterTag = data.getCompound(TAG_MASTER);
if (masterTag.contains(TAG_MASTER_DIMENSION, Tag.TAG_STRING) && masterTag.contains(TAG_MASTER_POS, Tag.TAG_LONG)) {
this.masterDimension = ResourceKey.create(
Registries.DIMENSION,
ResourceLocation.parse(masterTag.getString(TAG_MASTER_DIMENSION)));
this.masterPos = BlockPos.of(masterTag.getLong(TAG_MASTER_POS));
}
}
}
@Override
public void addAdditionalDrops(Level level, BlockPos pos, List<ItemStack> drops) {
var patternInventory = this.getPatternInventory();
var mirroredPatterns = patternInventory.toItemContainerContents();
patternInventory.fromItemContainerContents(ItemContainerContents.EMPTY);
this.getLogic().updatePatterns();
this.getLogic().addDrops(drops);
patternInventory.fromItemContainerContents(mirroredPatterns);
this.getLogic().updatePatterns();
}
@Override
public void clearContent() {
this.getLogic().clearContent();
}
public boolean tryBindToAdjacentMaster() {
if (!(this.getLevel() instanceof ServerLevel)) {
return false;
}
var master = this.findAdjacentMaster();
if (master == null) {
return false;
}
this.bindToMaster(master);
this.syncFromMaster(master);
return true;
}
@Nullable
public PatternProviderBlockEntity getMaster() {
if (this.masterDimension == null || this.masterPos == null || !(this.getLevel() instanceof ServerLevel serverLevel)) {
return null;
}
ServerLevel masterLevel;
if (serverLevel.dimension() == this.masterDimension) {
masterLevel = serverLevel;
} else {
masterLevel = serverLevel.getServer().getLevel(this.masterDimension);
}
if (masterLevel == null || !masterLevel.hasChunkAt(this.masterPos)) {
return null;
}
var blockEntity = masterLevel.getBlockEntity(this.masterPos);
return isValidMaster(blockEntity) ? (PatternProviderBlockEntity) blockEntity : null;
}
public Component createBoundMessage() {
if (this.masterPos != null) {
return Component.translatable(
"extendedae_plus.message.mirror_pattern_provider.bound",
this.masterPos.getX(),
this.masterPos.getY(),
this.masterPos.getZ());
}
return Component.translatable("extendedae_plus.message.mirror_pattern_provider.missing_master");
}
public Component getStatusMessage() {
if (this.masterPos != null) {
return Component.translatable(
"extendedae_plus.message.mirror_pattern_provider.following",
this.masterPos.getX(),
this.masterPos.getY(),
this.masterPos.getZ());
}
return Component.translatable("extendedae_plus.message.mirror_pattern_provider.missing_master");
}
private void syncOrRebind(boolean allowRebind) {
var master = this.getMaster();
if (master != null) {
this.syncFromMaster(master);
return;
}
if (allowRebind || this.hasNoBoundMaster()) {
if (this.tryBindToAdjacentMaster()) {
return;
}
}
if (this.shouldClearBrokenBinding()) {
this.clearMasterBinding(true);
}
}
private boolean hasNoBoundMaster() {
return this.masterDimension == null || this.masterPos == null;
}
private boolean shouldClearBrokenBinding() {
if (this.masterDimension == null || this.masterPos == null || !(this.getLevel() instanceof ServerLevel serverLevel)) {
return true;
}
ServerLevel masterLevel;
if (serverLevel.dimension() == this.masterDimension) {
masterLevel = serverLevel;
} else {
masterLevel = serverLevel.getServer().getLevel(this.masterDimension);
}
if (masterLevel == null || !masterLevel.hasChunkAt(this.masterPos)) {
return false;
}
return !isValidMaster(masterLevel.getBlockEntity(this.masterPos));
}
private void clearMasterBinding(boolean clearMirroredPatterns) {
var hadBinding = this.masterDimension != null || this.masterPos != null;
this.masterDimension = null;
this.masterPos = null;
var changed = hadBinding;
if (clearMirroredPatterns) {
changed |= this.clearMirroredPatterns();
}
if (changed) {
this.saveChanges();
this.markForClientUpdate();
}
}
private boolean bindToMaster(PatternProviderBlockEntity master) {
var masterLevel = master.getLevel();
if (masterLevel == null) {
return false;
}
var newDimension = masterLevel.dimension();
var newPos = master.getBlockPos().immutable();
var changed = !Objects.equals(this.masterDimension, newDimension) || !Objects.equals(this.masterPos, newPos);
this.masterDimension = newDimension;
this.masterPos = newPos;
if (changed) {
this.saveChanges();
this.markForClientUpdate();
}
return changed;
}
@Nullable
private PatternProviderBlockEntity findAdjacentMaster() {
var level = this.getLevel();
if (level == null) {
return null;
}
for (var direction : Direction.values()) {
var adjacent = level.getBlockEntity(this.getBlockPos().relative(direction));
if (isValidMaster(adjacent)) {
return (PatternProviderBlockEntity) adjacent;
}
}
return null;
}
private boolean syncFromMaster(PatternProviderBlockEntity master) {
var changed = this.bindToMaster(master);
changed |= this.syncMirroredSettings(master);
changed |= this.syncMirroredPatterns(master);
if (changed) {
this.saveChanges();
this.markForClientUpdate();
}
return changed;
}
private boolean syncMirroredSettings(PatternProviderBlockEntity master) {
if (!this.hasDifferentMirroredSettings(master)) {
return false;
}
var builder = DataComponentMap.builder();
if (master.getCustomName() != null) {
builder.set(AEComponents.EXPORTED_CUSTOM_NAME, master.getCustomName());
}
builder.set(AEComponents.EXPORTED_SETTINGS, master.getConfigManager().exportSettings());
builder.set(AEComponents.EXPORTED_PRIORITY, master.getPriority());
builder.set(AEComponents.EXPORTED_PUSH_DIRECTION, master.getBlockState().getValue(PatternProviderBlock.PUSH_DIRECTION));
this.importSettings(SettingsFrom.MEMORY_CARD, builder.build(), null);
return true;
}
private boolean hasDifferentMirroredSettings(PatternProviderBlockEntity master) {
var mirrorLogic = this.getLogic();
var masterLogic = master.getLogic();
return !Objects.equals(this.getCustomName(), master.getCustomName())
|| mirrorLogic.getPriority() != masterLogic.getPriority()
|| mirrorLogic.getConfigManager().getSetting(Settings.BLOCKING_MODE)
!= masterLogic.getConfigManager().getSetting(Settings.BLOCKING_MODE)
|| mirrorLogic.getConfigManager().getSetting(Settings.PATTERN_ACCESS_TERMINAL)
!= masterLogic.getConfigManager().getSetting(Settings.PATTERN_ACCESS_TERMINAL)
|| mirrorLogic.getConfigManager().getSetting(Settings.LOCK_CRAFTING_MODE)
!= masterLogic.getConfigManager().getSetting(Settings.LOCK_CRAFTING_MODE)
|| this.getBlockState().getValue(PatternProviderBlock.PUSH_DIRECTION)
!= master.getBlockState().getValue(PatternProviderBlock.PUSH_DIRECTION);
}
private boolean syncMirroredPatterns(PatternProviderBlockEntity master) {
var mirrorInventory = this.getPatternInventory();
var masterInventory = asPatternInventory(master.getLogic().getPatternInv());
if (this.hasSamePatterns(masterInventory, mirrorInventory)) {
return false;
}
mirrorInventory.fromItemContainerContents(masterInventory.toItemContainerContents());
this.getLogic().updatePatterns();
return true;
}
private boolean clearMirroredPatterns() {
var patternInventory = this.getPatternInventory();
if (this.isPatternInventoryEmpty(patternInventory)) {
return false;
}
patternInventory.fromItemContainerContents(ItemContainerContents.EMPTY);
this.getLogic().updatePatterns();
return true;
}
private boolean hasSamePatterns(AppEngInternalInventory masterInventory, AppEngInternalInventory mirrorInventory) {
if (masterInventory.size() != mirrorInventory.size()) {
return false;
}
for (int slot = 0; slot < masterInventory.size(); slot++) {
if (!sameStack(masterInventory.getStackInSlot(slot), mirrorInventory.getStackInSlot(slot))) {
return false;
}
}
return true;
}
private boolean isPatternInventoryEmpty(AppEngInternalInventory inventory) {
for (int slot = 0; slot < inventory.size(); slot++) {
if (!inventory.getStackInSlot(slot).isEmpty()) {
return false;
}
}
return true;
}
private AppEngInternalInventory getPatternInventory() {
return asPatternInventory(this.getLogic().getPatternInv());
}
private static AppEngInternalInventory asPatternInventory(Object inventory) {
return (AppEngInternalInventory) inventory;
}
private static boolean sameStack(ItemStack left, ItemStack right) {
if (left.isEmpty() && right.isEmpty()) {
return true;
}
return ItemStack.isSameItemSameComponents(left, right) && left.getCount() == right.getCount();
}
private static boolean isValidMaster(@Nullable BlockEntity blockEntity) {
return blockEntity instanceof PatternProviderBlockEntity
&& !(blockEntity instanceof MirrorPatternProviderBlockEntity)
&& !blockEntity.isRemoved();
}
private static final class MirrorLogic extends PatternProviderLogic {
private final MirrorPatternProviderBlockEntity mirrorHost;
private MirrorLogic(IManagedGridNode mainNode, MirrorPatternProviderBlockEntity mirrorHost) {
super(mainNode, mirrorHost);
this.mirrorHost = mirrorHost;
}
@Override
public LockCraftingMode getCraftingLockedReason() {
var master = this.mirrorHost.getMaster();
if (master != null) {
var masterReason = master.getLogic().getCraftingLockedReason();
if (masterReason != LockCraftingMode.NONE) {
return masterReason;
}
}
return super.getCraftingLockedReason();
}
@Override
public @Nullable GenericStack getUnlockStack() {
var master = this.mirrorHost.getMaster();
if (master != null && master.getLogic().getCraftingLockedReason() == LockCraftingMode.LOCK_UNTIL_RESULT) {
return master.getLogic().getUnlockStack();
}
return super.getUnlockStack();
}
}
}

View File

@ -1,9 +1,33 @@
{
"multipart": [
{
"apply": {
"model": "extendedae_plus:block/mirror_pattern_provider"
}
"variants": {
"push_direction=all": {
"model": "extendedae_plus:block/mirror_pattern_provider"
},
"push_direction=down": {
"model": "extendedae_plus:block/mirror_pattern_provider_oriented",
"x": 180
},
"push_direction=east": {
"model": "extendedae_plus:block/mirror_pattern_provider_oriented",
"x": 90,
"y": 90
},
"push_direction=north": {
"model": "extendedae_plus:block/mirror_pattern_provider_oriented",
"x": 90
},
"push_direction=south": {
"model": "extendedae_plus:block/mirror_pattern_provider_oriented",
"x": 90,
"y": 180
},
"push_direction=up": {
"model": "extendedae_plus:block/mirror_pattern_provider_oriented"
},
"push_direction=west": {
"model": "extendedae_plus:block/mirror_pattern_provider_oriented",
"x": 90,
"y": 270
}
]
}
}

View File

@ -230,6 +230,10 @@
"extendedae_plus.message.transceiver.channel": "Channel: %d",
"extendedae_plus.message.transceiver.mode_master": "Mode: Master",
"extendedae_plus.message.transceiver.mode_slave": "Mode: Slave",
"extendedae_plus.message.mirror_pattern_provider.bound": "Mirror pattern provider bound to master provider: (%d, %d, %d)",
"extendedae_plus.message.mirror_pattern_provider.following": "Mirror pattern provider is following master provider: (%d, %d, %d)",
"extendedae_plus.message.mirror_pattern_provider.missing_master": "No adjacent master pattern provider was found.",
"extendedae_plus.message.mirror_pattern_provider.readonly": "Mirror pattern provider has no standalone UI and follows the master's state and direction.",
"extendedae_plus.state.master": "Master",
"extendedae_plus.state.slave": "Slave",

View File

@ -225,6 +225,10 @@
"extendedae_plus.message.transceiver.channel": "频道:%d",
"extendedae_plus.message.transceiver.mode_master": "模式:主端",
"extendedae_plus.message.transceiver.mode_slave": "模式:从端",
"extendedae_plus.message.mirror_pattern_provider.bound": "镜像样板供应器已绑定到主供应器:(%d, %d, %d)",
"extendedae_plus.message.mirror_pattern_provider.following": "镜像样板供应器当前跟随主供应器:(%d, %d, %d)",
"extendedae_plus.message.mirror_pattern_provider.missing_master": "未找到相邻的主样板供应器。",
"extendedae_plus.message.mirror_pattern_provider.readonly": "镜像样板供应器没有独立界面,状态与朝向会跟随主供应器。",
"extendedae_plus.state.master": "主模式",
"extendedae_plus.state.slave": "从模式",

View File

@ -1,3 +1,6 @@
{
"parent": "ae2:block/pattern_provider_oriented"
"parent": "block/cube_all",
"textures": {
"all": "extendedae_plus:block/mirror_pattern_provider/mirror_pattern_provider_default"
}
}

View File

@ -0,0 +1,12 @@
{
"parent": "block/cube",
"textures": {
"particle": "extendedae_plus:block/mirror_pattern_provider/mirror_pattern_provider_default",
"down": "extendedae_plus:block/mirror_pattern_provider/mirror_pattern_provider_back",
"up": "extendedae_plus:block/mirror_pattern_provider/mirror_pattern_provider_front",
"north": "extendedae_plus:block/mirror_pattern_provider/mirror_pattern_provider_side",
"south": "extendedae_plus:block/mirror_pattern_provider/mirror_pattern_provider_side",
"east": "extendedae_plus:block/mirror_pattern_provider/mirror_pattern_provider_side",
"west": "extendedae_plus:block/mirror_pattern_provider/mirror_pattern_provider_side"
}
}