From 4328dfc03fe16ab212bca0335007fbb56beace7f Mon Sep 17 00:00:00 2001 From: SenecaIO Date: Tue, 18 Nov 2025 17:14:56 -0700 Subject: [PATCH 1/4] Diving bell implementation The whole dang thing. --- .../api/capability/CosmicCapabilities.java | 4 + .../api/capability/ITeleportOrigin.java | 27 +++ .../common/block/DivingBellEscapePad.java | 152 ++++++++++++ .../cosmiccore/common/data/CosmicBlocks.java | 7 + .../machine/multiblock/multi/DivingBell.java | 49 ++++ .../multi/logic/DivingBellMachine.java | 225 ++++++++++++++++++ .../multi/modular/MultiblockInit.java | 1 + .../common/teleporter/LandingZoneHelper.java | 74 ++++++ .../common/teleporter/SafeTeleporter.java | 69 ++++++ .../common/teleporter/TeleportOrigin.java | 132 ++++++++++ .../common/teleporter/TeleportOriginCap.java | 76 ++++++ .../teleporter/TeleportPadRegistry.java | 79 ++++++ 12 files changed, 895 insertions(+) create mode 100644 src/main/java/com/ghostipedia/cosmiccore/api/capability/ITeleportOrigin.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/block/DivingBellEscapePad.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/DivingBell.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/teleporter/LandingZoneHelper.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/teleporter/SafeTeleporter.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOrigin.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOriginCap.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportPadRegistry.java diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/capability/CosmicCapabilities.java b/src/main/java/com/ghostipedia/cosmiccore/api/capability/CosmicCapabilities.java index 615f7bc62..734aa683f 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/api/capability/CosmicCapabilities.java +++ b/src/main/java/com/ghostipedia/cosmiccore/api/capability/CosmicCapabilities.java @@ -10,7 +10,11 @@ public class CosmicCapabilities { public static Capability CAPABILITY_SOUL_CONTAINER = CapabilityManager .get(new CapabilityToken<>() {}); + public static Capability CAPABILITY_TELEPORT_ORIGIN = CapabilityManager + .get(new CapabilityToken<>() {}); + public static void register(RegisterCapabilitiesEvent event) { event.register(ISoulContainer.class); + event.register(ITeleportOrigin.class); } } diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/capability/ITeleportOrigin.java b/src/main/java/com/ghostipedia/cosmiccore/api/capability/ITeleportOrigin.java new file mode 100644 index 000000000..fb30e2e44 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/api/capability/ITeleportOrigin.java @@ -0,0 +1,27 @@ +package com.ghostipedia.cosmiccore.api.capability; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; + +// Capability for storing teleportation origin of a player. +public interface ITeleportOrigin { + + void setOriginDimension(ResourceKey dimension); // Set the origin dimension the player teleported from. + + ResourceKey getOriginDimension(); // Get the origin dimension, or null if not set. + + void setOriginPosition(Vec3 position); // Set the origin position the player teleported from. + + Vec3 getOriginPosition(); // Get the origin position, or null if not set. + + void setOriginRotation(float yaw, float pitch); // Set the player's rotation when they teleported. + + float getOriginYaw(); // Get the origin yaw rotation. + + float getOriginPitch(); // Get the origin pitch rotation. + + boolean hasValidOrigin(); // Check if this player has valid origin data. + + void clearOriginData(); // Clear all origin data. +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/block/DivingBellEscapePad.java b/src/main/java/com/ghostipedia/cosmiccore/common/block/DivingBellEscapePad.java new file mode 100644 index 000000000..5e4b7229d --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/block/DivingBellEscapePad.java @@ -0,0 +1,152 @@ +package com.ghostipedia.cosmiccore.common.block; + +import com.ghostipedia.cosmiccore.common.abyss.AbyssRules; +import com.ghostipedia.cosmiccore.common.teleporter.SafeTeleporter; +import com.ghostipedia.cosmiccore.common.teleporter.TeleportOriginCap; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +// Escape Pad block that teleports players back to their origin dimension. Placed automatically by the Diving Bell. +public class DivingBellEscapePad extends Block { + + public DivingBellEscapePad(Properties properties) { + super(properties); + } + + @Override + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, + InteractionHand hand, BlockHitResult hit) { + if (level.isClientSide) { + return InteractionResult.SUCCESS; + } + + ServerPlayer serverPlayer = (ServerPlayer) player; + serverPlayer.getCapability(TeleportOriginCap.CAP).ifPresent(cap -> { + if (!cap.hasValidOrigin()) { + // No valid origin data - send to respawn point + serverPlayer.displayClientMessage( + Component.translatable("cosmiccore.divingbell.no_return"), true); + teleportToRespawn(serverPlayer); + return; + } + + // Get return destination + var originDim = cap.getOriginDimension(); + Vec3 originPos = cap.getOriginPosition(); + float originYaw = cap.getOriginYaw(); + float originPitch = cap.getOriginPitch(); + + MinecraftServer server = level.getServer(); + if (server == null) return; + + ServerLevel originLevel = server.getLevel(originDim); + if (originLevel == null) { + // Origin dimension doesn't exist + serverPlayer.displayClientMessage( + Component.translatable("cosmiccore.divingbell.invalid_origin"), true); + cap.clearOriginData(); + teleportToRespawn(serverPlayer); + return; + } + + // Validate origin is safe (chunk loaded, not void) + BlockPos originBlockPos = BlockPos.containing(originPos); + if (!isOriginSafe(originLevel, originBlockPos)) { + serverPlayer.displayClientMessage( + Component.translatable("cosmiccore.divingbell.unsafe_origin"), true); + cap.clearOriginData(); + teleportToRespawn(serverPlayer); + return; + } + + // Teleport back to origin + serverPlayer.changeDimension(originLevel, new SafeTeleporter(originBlockPos)); + + // Restore rotation + serverPlayer.setYRot(originYaw); + serverPlayer.setXRot(originPitch); + + // Clear Abyss decay flag + // Don't think this is needed... +// serverPlayer.getCapability(com.ghostipedia.cosmiccore.common.abyss.AbyssBudgetCap.CAP) +// .ifPresent(abyssCap -> { +// abyssCap.setDecaying(AbyssRules.DIM, false); +// }); + + // Success message + serverPlayer.displayClientMessage( + Component.translatable("cosmiccore.divingbell.returned"), true); + + // Clear origin data + cap.clearOriginData(); + }); + + return InteractionResult.CONSUME; + } + + // Check if the origin position is safe to teleport to. + private boolean isOriginSafe(ServerLevel level, BlockPos pos) { + // Check if chunk is loaded + if (!level.isLoaded(pos)) { + return false; + } + + // Check if it's not in void + if (pos.getY() < level.getMinBuildHeight()) { + return false; + } + + // Check 2-block tall air column where player stands + BlockState at = level.getBlockState(pos); + BlockState above = level.getBlockState(pos.above()); + +// if (at.isSuffocating(level, pos)) { +// return false; +// } +// +// if (!at.getFluidState().isEmpty()) { +// return false; +// } + + // Head level: same checks to ensure full 2-block clearance + if (above.isSuffocating(level, pos.above())) { + return false; + } + + if (!above.getFluidState().isEmpty()) { + return false; + } + + return true; + } + + // Teleport player to their respawn point as fallback. + private void teleportToRespawn(ServerPlayer player) { + BlockPos respawn = player.getRespawnPosition(); + ServerLevel respawnLevel = player.server.getLevel(player.getRespawnDimension()); + + if (respawn != null && respawnLevel != null) { + // Teleport to bed/respawn anchor + player.teleportTo(respawnLevel, respawn.getX() + 0.5, respawn.getY(), respawn.getZ() + 0.5, + player.getYRot(), player.getXRot()); + } else { + // Ultimate fallback - overworld spawn (should not fail under normal circumstances) + ServerLevel overworld = player.server.overworld(); + BlockPos spawn = overworld.getSharedSpawnPos(); + player.teleportTo(overworld, spawn.getX() + 0.5, spawn.getY(), spawn.getZ() + 0.5, + player.getYRot(), player.getXRot()); + } + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java b/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java index 78e9ce055..7439c025b 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java @@ -4,6 +4,7 @@ import com.ghostipedia.cosmiccore.api.CosmicCoreAPI; import com.ghostipedia.cosmiccore.api.block.IMagnetType; import com.ghostipedia.cosmiccore.client.renderer.block.NebulaeCoilRenderer; +import com.ghostipedia.cosmiccore.common.block.DivingBellEscapePad; import com.ghostipedia.cosmiccore.common.block.MagnetBlock; import com.ghostipedia.cosmiccore.common.blockentity.CosmicCoilBlockEntity; import com.ghostipedia.cosmiccore.ember.CosmicEmberEmitterBlock; @@ -630,6 +631,12 @@ public class CosmicBlocks { public static final BlockEntry ZBLAN_REINFORCED_GLASS = createGlassCasingBlock( "zblan_glass", CosmicCore.id("block/casings/glass/zblan_glass"), () -> RenderType::translucent); + public static final BlockEntry DIVING_BELL_ESCAPE_PAD = REGISTRATE + .block("diving_bell_escape_pad", DivingBellEscapePad::new) + .initialProperties(() -> Blocks.STONE) + .simpleItem() + .register(); + private static BlockEntry createGlassCasingBlock(String name, ResourceLocation texture, Supplier> type) { NonNullFunction supplier = GlassBlock::new; diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/DivingBell.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/DivingBell.java new file mode 100644 index 000000000..0fac80a63 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/DivingBell.java @@ -0,0 +1,49 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi; + +import com.ghostipedia.cosmiccore.CosmicCore; +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic.DivingBellMachine; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.data.RotationState; +import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; +import com.gregtechceu.gtceu.api.machine.multiblock.PartAbility; +import com.gregtechceu.gtceu.api.pattern.FactoryBlockPattern; +import com.gregtechceu.gtceu.common.data.GTRecipeTypes; + +import static com.ghostipedia.cosmiccore.api.registries.CosmicRegistration.REGISTRATE; +import static com.ghostipedia.cosmiccore.common.data.CosmicBlocks.*; +import static com.gregtechceu.gtceu.api.pattern.Predicates.*; +import static com.gregtechceu.gtceu.common.data.models.GTMachineModels.createWorkableCasingMachineModel; + +public class DivingBell { + + public final static MultiblockMachineDefinition DIVING_BELL = REGISTRATE + .multiblock("diving_bell", DivingBellMachine::new) + .langValue("Diving Bell") + .rotationState(RotationState.NON_Y_AXIS) + .recipeType(GTRecipeTypes.DUMMY_RECIPES) + .appearanceBlock(REINFORCED_NAQUADRIA_CASING) + // spotless:off + .pattern(definition -> FactoryBlockPattern.start() + // Front row (all vertical layers bottom to top) + .aisle("CCC", "GGG", "GGG", "CCC", "CCC") + // Middle row (all vertical layers bottom to top) + .aisle("CQC", "G G", "G G", "C C", "C C") + // Back row (all vertical layers bottom to top) + .aisle("CCC", "GGG", "GGG", "CCC", "CCC") + .where(' ', any()) + .where('Q', controller(blocks(definition.getBlock()))) + .where('G', blocks(ZBLAN_REINFORCED_GLASS.get())) + .where('C', blocks(REINFORCED_NAQUADRIA_CASING.get()) + .or(abilities(PartAbility.INPUT_ENERGY).setMinGlobalLimited(1).setMaxGlobalLimited(2)) + .or(abilities(PartAbility.MAINTENANCE).setExactLimit(1))) + .build()) + // spotless:on + .model( + createWorkableCasingMachineModel( + CosmicCore.id("block/casings/solid/reinforced_naquadria_casing"), + GTCEu.id("block/multiblock/generator/large_gas_turbine"))) + .register(); + + public static void init() {} +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java new file mode 100644 index 000000000..47a4ce770 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java @@ -0,0 +1,225 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic; + +import com.ghostipedia.cosmiccore.common.abyss.AbyssBudgetCap; +import com.ghostipedia.cosmiccore.common.abyss.AbyssRules; +import com.ghostipedia.cosmiccore.common.data.CosmicBlocks; +import com.ghostipedia.cosmiccore.common.teleporter.LandingZoneHelper; +import com.ghostipedia.cosmiccore.common.teleporter.SafeTeleporter; +import com.ghostipedia.cosmiccore.common.teleporter.TeleportOriginCap; +import com.ghostipedia.cosmiccore.common.teleporter.TeleportPadRegistry; + +import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; +import com.gregtechceu.gtceu.api.machine.TickableSubscription; +import com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine; + +import com.lowdragmc.lowdraglib.syncdata.annotation.Persisted; +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.core.BlockPos; +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.server.level.ServerPlayer; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.AABB; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Diving Bell Machine Controller + * + * Detects players standing directly on top of the controller block and teleports them to the Deep Below dimension. + * - Energy cost: 500,000 EU per teleport + * - Cooldown: 100 ticks (5 seconds) + * - Only teleports one player per activation + */ +public class DivingBellMachine extends WorkableElectricMultiblockMachine { + + protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( + DivingBellMachine.class, + WorkableElectricMultiblockMachine.MANAGED_FIELD_HOLDER); + + // Configuration values + private static final int TELEPORT_COST_EU = 500000; // 500k EU per teleport + private static final int COOLDOWN_TICKS = 100; // 5 seconds - idk just in case players try to abuse it. + + // Teleport destination settings + private static final String TARGET_DIMENSION = "frontiers:the_deep_below"; // Dimension to teleport to + private static final int DESTINATION_SEARCH_START_Y = 100; // Y level to start searching for safe ground + + // State + @Persisted + private int cooldownRemaining = 0; + + protected TickableSubscription tickSubscription; + + public DivingBellMachine(IMachineBlockEntity holder, Object... args) { + super(holder, args); + } + + @Override + @NotNull + public ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + @Override + public void onStructureFormed() { + super.onStructureFormed(); + // Subscribe to server ticks when structure forms + tickSubscription = subscribeServerTick(tickSubscription, this::checkForPlayers); + } + + @Override + public void onStructureInvalid() { + super.onStructureInvalid(); + // Unsubscribe when structure breaks + if (tickSubscription != null) { + tickSubscription.unsubscribe(); + tickSubscription = null; + } + } + + // Tick handler that checks for players and teleports them. + private void checkForPlayers() { + // Decrement cooldown + if (cooldownRemaining > 0) { + cooldownRemaining--; + return; + } + + // Check if we have enough energy + if (!hasEnoughEnergy()) { + return; + } + + // Detect players on top platform + List players = getPlayersOnPlatform(); + if (players.isEmpty()) { + return; + } + + // Teleport first player only (one at a time) + ServerPlayer target = players.get(0); + if (teleportPlayerToDeepBelow(target)) { + // Consume energy and start cooldown + consumeEnergy(TELEPORT_COST_EU); + cooldownRemaining = COOLDOWN_TICKS; + } + } + + // Check if there is enough energy for a teleport. + private boolean hasEnoughEnergy() { + long available = energyContainer.getEnergyStored(); + return available >= TELEPORT_COST_EU; + } + + // Consume energy for teleportation. + private void consumeEnergy(long amount) { + energyContainer.removeEnergy(amount); + } + + // Get all players standing directly on top of the controller block. + private List getPlayersOnPlatform() { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return List.of(); + } + + // Detection zone is 1 block directly above the controller + BlockPos controllerPos = getPos(); + BlockPos detectionPos = controllerPos.above(1); + + // 1x1 detection area (requires players to stand on top of the controller) + AABB detectionZone = new AABB( + detectionPos, + detectionPos.offset(1, 1, 1)); + + return serverLevel.getEntitiesOfClass(ServerPlayer.class, detectionZone); + } + + // Teleport a player to the Deep Below dimension. + private boolean teleportPlayerToDeepBelow(ServerPlayer player) { + if (!(getLevel() instanceof ServerLevel currentLevel)) { + return false; + } + + // Save origin data to player capability + player.getCapability(TeleportOriginCap.CAP).ifPresent(cap -> { + cap.setOriginDimension(currentLevel.dimension()); + cap.setOriginPosition(player.position()); + cap.setOriginRotation(player.getYRot(), player.getXRot()); + }); + + // Get Deep Below dimension + ResourceKey targetDim = getTargetDimension(); + ServerLevel deepBelow = player.server.getLevel(targetDim); + + if (deepBelow == null) { + player.displayClientMessage( + Component.translatable("cosmiccore.divingbell.dimension_missing"), true); + return false; + } + + // Find or create safe landing + BlockPos landingPos = getOrCreateSafeLanding(deepBelow, player); + + // Set Abyss decay flag + // Don't think this is necessary... +// player.getCapability(AbyssBudgetCap.CAP).ifPresent(cap -> { +// cap.setDecaying(AbyssRules.DIM, true); +// }); + + // Teleport (SafeTeleporter handles safety effects) + player.changeDimension(deepBelow, new SafeTeleporter(landingPos)); + + // Success message + player.displayClientMessage( + Component.translatable("cosmiccore.divingbell.descended"), true); + + return true; + } + + // Get the target dimension (Deep Below). + private ResourceKey getTargetDimension() { + + ResourceLocation dimLoc = new ResourceLocation(TARGET_DIMENSION); + return ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, dimLoc); + } + + // Find or create a safe landing platform in the Deep Below. + private BlockPos getOrCreateSafeLanding(ServerLevel deepBelow, ServerPlayer player) { + TeleportPadRegistry registry = TeleportPadRegistry.get(deepBelow); + + // Landing pad uses same X/Z as player's current position (vertical teleport) + BlockPos currentPos = player.blockPosition(); + int targetX = currentPos.getX(); + int targetZ = currentPos.getZ(); + + // Find safe Y level for this X/Z position + BlockPos safePos = LandingZoneHelper.findSafeYLevel(deepBelow, targetX, targetZ, DESTINATION_SEARCH_START_Y); + + // Check if escape pad already exists at this position + if (registry.hasPadAt(safePos) && + LandingZoneHelper.isPadIntact(deepBelow, safePos, CosmicBlocks.DIVING_BELL_ESCAPE_PAD.get())) { + // Reuse existing pad + return safePos; + } + + // Need to create new platform + LandingZoneHelper.buildPlatform(deepBelow, safePos, new LandingZoneHelper.PlatformOptions( + Blocks.STONE, + CosmicBlocks.DIVING_BELL_ESCAPE_PAD.get(), + 1 // 3x3 platform + )); + + // Register in saved data + registry.registerPad(safePos); + + return safePos; + } + +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java index fb0dd68bd..f7248b357 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java @@ -63,6 +63,7 @@ public static void init() { VileFissionReactor.init(); VoidSaltReactor.init(); AtomicReconstructor.init(); + DivingBell.init(); // KryosynCrackingChamber.init(); } } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/LandingZoneHelper.java b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/LandingZoneHelper.java new file mode 100644 index 000000000..6514a4adf --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/LandingZoneHelper.java @@ -0,0 +1,74 @@ +package com.ghostipedia.cosmiccore.common.teleporter; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; + +public class LandingZoneHelper { + + private static final int MIN_SEARCH_HEIGHT_BUFFER = 5; // Don't search below world limit + this buffer + private static final int CLEAR_AIR_HEIGHT = 3; // Clear this many blocks above platform for headroom + + public static class PlatformOptions { + + public final Block platformMaterial; + public final Block padBlock; + public final int platformRadius; + + // Pplatform options. + public PlatformOptions(Block platformMaterial, Block padBlock, int platformRadius) { + this.platformMaterial = platformMaterial; + this.padBlock = padBlock; + this.platformRadius = platformRadius; + } + } + + // Search downward from startY to find solid ground. + // Searches down to minBuildHeight + 5, then falls back to startY if no ground found. + public static BlockPos findSafeYLevel(ServerLevel level, int x, int z, int startY) { + for (int y = startY; y >= level.getMinBuildHeight() + MIN_SEARCH_HEIGHT_BUFFER; y--) { + BlockPos checkPos = new BlockPos(x, y, z); + if (level.getBlockState(checkPos.below()).isSolid()) { + // Found solid ground + return checkPos; + } + } + + return new BlockPos(x, startY, z); + } + + // Check if a pad block is intact at the given position. + public static boolean isPadIntact(ServerLevel level, BlockPos pos, Block expectedPad) { + return level.getBlockState(pos).is(expectedPad); + } + + // Build a landing platform with escape pad at center. + public static void buildPlatform(ServerLevel level, BlockPos center, PlatformOptions options) { + int radius = options.platformRadius; + + // Build platform 1 block below center (so players stand on it, pad is at center) + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + BlockPos platformPos = center.offset(x, -1, z); + level.setBlock(platformPos, options.platformMaterial.defaultBlockState(), 3); + } + } + + // Place pad at center + level.setBlock(center, options.padBlock.defaultBlockState(), 3); + + // Clear air above for headroom + for (int y = 0; y < CLEAR_AIR_HEIGHT; y++) { + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + BlockPos airPos = center.offset(x, y, z); + if (airPos.equals(center)) continue; // Don't clear the pad itself + if (level.getBlockState(airPos).isSolid()) { + level.setBlock(airPos, Blocks.AIR.defaultBlockState(), 3); + } + } + } + } + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/SafeTeleporter.java b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/SafeTeleporter.java new file mode 100644 index 000000000..9ec3b9628 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/SafeTeleporter.java @@ -0,0 +1,69 @@ +package com.ghostipedia.cosmiccore.common.teleporter; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.portal.PortalInfo; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.util.ITeleporter; + +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; + +// Saftey first kids (reusable ITeleporter that ensures safe arrival after dimension changes) +public class SafeTeleporter implements ITeleporter { + + private final BlockPos targetPos; + private final boolean applySafetyBuffs; + private final int buffDuration; // ticks + + // Create a SafeTeleporter with default settings (buffs enabled, 100 tick duration). + public SafeTeleporter(BlockPos targetPos) { + this(targetPos, true, 100); + } + + // Create a SafeTeleporter with custom settings. + public SafeTeleporter(BlockPos targetPos, boolean applySafetyBuffs, int buffDuration) { + this.targetPos = targetPos; + this.applySafetyBuffs = applySafetyBuffs; + this.buffDuration = buffDuration; + } + + @Override + @Nullable + public PortalInfo getPortalInfo(Entity entity, ServerLevel destWorld, + Function defaultPortalInfo) { + // Place entity at center of block, slightly above the pad + Vec3 pos = new Vec3( + targetPos.getX() + 0.5, + targetPos.getY() + 0.1, // Slightly above to prevent clipping + targetPos.getZ() + 0.5); + + // Zero velocity to prevent fall damage + Vec3 velocity = Vec3.ZERO; + + // Preserve rotation + return new PortalInfo(pos, velocity, entity.getYRot(), entity.getXRot()); + } + + @Override + public Entity placeEntity(Entity entity, ServerLevel currentWorld, ServerLevel destWorld, + float yaw, Function repositionEntity) { + Entity result = repositionEntity.apply(false); + + // Apply safety effects + applySafetyEffects(result); + + return result; + } + + private void applySafetyEffects(Entity entity) { + // Clear fire + entity.clearFire(); + + // Reset fall distance to prevent fall damage + entity.fallDistance = 0; + + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOrigin.java b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOrigin.java new file mode 100644 index 000000000..0c3a8a8f1 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOrigin.java @@ -0,0 +1,132 @@ +package com.ghostipedia.cosmiccore.common.teleporter; + +import com.ghostipedia.cosmiccore.api.capability.ITeleportOrigin; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; + +import org.jetbrains.annotations.Nullable; + +// Implementation of the teleport origin capability - Stores origin dimension, position, and rotation for teleport return trips. +public class TeleportOrigin implements ITeleportOrigin { + + @Nullable + private ResourceKey originDimension; + @Nullable + private Vec3 originPosition; + private float originYaw; + private float originPitch; + @Nullable + private BlockPos escapePadPosition; + + // spotless: off + @Override + public void setOriginDimension(ResourceKey dimension) { + this.originDimension = dimension; + } + + @Override + @Nullable + public ResourceKey getOriginDimension() { + return originDimension; + } + + @Override + public void setOriginPosition(Vec3 position) { + this.originPosition = position; + } + + @Override + @Nullable + public Vec3 getOriginPosition() { + return originPosition; + } + + @Override + public void setOriginRotation(float yaw, float pitch) { + this.originYaw = yaw; + this.originPitch = pitch; + } + + @Override + public float getOriginYaw() { + return originYaw; + } + + @Override + public float getOriginPitch() { + return originPitch; + } + + @Override + public boolean hasValidOrigin() { + return originDimension != null && originPosition != null; + } + + @Override + public void clearOriginData() { + originDimension = null; + originPosition = null; + originYaw = 0; + originPitch = 0; + escapePadPosition = null; + } + // spotless: on + + // Save capability data to NBT. + public CompoundTag save() { + CompoundTag tag = new CompoundTag(); + + if (originDimension != null) { + tag.putString("OriginDimension", originDimension.location().toString()); + } + + if (originPosition != null) { + tag.putDouble("OriginX", originPosition.x); + tag.putDouble("OriginY", originPosition.y); + tag.putDouble("OriginZ", originPosition.z); + } + + tag.putFloat("OriginYaw", originYaw); + tag.putFloat("OriginPitch", originPitch); + + if (escapePadPosition != null) { + tag.putLong("EscapePadPos", escapePadPosition.asLong()); + } + + return tag; + } + + // Load capability data from NBT. + public void load(CompoundTag tag) { + if (tag.contains("OriginDimension")) { + ResourceLocation dimLoc = new ResourceLocation(tag.getString("OriginDimension")); + this.originDimension = ResourceKey.create(Registries.DIMENSION, dimLoc); + } else { + this.originDimension = null; + } + + if (tag.contains("OriginX")) { + double x = tag.getDouble("OriginX"); + double y = tag.getDouble("OriginY"); + double z = tag.getDouble("OriginZ"); + this.originPosition = new Vec3(x, y, z); + } else { + this.originPosition = null; + } + + this.originYaw = tag.getFloat("OriginYaw"); + this.originPitch = tag.getFloat("OriginPitch"); + + if (tag.contains("EscapePadPos")) { + this.escapePadPosition = BlockPos.of(tag.getLong("EscapePadPos")); + } else { + this.escapePadPosition = null; + } + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOriginCap.java b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOriginCap.java new file mode 100644 index 000000000..9d1eb3097 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOriginCap.java @@ -0,0 +1,76 @@ +package com.ghostipedia.cosmiccore.common.teleporter; + +import com.ghostipedia.cosmiccore.CosmicCore; +import com.ghostipedia.cosmiccore.api.capability.ITeleportOrigin; + +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.capabilities.ICapabilitySerializable; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +// Capability provider for teleport origin data. +// Attaches to all players to track their teleportation origin for return trips. +@Mod.EventBusSubscriber(modid = CosmicCore.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class TeleportOriginCap { + + public static final ResourceLocation KEY = new ResourceLocation("cosmiccore", "teleport_origin"); + public static final Capability CAP = CapabilityManager.get(new CapabilityToken<>() {}); + + public static class Provider implements ICapabilityProvider, ICapabilitySerializable { + + private final TeleportOrigin impl = new TeleportOrigin(); + private final LazyOptional lazyOpt = LazyOptional.of(() -> impl); + + @Override + public @NotNull LazyOptional getCapability(@NotNull Capability capability, + @Nullable Direction direction) { + return capability == CAP ? lazyOpt.cast() : LazyOptional.empty(); + } + + @Override + public CompoundTag serializeNBT() { + return impl.save(); + } + + @Override + public void deserializeNBT(CompoundTag tag) { + impl.load(tag); + } + } + + // Attach capability to all players. + @SubscribeEvent + public static void attachCap(AttachCapabilitiesEvent event) { + if (event.getObject() instanceof Player) { + event.addCapability(KEY, new Provider()); + } + } + + // Clone capability data on player respawn/dimension change. + @SubscribeEvent + public static void cloneCap(PlayerEvent.Clone event) { + event.getOriginal().reviveCaps(); + event.getOriginal().getCapability(CAP).ifPresent(old -> { + event.getEntity().getCapability(CAP).ifPresent(now -> { + if (now instanceof TeleportOrigin originNow && old instanceof TeleportOrigin originOld) { + originNow.load(originOld.save()); + } + }); + }); + event.getOriginal().invalidateCaps(); + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportPadRegistry.java b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportPadRegistry.java new file mode 100644 index 000000000..91d8c7401 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportPadRegistry.java @@ -0,0 +1,79 @@ +package com.ghostipedia.cosmiccore.common.teleporter; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Set; + +// Tracks teleport pads that have been placed. Prevents duplicate platform spawning. +public class TeleportPadRegistry extends SavedData { + + private static final String DATA_NAME = "cosmiccore_teleport_pads"; + + private final Set pads = new HashSet<>(); + + public TeleportPadRegistry() { + super(); + } + + // Get the saved data instance for a specific dimension. + public static TeleportPadRegistry get(ServerLevel level) { + return level.getDataStorage().computeIfAbsent( + TeleportPadRegistry::load, + TeleportPadRegistry::new, + DATA_NAME); + } + + // Check if a pad exists at the given position. + public boolean hasPadAt(BlockPos pos) { + return pads.contains(pos); + } + + // Register a new pad at the given position. + public void registerPad(BlockPos pos) { + if (pads.add(pos)) { + setDirty(); + } + } + + // Remove a pad registration (like if it gets broken) + public void removePad(BlockPos pos) { + if (pads.remove(pos)) { + setDirty(); + } + } + + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag tag) { + ListTag padsList = new ListTag(); + + for (BlockPos pos : pads) { + CompoundTag padTag = new CompoundTag(); + padTag.putLong("Pos", pos.asLong()); + padsList.add(padTag); + } + + tag.put("Pads", padsList); + return tag; + } + + public static TeleportPadRegistry load(CompoundTag tag) { + TeleportPadRegistry registry = new TeleportPadRegistry(); + + ListTag padsList = tag.getList("Pads", Tag.TAG_COMPOUND); + for (Tag padTagRaw : padsList) { + CompoundTag padTag = (CompoundTag) padTagRaw; + BlockPos pos = BlockPos.of(padTag.getLong("Pos")); + registry.pads.add(pos); + } + + return registry; + } +} From 70da85a728b149590c78b8c823685dc28be79f88 Mon Sep 17 00:00:00 2001 From: SenecaIO Date: Tue, 18 Nov 2025 18:36:47 -0700 Subject: [PATCH 2/4] Go go gadget apply spotless --- .../common/block/DivingBellEscapePad.java | 23 +++++++++---------- .../multi/logic/DivingBellMachine.java | 10 +++----- .../common/teleporter/SafeTeleporter.java | 1 - .../common/teleporter/TeleportOrigin.java | 3 ++- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/block/DivingBellEscapePad.java b/src/main/java/com/ghostipedia/cosmiccore/common/block/DivingBellEscapePad.java index 5e4b7229d..3ee707755 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/block/DivingBellEscapePad.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/block/DivingBellEscapePad.java @@ -1,6 +1,5 @@ package com.ghostipedia.cosmiccore.common.block; -import com.ghostipedia.cosmiccore.common.abyss.AbyssRules; import com.ghostipedia.cosmiccore.common.teleporter.SafeTeleporter; import com.ghostipedia.cosmiccore.common.teleporter.TeleportOriginCap; @@ -80,10 +79,10 @@ public InteractionResult use(BlockState state, Level level, BlockPos pos, Player // Clear Abyss decay flag // Don't think this is needed... -// serverPlayer.getCapability(com.ghostipedia.cosmiccore.common.abyss.AbyssBudgetCap.CAP) -// .ifPresent(abyssCap -> { -// abyssCap.setDecaying(AbyssRules.DIM, false); -// }); + // serverPlayer.getCapability(com.ghostipedia.cosmiccore.common.abyss.AbyssBudgetCap.CAP) + // .ifPresent(abyssCap -> { + // abyssCap.setDecaying(AbyssRules.DIM, false); + // }); // Success message serverPlayer.displayClientMessage( @@ -112,13 +111,13 @@ private boolean isOriginSafe(ServerLevel level, BlockPos pos) { BlockState at = level.getBlockState(pos); BlockState above = level.getBlockState(pos.above()); -// if (at.isSuffocating(level, pos)) { -// return false; -// } -// -// if (!at.getFluidState().isEmpty()) { -// return false; -// } + // if (at.isSuffocating(level, pos)) { + // return false; + // } + // + // if (!at.getFluidState().isEmpty()) { + // return false; + // } // Head level: same checks to ensure full 2-block clearance if (above.isSuffocating(level, pos.above())) { diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java index 47a4ce770..903ec33ef 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java @@ -1,7 +1,5 @@ package com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic; -import com.ghostipedia.cosmiccore.common.abyss.AbyssBudgetCap; -import com.ghostipedia.cosmiccore.common.abyss.AbyssRules; import com.ghostipedia.cosmiccore.common.data.CosmicBlocks; import com.ghostipedia.cosmiccore.common.teleporter.LandingZoneHelper; import com.ghostipedia.cosmiccore.common.teleporter.SafeTeleporter; @@ -169,9 +167,9 @@ private boolean teleportPlayerToDeepBelow(ServerPlayer player) { // Set Abyss decay flag // Don't think this is necessary... -// player.getCapability(AbyssBudgetCap.CAP).ifPresent(cap -> { -// cap.setDecaying(AbyssRules.DIM, true); -// }); + // player.getCapability(AbyssBudgetCap.CAP).ifPresent(cap -> { + // cap.setDecaying(AbyssRules.DIM, true); + // }); // Teleport (SafeTeleporter handles safety effects) player.changeDimension(deepBelow, new SafeTeleporter(landingPos)); @@ -185,7 +183,6 @@ private boolean teleportPlayerToDeepBelow(ServerPlayer player) { // Get the target dimension (Deep Below). private ResourceKey getTargetDimension() { - ResourceLocation dimLoc = new ResourceLocation(TARGET_DIMENSION); return ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, dimLoc); } @@ -221,5 +218,4 @@ private BlockPos getOrCreateSafeLanding(ServerLevel deepBelow, ServerPlayer play return safePos; } - } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/SafeTeleporter.java b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/SafeTeleporter.java index 9ec3b9628..6100221a5 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/SafeTeleporter.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/SafeTeleporter.java @@ -64,6 +64,5 @@ private void applySafetyEffects(Entity entity) { // Reset fall distance to prevent fall damage entity.fallDistance = 0; - } } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOrigin.java b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOrigin.java index 0c3a8a8f1..67c92c035 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOrigin.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/teleporter/TeleportOrigin.java @@ -12,7 +12,8 @@ import org.jetbrains.annotations.Nullable; -// Implementation of the teleport origin capability - Stores origin dimension, position, and rotation for teleport return trips. +// Implementation of the teleport origin capability - Stores origin dimension, position, and rotation for teleport +// return trips. public class TeleportOrigin implements ITeleportOrigin { @Nullable From 26dfe0abbbd62e19ebb4d1386328277ae6deddea Mon Sep 17 00:00:00 2001 From: SenecaIO Date: Mon, 12 Jan 2026 13:34:15 -0700 Subject: [PATCH 3/4] Add linked multiblock system and moth cargo stations --- docs/CARGO_MOTHS.md | 226 ++++++ docs/CROSS_DIMENSIONAL_LINKING.md | 318 +++++++++ .../api/capability/ILinkedMultiblock.java | 107 +++ .../api/data/savedData/LinkEntry.java | 79 +++ .../savedData/LinkedMultiblockSavedData.java | 297 ++++++++ ...nkedWorkableElectricMultiblockMachine.java | 621 +++++++++++++++++ .../LinkedWorkableMultiblockMachine.java | 624 +++++++++++++++++ .../api/pattern/CosmicPredicates.java | 40 ++ .../common/block/MothHomeBlock.java | 20 + .../cosmiccore/common/data/CosmicBlocks.java | 21 + .../common/data/lang/CosmicLangHandler.java | 37 + .../multiblock/LinkedMultiblockHelper.java | 544 +++++++++++++++ .../multiblock/multi/LinkTestStation.java | 54 ++ .../multiblock/multi/MothCargoDropOff.java | 55 ++ .../multiblock/multi/MothCargoStation.java | 83 +++ .../multi/logic/DivingBellMachine.java | 6 - .../multi/logic/LinkTestStationMachine.java | 114 +++ .../multi/logic/MothCargoDropOffMachine.java | 140 ++++ .../multi/logic/MothCargoStationMachine.java | 657 ++++++++++++++++++ .../multi/modular/MultiblockInit.java | 5 + .../recipe/condition/CosmicConditions.java | 4 + .../condition/LinkedPartnerCondition.java | 116 ++++ .../LinkedPartnerDimensionCondition.java | 92 +++ .../LinkedPartnerDimensionFluidCondition.java | 136 ++++ .../LinkedPartnerDimensionItemCondition.java | 136 ++++ .../gtbridge/CosmicCoreRecipes.java | 70 ++ .../gtbridge/CosmicRecipeTypes.java | 7 + 27 files changed, 4603 insertions(+), 6 deletions(-) create mode 100644 docs/CARGO_MOTHS.md create mode 100644 docs/CROSS_DIMENSIONAL_LINKING.md create mode 100644 src/main/java/com/ghostipedia/cosmiccore/api/capability/ILinkedMultiblock.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/api/data/savedData/LinkEntry.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/api/data/savedData/LinkedMultiblockSavedData.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/api/machine/multiblock/LinkedWorkableElectricMultiblockMachine.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/api/machine/multiblock/LinkedWorkableMultiblockMachine.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/block/MothHomeBlock.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/LinkedMultiblockHelper.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/LinkTestStation.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/MothCargoDropOff.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/MothCargoStation.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/LinkTestStationMachine.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/MothCargoDropOffMachine.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/MothCargoStationMachine.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerCondition.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionCondition.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionFluidCondition.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionItemCondition.java diff --git a/docs/CARGO_MOTHS.md b/docs/CARGO_MOTHS.md new file mode 100644 index 000000000..fd6552b22 --- /dev/null +++ b/docs/CARGO_MOTHS.md @@ -0,0 +1,226 @@ +# Cargo Moths System + +## Current Status: **IN PROGRESS** + +Last updated: Debug output removed, core functionality working. + +--- + +## 1. Overview + +The Cargo Moths system provides local (same-dimension) item and fluid transport using a whimsical moth-based logistics network. Unlike the cross-dimensional linking system, Cargo Moths are designed for short-range automation within a single dimension. + +### Design Philosophy +- **No power required** - Moths work for free! +- **Scalable capacity** - Add more moth homes to increase throughput +- **Tiered progression** - Different beehive types provide different speeds/capacities +- **Feeding bonuses** - Optional honey/oil feeding for multipliers + +--- + +## 2. What's Implemented + +### 2.1 Core Components + +| Component | File | Status | +|-----------|------|--------| +| `MothCargoStation` | `common/machine/multiblock/multi/MothCargoStation.java` | ✅ Complete | +| `MothCargoStationMachine` | `common/machine/multiblock/multi/logic/MothCargoStationMachine.java` | ✅ Complete | +| `MothCargoDropOff` | `common/machine/multiblock/multi/MothCargoDropOff.java` | ✅ Complete | +| `MothCargoDropOffMachine` | `common/machine/multiblock/multi/logic/MothCargoDropOffMachine.java` | ✅ Complete | +| `LinkedWorkableMultiblockMachine` | `api/machine/multiblock/LinkedWorkableMultiblockMachine.java` | ✅ Complete | + +### 2.2 Feature Checklist + +- [x] Multiblock structure definitions +- [x] Datastick-based linking (reuses cross-dimensional linking infrastructure) +- [x] Same-dimension restriction +- [x] Item transfer from Station to Drop Off +- [x] Fluid transfer from Station to Drop Off +- [x] Tiered moth homes (Forestry beehives) +- [x] Cycle time based on moth tier +- [x] Capacity based on moth count × tier multiplier +- [x] Distribution modes (DIRECT, FILL_FIRST, ROUND_ROBIN) +- [x] GUI display (moth homes, cycle time, capacity, linked drop-offs) +- [x] Screwdriver to cycle distribution mode +- [ ] Feeding bonuses (honey/oil multipliers) + +--- + +## 3. Multiblock Structures + +### 3.1 Moth Cargo Station (Sender) + +Tower structure: 3×3 footprint, 6 blocks tall + +``` +Layer 0 (bottom): Layer 1-4: Layer 5 (top): +C C C C M C C C C +C C C C M C C C C +C Q C C M C C C C +``` + +Where: +- `C` = Steel Solid Casing (or input/output buses/hatches, maintenance hatch) +- `M` = Moth Home (Forestry beehive) OR Steel Solid Casing +- `Q` = Controller + +**Moth homes only go in the center column** (up to 4 can be placed). + +Allowed hatches: +- 1 Maintenance Hatch (required) +- Up to 4 Item Input Buses +- Up to 4 Item Output Buses +- Up to 4 Fluid Input Hatches +- Up to 4 Fluid Output Hatches + +### 3.2 Moth Cargo Drop Off (Receiver) + +Simple 3×3×2 structure: + +``` +Layer 0 (bottom): Layer 1 (top): +C C C C C C +C Q C C C C +C C C C C C +``` + +Where: +- `C` = Steel Solid Casing (or output buses/hatches, maintenance hatch) +- `Q` = Controller + +Allowed hatches: +- 1 Maintenance Hatch (required) +- Up to 4 Item Output Buses +- Up to 4 Fluid Output Hatches + +--- + +## 4. Moth Home Tiers + +Moth homes use Forestry beehive blocks: + +| Tier | Block | Cycle Time | Moths per Home | +|------|-------|------------|----------------| +| T1 | `forestry:beehive_forest` | 60s | 1 | +| T2 | `forestry:beehive_lush` | 30s | 2 | +| T3 | `forestry:beehive_desert` | 15s | 4 | +| T4 | `forestry:beehive_end` | 5s | 8 | + +**All moth homes must be the same tier.** Mixed tiers will trigger a warning. + +### Capacity Calculation + +``` +Items per cycle = Total Moths × 64 × Feeding Multiplier +Fluids per cycle = Total Moths × 1000mB × Feeding Multiplier +Total Moths = Moth Home Count × Moths per Home (by tier) +``` + +Example: 4× T3 beehives = 4 × 4 = 16 moths = 1024 items per cycle (every 15s) + +--- + +## 5. Distribution Modes + +Cycle through modes with screwdriver on the controller. + +| Mode | Behavior | +|------|----------| +| `DIRECT` | Ships to first linked drop-off only (1:1) | +| `FILL_FIRST` | Fills each drop-off in order until full, then moves to next | +| `ROUND_ROBIN` | Distributes evenly across all linked drop-offs | + +--- + +## 6. Feeding Bonuses (TODO) + +Planned multipliers for feeding moths: + +| Feed Item | Multiplier | +|-----------|------------| +| Regular Honey | 2× | +| Lofty Honey | 4× | +| Pale Oil | 8× | + +Feed is consumed per cycle from the Station's input bus. + +--- + +## 7. GUI Information + +The Station GUI displays: +- Moth Homes count and tier +- Total Moths +- Cycle Time (seconds) +- Distribution Mode +- Linked Drop-Offs count +- Capacity per cycle (items and fluids) + +The Drop Off GUI displays: +- Structure status +- Number of stations linked to it + +--- + +## 8. Linking + +Uses the same datastick-based linking as the cross-dimensional system: + +1. Shift+right-click the Moth Cargo Station with a datastick to copy link data +2. Right-click a Moth Cargo Drop Off to establish the link + +**Restrictions:** +- Same dimension only (moths can't fly between dimensions!) +- Station can link to up to 16 Drop Offs (1:N) +- Drop Off can receive from up to 16 Stations (N:1) + +--- + +## 9. How It Works + +1. Every tick, the Station checks if enough time has passed since the last cycle +2. When cycle time is reached: + - Get all linked, formed Drop Offs + - Calculate item/fluid capacity based on moths and feeding multiplier + - Extract items from Station's input buses (using internal methods to bypass IO checks) + - Insert items into Drop Off's output buses (using internal methods to bypass IO checks) + - Same process for fluids + - Consume feeding materials (TODO) + +The internal extraction/insertion methods bypass GTCEu's IO direction checks, which is the same pattern used by DroneStationMachine. + +--- + +## 10. File Structure + +``` +src/main/java/com/ghostipedia/cosmiccore/ +├── api/machine/multiblock/ +│ └── LinkedWorkableMultiblockMachine.java # Base class (no power requirement) +└── common/machine/multiblock/multi/ + ├── MothCargoStation.java # Station multiblock definition + ├── MothCargoDropOff.java # Drop Off multiblock definition + └── logic/ + ├── MothCargoStationMachine.java # Station logic (shipping cycles) + └── MothCargoDropOffMachine.java # Drop Off logic (receives items) +``` + +--- + +## 11. Known Limitations + +1. **Forestry dependency** - Falls back to vanilla beehive if Forestry not loaded +2. **No visual feedback** - No moth entity/particle flying between stations +3. **No feeding implementation** - Multiplier is always 1× currently +4. **No chunk loading** - Both Station and Drop Off must be loaded + +--- + +## 12. Future Work + +- [ ] Implement feeding bonuses (honey/oil consumption and multipliers) +- [ ] Add moth particle effects during transfers +- [ ] Consider cross-dimension variant (interdimensional moths?) +- [ ] Add JEI/EMI integration showing capacity calculations +- [ ] Custom textures/models for the multiblocks diff --git a/docs/CROSS_DIMENSIONAL_LINKING.md b/docs/CROSS_DIMENSIONAL_LINKING.md new file mode 100644 index 000000000..56f3cdbc2 --- /dev/null +++ b/docs/CROSS_DIMENSIONAL_LINKING.md @@ -0,0 +1,318 @@ +# Cross-Dimensional Multiblock Linking System + +## Current Status: **IMPLEMENTED & TESTED** + +Last updated: Session implementing partner query utilities and recipe conditions. + +--- + +## 1. Overview + +This system enables multiblock machines to communicate across dimensions using GTCEu's datastick as the linking mechanism. Links are persisted in SavedData and support role-based access control. + +### Use Cases +- **Star Ladder**: Manufacturing chains spanning multiple dimensions +- **Cross-dimensional recipes**: "Recipe requires partner in Sun Orbit with Solar Plasma" +- **Remote resource access**: Query partner's inventory/fluids/energy +- **Dimension-gated progression**: Certain recipes only available when linked to specific dimensions + +--- + +## 2. What's Implemented + +### 2.1 Core Infrastructure + +| Component | File | Status | +|-----------|------|--------| +| `ILinkedMultiblock` | `api/capability/ILinkedMultiblock.java` | ✅ Complete | +| `LinkEntry` | `api/data/savedData/LinkEntry.java` | ✅ Complete | +| `LinkedMultiblockSavedData` | `api/data/savedData/LinkedMultiblockSavedData.java` | ✅ Complete | +| `LinkedMultiblockHelper` | `common/machine/multiblock/LinkedMultiblockHelper.java` | ✅ Complete | +| `LinkedWorkableElectricMultiblockMachine` | `api/machine/multiblock/LinkedWorkableElectricMultiblockMachine.java` | ✅ Complete | + +### 2.2 Test Multiblock + +| Component | File | Status | +|-----------|------|--------| +| `LinkTestStation` | `common/machine/multiblock/multi/LinkTestStation.java` | ✅ Complete | +| `LinkTestStationMachine` | `common/machine/multiblock/multi/logic/LinkTestStationMachine.java` | ✅ Complete | + +### 2.3 Recipe Conditions + +| Condition | File | Description | +|-----------|------|-------------| +| `LinkedPartnerCondition` | `common/recipe/condition/LinkedPartnerCondition.java` | Requires N linked partners, optionally formed/working | +| `LinkedPartnerDimensionCondition` | `common/recipe/condition/LinkedPartnerDimensionCondition.java` | Requires partner in specific dimension | +| `LinkedPartnerDimensionItemCondition` | `common/recipe/condition/LinkedPartnerDimensionItemCondition.java` | Requires partner in dimension with specific item | +| `LinkedPartnerDimensionFluidCondition` | `common/recipe/condition/LinkedPartnerDimensionFluidCondition.java` | Requires partner in dimension with specific fluid | + +### 2.4 Partner Query Utilities + +Methods in `LinkedMultiblockHelper`: +- `queryPartner()` - Generic query with chunk loading +- `getPartnerItemHandlers()` / `getPartnerFluidHandlers()` / `getPartnerEnergyHandlers()` +- `partnerHasItem()` / `partnerHasFluid()` +- `getPartnerEnergyStored()` +- `isPartnerFormed()` / `isPartnerWorking()` + +Convenience methods in `LinkedWorkableElectricMultiblockMachine`: +- `partnerHasItem()` / `partnerHasFluid()` / `getPartnerEnergyStored()` +- `isPartnerFormed()` / `isPartnerWorking()` +- `anyPartnerHasItem()` / `anyPartnerHasFluid()` / `anyPartnerWorking()` +- `countFormedPartners()` + +--- + +## 3. How to Use + +### 3.1 Linking Machines + +1. **Copy link data**: Shift+right-click a linkable multiblock with a datastick +2. **Paste/establish link**: Right-click another linkable multiblock with the datastick +3. The system validates ownership, roles, and compatibility before establishing the link + +### 3.2 Creating a Linkable Multiblock + +Extend `LinkedWorkableElectricMultiblockMachine`: + +```java +public class MyLinkedMachine extends LinkedWorkableElectricMultiblockMachine { + + public MyLinkedMachine(IMachineBlockEntity holder, Object... args) { + super(holder, args); + } + + @Override + public LinkRole getLinkRole() { + return LinkRole.PEER; // or CONTROLLER, REMOTE + } + + @Override + public int getMaxPartners() { + return 4; // default + } + + @Override + public boolean canLinkTo(GlobalPos partner, ILinkedMultiblock partnerMachine) { + // Custom validation (e.g., only link to specific machine types) + return true; + } + + @Override + public void onLinkEstablished(GlobalPos partner) { + super.onLinkEstablished(partner); + // React to new link + } + + @Override + public void onLinkBroken(GlobalPos partner) { + super.onLinkBroken(partner); + // Cleanup when link breaks + } +} +``` + +### 3.3 Using Recipe Conditions + +```java +// Requires at least 1 linked partner +.addCondition(new LinkedPartnerCondition(1)) + +// Requires 2 partners, at least 1 formed +.addCondition(new LinkedPartnerCondition(2, true, false)) + +// Requires partner in specific dimension +.addCondition(new LinkedPartnerDimensionCondition("frontiers:sun_orbit")) + +// Requires partner in dimension with specific item +.addCondition(new LinkedPartnerDimensionItemCondition("frontiers:sun_orbit", Items.BUCKET, 1)) + +// Requires partner in dimension with specific fluid (1000mB) +.addCondition(new LinkedPartnerDimensionFluidCondition("frontiers:sun_orbit", SolarPlasma.getFluid(), 1000)) +``` + +### 3.4 Querying Partner Resources + +From within a `LinkedWorkableElectricMultiblockMachine`: + +```java +// Check if any partner has a specific item +if (anyPartnerHasItem(stack -> stack.is(Items.DIAMOND))) { + // ... +} + +// Check specific partner +for (GlobalPos partner : getLinkedPartners()) { + if (partnerHasFluid(partner, fluid -> fluid.getFluid().is(Fluids.LAVA))) { + // Partner has lava + } + + long energy = getPartnerEnergyStored(partner); + boolean working = isPartnerWorking(partner); +} + +// Custom queries +String partnerName = queryPartner(partner, machine -> + machine.getDefinition().getName()); +``` + +--- + +## 4. Link Roles + +### Role Types + +| Role | Can Query Partners | Can Be Queried | +|------|-------------------|----------------| +| `PEER` | ✅ | ✅ | +| `CONTROLLER` | ✅ | ❌ | +| `REMOTE` | ❌ | ✅ | + +### Role Negotiation + +When two machines link, their declared roles are negotiated: + +| A's Role | B's Role | Result | A's Effective | B's Effective | +|----------|----------|--------|---------------|---------------| +| PEER | PEER | ✅ Valid | PEER | PEER | +| PEER | CONTROLLER | ✅ Valid | REMOTE | CONTROLLER | +| PEER | REMOTE | ✅ Valid | CONTROLLER | REMOTE | +| CONTROLLER | REMOTE | ✅ Valid | CONTROLLER | REMOTE | +| CONTROLLER | CONTROLLER | ❌ Reject | - | - | +| REMOTE | REMOTE | ❌ Reject | - | - | + +--- + +## 5. Test Recipes + +The Link Test Station includes these recipes for testing: + +| Recipe | Input | Output | Condition | +|--------|-------|--------|-----------| +| `link_test_basic` | 1x Iron Ingot | 9x Iron Nugget | None | +| `link_test_linked` | 1x Gold Ingot | 1x Diamond | 1 linked partner | +| `link_test_formed_partner` | 1x Emerald | 1x Nether Star | 1 formed partner | +| `link_test_moon_partner` | 4x Lapis | 1x Ender Pearl | Partner in `ad_astra:moon` | +| `link_test_overworld_partner` | 4x Redstone | 4x Glowstone | Partner in `minecraft:overworld` | +| `link_test_dimension_item` | 8x Coal | 1x Diamond | Partner in Overworld with Diamond | +| `link_test_dimension_fluid` | 1x Sponge | 1x Wet Sponge | Partner in Overworld with Water | + +--- + +## 6. Translation Keys + +```properties +# Link operations +cosmiccore.datastick.link_copied=Link: %s +cosmiccore.link.copied=Link data copied from %s +cosmiccore.link.established=Link established: %s ↔ %s + +# Errors +cosmiccore.link.not_ready=Machine not ready for linking +cosmiccore.link.invalid_data=Invalid link data on datastick +cosmiccore.link.cannot_self_link=Cannot link a machine to itself +cosmiccore.link.partner_not_loaded=Partner machine must be loaded to establish link +cosmiccore.link.partner_missing=Partner machine no longer exists +cosmiccore.link.not_linkable=Target machine does not support linking +cosmiccore.link.different_owner=Cannot link machines owned by different teams +cosmiccore.link.incompatible_roles=Incompatible link roles: %s cannot link to %s +cosmiccore.link.limit_reached_self=This machine has reached its link limit +cosmiccore.link.limit_reached_partner=Partner machine has reached its link limit +cosmiccore.link.incompatible_self=This machine cannot link to that type +cosmiccore.link.incompatible_partner=Partner machine cannot link to this type +cosmiccore.link.already_linked=These machines are already linked + +# Recipe conditions +cosmiccore.recipe.condition.linked_partner.tooltip=Requires %s linked partner(s) +cosmiccore.recipe.condition.linked_partner.formed=Requires %s linked partner(s) with valid structure +cosmiccore.recipe.condition.linked_partner.working=Requires %s linked partner(s) actively working +cosmiccore.recipe.condition.linked_partner_dimension.tooltip=Requires linked partner in %s +cosmiccore.recipe.condition.linked_partner_dimension_item.tooltip=Requires %sx %s in partner in %s +cosmiccore.recipe.condition.linked_partner_dimension_fluid.tooltip=Requires %smB %s in partner in %s +``` + +--- + +## 7. File Structure + +``` +src/main/java/com/ghostipedia/cosmiccore/ +├── api/ +│ ├── capability/ +│ │ └── ILinkedMultiblock.java # Interface for linkable machines +│ ├── data/savedData/ +│ │ ├── LinkEntry.java # Single link record +│ │ └── LinkedMultiblockSavedData.java # Persistence layer +│ └── machine/multiblock/ +│ └── LinkedWorkableElectricMultiblockMachine.java # Base class +├── common/ +│ ├── machine/multiblock/ +│ │ ├── LinkedMultiblockHelper.java # Utilities, chunk loading, queries +│ │ └── multi/ +│ │ ├── LinkTestStation.java # Test multiblock registration +│ │ └── logic/ +│ │ └── LinkTestStationMachine.java # Test multiblock logic +│ └── recipe/condition/ +│ ├── CosmicConditions.java # Condition registration +│ ├── LinkedPartnerCondition.java +│ ├── LinkedPartnerDimensionCondition.java +│ ├── LinkedPartnerDimensionItemCondition.java +│ └── LinkedPartnerDimensionFluidCondition.java +└── gtbridge/ + ├── CosmicRecipeTypes.java # LINK_TEST_RECIPES type + └── CosmicCoreRecipes.java # Test recipes +``` + +--- + +## 8. Security Notes + +1. **Ownership is always verified at runtime** - Never trust datastick NBT for ownership +2. **Partner must be loaded for link validation** - Prevents linking to arbitrary positions +3. **Role negotiation prevents privilege escalation** - CONTROLLER+CONTROLLER rejected +4. **Chunk loading is capped** - MAX_FORCED_CHUNKS_PER_MACHINE = 4 + +--- + +## 9. Known Limitations + +1. **Partner must be loaded to establish link** - No config option for force-load during linking yet +2. **No GUI for link management** - Links can only be viewed via machine display text +3. **No visual feedback** - No particles/beams between linked machines +4. **No admin commands** - No way to inspect/remove links via commands + +--- + +## 10. Future Work + +### Phase 2: Recipe Integration +- [ ] Cross-dimensional ingredient consumption (consume from partner's inputs) +- [ ] Cross-dimensional output insertion (insert into partner's outputs) +- [ ] Recipe modifier based on partner state + +### Phase 3: Quality of Life +- [ ] Config option for force-load during linking +- [ ] Link management GUI +- [ ] Visual feedback (particles, beams) +- [ ] Admin commands (`/cosmiccore link list/remove/info`) + +### Phase 4: Advanced Features +- [ ] Energy/fluid transfer between linked machines +- [ ] Item teleportation through links +- [ ] Wireless redstone/data through links + +--- + +## 11. Testing Checklist + +- [x] Basic linking between two machines (same dimension) +- [x] Cross-dimensional linking (Overworld ↔ Moon) +- [x] Link persistence across server restart +- [x] Link broken when machine destroyed +- [x] Role negotiation (PEER+PEER, PEER+CONTROLLER, etc.) +- [x] Partner limit enforcement +- [x] Recipe condition: LinkedPartnerCondition +- [x] Recipe condition: LinkedPartnerDimensionCondition +- [x] Recipe condition: LinkedPartnerDimensionItemCondition +- [x] Recipe condition: LinkedPartnerDimensionFluidCondition +- [x] Partner query utilities (items, fluids, energy, formed, working) diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/capability/ILinkedMultiblock.java b/src/main/java/com/ghostipedia/cosmiccore/api/capability/ILinkedMultiblock.java new file mode 100644 index 000000000..6b534d57b --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/api/capability/ILinkedMultiblock.java @@ -0,0 +1,107 @@ +package com.ghostipedia.cosmiccore.api.capability; + +import com.gregtechceu.gtceu.api.machine.feature.IDataStickInteractable; + +import net.minecraft.core.GlobalPos; + +import org.jetbrains.annotations.Nullable; + +import java.util.Set; +import java.util.UUID; + +/** + * Interface for multiblocks that support cross-dimensional linking. + * Extends GTCEu's IDataStickInteractable for datastick-based linking. + *

+ * SECURITY: Link validation MUST load and verify the partner machine. + * Never trust datastick NBT for ownership or compatibility checks. + */ +public interface ILinkedMultiblock extends IDataStickInteractable { + + /** + * Role this machine prefers in links it creates. + * Actual role is determined by negotiation with partner. + */ + enum LinkRole { + /** Bidirectional - both machines can query each other */ + PEER, + /** This machine controls partners - can query them, they cannot query us */ + CONTROLLER, + /** This machine is controlled by partners - they can query us, we cannot query them */ + REMOTE + } + + // ==================== Configuration ==================== + + /** + * Check if this machine can link to the given partner. + * Called AFTER partner machine is loaded and ownership is verified. + * Called AFTER role negotiation succeeds. + *

+ * Use for type compatibility, distance limits, dimension restrictions, etc. + * Ownership and role checks are handled by the linking system. + * + * @param partner The partner's position + * @param partnerMachine The actual loaded partner machine + */ + boolean canLinkTo(GlobalPos partner, ILinkedMultiblock partnerMachine); + + /** + * Get the role this machine prefers when linking. + * Actual effective role is determined by negotiation. + * + * @see com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper#negotiateRoles + */ + LinkRole getLinkRole(); + + /** + * Maximum number of partners this machine can link to. + * Default: 4 + */ + default int getMaxPartners() { + return 4; + } + + // ==================== Lifecycle ==================== + + /** + * Called when a link is successfully established. + * May be called immediately (if partner loaded) or deferred (on this machine's load). + * + * @param partner The linked partner's position + */ + void onLinkEstablished(GlobalPos partner); + + /** + * Called when a link is broken. + * Reasons: partner destroyed, manual unlink, ownership change, etc. + * + * @param partner The unlinked partner's position + */ + void onLinkBroken(GlobalPos partner); + + /** + * Called during machine load to process deferred link notifications. + * Implementation should compare SavedData links vs known partners. + */ + void processLinkNotifications(); + + // ==================== Query ==================== + + /** + * Get all currently linked partners from SavedData. + */ + Set getLinkedPartners(); + + /** + * Get this machine's GlobalPos for link registration. + */ + GlobalPos getGlobalPos(); + + /** + * Get the owner UUID (team or player) for access control. + * Should use FTB Teams integration via existing getTeamUUID() pattern. + */ + @Nullable + UUID getTeamUUID(); +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/data/savedData/LinkEntry.java b/src/main/java/com/ghostipedia/cosmiccore/api/data/savedData/LinkEntry.java new file mode 100644 index 000000000..742522fc7 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/api/data/savedData/LinkEntry.java @@ -0,0 +1,79 @@ +package com.ghostipedia.cosmiccore.api.data.savedData; + +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock.LinkRole; + +import net.minecraft.core.GlobalPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; + +import java.util.Objects; + +/** + * Represents one end of a link from this machine's perspective. + * Stores the target position and this machine's EFFECTIVE role + * (after negotiation, not declared role). + */ +public final class LinkEntry { + + private static final String TAG_TARGET = "Target"; + private static final String TAG_ROLE = "Role"; + + private final GlobalPos target; + private final LinkRole effectiveRole; + + public LinkEntry(GlobalPos target, LinkRole effectiveRole) { + this.target = target; + this.effectiveRole = effectiveRole; + } + + public GlobalPos target() { + return target; + } + + public LinkRole effectiveRole() { + return effectiveRole; + } + + public CompoundTag save() { + CompoundTag tag = new CompoundTag(); + GlobalPos.CODEC.encodeStart(NbtOps.INSTANCE, target) + .result() + .ifPresent(encoded -> tag.put(TAG_TARGET, encoded)); + tag.putString(TAG_ROLE, effectiveRole.name()); + return tag; + } + + public static LinkEntry load(CompoundTag tag) { + GlobalPos target = GlobalPos.CODEC + .decode(NbtOps.INSTANCE, tag.get(TAG_TARGET)) + .result() + .map(pair -> pair.getFirst()) + .orElseThrow(() -> new IllegalStateException("Invalid LinkEntry: missing target")); + + LinkRole role = LinkRole.valueOf(tag.getString(TAG_ROLE)); + return new LinkEntry(target, role); + } + + /** + * Equality is based ONLY on target, not role. + * This ensures only one link per target in a Set, and re-linking + * with a different role will replace the existing entry. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LinkEntry linkEntry = (LinkEntry) o; + return Objects.equals(target, linkEntry.target); + } + + @Override + public int hashCode() { + return Objects.hash(target); + } + + @Override + public String toString() { + return "LinkEntry{target=" + target + ", role=" + effectiveRole + "}"; + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/data/savedData/LinkedMultiblockSavedData.java b/src/main/java/com/ghostipedia/cosmiccore/api/data/savedData/LinkedMultiblockSavedData.java new file mode 100644 index 000000000..44ae168c5 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/api/data/savedData/LinkedMultiblockSavedData.java @@ -0,0 +1,297 @@ +package com.ghostipedia.cosmiccore.api.data.savedData; + +import com.ghostipedia.cosmiccore.CosmicCore; +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock.LinkRole; + +import net.minecraft.core.GlobalPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; + +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * Persists multiblock link relationships across server restarts. + * Links are keyed by team UUID for access control. + *

+ * Each machine stores its own perspective with EFFECTIVE role + * (result of negotiation, not declared role). + */ +public class LinkedMultiblockSavedData extends SavedData { + + private static final String DATA_NAME = "cosmiccore_linked_multiblocks"; + + // Owner UUID -> (Machine GlobalPos -> Set of LinkEntry) + private final Map>> links = new HashMap<>(); + + public LinkedMultiblockSavedData() {} + + public LinkedMultiblockSavedData(CompoundTag tag) { + load(tag); + } + + // ==================== Access ==================== + + /** + * Get or create the SavedData instance. + * Stored in overworld to ensure single source of truth. + */ + public static LinkedMultiblockSavedData getOrCreate(MinecraftServer server) { + ServerLevel overworld = server.overworld(); + return overworld.getDataStorage().computeIfAbsent( + LinkedMultiblockSavedData::new, + LinkedMultiblockSavedData::new, + DATA_NAME); + } + + public static LinkedMultiblockSavedData getOrCreate(ServerLevel level) { + return getOrCreate(level.getServer()); + } + + // ==================== Link Management ==================== + + /** + * Establish a link between two machines with pre-negotiated roles. + *

+ * If a link already exists between these machines, it will be replaced + * with the new roles. + *

+ * IMPORTANT: Roles should be the result of negotiateRoles(), not raw declared roles. + * + * @param owner Team/player UUID (must match for both machines) + * @param a First machine's position + * @param b Second machine's position + * @param aEffectiveRole A's effective role after negotiation + * @param bEffectiveRole B's effective role after negotiation + */ + public void link(UUID owner, GlobalPos a, GlobalPos b, + LinkRole aEffectiveRole, LinkRole bEffectiveRole) { + // A's perspective - remove existing then add (to handle role changes) + Set aLinks = links.computeIfAbsent(owner, k -> new HashMap<>()) + .computeIfAbsent(a, k -> new HashSet<>()); + aLinks.remove(new LinkEntry(b, null)); // equals only checks target + aLinks.add(new LinkEntry(b, aEffectiveRole)); + + // B's perspective + Set bLinks = links.get(owner) + .computeIfAbsent(b, k -> new HashSet<>()); + bLinks.remove(new LinkEntry(a, null)); // equals only checks target + bLinks.add(new LinkEntry(a, bEffectiveRole)); + + setDirty(); + } + + /** + * Remove a specific link between two machines. + * Removes both perspectives. + */ + public void unlink(UUID owner, GlobalPos a, GlobalPos b) { + Map> ownerLinks = links.get(owner); + if (ownerLinks == null) return; + + // Remove A -> B + Set aLinks = ownerLinks.get(a); + if (aLinks != null) { + aLinks.removeIf(entry -> entry.target().equals(b)); + if (aLinks.isEmpty()) { + ownerLinks.remove(a); + } + } + + // Remove B -> A + Set bLinks = ownerLinks.get(b); + if (bLinks != null) { + bLinks.removeIf(entry -> entry.target().equals(a)); + if (bLinks.isEmpty()) { + ownerLinks.remove(b); + } + } + + // Cleanup empty owner entry + if (ownerLinks.isEmpty()) { + links.remove(owner); + } + + setDirty(); + } + + /** + * Remove all links for a machine (called when multiblock is destroyed). + * Also removes reverse links from all partners. + */ + public void removeAllLinks(UUID owner, GlobalPos pos) { + Map> ownerLinks = links.get(owner); + if (ownerLinks == null) return; + + // Get partners before removal + Set myLinks = ownerLinks.remove(pos); + + // Remove reverse links from partners + if (myLinks != null) { + for (LinkEntry entry : myLinks) { + Set partnerLinks = ownerLinks.get(entry.target()); + if (partnerLinks != null) { + partnerLinks.removeIf(e -> e.target().equals(pos)); + if (partnerLinks.isEmpty()) { + ownerLinks.remove(entry.target()); + } + } + } + } + + if (ownerLinks.isEmpty()) { + links.remove(owner); + } + + setDirty(); + } + + // ==================== Queries ==================== + + /** + * Get all links for a machine. + * Returns an unmodifiable copy to prevent external mutation. + */ + public Set getLinks(UUID owner, GlobalPos pos) { + Set result = links.getOrDefault(owner, Collections.emptyMap()) + .getOrDefault(pos, Collections.emptySet()); + return Collections.unmodifiableSet(new HashSet<>(result)); + } + + /** + * Get just the partner positions (without role info). + */ + public Set getPartnerPositions(UUID owner, GlobalPos pos) { + Set result = new HashSet<>(); + for (LinkEntry entry : getLinks(owner, pos)) { + result.add(entry.target()); + } + return result; + } + + /** + * Get the specific link to a partner, if it exists. + */ + @Nullable + public LinkEntry getLinkTo(UUID owner, GlobalPos self, GlobalPos partner) { + for (LinkEntry entry : getLinks(owner, self)) { + if (entry.target().equals(partner)) { + return entry; + } + } + return null; + } + + /** + * Check if this machine can query the partner (based on effective role). + * PEER and CONTROLLER can query; REMOTE cannot. + */ + public boolean canQuery(UUID owner, GlobalPos self, GlobalPos partner) { + LinkEntry link = getLinkTo(owner, self, partner); + if (link == null) return false; + + return link.effectiveRole() == LinkRole.PEER || link.effectiveRole() == LinkRole.CONTROLLER; + } + + /** + * Check if this machine can be queried by the partner (based on effective role). + * PEER and REMOTE can be queried; CONTROLLER cannot. + */ + public boolean canBeQueriedBy(UUID owner, GlobalPos self, GlobalPos partner) { + LinkEntry link = getLinkTo(owner, self, partner); + if (link == null) return false; + + return link.effectiveRole() == LinkRole.PEER || link.effectiveRole() == LinkRole.REMOTE; + } + + /** + * Check if two machines are linked (regardless of role). + */ + public boolean isLinked(UUID owner, GlobalPos a, GlobalPos b) { + return getLinkTo(owner, a, b) != null; + } + + // ==================== Serialization ==================== + + @Override + public CompoundTag save(CompoundTag root) { + ListTag ownersList = new ListTag(); + + for (Map.Entry>> ownerEntry : links.entrySet()) { + CompoundTag ownerTag = new CompoundTag(); + ownerTag.putUUID("Owner", ownerEntry.getKey()); + + ListTag machinesList = new ListTag(); + for (Map.Entry> machineEntry : ownerEntry.getValue().entrySet()) { + CompoundTag machineTag = new CompoundTag(); + + GlobalPos.CODEC.encodeStart(NbtOps.INSTANCE, machineEntry.getKey()) + .result() + .ifPresent(encoded -> machineTag.put("Pos", encoded)); + + ListTag linksList = new ListTag(); + for (LinkEntry link : machineEntry.getValue()) { + linksList.add(link.save()); + } + machineTag.put("Links", linksList); + + machinesList.add(machineTag); + } + ownerTag.put("Machines", machinesList); + + ownersList.add(ownerTag); + } + + root.put("Owners", ownersList); + return root; + } + + private void load(CompoundTag root) { + links.clear(); + + ListTag ownersList = root.getList("Owners", Tag.TAG_COMPOUND); + for (int i = 0; i < ownersList.size(); i++) { + CompoundTag ownerTag = ownersList.getCompound(i); + UUID owner = ownerTag.getUUID("Owner"); + + Map> ownerLinks = new HashMap<>(); + + ListTag machinesList = ownerTag.getList("Machines", Tag.TAG_COMPOUND); + for (int j = 0; j < machinesList.size(); j++) { + CompoundTag machineTag = machinesList.getCompound(j); + + GlobalPos pos = GlobalPos.CODEC + .decode(NbtOps.INSTANCE, machineTag.get("Pos")) + .result() + .map(pair -> pair.getFirst()) + .orElse(null); + + if (pos == null) continue; + + Set machineLinks = new HashSet<>(); + ListTag linksList = machineTag.getList("Links", Tag.TAG_COMPOUND); + for (int k = 0; k < linksList.size(); k++) { + try { + machineLinks.add(LinkEntry.load(linksList.getCompound(k))); + } catch (Exception e) { + CosmicCore.LOGGER.warn("Failed to load link entry: {}", e.getMessage()); + } + } + + if (!machineLinks.isEmpty()) { + ownerLinks.put(pos, machineLinks); + } + } + + if (!ownerLinks.isEmpty()) { + links.put(owner, ownerLinks); + } + } + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/machine/multiblock/LinkedWorkableElectricMultiblockMachine.java b/src/main/java/com/ghostipedia/cosmiccore/api/machine/multiblock/LinkedWorkableElectricMultiblockMachine.java new file mode 100644 index 000000000..a4c81daf2 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/api/machine/multiblock/LinkedWorkableElectricMultiblockMachine.java @@ -0,0 +1,621 @@ +package com.ghostipedia.cosmiccore.api.machine.multiblock; + +import com.ghostipedia.cosmiccore.CosmicCore; +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock; +import com.ghostipedia.cosmiccore.api.data.savedData.LinkEntry; +import com.ghostipedia.cosmiccore.api.data.savedData.LinkedMultiblockSavedData; +import com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper; +import com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper.RolePair; + +import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; +import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.machine.feature.IMachineLife; +import com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine; +import com.gregtechceu.gtceu.common.machine.owner.FTBOwner; + +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.GlobalPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; + +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Predicate; + +/** + * Base class for multiblocks that support cross-dimensional linking. + *

+ * Subclasses should override: + *

    + *
  • {@link #canLinkTo(GlobalPos, ILinkedMultiblock)} - Type compatibility checks
  • + *
  • {@link #getLinkRole()} - Define role preference (PEER, CONTROLLER, REMOTE)
  • + *
  • {@link #getMaxPartners()} - Override if more/fewer than 4 partners needed
  • + *
  • {@link #onLinkEstablished(GlobalPos)} - React to new links
  • + *
  • {@link #onLinkBroken(GlobalPos)} - Cleanup when links break
  • + *
+ */ +public abstract class LinkedWorkableElectricMultiblockMachine extends WorkableElectricMultiblockMachine + implements ILinkedMultiblock, IMachineLife { + + protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( + LinkedWorkableElectricMultiblockMachine.class, + WorkableElectricMultiblockMachine.MANAGED_FIELD_HOLDER); + + private static final String DATASTICK_TAG_KEY = "cosmiccore:link_data"; + private static final String TAG_POS = "Pos"; + private static final String TAG_OWNER = "Owner"; + + /** + * Local cache of known partners, rebuilt from SavedData on structure form. + * Used to detect changes for lifecycle callbacks. + * NOT persisted - rebuilt from SavedData which is the source of truth. + */ + protected Set knownPartners = new HashSet<>(); + + public LinkedWorkableElectricMultiblockMachine(IMachineBlockEntity holder, Object... args) { + super(holder, args); + } + + @Override + public ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + // ==================== ILinkedMultiblock Implementation ==================== + + @Override + public GlobalPos getGlobalPos() { + if (getLevel() instanceof ServerLevel serverLevel) { + return GlobalPos.of(serverLevel.dimension(), getPos()); + } + return null; + } + + @Override + @Nullable + public UUID getTeamUUID() { + var team = ((FTBOwner) getOwner()).getPlayerTeam(getOwnerUUID()); + return team != null ? team.getTeamId() : getOwnerUUID(); + } + + @Override + public Set getLinkedPartners() { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return Collections.emptySet(); + } + + UUID owner = getTeamUUID(); + if (owner == null) return Collections.emptySet(); + + LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(serverLevel); + return savedData.getPartnerPositions(owner, getGlobalPos()); + } + + @Override + public void processLinkNotifications() { + if (!(getLevel() instanceof ServerLevel serverLevel)) return; + + UUID owner = getTeamUUID(); + if (owner == null) return; + + Set currentPartners = getLinkedPartners(); + + // Find new partners (in SavedData but not in our cache) + for (GlobalPos partner : currentPartners) { + if (!knownPartners.contains(partner)) { + onLinkEstablished(partner); + } + } + + // Find removed partners (in our cache but not in SavedData) + Set removed = new HashSet<>(knownPartners); + removed.removeAll(currentPartners); + for (GlobalPos partner : removed) { + onLinkBroken(partner); + } + + // Update cache + knownPartners = new HashSet<>(currentPartners); + } + + // ==================== Lifecycle ==================== + + @Override + public void onStructureFormed() { + super.onStructureFormed(); + // Process any pending link notifications from when we were unloaded + processLinkNotifications(); + } + + @Override + public void onStructureInvalid() { + super.onStructureInvalid(); + // Don't remove links when structure breaks - links persist to SavedData + // They'll be cleaned up when the machine is actually destroyed + } + + @Override + public void onMachineRemoved() { + IMachineLife.super.onMachineRemoved(); + + if (getLevel() instanceof ServerLevel serverLevel) { + UUID owner = getTeamUUID(); + GlobalPos myPos = getGlobalPos(); + + if (owner != null && myPos != null) { + // Release any force-loaded chunks + LinkedMultiblockHelper.releaseAllTickets(serverLevel.getServer(), myPos); + + // Remove all links from SavedData + LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(serverLevel); + savedData.removeAllLinks(owner, myPos); + + // Notify partners (if loaded) + for (GlobalPos partner : knownPartners) { + ILinkedMultiblock partnerMachine = LinkedMultiblockHelper.getLinkedMachine( + serverLevel.getServer(), partner); + if (partnerMachine != null) { + partnerMachine.onLinkBroken(myPos); + } + } + } + } + } + + // ==================== Datastick Handling ==================== + + @Override + public InteractionResult onDataStickShiftUse(Player player, ItemStack dataStick) { + if (isRemote()) { + return InteractionResult.SUCCESS; + } + + GlobalPos myPos = getGlobalPos(); + UUID owner = getTeamUUID(); + + if (myPos == null || owner == null) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.not_ready") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // Write link data to datastick + CompoundTag linkData = new CompoundTag(); + GlobalPos.CODEC.encodeStart(NbtOps.INSTANCE, myPos) + .result() + .ifPresent(encoded -> linkData.put(TAG_POS, encoded)); + linkData.putUUID(TAG_OWNER, owner); + + // Store in namespaced tag to preserve other datastick data + CompoundTag rootTag = dataStick.getOrCreateTag(); + rootTag.put(DATASTICK_TAG_KEY, linkData); + + // Update datastick name + String machineName = getDefinition().getName(); + dataStick.setHoverName(Component.translatable("cosmiccore.datastick.link_copied", machineName)); + + // Feedback + player.sendSystemMessage(Component.translatable("cosmiccore.link.copied", machineName) + .withStyle(ChatFormatting.GREEN)); + + return InteractionResult.SUCCESS; + } + + @Override + public InteractionResult onDataStickUse(Player player, ItemStack dataStick) { + if (isRemote()) { + return InteractionResult.sidedSuccess(true); + } + + CompoundTag rootTag = dataStick.getTag(); + if (rootTag == null || !rootTag.contains(DATASTICK_TAG_KEY)) { + return InteractionResult.PASS; // Not our data, let other handlers try + } + + CompoundTag linkData = rootTag.getCompound(DATASTICK_TAG_KEY); + + // Parse partner info from datastick + GlobalPos partnerPos = GlobalPos.CODEC + .decode(NbtOps.INSTANCE, linkData.get(TAG_POS)) + .result() + .map(pair -> pair.getFirst()) + .orElse(null); + + if (partnerPos == null) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.invalid_data") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + UUID partnerOwner = linkData.getUUID(TAG_OWNER); + + // Attempt to establish link + return tryLink(player, partnerPos, partnerOwner); + } + + /** + * Attempt to establish a link with the partner machine. + * Handles all validation, negotiation, and persistence. + */ + protected InteractionResult tryLink(Player player, GlobalPos partnerPos, UUID partnerOwner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return InteractionResult.FAIL; + } + + MinecraftServer server = serverLevel.getServer(); + GlobalPos myPos = getGlobalPos(); + UUID myOwner = getTeamUUID(); + + // === Validation === + + // Self-link check + if (myPos.equals(partnerPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.cannot_self_link") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // NOTE: We intentionally do NOT check partnerOwner from datastick NBT here. + // The datastick may be stale (team changed since it was written). + // Ownership is verified at runtime after loading the partner machine. + + // Partner limit check (this machine) + Set currentPartners = getLinkedPartners(); + if (currentPartners.size() >= getMaxPartners() && !currentPartners.contains(partnerPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.limit_reached_self") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // Already linked check + if (currentPartners.contains(partnerPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.already_linked") + .withStyle(ChatFormatting.YELLOW)); + return InteractionResult.FAIL; + } + + // === Load and verify partner === + // SECURITY: Always load partner to verify ownership and compatibility + boolean needsUnload = false; + if (!LinkedMultiblockHelper.isPartnerOnline(server, partnerPos)) { + // Try to force-load partner temporarily + if (!LinkedMultiblockHelper.forceLoadPartnerChunk(server, myPos, partnerPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.partner_not_loaded") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + needsUnload = true; + } + + try { + MetaMachine rawPartner = LinkedMultiblockHelper.getMachine(server, partnerPos); + if (rawPartner == null) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.partner_missing") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + if (!(rawPartner instanceof ILinkedMultiblock partnerMachine)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.not_linkable") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // Verify ownership matches at runtime + UUID actualPartnerOwner = partnerMachine.getTeamUUID(); + if (!Objects.equals(myOwner, actualPartnerOwner)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.different_owner") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // === Partner capacity check === + Set partnerLinks = partnerMachine.getLinkedPartners(); + if (partnerLinks.size() >= partnerMachine.getMaxPartners() && !partnerLinks.contains(myPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.limit_reached_partner") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // === Role Negotiation === + RolePair roles = LinkedMultiblockHelper.negotiateRoles(getLinkRole(), partnerMachine.getLinkRole()); + if (roles == null) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.incompatible_roles", + getLinkRole().name(), partnerMachine.getLinkRole().name()) + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // === Type compatibility check === + if (!canLinkTo(partnerPos, partnerMachine)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.incompatible_self") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + if (!partnerMachine.canLinkTo(myPos, this)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.incompatible_partner") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // === Persist link === + LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(serverLevel); + savedData.link(myOwner, myPos, partnerPos, roles.aRole(), roles.bRole()); + + // === Notify both machines === + onLinkEstablished(partnerPos); + knownPartners.add(partnerPos); + + partnerMachine.onLinkEstablished(myPos); + + // Success feedback + String myName = getDefinition().getName(); + String partnerName = rawPartner.getDefinition().getName(); + player.sendSystemMessage(Component.translatable("cosmiccore.link.established", myName, partnerName) + .withStyle(ChatFormatting.GREEN)); + + return InteractionResult.SUCCESS; + + } finally { + // Release temporary chunk load + if (needsUnload) { + LinkedMultiblockHelper.releasePartnerChunk(server, myPos, partnerPos); + } + } + } + + // ==================== Default Implementations ==================== + + @Override + public boolean canLinkTo(GlobalPos partner, ILinkedMultiblock partnerMachine) { + // Default: allow linking to any ILinkedMultiblock + // Subclasses should override for type-specific restrictions + return true; + } + + @Override + public LinkRole getLinkRole() { + // Default: bidirectional peer + return LinkRole.PEER; + } + + @Override + public void onLinkEstablished(GlobalPos partner) { + // Default: just log + CosmicCore.LOGGER.debug("Link established: {} -> {}", getGlobalPos(), partner); + } + + @Override + public void onLinkBroken(GlobalPos partner) { + // Default: just log and update cache + CosmicCore.LOGGER.debug("Link broken: {} -> {}", getGlobalPos(), partner); + knownPartners.remove(partner); + } + + // ==================== Utility Methods ==================== + + /** + * Get a linked partner's machine instance. + * Does NOT force-load chunks - returns null if partner is unloaded. + */ + @Nullable + protected ILinkedMultiblock getPartnerMachine(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return null; + } + return LinkedMultiblockHelper.getLinkedMachine(serverLevel.getServer(), partner); + } + + /** + * Check if this machine can query the given partner (based on effective role). + */ + protected boolean canQueryPartner(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + return LinkedMultiblockHelper.canQuery(serverLevel.getServer(), owner, getGlobalPos(), partner); + } + + /** + * Get the effective role for this machine in relation to a specific partner. + */ + @Nullable + protected LinkRole getEffectiveRole(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return null; + } + UUID owner = getTeamUUID(); + if (owner == null) return null; + + LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(serverLevel); + LinkEntry link = savedData.getLinkTo(owner, getGlobalPos(), partner); + return link != null ? link.effectiveRole() : null; + } + + // ==================== Partner Resource Queries ==================== + + /** + * Check if a partner has a specific item in its input handlers. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @param itemPredicate Predicate to test items (e.g., stack -> stack.is(Items.DIAMOND)) + * @return true if partner has matching item, false otherwise or if unavailable + */ + protected boolean partnerHasItem(GlobalPos partner, Predicate itemPredicate) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + + return LinkedMultiblockHelper.partnerHasItem( + serverLevel.getServer(), owner, getGlobalPos(), partner, itemPredicate); + } + + /** + * Check if a partner has a specific fluid in its input handlers. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @param fluidPredicate Predicate to test fluids (e.g., stack -> stack.getFluid().is(Fluids.LAVA)) + * @return true if partner has matching fluid, false otherwise or if unavailable + */ + protected boolean partnerHasFluid(GlobalPos partner, Predicate fluidPredicate) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + + return LinkedMultiblockHelper.partnerHasFluid( + serverLevel.getServer(), owner, getGlobalPos(), partner, fluidPredicate); + } + + /** + * Get total energy stored in a partner's energy containers. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @return Energy stored in EU, or 0 if unavailable + */ + protected long getPartnerEnergyStored(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return 0L; + } + UUID owner = getTeamUUID(); + if (owner == null) return 0L; + + return LinkedMultiblockHelper.getPartnerEnergyStored( + serverLevel.getServer(), owner, getGlobalPos(), partner); + } + + /** + * Check if a partner's multiblock is formed. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @return true if partner is formed, false otherwise or if unavailable + */ + protected boolean isPartnerFormed(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + + return LinkedMultiblockHelper.isPartnerFormed( + serverLevel.getServer(), owner, getGlobalPos(), partner); + } + + /** + * Check if a partner is currently running a recipe. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @return true if partner is working, false otherwise or if unavailable + */ + protected boolean isPartnerWorking(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + + return LinkedMultiblockHelper.isPartnerWorking( + serverLevel.getServer(), owner, getGlobalPos(), partner); + } + + /** + * Execute a custom query on a partner machine. + * Handles chunk loading and permission checks automatically. + * + * @param partner The partner to query + * @param query The query function + * @return Query result, or null if unavailable + */ + @Nullable + protected T queryPartner(GlobalPos partner, LinkedMultiblockHelper.PartnerQuery query) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return null; + } + UUID owner = getTeamUUID(); + if (owner == null) return null; + + return LinkedMultiblockHelper.queryPartner( + serverLevel.getServer(), owner, getGlobalPos(), partner, query); + } + + /** + * Check if ANY linked partner has a specific item. + * Useful for recipe conditions that require "a linked partner has X". + * + * @param itemPredicate Predicate to test items + * @return true if any partner has the item + */ + protected boolean anyPartnerHasItem(Predicate itemPredicate) { + for (GlobalPos partner : getLinkedPartners()) { + if (partnerHasItem(partner, itemPredicate)) { + return true; + } + } + return false; + } + + /** + * Check if ANY linked partner has a specific fluid. + * + * @param fluidPredicate Predicate to test fluids + * @return true if any partner has the fluid + */ + protected boolean anyPartnerHasFluid(Predicate fluidPredicate) { + for (GlobalPos partner : getLinkedPartners()) { + if (partnerHasFluid(partner, fluidPredicate)) { + return true; + } + } + return false; + } + + /** + * Check if ANY linked partner is formed and working. + * + * @return true if any partner is actively working + */ + public boolean anyPartnerWorking() { + for (GlobalPos partner : getLinkedPartners()) { + if (isPartnerWorking(partner)) { + return true; + } + } + return false; + } + + /** + * Count how many linked partners are currently formed. + * + * @return Number of formed partners + */ + public int countFormedPartners() { + int count = 0; + for (GlobalPos partner : getLinkedPartners()) { + if (isPartnerFormed(partner)) { + count++; + } + } + return count; + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/machine/multiblock/LinkedWorkableMultiblockMachine.java b/src/main/java/com/ghostipedia/cosmiccore/api/machine/multiblock/LinkedWorkableMultiblockMachine.java new file mode 100644 index 000000000..c202e885b --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/api/machine/multiblock/LinkedWorkableMultiblockMachine.java @@ -0,0 +1,624 @@ +package com.ghostipedia.cosmiccore.api.machine.multiblock; + +import com.ghostipedia.cosmiccore.CosmicCore; +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock; +import com.ghostipedia.cosmiccore.api.data.savedData.LinkEntry; +import com.ghostipedia.cosmiccore.api.data.savedData.LinkedMultiblockSavedData; +import com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper; +import com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper.RolePair; + +import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; +import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.machine.feature.IMachineLife; +import com.gregtechceu.gtceu.api.machine.feature.multiblock.IDisplayUIMachine; +import com.gregtechceu.gtceu.api.machine.multiblock.WorkableMultiblockMachine; +import com.gregtechceu.gtceu.common.machine.owner.FTBOwner; + +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.GlobalPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; + +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Predicate; + +/** + * Base class for non-electric multiblocks that support cross-dimensional linking. + *

+ * For electric multiblocks, use {@link LinkedWorkableElectricMultiblockMachine} instead. + *

+ * Subclasses should override: + *

    + *
  • {@link #canLinkTo(GlobalPos, ILinkedMultiblock)} - Type compatibility checks
  • + *
  • {@link #getLinkRole()} - Define role preference (PEER, CONTROLLER, REMOTE)
  • + *
  • {@link #getMaxPartners()} - Override if more/fewer than 4 partners needed
  • + *
  • {@link #onLinkEstablished(GlobalPos)} - React to new links
  • + *
  • {@link #onLinkBroken(GlobalPos)} - Cleanup when links break
  • + *
+ */ +public abstract class LinkedWorkableMultiblockMachine extends WorkableMultiblockMachine + implements ILinkedMultiblock, IMachineLife, IDisplayUIMachine { + + protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( + LinkedWorkableMultiblockMachine.class, + WorkableMultiblockMachine.MANAGED_FIELD_HOLDER); + + private static final String DATASTICK_TAG_KEY = "cosmiccore:link_data"; + private static final String TAG_POS = "Pos"; + private static final String TAG_OWNER = "Owner"; + + /** + * Local cache of known partners, rebuilt from SavedData on structure form. + * Used to detect changes for lifecycle callbacks. + * NOT persisted - rebuilt from SavedData which is the source of truth. + */ + protected Set knownPartners = new HashSet<>(); + + public LinkedWorkableMultiblockMachine(IMachineBlockEntity holder, Object... args) { + super(holder, args); + } + + @Override + public ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + // ==================== ILinkedMultiblock Implementation ==================== + + @Override + public GlobalPos getGlobalPos() { + if (getLevel() instanceof ServerLevel serverLevel) { + return GlobalPos.of(serverLevel.dimension(), getPos()); + } + return null; + } + + @Override + @Nullable + public UUID getTeamUUID() { + var team = ((FTBOwner) getOwner()).getPlayerTeam(getOwnerUUID()); + return team != null ? team.getTeamId() : getOwnerUUID(); + } + + @Override + public Set getLinkedPartners() { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return Collections.emptySet(); + } + + UUID owner = getTeamUUID(); + if (owner == null) return Collections.emptySet(); + + LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(serverLevel); + return savedData.getPartnerPositions(owner, getGlobalPos()); + } + + @Override + public void processLinkNotifications() { + if (!(getLevel() instanceof ServerLevel serverLevel)) return; + + UUID owner = getTeamUUID(); + if (owner == null) return; + + Set currentPartners = getLinkedPartners(); + + // Find new partners (in SavedData but not in our cache) + for (GlobalPos partner : currentPartners) { + if (!knownPartners.contains(partner)) { + onLinkEstablished(partner); + } + } + + // Find removed partners (in our cache but not in SavedData) + Set removed = new HashSet<>(knownPartners); + removed.removeAll(currentPartners); + for (GlobalPos partner : removed) { + onLinkBroken(partner); + } + + // Update cache + knownPartners = new HashSet<>(currentPartners); + } + + // ==================== Lifecycle ==================== + + @Override + public void onStructureFormed() { + super.onStructureFormed(); + // Process any pending link notifications from when we were unloaded + processLinkNotifications(); + } + + @Override + public void onStructureInvalid() { + super.onStructureInvalid(); + // Don't remove links when structure breaks - links persist to SavedData + // They'll be cleaned up when the machine is actually destroyed + } + + @Override + public void onMachineRemoved() { + IMachineLife.super.onMachineRemoved(); + + if (getLevel() instanceof ServerLevel serverLevel) { + UUID owner = getTeamUUID(); + GlobalPos myPos = getGlobalPos(); + + if (owner != null && myPos != null) { + // Release any force-loaded chunks + LinkedMultiblockHelper.releaseAllTickets(serverLevel.getServer(), myPos); + + // Remove all links from SavedData + LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(serverLevel); + savedData.removeAllLinks(owner, myPos); + + // Notify partners (if loaded) + for (GlobalPos partner : knownPartners) { + ILinkedMultiblock partnerMachine = LinkedMultiblockHelper.getLinkedMachine( + serverLevel.getServer(), partner); + if (partnerMachine != null) { + partnerMachine.onLinkBroken(myPos); + } + } + } + } + } + + // ==================== Datastick Handling ==================== + + @Override + public InteractionResult onDataStickShiftUse(Player player, ItemStack dataStick) { + if (isRemote()) { + return InteractionResult.SUCCESS; + } + + GlobalPos myPos = getGlobalPos(); + UUID owner = getTeamUUID(); + + if (myPos == null || owner == null) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.not_ready") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // Write link data to datastick + CompoundTag linkData = new CompoundTag(); + GlobalPos.CODEC.encodeStart(NbtOps.INSTANCE, myPos) + .result() + .ifPresent(encoded -> linkData.put(TAG_POS, encoded)); + linkData.putUUID(TAG_OWNER, owner); + + // Store in namespaced tag to preserve other datastick data + CompoundTag rootTag = dataStick.getOrCreateTag(); + rootTag.put(DATASTICK_TAG_KEY, linkData); + + // Update datastick name + String machineName = getDefinition().getName(); + dataStick.setHoverName(Component.translatable("cosmiccore.datastick.link_copied", machineName)); + + // Feedback + player.sendSystemMessage(Component.translatable("cosmiccore.link.copied", machineName) + .withStyle(ChatFormatting.GREEN)); + + return InteractionResult.SUCCESS; + } + + @Override + public InteractionResult onDataStickUse(Player player, ItemStack dataStick) { + if (isRemote()) { + return InteractionResult.sidedSuccess(true); + } + + CompoundTag rootTag = dataStick.getTag(); + if (rootTag == null || !rootTag.contains(DATASTICK_TAG_KEY)) { + return InteractionResult.PASS; // Not our data, let other handlers try + } + + CompoundTag linkData = rootTag.getCompound(DATASTICK_TAG_KEY); + + // Parse partner info from datastick + GlobalPos partnerPos = GlobalPos.CODEC + .decode(NbtOps.INSTANCE, linkData.get(TAG_POS)) + .result() + .map(pair -> pair.getFirst()) + .orElse(null); + + if (partnerPos == null) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.invalid_data") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + UUID partnerOwner = linkData.getUUID(TAG_OWNER); + + // Attempt to establish link + return tryLink(player, partnerPos, partnerOwner); + } + + /** + * Attempt to establish a link with the partner machine. + * Handles all validation, negotiation, and persistence. + */ + protected InteractionResult tryLink(Player player, GlobalPos partnerPos, UUID partnerOwner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return InteractionResult.FAIL; + } + + MinecraftServer server = serverLevel.getServer(); + GlobalPos myPos = getGlobalPos(); + UUID myOwner = getTeamUUID(); + + // === Validation === + + // Self-link check + if (myPos.equals(partnerPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.cannot_self_link") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // NOTE: We intentionally do NOT check partnerOwner from datastick NBT here. + // The datastick may be stale (team changed since it was written). + // Ownership is verified at runtime after loading the partner machine. + + // Partner limit check (this machine) + Set currentPartners = getLinkedPartners(); + if (currentPartners.size() >= getMaxPartners() && !currentPartners.contains(partnerPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.limit_reached_self") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // Already linked check + if (currentPartners.contains(partnerPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.already_linked") + .withStyle(ChatFormatting.YELLOW)); + return InteractionResult.FAIL; + } + + // === Load and verify partner === + // SECURITY: Always load partner to verify ownership and compatibility + boolean needsUnload = false; + if (!LinkedMultiblockHelper.isPartnerOnline(server, partnerPos)) { + // Try to force-load partner temporarily + if (!LinkedMultiblockHelper.forceLoadPartnerChunk(server, myPos, partnerPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.partner_not_loaded") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + needsUnload = true; + } + + try { + MetaMachine rawPartner = LinkedMultiblockHelper.getMachine(server, partnerPos); + if (rawPartner == null) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.partner_missing") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + if (!(rawPartner instanceof ILinkedMultiblock partnerMachine)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.not_linkable") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // Verify ownership matches at runtime + UUID actualPartnerOwner = partnerMachine.getTeamUUID(); + if (!Objects.equals(myOwner, actualPartnerOwner)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.different_owner") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // === Partner capacity check === + Set partnerLinks = partnerMachine.getLinkedPartners(); + if (partnerLinks.size() >= partnerMachine.getMaxPartners() && !partnerLinks.contains(myPos)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.limit_reached_partner") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // === Role Negotiation === + RolePair roles = LinkedMultiblockHelper.negotiateRoles(getLinkRole(), partnerMachine.getLinkRole()); + if (roles == null) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.incompatible_roles", + getLinkRole().name(), partnerMachine.getLinkRole().name()) + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // === Type compatibility check === + if (!canLinkTo(partnerPos, partnerMachine)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.incompatible_self") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + if (!partnerMachine.canLinkTo(myPos, this)) { + player.sendSystemMessage(Component.translatable("cosmiccore.link.incompatible_partner") + .withStyle(ChatFormatting.RED)); + return InteractionResult.FAIL; + } + + // === Persist link === + LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(serverLevel); + savedData.link(myOwner, myPos, partnerPos, roles.aRole(), roles.bRole()); + + // === Notify both machines === + onLinkEstablished(partnerPos); + knownPartners.add(partnerPos); + + partnerMachine.onLinkEstablished(myPos); + + // Success feedback + String myName = getDefinition().getName(); + String partnerName = rawPartner.getDefinition().getName(); + player.sendSystemMessage(Component.translatable("cosmiccore.link.established", myName, partnerName) + .withStyle(ChatFormatting.GREEN)); + + return InteractionResult.SUCCESS; + + } finally { + // Release temporary chunk load + if (needsUnload) { + LinkedMultiblockHelper.releasePartnerChunk(server, myPos, partnerPos); + } + } + } + + // ==================== Default Implementations ==================== + + @Override + public boolean canLinkTo(GlobalPos partner, ILinkedMultiblock partnerMachine) { + // Default: allow linking to any ILinkedMultiblock + // Subclasses should override for type-specific restrictions + return true; + } + + @Override + public LinkRole getLinkRole() { + // Default: bidirectional peer + return LinkRole.PEER; + } + + @Override + public void onLinkEstablished(GlobalPos partner) { + // Default: just log + CosmicCore.LOGGER.debug("Link established: {} -> {}", getGlobalPos(), partner); + } + + @Override + public void onLinkBroken(GlobalPos partner) { + // Default: just log and update cache + CosmicCore.LOGGER.debug("Link broken: {} -> {}", getGlobalPos(), partner); + knownPartners.remove(partner); + } + + // ==================== Utility Methods ==================== + + /** + * Get a linked partner's machine instance. + * Does NOT force-load chunks - returns null if partner is unloaded. + */ + @Nullable + protected ILinkedMultiblock getPartnerMachine(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return null; + } + return LinkedMultiblockHelper.getLinkedMachine(serverLevel.getServer(), partner); + } + + /** + * Check if this machine can query the given partner (based on effective role). + */ + protected boolean canQueryPartner(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + return LinkedMultiblockHelper.canQuery(serverLevel.getServer(), owner, getGlobalPos(), partner); + } + + /** + * Get the effective role for this machine in relation to a specific partner. + */ + @Nullable + protected LinkRole getEffectiveRole(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return null; + } + UUID owner = getTeamUUID(); + if (owner == null) return null; + + LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(serverLevel); + LinkEntry link = savedData.getLinkTo(owner, getGlobalPos(), partner); + return link != null ? link.effectiveRole() : null; + } + + // ==================== Partner Resource Queries ==================== + + /** + * Check if a partner has a specific item in its input handlers. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @param itemPredicate Predicate to test items (e.g., stack -> stack.is(Items.DIAMOND)) + * @return true if partner has matching item, false otherwise or if unavailable + */ + protected boolean partnerHasItem(GlobalPos partner, Predicate itemPredicate) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + + return LinkedMultiblockHelper.partnerHasItem( + serverLevel.getServer(), owner, getGlobalPos(), partner, itemPredicate); + } + + /** + * Check if a partner has a specific fluid in its input handlers. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @param fluidPredicate Predicate to test fluids (e.g., stack -> stack.getFluid().is(Fluids.LAVA)) + * @return true if partner has matching fluid, false otherwise or if unavailable + */ + protected boolean partnerHasFluid(GlobalPos partner, Predicate fluidPredicate) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + + return LinkedMultiblockHelper.partnerHasFluid( + serverLevel.getServer(), owner, getGlobalPos(), partner, fluidPredicate); + } + + /** + * Get total energy stored in a partner's energy containers. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @return Energy stored in EU, or 0 if unavailable + */ + protected long getPartnerEnergyStored(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return 0L; + } + UUID owner = getTeamUUID(); + if (owner == null) return 0L; + + return LinkedMultiblockHelper.getPartnerEnergyStored( + serverLevel.getServer(), owner, getGlobalPos(), partner); + } + + /** + * Check if a partner's multiblock is formed. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @return true if partner is formed, false otherwise or if unavailable + */ + protected boolean isPartnerFormed(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + + return LinkedMultiblockHelper.isPartnerFormed( + serverLevel.getServer(), owner, getGlobalPos(), partner); + } + + /** + * Check if a partner is currently running a recipe. + * Handles chunk loading automatically. + * + * @param partner The partner to query + * @return true if partner is working, false otherwise or if unavailable + */ + protected boolean isPartnerWorking(GlobalPos partner) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + UUID owner = getTeamUUID(); + if (owner == null) return false; + + return LinkedMultiblockHelper.isPartnerWorking( + serverLevel.getServer(), owner, getGlobalPos(), partner); + } + + /** + * Execute a custom query on a partner machine. + * Handles chunk loading and permission checks automatically. + * + * @param partner The partner to query + * @param query The query function + * @return Query result, or null if unavailable + */ + @Nullable + protected T queryPartner(GlobalPos partner, LinkedMultiblockHelper.PartnerQuery query) { + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return null; + } + UUID owner = getTeamUUID(); + if (owner == null) return null; + + return LinkedMultiblockHelper.queryPartner( + serverLevel.getServer(), owner, getGlobalPos(), partner, query); + } + + /** + * Check if ANY linked partner has a specific item. + * Useful for recipe conditions that require "a linked partner has X". + * + * @param itemPredicate Predicate to test items + * @return true if any partner has the item + */ + protected boolean anyPartnerHasItem(Predicate itemPredicate) { + for (GlobalPos partner : getLinkedPartners()) { + if (partnerHasItem(partner, itemPredicate)) { + return true; + } + } + return false; + } + + /** + * Check if ANY linked partner has a specific fluid. + * + * @param fluidPredicate Predicate to test fluids + * @return true if any partner has the fluid + */ + protected boolean anyPartnerHasFluid(Predicate fluidPredicate) { + for (GlobalPos partner : getLinkedPartners()) { + if (partnerHasFluid(partner, fluidPredicate)) { + return true; + } + } + return false; + } + + /** + * Check if ANY linked partner is formed and working. + * + * @return true if any partner is actively working + */ + public boolean anyPartnerWorking() { + for (GlobalPos partner : getLinkedPartners()) { + if (isPartnerWorking(partner)) { + return true; + } + } + return false; + } + + /** + * Count how many linked partners are currently formed. + * + * @return Number of formed partners + */ + public int countFormedPartners() { + int count = 0; + for (GlobalPos partner : getLinkedPartners()) { + if (isPartnerFormed(partner)) { + count++; + } + } + return count; + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/pattern/CosmicPredicates.java b/src/main/java/com/ghostipedia/cosmiccore/api/pattern/CosmicPredicates.java index dffb6f0fe..21f033e1d 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/api/pattern/CosmicPredicates.java +++ b/src/main/java/com/ghostipedia/cosmiccore/api/pattern/CosmicPredicates.java @@ -3,6 +3,7 @@ import com.ghostipedia.cosmiccore.api.CosmicCoreAPI; import com.ghostipedia.cosmiccore.api.block.IMagnetType; import com.ghostipedia.cosmiccore.common.block.MagnetBlock; +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.MothCargoStation; import com.gregtechceu.gtceu.api.pattern.TraceabilityPredicate; import com.gregtechceu.gtceu.api.pattern.error.PatternStringError; @@ -10,7 +11,11 @@ import com.lowdragmc.lowdraglib.utils.BlockInfo; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -18,6 +23,8 @@ import java.util.Map; import java.util.function.Supplier; +import static com.gregtechceu.gtceu.common.data.GTBlocks.CASING_STEEL_SOLID; + public class CosmicPredicates { public static TraceabilityPredicate magnetCoils() { @@ -63,5 +70,38 @@ public static TraceabilityPredicate starLadderModules() { .addTooltips(Component.translatable("gtceu.multiblock.pattern.error.filters")); } + /** + * Predicate for Moth Cargo Station moth homes (Forestry beehives or steel casing). + * Looks up blocks lazily at match time so Forestry blocks are properly resolved. + */ + public static TraceabilityPredicate mothHomes() { + return new TraceabilityPredicate(blockWorldState -> { + var blockState = blockWorldState.getBlockState(); + // Check if it's steel casing (always allowed as placeholder) + if (blockState.is(CASING_STEEL_SOLID.get())) { + return true; + } + // Check if it's a valid Forestry beehive + return MothCargoStation.isMothHome(blockState); + }, () -> { + // Provide block previews for JEI - look up Forestry blocks at render time + return new BlockInfo[] { + BlockInfo.fromBlockState(getBlockOrFallback(MothCargoStation.BEEHIVE_FOREST)), + BlockInfo.fromBlockState(getBlockOrFallback(MothCargoStation.BEEHIVE_LUSH)), + BlockInfo.fromBlockState(getBlockOrFallback(MothCargoStation.BEEHIVE_DESERT)), + BlockInfo.fromBlockState(getBlockOrFallback(MothCargoStation.BEEHIVE_END)), + BlockInfo.fromBlockState(CASING_STEEL_SOLID.get().defaultBlockState()) + }; + }).addTooltips(Component.literal("Forestry Beehive or Steel Casing")); + } + + /** + * Get a block by ResourceLocation, or vanilla beehive as fallback. + */ + private static net.minecraft.world.level.block.state.BlockState getBlockOrFallback(ResourceLocation loc) { + Block block = BuiltInRegistries.BLOCK.get(loc); + return (block != Blocks.AIR ? block : Blocks.BEEHIVE).defaultBlockState(); + } + public static void init() {} } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/block/MothHomeBlock.java b/src/main/java/com/ghostipedia/cosmiccore/common/block/MothHomeBlock.java new file mode 100644 index 000000000..cb15dabcc --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/block/MothHomeBlock.java @@ -0,0 +1,20 @@ +package com.ghostipedia.cosmiccore.common.block; + +import net.minecraft.world.level.block.Block; + +import lombok.Getter; + +/** + * Moth Home block - provides moths for the Cargo Moth system. + * Different tiers provide faster cycles and more moths per home. + */ +public class MothHomeBlock extends Block { + + @Getter + private final int tier; + + public MothHomeBlock(Properties properties, int tier) { + super(properties); + this.tier = tier; + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java b/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java index 7439c025b..f14228246 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java @@ -6,6 +6,7 @@ import com.ghostipedia.cosmiccore.client.renderer.block.NebulaeCoilRenderer; import com.ghostipedia.cosmiccore.common.block.DivingBellEscapePad; import com.ghostipedia.cosmiccore.common.block.MagnetBlock; +import com.ghostipedia.cosmiccore.common.block.MothHomeBlock; import com.ghostipedia.cosmiccore.common.blockentity.CosmicCoilBlockEntity; import com.ghostipedia.cosmiccore.ember.CosmicEmberEmitterBlock; import com.ghostipedia.cosmiccore.ember.CosmicEmberReceptorBlock; @@ -637,6 +638,26 @@ public class CosmicBlocks { .simpleItem() .register(); + // MOTH HOME BLOCKS - For Cargo Moths system + public static final BlockEntry MOTH_HOME_T1 = createMothHomeBlock(1); + public static final BlockEntry MOTH_HOME_T2 = createMothHomeBlock(2); + public static final BlockEntry MOTH_HOME_T3 = createMothHomeBlock(3); + public static final BlockEntry MOTH_HOME_T4 = createMothHomeBlock(4); + + // Moth Station Casing + public static final BlockEntry MOTH_STATION_CASING = createCasingBlock("moth_station_casing", + CosmicCore.id("block/casings/solid/moth_station_casing")); + + private static BlockEntry createMothHomeBlock(int tier) { + return REGISTRATE + .block("moth_home_t" + tier, p -> new MothHomeBlock(p, tier)) + .lang("Moth Home (T" + tier + ")") + .initialProperties(() -> Blocks.IRON_BLOCK) + .properties(p -> p.strength(3.0f, 6.0f)) + .simpleItem() + .register(); + } + private static BlockEntry createGlassCasingBlock(String name, ResourceLocation texture, Supplier> type) { NonNullFunction supplier = GlassBlock::new; diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/data/lang/CosmicLangHandler.java b/src/main/java/com/ghostipedia/cosmiccore/common/data/lang/CosmicLangHandler.java index 047ba61ce..5539c7f79 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/data/lang/CosmicLangHandler.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/data/lang/CosmicLangHandler.java @@ -156,6 +156,18 @@ public static void init(RegistrateLangProvider provider) { provider.add("cosmiccore.recipe.fieldSlam", "§fField Consumed: %sT"); provider.add("cosmiccore.recipe.condition.titan.tooltip", "Requires Titan Reactor Tier: %s"); + // Linked Partner Condition + provider.add("cosmiccore.recipe.condition.linked_partner.tooltip", "Requires %s linked partner(s)"); + provider.add("cosmiccore.recipe.condition.linked_partner.formed", + "Requires %s linked partner(s) with valid structure"); + provider.add("cosmiccore.recipe.condition.linked_partner.working", + "Requires %s linked partner(s) actively working"); + provider.add("cosmiccore.recipe.condition.linked_partner_dimension.tooltip", "Requires linked partner in %s"); + provider.add("cosmiccore.recipe.condition.linked_partner_dimension_item.tooltip", + "Requires %sx %s in partner in %s"); + provider.add("cosmiccore.recipe.condition.linked_partner_dimension_fluid.tooltip", + "Requires %smB %s in partner in %s"); + provider.add("cosmiccore.multiblock.heat_value", "§6Current Heat: %s"); provider.add("cosmiccore.multiblock.heat_capacity", "§cMax Heat: %s"); @@ -499,5 +511,30 @@ public static void init(RegistrateLangProvider provider) { "§fFilter data can be copy/pasted with a data stick§r", "§b'If you're wondering how to parallel assembly lines§r", "§fthis is how. Welcome to subnets!§r"); + + // Cross-Dimensional Multiblock Linking + provider.add("cosmiccore.datastick.link_copied", "Link: %s"); + provider.add("cosmiccore.link.copied", "Link data copied from %s"); + provider.add("cosmiccore.link.established", "Link established: %s ↔ %s"); + + // Link validation errors + provider.add("cosmiccore.link.not_ready", "Machine not ready for linking"); + provider.add("cosmiccore.link.invalid_data", "Invalid link data on datastick"); + provider.add("cosmiccore.link.cannot_self_link", "Cannot link a machine to itself"); + provider.add("cosmiccore.link.partner_not_loaded", "Partner machine must be loaded to establish link"); + provider.add("cosmiccore.link.partner_missing", "Partner machine no longer exists"); + provider.add("cosmiccore.link.not_linkable", "Target machine does not support linking"); + provider.add("cosmiccore.link.different_owner", "Cannot link machines owned by different teams"); + provider.add("cosmiccore.link.incompatible_roles", "Incompatible link roles: %s cannot link to %s"); + provider.add("cosmiccore.link.limit_reached_self", "This machine has reached its link limit"); + provider.add("cosmiccore.link.limit_reached_partner", "Partner machine has reached its link limit"); + provider.add("cosmiccore.link.incompatible_self", "This machine cannot link to that type"); + provider.add("cosmiccore.link.incompatible_partner", "Partner machine cannot link to this type"); + provider.add("cosmiccore.link.already_linked", "These machines are already linked"); + provider.add("cosmiccore.link.too_far", "Partner is too far away to force-load for linking"); + + // Link runtime status + provider.add("cosmiccore.recipe.waiting_for_partner", "Waiting for linked partner"); + provider.add("cosmiccore.link.partner_offline", "Linked partner offline"); } } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/LinkedMultiblockHelper.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/LinkedMultiblockHelper.java new file mode 100644 index 000000000..3f37c4bbe --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/LinkedMultiblockHelper.java @@ -0,0 +1,544 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock; + +import com.ghostipedia.cosmiccore.CosmicCore; +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock; +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock.LinkRole; +import com.ghostipedia.cosmiccore.api.data.savedData.LinkedMultiblockSavedData; + +import com.gregtechceu.gtceu.api.capability.recipe.EURecipeCapability; +import com.gregtechceu.gtceu.api.capability.recipe.FluidRecipeCapability; +import com.gregtechceu.gtceu.api.capability.recipe.IO; +import com.gregtechceu.gtceu.api.capability.recipe.IRecipeHandler; +import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability; +import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine; +import com.gregtechceu.gtceu.api.machine.trait.RecipeLogic; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraftforge.common.world.ForgeChunkManager; + +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * Utilities for cross-dimensional multiblock access. + * Handles chunk loading with proper lifecycle management. + */ +public class LinkedMultiblockHelper { + + /** Maximum forced chunks per requesting machine */ + public static final int MAX_FORCED_CHUNKS_PER_MACHINE = 4; + + /** + * Tracks active force-load tickets. + * Maps: Requester GlobalPos -> (Target GlobalPos -> Owner BlockPos used for ticket) + *

+ * IMPORTANT: We use the requester's position as the ticket owner to ensure + * each requester has independent tickets. This prevents one requester from + * accidentally releasing another's ticket. + *

+ * NOTE: Tickets are tracked in memory only. A hard crash while tickets are active + * can leave chunks loaded until restart. Mitigation: tickets are short-lived + * (released after use), and Forge clears orphaned tickets on dimension unload. + */ + private static final Map> activeTickets = new HashMap<>(); + + // ==================== Role Negotiation ==================== + + /** + * Result of role negotiation between two machines. + */ + public record RolePair(LinkRole aRole, LinkRole bRole) {} + + /** + * Negotiate effective roles for a link between two machines. + *

+ * Rules: + *

    + *
  • PEER + PEER = PEER/PEER (bidirectional)
  • + *
  • PEER adapts to stricter partner: + *
      + *
    • PEER + CONTROLLER → REMOTE/CONTROLLER
    • + *
    • PEER + REMOTE → CONTROLLER/REMOTE
    • + *
    + *
  • + *
  • CONTROLLER + REMOTE = CONTROLLER/REMOTE (asymmetric)
  • + *
  • CONTROLLER + CONTROLLER = incompatible
  • + *
  • REMOTE + REMOTE = incompatible
  • + *
+ * + * @param aDeclared Machine A's declared role preference + * @param bDeclared Machine B's declared role preference + * @return Negotiated roles, or null if incompatible + */ + @Nullable + public static RolePair negotiateRoles(LinkRole aDeclared, LinkRole bDeclared) { + // PEER + PEER = PEER/PEER + if (aDeclared == LinkRole.PEER && bDeclared == LinkRole.PEER) { + return new RolePair(LinkRole.PEER, LinkRole.PEER); + } + + // PEER adapts to stricter partner (downgrade to preserve partner's intent) + if (aDeclared == LinkRole.PEER && bDeclared == LinkRole.CONTROLLER) { + return new RolePair(LinkRole.REMOTE, LinkRole.CONTROLLER); + } + if (aDeclared == LinkRole.PEER && bDeclared == LinkRole.REMOTE) { + return new RolePair(LinkRole.CONTROLLER, LinkRole.REMOTE); + } + if (aDeclared == LinkRole.CONTROLLER && bDeclared == LinkRole.PEER) { + return new RolePair(LinkRole.CONTROLLER, LinkRole.REMOTE); + } + if (aDeclared == LinkRole.REMOTE && bDeclared == LinkRole.PEER) { + return new RolePair(LinkRole.REMOTE, LinkRole.CONTROLLER); + } + + // CONTROLLER + REMOTE = valid asymmetric link + if (aDeclared == LinkRole.CONTROLLER && bDeclared == LinkRole.REMOTE) { + return new RolePair(LinkRole.CONTROLLER, LinkRole.REMOTE); + } + if (aDeclared == LinkRole.REMOTE && bDeclared == LinkRole.CONTROLLER) { + return new RolePair(LinkRole.REMOTE, LinkRole.CONTROLLER); + } + + // CONTROLLER + CONTROLLER = incompatible (conflict) + // REMOTE + REMOTE = incompatible (deadlock) + return null; + } + + // ==================== Machine Access ==================== + + /** + * Safely retrieve a machine from any dimension. + * Returns null if dimension doesn't exist or chunk isn't loaded. + * Does NOT force-load the chunk. + */ + @Nullable + public static MetaMachine getMachine(MinecraftServer server, GlobalPos pos) { + ServerLevel level = server.getLevel(pos.dimension()); + if (level == null) return null; + + if (!level.isLoaded(pos.pos())) { + return null; + } + + return MetaMachine.getMachine(level, pos.pos()); + } + + /** + * Get machine as ILinkedMultiblock if it implements the interface. + */ + @Nullable + public static ILinkedMultiblock getLinkedMachine(MinecraftServer server, GlobalPos pos) { + MetaMachine machine = getMachine(server, pos); + if (machine instanceof ILinkedMultiblock linked) { + return linked; + } + return null; + } + + /** + * Check if a linked partner is currently accessible (chunk loaded). + */ + public static boolean isPartnerOnline(MinecraftServer server, GlobalPos pos) { + return getMachine(server, pos) != null; + } + + // ==================== Chunk Loading ==================== + + /** + * Force-load a partner's chunk for cross-dimensional access. + * Tracks the ticket with proper owner position for later removal. + *

+ * Uses the REQUESTER's position as the ticket owner to ensure each + * requester has independent tickets. + * + * @param server The server + * @param requester The machine requesting the load (for ticket tracking) + * @param target The partner machine's position to load + * @return true if successfully loaded (or already loaded), false if at limit or failed + */ + public static boolean forceLoadPartnerChunk(MinecraftServer server, GlobalPos requester, GlobalPos target) { + // Check per-machine limit + Map existing = activeTickets.getOrDefault(requester, Collections.emptyMap()); + + // Already loaded by this requester? + if (existing.containsKey(target)) { + return true; + } + + if (existing.size() >= MAX_FORCED_CHUNKS_PER_MACHINE) { + CosmicCore.LOGGER.warn("Machine at {} has reached force-load limit of {}", + requester, MAX_FORCED_CHUNKS_PER_MACHINE); + return false; + } + + ServerLevel level = server.getLevel(target.dimension()); + if (level == null) { + CosmicCore.LOGGER.warn("Cannot force-load chunk: dimension {} does not exist", + target.dimension().location()); + return false; + } + + ChunkPos chunkPos = new ChunkPos(target.pos()); + + // Use REQUESTER position as the ticket owner (unique per requester) + // This prevents one requester from releasing another's ticket + BlockPos ownerPos = requester.pos(); + + boolean success = ForgeChunkManager.forceChunk( + level, + CosmicCore.MOD_ID, + ownerPos, + chunkPos.x, + chunkPos.z, + true, // add + true // ticking + ); + + if (success) { + activeTickets.computeIfAbsent(requester, k -> new HashMap<>()) + .put(target, ownerPos); + CosmicCore.LOGGER.debug("Force-loaded chunk {} in {} for machine at {} (owner: {})", + chunkPos, target.dimension().location(), requester, ownerPos); + } else { + CosmicCore.LOGGER.warn("Failed to force-load chunk {} in {}", + chunkPos, target.dimension().location()); + } + + return success; + } + + /** + * Release a specific force-loaded chunk. + * Uses the same owner position that was used when adding the ticket. + */ + public static void releasePartnerChunk(MinecraftServer server, GlobalPos requester, GlobalPos target) { + Map tickets = activeTickets.get(requester); + if (tickets == null) return; + + BlockPos ownerPos = tickets.remove(target); + if (ownerPos == null) return; // Wasn't loaded by this requester + + ServerLevel level = server.getLevel(target.dimension()); + if (level == null) return; + + ChunkPos chunkPos = new ChunkPos(target.pos()); + + // Use the SAME owner position that was used for add + ForgeChunkManager.forceChunk( + level, + CosmicCore.MOD_ID, + ownerPos, + chunkPos.x, + chunkPos.z, + false, // remove + true); + + CosmicCore.LOGGER.debug("Released chunk {} in {} for machine at {} (owner: {})", + chunkPos, target.dimension().location(), requester, ownerPos); + + if (tickets.isEmpty()) { + activeTickets.remove(requester); + } + } + + /** + * Release ALL force-loaded chunks for a machine. + * MUST be called in onMachineRemoved() to prevent ticket leaks. + */ + public static void releaseAllTickets(MinecraftServer server, GlobalPos requester) { + Map tickets = activeTickets.remove(requester); + if (tickets == null) return; + + int released = 0; + for (Map.Entry entry : tickets.entrySet()) { + GlobalPos target = entry.getKey(); + BlockPos ownerPos = entry.getValue(); + + ServerLevel level = server.getLevel(target.dimension()); + if (level != null) { + ChunkPos chunkPos = new ChunkPos(target.pos()); + ForgeChunkManager.forceChunk( + level, + CosmicCore.MOD_ID, + ownerPos, + chunkPos.x, + chunkPos.z, + false, + true); + released++; + } + } + + CosmicCore.LOGGER.debug("Released {} force-load tickets for machine at {}", + released, requester); + } + + /** + * Get number of active force-load tickets for a machine. + */ + public static int getActiveTicketCount(GlobalPos requester) { + return activeTickets.getOrDefault(requester, Collections.emptyMap()).size(); + } + + // ==================== Link Validation ==================== + + /** + * Validate that both machines still exist and link is valid. + * Does not force-load chunks - returns false if either is unloaded. + */ + public static boolean validateLink(MinecraftServer server, UUID owner, GlobalPos a, GlobalPos b) { + // Check SavedData + LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(server); + if (!savedData.isLinked(owner, a, b)) { + return false; + } + + // Check machines exist (if chunks loaded) + MetaMachine machineA = getMachine(server, a); + MetaMachine machineB = getMachine(server, b); + + // If chunk is loaded but machine is gone, link is invalid + ServerLevel levelA = server.getLevel(a.dimension()); + if (levelA != null && levelA.isLoaded(a.pos()) && machineA == null) { + return false; + } + + ServerLevel levelB = server.getLevel(b.dimension()); + if (levelB != null && levelB.isLoaded(b.pos()) && machineB == null) { + return false; + } + + return true; + } + + // ==================== Permission Helpers ==================== + + /** + * Check if requester can query the target (convenience method). + */ + public static boolean canQuery(MinecraftServer server, UUID owner, GlobalPos requester, GlobalPos target) { + return LinkedMultiblockSavedData.getOrCreate(server).canQuery(owner, requester, target); + } + + // ==================== Partner Resource Query ==================== + + /** + * Functional interface for querying a partner machine. + */ + @FunctionalInterface + public interface PartnerQuery { + + T query(WorkableElectricMultiblockMachine partner); + } + + /** + * Query a partner machine with temporary chunk loading. + * Loads the partner's chunk if needed, executes the query, then releases. + *

+ * IMPORTANT: This method handles short-lived queries. For sustained access, + * use forceLoadPartnerChunk/releasePartnerChunk directly. + * + * @param server The server + * @param owner Team/player UUID for permission check + * @param requester The requesting machine's position + * @param target The partner machine's position + * @param query The query function to execute + * @return Query result, or null if partner unavailable or permission denied + */ + @Nullable + public static T queryPartner( + MinecraftServer server, + UUID owner, + GlobalPos requester, + GlobalPos target, + PartnerQuery query) { + // Permission check + if (!canQuery(server, owner, requester, target)) { + CosmicCore.LOGGER.debug("Query denied: {} cannot query {}", requester, target); + return null; + } + + boolean needsUnload = false; + if (!isPartnerOnline(server, target)) { + if (!forceLoadPartnerChunk(server, requester, target)) { + return null; + } + needsUnload = true; + } + + try { + MetaMachine machine = getMachine(server, target); + if (machine instanceof WorkableElectricMultiblockMachine workable) { + return query.query(workable); + } + return null; + } finally { + if (needsUnload) { + releasePartnerChunk(server, requester, target); + } + } + } + + /** + * Get a partner's item handler capabilities. + * Returns empty list if partner unavailable or permission denied. + * + * @param io IO.IN for input handlers, IO.OUT for output handlers + */ + public static List> getPartnerItemHandlers( + MinecraftServer server, + UUID owner, + GlobalPos requester, + GlobalPos target, + IO io) { + List> result = queryPartner(server, owner, requester, target, + partner -> partner.getCapabilitiesFlat(io, ItemRecipeCapability.CAP)); + return result != null ? result : Collections.emptyList(); + } + + /** + * Get a partner's fluid handler capabilities. + * Returns empty list if partner unavailable or permission denied. + * + * @param io IO.IN for input handlers, IO.OUT for output handlers + */ + public static List> getPartnerFluidHandlers( + MinecraftServer server, + UUID owner, + GlobalPos requester, + GlobalPos target, + IO io) { + List> result = queryPartner(server, owner, requester, target, + partner -> partner.getCapabilitiesFlat(io, FluidRecipeCapability.CAP)); + return result != null ? result : Collections.emptyList(); + } + + /** + * Get a partner's energy container capabilities. + * Returns empty list if partner unavailable or permission denied. + * + * @param io IO.IN for input energy, IO.OUT for output energy + */ + public static List> getPartnerEnergyHandlers( + MinecraftServer server, + UUID owner, + GlobalPos requester, + GlobalPos target, + IO io) { + List> result = queryPartner(server, owner, requester, target, + partner -> partner.getCapabilitiesFlat(io, EURecipeCapability.CAP)); + return result != null ? result : Collections.emptyList(); + } + + /** + * Check if a partner has a specific item in any of its input handlers. + */ + public static boolean partnerHasItem( + MinecraftServer server, + UUID owner, + GlobalPos requester, + GlobalPos target, + java.util.function.Predicate itemPredicate) { + Boolean result = queryPartner(server, owner, requester, target, partner -> { + var handlers = partner.getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP); + if (handlers == null) return false; + + for (Object handler : handlers) { + if (handler instanceof net.minecraftforge.items.IItemHandler itemHandler) { + for (int i = 0; i < itemHandler.getSlots(); i++) { + if (itemPredicate.test(itemHandler.getStackInSlot(i))) { + return true; + } + } + } + } + return false; + }); + return result != null && result; + } + + /** + * Check if a partner has a specific fluid in any of its input handlers. + */ + public static boolean partnerHasFluid( + MinecraftServer server, + UUID owner, + GlobalPos requester, + GlobalPos target, + java.util.function.Predicate fluidPredicate) { + Boolean result = queryPartner(server, owner, requester, target, partner -> { + var handlers = partner.getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP); + if (handlers == null) return false; + + for (Object handler : handlers) { + if (handler instanceof net.minecraftforge.fluids.capability.IFluidHandler fluidHandler) { + for (int i = 0; i < fluidHandler.getTanks(); i++) { + if (fluidPredicate.test(fluidHandler.getFluidInTank(i))) { + return true; + } + } + } + } + return false; + }); + return result != null && result; + } + + /** + * Get total energy stored across all of a partner's energy containers. + * Returns 0 if partner unavailable or permission denied. + */ + public static long getPartnerEnergyStored( + MinecraftServer server, + UUID owner, + GlobalPos requester, + GlobalPos target) { + Long result = queryPartner(server, owner, requester, target, partner -> { + var handlers = partner.getCapabilitiesFlat(IO.IN, EURecipeCapability.CAP); + if (handlers == null) return 0L; + + long total = 0; + for (Object handler : handlers) { + if (handler instanceof com.gregtechceu.gtceu.api.capability.IEnergyContainer energyContainer) { + total += energyContainer.getEnergyStored(); + } + } + return total; + }); + return result != null ? result : 0L; + } + + /** + * Check if partner's multiblock is formed and working. + */ + public static boolean isPartnerFormed( + MinecraftServer server, + UUID owner, + GlobalPos requester, + GlobalPos target) { + Boolean result = queryPartner(server, owner, requester, target, + WorkableElectricMultiblockMachine::isFormed); + return result != null && result; + } + + /** + * Check if partner is currently running a recipe. + */ + public static boolean isPartnerWorking( + MinecraftServer server, + UUID owner, + GlobalPos requester, + GlobalPos target) { + Boolean result = queryPartner(server, owner, requester, target, partner -> { + RecipeLogic logic = partner.getRecipeLogic(); + return logic != null && logic.isWorking(); + }); + return result != null && result; + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/LinkTestStation.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/LinkTestStation.java new file mode 100644 index 000000000..5b94e1908 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/LinkTestStation.java @@ -0,0 +1,54 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi; + +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic.LinkTestStationMachine; +import com.ghostipedia.cosmiccore.gtbridge.CosmicRecipeTypes; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.data.RotationState; +import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; +import com.gregtechceu.gtceu.api.machine.multiblock.PartAbility; +import com.gregtechceu.gtceu.api.pattern.FactoryBlockPattern; +import com.gregtechceu.gtceu.common.data.GTBlocks; + +import net.minecraft.network.chat.Component; + +import static com.ghostipedia.cosmiccore.api.registries.CosmicRegistration.REGISTRATE; +import static com.gregtechceu.gtceu.api.pattern.Predicates.*; +import static com.gregtechceu.gtceu.common.data.models.GTMachineModels.createWorkableCasingMachineModel; + +/** + * Simple test multiblock for verifying cross-dimensional linking. + * Minimal 3x3x3 structure using steel casings. + */ +public class LinkTestStation { + + public final static MultiblockMachineDefinition LINK_TEST_STATION = REGISTRATE + .multiblock("link_test_station", LinkTestStationMachine::new) + .langValue("Link Test Station") + .tooltips( + Component.literal("Test multiblock for cross-dimensional linking"), + Component.literal("Use datastick: Shift+click to copy, click to link"), + Component.literal("Some recipes require linked partners")) + .rotationState(RotationState.NON_Y_AXIS) + .recipeType(CosmicRecipeTypes.LINK_TEST_RECIPES) + .appearanceBlock(GTBlocks.CASING_STEEL_SOLID) + .pattern(definition -> FactoryBlockPattern.start() + .aisle("CCC", "CCC", "CCC") + .aisle("CCC", "C C", "CCC") + .aisle("CCC", "CQC", "CCC") + .where(' ', any()) + .where('Q', controller(blocks(definition.getBlock()))) + .where('C', blocks(GTBlocks.CASING_STEEL_SOLID.get()) + .or(abilities(PartAbility.INPUT_ENERGY).setMinGlobalLimited(1).setMaxGlobalLimited(1)) + .or(abilities(PartAbility.MAINTENANCE).setExactLimit(1)) + .or(abilities(PartAbility.IMPORT_ITEMS).setMaxGlobalLimited(1)) + .or(abilities(PartAbility.EXPORT_ITEMS).setMaxGlobalLimited(1))) + .build()) + .model( + createWorkableCasingMachineModel( + GTCEu.id("block/casings/solid/machine_casing_solid_steel"), + GTCEu.id("block/multiblock/implosion_compressor"))) + .register(); + + public static void init() {} +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/MothCargoDropOff.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/MothCargoDropOff.java new file mode 100644 index 000000000..d2240c77d --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/MothCargoDropOff.java @@ -0,0 +1,55 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi; + +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic.MothCargoDropOffMachine; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.data.RotationState; +import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; +import com.gregtechceu.gtceu.api.machine.multiblock.PartAbility; +import com.gregtechceu.gtceu.api.pattern.FactoryBlockPattern; +import com.gregtechceu.gtceu.common.data.GTRecipeTypes; + +import net.minecraft.network.chat.Component; + +import static com.ghostipedia.cosmiccore.api.registries.CosmicRegistration.REGISTRATE; +import static com.gregtechceu.gtceu.api.pattern.Predicates.*; +import static com.gregtechceu.gtceu.common.data.GTBlocks.CASING_STEEL_SOLID; + +/** + * Moth Cargo Drop Off - Receiver multiblock for the Cargo Moths system. + * Receives items and fluids from linked Moth Cargo Stations. + * Small, compact design for easy placement at outposts. + */ +public class MothCargoDropOff { + + public static final MultiblockMachineDefinition MOTH_CARGO_DROP_OFF = REGISTRATE + .multiblock("moth_cargo_drop_off", MothCargoDropOffMachine::new) + .langValue("Moth Cargo Drop Off") + .tooltips( + Component.literal("Receives shipments from Moth Cargo Stations"), + Component.literal("Link to stations with a datastick"), + Component.literal("Small footprint for easy outpost placement")) + .rotationState(RotationState.NON_Y_AXIS) + .recipeType(GTRecipeTypes.DUMMY_RECIPES) + .appearanceBlock(CASING_STEEL_SOLID) + // spotless:off + .pattern(definition -> FactoryBlockPattern.start() + // Compact 3x3x3 structure + .aisle("CCC", "CCC", "C C") + .aisle("CCC", "C C", " ") + .aisle("CCC", "CQC", "C C") + .where(' ', any()) + .where('Q', controller(blocks(definition.getBlock()))) + .where('C', blocks(CASING_STEEL_SOLID.get()) + .or(abilities(PartAbility.MAINTENANCE).setExactLimit(1)) + .or(abilities(PartAbility.EXPORT_ITEMS).setMaxGlobalLimited(4)) + .or(abilities(PartAbility.EXPORT_FLUIDS).setMaxGlobalLimited(4))) + .build()) + // spotless:on + .workableCasingModel( + GTCEu.id("block/casings/solid/machine_casing_solid_steel"), + GTCEu.id("block/multiblock/implosion_compressor")) + .register(); + + public static void init() {} +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/MothCargoStation.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/MothCargoStation.java new file mode 100644 index 000000000..e02ac4067 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/MothCargoStation.java @@ -0,0 +1,83 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi; + +import com.ghostipedia.cosmiccore.api.pattern.CosmicPredicates; +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic.MothCargoStationMachine; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.data.RotationState; +import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; +import com.gregtechceu.gtceu.api.machine.multiblock.PartAbility; +import com.gregtechceu.gtceu.api.pattern.FactoryBlockPattern; +import com.gregtechceu.gtceu.common.data.GTRecipeTypes; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +import static com.ghostipedia.cosmiccore.api.registries.CosmicRegistration.REGISTRATE; +import static com.gregtechceu.gtceu.api.pattern.Predicates.*; +import static com.gregtechceu.gtceu.common.data.GTBlocks.CASING_STEEL_SOLID; + +/** + * Moth Cargo Station - Sender multiblock for the Cargo Moths system. + * Ships items and fluids to linked Moth Cargo Drop Off stations. + */ +public class MothCargoStation { + + // Forestry beehive blocks used as moth homes + public static final ResourceLocation BEEHIVE_FOREST = new ResourceLocation("forestry", "beehive_forest"); + public static final ResourceLocation BEEHIVE_LUSH = new ResourceLocation("forestry", "beehive_lush"); + public static final ResourceLocation BEEHIVE_DESERT = new ResourceLocation("forestry", "beehive_desert"); + public static final ResourceLocation BEEHIVE_END = new ResourceLocation("forestry", "beehive_end"); + + /** + * Check if a block is a valid moth home (any tier of Forestry beehive). + */ + public static boolean isMothHome(BlockState state) { + Block block = state.getBlock(); + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(block); + return blockId.equals(BEEHIVE_FOREST) || + blockId.equals(BEEHIVE_LUSH) || + blockId.equals(BEEHIVE_DESERT) || + blockId.equals(BEEHIVE_END); + } + + public static final MultiblockMachineDefinition MOTH_CARGO_STATION = REGISTRATE + .multiblock("moth_cargo_station", MothCargoStationMachine::new) + .langValue("Moth Cargo Station") + .tooltips( + Component.literal("Ships items and fluids using cargo moths"), + Component.literal("Link to Moth Cargo Drop Offs with a datastick"), + Component.literal("Add Moth Homes to increase capacity and speed"), + Component.literal("Feed moths honey or pale oil for bonuses!")) + .rotationState(RotationState.NON_Y_AXIS) + .recipeType(GTRecipeTypes.DUMMY_RECIPES) + .appearanceBlock(CASING_STEEL_SOLID) + // spotless:off + .pattern(definition -> FactoryBlockPattern.start() + // Tower structure: 3x3 footprint, 6 blocks tall + // Moth homes (beehives) in center column - up to 4 can be placed + // Open walls (air in center) so beehives are visible from all sides + .aisle("CCC", "C C", "C C", "C C", "C C", "CCC") + .aisle("CCC", " M ", " M ", " M ", " M ", "CCC") + .aisle("CQC", "C C", "C C", "C C", "C C", "CCC") + .where(' ', any()) + .where('Q', controller(blocks(definition.getBlock()))) + .where('C', blocks(CASING_STEEL_SOLID.get()) + .or(abilities(PartAbility.MAINTENANCE).setExactLimit(1)) + .or(abilities(PartAbility.IMPORT_ITEMS).setMaxGlobalLimited(4)) + .or(abilities(PartAbility.EXPORT_ITEMS).setMaxGlobalLimited(4)) + .or(abilities(PartAbility.IMPORT_FLUIDS).setMaxGlobalLimited(4)) + .or(abilities(PartAbility.EXPORT_FLUIDS).setMaxGlobalLimited(4))) + .where('M', CosmicPredicates.mothHomes()) + .build()) + // spotless:on + .workableCasingModel( + GTCEu.id("block/casings/solid/machine_casing_solid_steel"), + GTCEu.id("block/multiblock/implosion_compressor")) + .register(); + + public static void init() {} +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java index 903ec33ef..3643535e0 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/DivingBellMachine.java @@ -165,12 +165,6 @@ private boolean teleportPlayerToDeepBelow(ServerPlayer player) { // Find or create safe landing BlockPos landingPos = getOrCreateSafeLanding(deepBelow, player); - // Set Abyss decay flag - // Don't think this is necessary... - // player.getCapability(AbyssBudgetCap.CAP).ifPresent(cap -> { - // cap.setDecaying(AbyssRules.DIM, true); - // }); - // Teleport (SafeTeleporter handles safety effects) player.changeDimension(deepBelow, new SafeTeleporter(landingPos)); diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/LinkTestStationMachine.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/LinkTestStationMachine.java new file mode 100644 index 000000000..eff877628 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/LinkTestStationMachine.java @@ -0,0 +1,114 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic; + +import com.ghostipedia.cosmiccore.CosmicCore; +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock; +import com.ghostipedia.cosmiccore.api.machine.multiblock.LinkedWorkableElectricMultiblockMachine; + +import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; + +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.GlobalPos; +import net.minecraft.network.chat.Component; + +import java.util.List; +import java.util.Set; + +/** + * Simple test multiblock for verifying cross-dimensional linking. + * Displays linked partners in UI and logs link events. + */ +public class LinkTestStationMachine extends LinkedWorkableElectricMultiblockMachine { + + protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( + LinkTestStationMachine.class, + LinkedWorkableElectricMultiblockMachine.MANAGED_FIELD_HOLDER); + + public LinkTestStationMachine(IMachineBlockEntity holder, Object... args) { + super(holder, args); + } + + @Override + public ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + // ==================== Link Configuration ==================== + + @Override + public LinkRole getLinkRole() { + // Test station is a peer - can link bidirectionally + return LinkRole.PEER; + } + + @Override + public int getMaxPartners() { + // Allow up to 4 partners for testing + return 4; + } + + @Override + public boolean canLinkTo(GlobalPos partner, ILinkedMultiblock partnerMachine) { + // Accept links from any ILinkedMultiblock for testing + return true; + } + + // ==================== Link Lifecycle ==================== + + @Override + public void onLinkEstablished(GlobalPos partner) { + super.onLinkEstablished(partner); + CosmicCore.LOGGER.info("[LinkTestStation] Link established to {} in {}", + partner.pos(), partner.dimension().location()); + } + + @Override + public void onLinkBroken(GlobalPos partner) { + super.onLinkBroken(partner); + CosmicCore.LOGGER.info("[LinkTestStation] Link broken to {} in {}", + partner.pos(), partner.dimension().location()); + } + + // ==================== Display ==================== + + @Override + public void addDisplayText(List textList) { + super.addDisplayText(textList); + + if (!isFormed()) return; + + // Show link status + Set partners = getLinkedPartners(); + if (partners.isEmpty()) { + textList.add(Component.literal("No linked partners") + .withStyle(ChatFormatting.GRAY)); + textList.add(Component.literal("Use datastick to link") + .withStyle(ChatFormatting.DARK_GRAY)); + } else { + textList.add(Component.literal("Linked Partners: " + partners.size()) + .withStyle(ChatFormatting.GREEN)); + + for (GlobalPos partner : partners) { + String dim = partner.dimension().location().toString(); + String pos = String.format("[%d, %d, %d]", + partner.pos().getX(), + partner.pos().getY(), + partner.pos().getZ()); + + // Show if partner is online + boolean online = getPartnerMachine(partner) != null; + ChatFormatting color = online ? ChatFormatting.GREEN : ChatFormatting.RED; + String status = online ? "[+]" : "[-]"; + + textList.add(Component.literal(" " + status + " " + dim + " " + pos) + .withStyle(color)); + } + } + + // Show effective roles + LinkRole myRole = getLinkRole(); + textList.add(Component.literal("Role: " + myRole.name()) + .withStyle(ChatFormatting.AQUA)); + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/MothCargoDropOffMachine.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/MothCargoDropOffMachine.java new file mode 100644 index 000000000..222eaf65a --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/MothCargoDropOffMachine.java @@ -0,0 +1,140 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic; + +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock; +import com.ghostipedia.cosmiccore.api.machine.multiblock.LinkedWorkableMultiblockMachine; + +import com.gregtechceu.gtceu.api.capability.recipe.FluidRecipeCapability; +import com.gregtechceu.gtceu.api.capability.recipe.IO; +import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability; +import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; +import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank; +import com.gregtechceu.gtceu.api.machine.trait.NotifiableItemStackHandler; + +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.GlobalPos; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.items.IItemHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * Moth Cargo Drop Off - The "receiver" multiblock for the Cargo Moths system. + *

+ * Receives items and fluids from linked Moth Cargo Stations. + * Does NOT require power - just a place for moths to land! + *

+ * This is a simple, compact multiblock designed for easy placement at outposts. + */ +public class MothCargoDropOffMachine extends LinkedWorkableMultiblockMachine { + + protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( + MothCargoDropOffMachine.class, + LinkedWorkableMultiblockMachine.MANAGED_FIELD_HOLDER); + + // ==================== Constructor ==================== + + public MothCargoDropOffMachine(IMachineBlockEntity holder, Object... args) { + super(holder, args); + } + + @Override + public ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + // ==================== Linking Overrides ==================== + + @Override + public LinkRole getLinkRole() { + // Drop Off is REMOTE - it receives from Stations but doesn't initiate + return LinkRole.REMOTE; + } + + @Override + public int getMaxPartners() { + // Can receive from multiple stations (N:1 support) + return 16; + } + + @Override + public boolean canLinkTo(GlobalPos partner, ILinkedMultiblock partnerMachine) { + // Only link to Cargo Stations + if (!(partnerMachine instanceof MothCargoStationMachine)) { + return false; + } + + // Same dimension only + GlobalPos myPos = getGlobalPos(); + if (myPos == null) return false; + + return myPos.dimension().equals(partner.dimension()); + } + + // ==================== Handler Access ==================== + + /** + * Get all item output handlers from the multiblock. + * Called by MothCargoStationMachine to insert items. + */ + public List getItemOutputHandlers() { + List handlers = new ArrayList<>(); + + var itemCaps = getCapabilitiesFlat(IO.OUT, ItemRecipeCapability.CAP); + if (itemCaps != null) { + for (var handler : itemCaps) { + if (handler instanceof NotifiableItemStackHandler itemHandler) { + handlers.add(itemHandler); + } + } + } + + return handlers; + } + + /** + * Get all fluid output handlers from the multiblock. + * Called by MothCargoStationMachine to insert fluids. + */ + public List getFluidOutputHandlers() { + List handlers = new ArrayList<>(); + + var fluidCaps = getCapabilitiesFlat(IO.OUT, FluidRecipeCapability.CAP); + if (fluidCaps != null) { + for (var handler : fluidCaps) { + if (handler instanceof NotifiableFluidTank fluidHandler) { + handlers.add(fluidHandler); + } + } + } + + return handlers; + } + + // ==================== UI ==================== + + @Override + public void addDisplayText(List textList) { + if (!isFormed()) { + textList.add(Component.literal("Structure not formed") + .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))); + return; + } + + // Linked stations + int linkedCount = getLinkedPartners().size(); + if (linkedCount > 0) { + textList.add(Component.literal("Receiving from " + linkedCount + " station(s)") + .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN))); + } else { + textList.add(Component.literal("No stations linked!") + .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))); + textList.add(Component.literal("Use a datastick to link to a Moth Cargo Station") + .setStyle(Style.EMPTY.withColor(ChatFormatting.GRAY))); + } + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/MothCargoStationMachine.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/MothCargoStationMachine.java new file mode 100644 index 000000000..1ae96a178 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/MothCargoStationMachine.java @@ -0,0 +1,657 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic; + +import com.ghostipedia.cosmiccore.CosmicCore; +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock; +import com.ghostipedia.cosmiccore.api.machine.multiblock.LinkedWorkableMultiblockMachine; +import com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper; +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.MothCargoStation; + +import com.gregtechceu.gtceu.api.capability.recipe.FluidRecipeCapability; +import com.gregtechceu.gtceu.api.capability.recipe.IO; +import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability; +import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; +import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.machine.TickableSubscription; +import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank; +import com.gregtechceu.gtceu.api.machine.trait.NotifiableItemStackHandler; + +import com.lowdragmc.lowdraglib.syncdata.annotation.DescSynced; +import com.lowdragmc.lowdraglib.syncdata.annotation.Persisted; +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.GlobalPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +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.Block; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.items.IItemHandler; + +import lombok.Getter; +import lombok.Setter; + +import java.util.*; + +/** + * Moth Cargo Station - The "sender" multiblock for the Cargo Moths system. + *

+ * Ships items and fluids to linked Moth Cargo Drop Off stations using moths. + * Does NOT require power - just moths! + *

+ * Features: + *

    + *
  • Cycle-based shipping (configurable via moth home tiers)
  • + *
  • Multiple distribution modes (1:1, 1:N fill first, 1:N round robin, N:1)
  • + *
  • Feeding bonuses from honey/oil
  • + *
  • Same-dimension only linking
  • + *
+ */ +public class MothCargoStationMachine extends LinkedWorkableMultiblockMachine { + + protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( + MothCargoStationMachine.class, + LinkedWorkableMultiblockMachine.MANAGED_FIELD_HOLDER); + + // ==================== Constants ==================== + + /** Base items per moth per cycle (1 stack) */ + public static final int BASE_ITEMS_PER_MOTH = 64; + /** Base fluid per moth per cycle (1000 mB) */ + public static final int BASE_FLUID_PER_MOTH = 1000; + + /** Cycle times in ticks per tier (T1=60s, T2=30s, T3=15s, T4=5s) */ + public static final int[] CYCLE_TICKS_BY_TIER = { 1200, 600, 300, 100 }; + /** Moths per home by tier (T1=1, T2=2, T3=4, T4=8) */ + public static final int[] MOTHS_PER_HOME_BY_TIER = { 1, 2, 4, 8 }; + /** Max moth homes per station */ + public static final int MAX_MOTH_HOMES = 5; + + /** Feeding multipliers */ + public static final int MULTIPLIER_REGULAR_HONEY = 2; + public static final int MULTIPLIER_LOFTY_HONEY = 4; + public static final int MULTIPLIER_PALE_OIL = 8; + + // ==================== Distribution Modes ==================== + + public enum DistributionMode { + /** Direct 1:1 transfer to single receiver */ + DIRECT, + /** Fill receivers in order until full, then move to next */ + FILL_FIRST, + /** Distribute equally across all receivers (round robin) */ + ROUND_ROBIN + } + + // ==================== State ==================== + + @Persisted + @DescSynced + @Getter + @Setter + private DistributionMode distributionMode = DistributionMode.FILL_FIRST; + + @Persisted + @Getter + private int mothHomeTier = 1; // 1-4 + + @Persisted + @Getter + private int mothHomeCount = 0; // 0-5 + + @Persisted + private int ticksSinceLastCycle = 0; + + @Persisted + private int roundRobinIndex = 0; + + @Persisted + @DescSynced + @Getter + private int currentFeedingMultiplier = 1; + + private TickableSubscription shippingSubscription; + + // ==================== Constructor ==================== + + public MothCargoStationMachine(IMachineBlockEntity holder, Object... args) { + super(holder, args); + } + + @Override + public ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + // ==================== Linking Overrides ==================== + + @Override + public LinkRole getLinkRole() { + // Station is the CONTROLLER - it initiates transfers to Drop Offs + return LinkRole.CONTROLLER; + } + + @Override + public int getMaxPartners() { + // Can link to multiple drop-off points + return 16; + } + + @Override + public boolean canLinkTo(GlobalPos partner, ILinkedMultiblock partnerMachine) { + // Only link to Drop Off stations + if (!(partnerMachine instanceof MothCargoDropOffMachine)) { + return false; + } + + // Same dimension only + GlobalPos myPos = getGlobalPos(); + if (myPos == null) return false; + + return myPos.dimension().equals(partner.dimension()); + } + + // ==================== Lifecycle ==================== + + @Override + public void onStructureFormed() { + super.onStructureFormed(); + + // Scan for moth homes in structure + scanForMothHomes(); + + subscribeToShipping(); + } + + @Override + public void onStructureInvalid() { + super.onStructureInvalid(); + unsubscribeFromShipping(); + // Reset moth home stats + mothHomeTier = 0; + mothHomeCount = 0; + } + + @Override + public void onUnload() { + super.onUnload(); + unsubscribeFromShipping(); + } + + /** + * Scan the multiblock structure for Forestry beehive blocks (used as moth homes). + * Sets mothHomeTier and mothHomeCount based on what's found. + * All moth homes must be the same tier. + * + * Tier mapping: + * T1: forestry:beehive_forest + * T2: forestry:beehive_lush + * T3: forestry:beehive_desert + * T4: forestry:beehive_end + */ + private void scanForMothHomes() { + Level level = getLevel(); + if (level == null) { + mothHomeTier = 0; + mothHomeCount = 0; + return; + } + + BlockPos controllerPos = getPos(); + int foundTier = 0; + int foundCount = 0; + boolean mixedTiers = false; + + // Scan a 7x7x7 region around the controller (covers 5x5x5 structure plus margin) + int scanRadius = 3; + for (int x = -scanRadius; x <= scanRadius; x++) { + for (int y = -scanRadius; y <= scanRadius; y++) { + for (int z = -scanRadius; z <= scanRadius; z++) { + BlockPos checkPos = controllerPos.offset(x, y, z); + Block block = level.getBlockState(checkPos).getBlock(); + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(block); + + int tier = getBeehiveTier(blockId); + if (tier > 0) { + if (foundCount == 0) { + // First moth home found - set the tier + foundTier = tier; + foundCount = 1; + } else if (tier == foundTier) { + // Same tier - count it + foundCount++; + } else { + // Mixed tiers detected + mixedTiers = true; + } + } + } + } + } + + // Enforce same-tier requirement + if (mixedTiers) { + CosmicCore.LOGGER.warn("Moth Cargo Station at {} has mixed tier moth homes - using lowest functionality", + controllerPos); + // Still use what we found, but warn + } + + // Cap at max moth homes + foundCount = Math.min(foundCount, MAX_MOTH_HOMES); + + mothHomeTier = foundTier; + mothHomeCount = foundCount; + + CosmicCore.LOGGER.debug("Moth Cargo Station at {} found {} T{} moth homes", + controllerPos, mothHomeCount, mothHomeTier); + } + + /** + * Get the tier of a beehive block by its registry name. + * + * @return tier (1-4) or 0 if not a valid beehive + */ + private int getBeehiveTier(ResourceLocation blockId) { + if (blockId.equals(MothCargoStation.BEEHIVE_FOREST)) return 1; + if (blockId.equals(MothCargoStation.BEEHIVE_LUSH)) return 2; + if (blockId.equals(MothCargoStation.BEEHIVE_DESERT)) return 3; + if (blockId.equals(MothCargoStation.BEEHIVE_END)) return 4; + return 0; + } + + private void subscribeToShipping() { + if (shippingSubscription == null) { + shippingSubscription = subscribeServerTick(this::onShippingTick); + } + } + + private void unsubscribeFromShipping() { + if (shippingSubscription != null) { + shippingSubscription.unsubscribe(); + shippingSubscription = null; + } + } + + // ==================== Shipping Logic ==================== + + private void onShippingTick() { + if (!isFormed() || getLevel() == null || getLevel().isClientSide()) { + return; + } + + // Check if we have moths + if (mothHomeCount <= 0) { + return; + } + + ticksSinceLastCycle++; + + int cycleTime = getCycleTimeTicks(); + if (ticksSinceLastCycle >= cycleTime) { + ticksSinceLastCycle = 0; + performShippingCycle(); + } + } + + /** + * Get the cycle time in ticks based on moth home tier. + */ + public int getCycleTimeTicks() { + int tierIndex = Math.max(0, Math.min(mothHomeTier - 1, CYCLE_TICKS_BY_TIER.length - 1)); + return CYCLE_TICKS_BY_TIER[tierIndex]; + } + + /** + * Get total moths available for shipping. + */ + public int getTotalMoths() { + int tierIndex = Math.max(0, Math.min(mothHomeTier - 1, MOTHS_PER_HOME_BY_TIER.length - 1)); + return mothHomeCount * MOTHS_PER_HOME_BY_TIER[tierIndex]; + } + + /** + * Get capacity per cycle (items or mB). + */ + public int getCapacityPerCycle(boolean isFluid) { + int baseCapacity = isFluid ? BASE_FLUID_PER_MOTH : BASE_ITEMS_PER_MOTH; + return getTotalMoths() * baseCapacity * currentFeedingMultiplier; + } + + /** + * Perform one shipping cycle - transfer items/fluids to linked drop-offs. + */ + private void performShippingCycle() { + Set partners = getLinkedPartners(); + if (partners.isEmpty()) { + return; + } + + // Get list of valid, formed drop-off partners + List dropOffs = getActiveDropOffs(partners); + if (dropOffs.isEmpty()) { + return; + } + + // Calculate capacity for this cycle + int itemCapacity = getCapacityPerCycle(false); + int fluidCapacity = getCapacityPerCycle(true); + + // Ship items + shipItems(dropOffs, itemCapacity); + + // Ship fluids + shipFluids(dropOffs, fluidCapacity); + + // Consume feeding materials (TODO) + consumeFeedingMaterials(); + } + + /** + * Get active (formed and loaded) drop-off machines from partner list. + */ + private List getActiveDropOffs(Set partners) { + List result = new ArrayList<>(); + + if (!(getLevel() instanceof ServerLevel serverLevel)) { + return result; + } + + for (GlobalPos partner : partners) { + MetaMachine machine = LinkedMultiblockHelper.getMachine(serverLevel.getServer(), partner); + if (machine instanceof MothCargoDropOffMachine dropOff && dropOff.isFormed()) { + result.add(dropOff); + } + } + + return result; + } + + /** + * Ship items to drop-offs based on distribution mode. + */ + private void shipItems(List dropOffs, int maxItems) { + // Get our input items + List inputHandlers = getItemInputHandlers(); + if (inputHandlers.isEmpty()) { + return; + } + + int remainingCapacity = maxItems; + + switch (distributionMode) { + case DIRECT -> { + // Ship to first drop-off only + if (!dropOffs.isEmpty()) { + remainingCapacity = shipItemsToDropOff(dropOffs.get(0), inputHandlers, remainingCapacity); + } + } + case FILL_FIRST -> { + // Fill each drop-off in order until capacity exhausted + for (MothCargoDropOffMachine dropOff : dropOffs) { + if (remainingCapacity <= 0) break; + remainingCapacity = shipItemsToDropOff(dropOff, inputHandlers, remainingCapacity); + } + } + case ROUND_ROBIN -> { + // Distribute items evenly starting from round robin index + int perDropOff = Math.max(1, remainingCapacity / dropOffs.size()); + for (int i = 0; i < dropOffs.size() && remainingCapacity > 0; i++) { + int index = (roundRobinIndex + i) % dropOffs.size(); + int toShip = Math.min(perDropOff, remainingCapacity); + int shipped = shipItemsToDropOff(dropOffs.get(index), inputHandlers, toShip); + remainingCapacity -= (toShip - shipped); + } + roundRobinIndex = (roundRobinIndex + 1) % dropOffs.size(); + } + } + } + + /** + * Ship items to a single drop-off, returns remaining capacity. + */ + private int shipItemsToDropOff(MothCargoDropOffMachine dropOff, List sources, int maxItems) { + List destHandlers = dropOff.getItemOutputHandlers(); + if (destHandlers.isEmpty()) { + return maxItems; + } + + int remaining = maxItems; + + for (IItemHandler source : sources) { + if (!(source instanceof NotifiableItemStackHandler sourceNotifiable)) { + continue; + } + + for (int slot = 0; slot < source.getSlots() && remaining > 0; slot++) { + // Use internal extract to bypass IO check + ItemStack stack = sourceNotifiable.extractItemInternal(slot, remaining, true); // Simulate + if (stack.isEmpty()) continue; + + // Try to insert into destination using internal method + ItemStack toInsert = stack.copy(); + int originalCount = toInsert.getCount(); + + for (IItemHandler dest : destHandlers) { + if (dest instanceof NotifiableItemStackHandler destNotifiable) { + // Use insertItemInternal which bypasses the IO check + for (int destSlot = 0; destSlot < dest.getSlots() && !toInsert.isEmpty(); destSlot++) { + toInsert = destNotifiable.insertItemInternal(destSlot, toInsert, false); + } + } else { + // Fallback to standard insertion + for (int destSlot = 0; destSlot < dest.getSlots() && !toInsert.isEmpty(); destSlot++) { + toInsert = dest.insertItem(destSlot, toInsert, false); + } + } + if (toInsert.isEmpty()) break; + } + + // Actually extract what we inserted + int inserted = originalCount - toInsert.getCount(); + if (inserted > 0) { + sourceNotifiable.extractItemInternal(slot, inserted, false); + remaining -= inserted; + } + } + } + + return remaining; + } + + /** + * Ship fluids to drop-offs based on distribution mode. + */ + private void shipFluids(List dropOffs, int maxFluid) { + List inputHandlers = getFluidInputHandlers(); + if (inputHandlers.isEmpty()) { + return; + } + + int remainingCapacity = maxFluid; + + switch (distributionMode) { + case DIRECT -> { + if (!dropOffs.isEmpty()) { + remainingCapacity = shipFluidsToDropOff(dropOffs.get(0), inputHandlers, remainingCapacity); + } + } + case FILL_FIRST -> { + for (MothCargoDropOffMachine dropOff : dropOffs) { + if (remainingCapacity <= 0) break; + remainingCapacity = shipFluidsToDropOff(dropOff, inputHandlers, remainingCapacity); + } + } + case ROUND_ROBIN -> { + int perDropOff = Math.max(1, remainingCapacity / dropOffs.size()); + for (int i = 0; i < dropOffs.size() && remainingCapacity > 0; i++) { + int index = (roundRobinIndex + i) % dropOffs.size(); + int toShip = Math.min(perDropOff, remainingCapacity); + int shipped = shipFluidsToDropOff(dropOffs.get(index), inputHandlers, toShip); + remainingCapacity -= (toShip - shipped); + } + } + } + } + + /** + * Ship fluids to a single drop-off, returns remaining capacity. + */ + private int shipFluidsToDropOff(MothCargoDropOffMachine dropOff, List sources, int maxFluid) { + List destHandlers = dropOff.getFluidOutputHandlers(); + if (destHandlers.isEmpty()) { + return maxFluid; + } + + int remaining = maxFluid; + + for (IFluidHandler source : sources) { + for (int tank = 0; tank < source.getTanks() && remaining > 0; tank++) { + FluidStack available = source.getFluidInTank(tank); + if (available.isEmpty()) continue; + + int toDrain = Math.min(available.getAmount(), remaining); + FluidStack drained = source.drain(new FluidStack(available, toDrain), + IFluidHandler.FluidAction.SIMULATE); + if (drained.isEmpty()) continue; + + // Try to insert into destination + int filled = 0; + for (IFluidHandler dest : destHandlers) { + int thisFill = dest.fill(drained.copy(), IFluidHandler.FluidAction.EXECUTE); + filled += thisFill; + drained.shrink(thisFill); + if (drained.isEmpty()) break; + } + + // Actually drain what we inserted + if (filled > 0) { + source.drain(new FluidStack(available, filled), IFluidHandler.FluidAction.EXECUTE); + remaining -= filled; + } + } + } + + return remaining; + } + + /** + * Consume feeding materials and update multiplier. + */ + private void consumeFeedingMaterials() { + // TODO: Check input bus for honey/oil and consume per cycle + // For now, default multiplier + currentFeedingMultiplier = 1; + } + + // ==================== Handler Access ==================== + + /** + * Get all item input handlers from the multiblock. + */ + private List getItemInputHandlers() { + List handlers = new ArrayList<>(); + + var itemCaps = getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP); + if (itemCaps != null) { + for (var handler : itemCaps) { + if (handler instanceof NotifiableItemStackHandler itemHandler) { + handlers.add(itemHandler); + } + } + } + + return handlers; + } + + /** + * Get all fluid input handlers from the multiblock. + */ + private List getFluidInputHandlers() { + List handlers = new ArrayList<>(); + + var fluidCaps = getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP); + if (fluidCaps != null) { + for (var handler : fluidCaps) { + if (handler instanceof NotifiableFluidTank fluidHandler) { + handlers.add(fluidHandler); + } + } + } + + return handlers; + } + + // ==================== UI ==================== + + @Override + public void addDisplayText(List textList) { + if (!isFormed()) { + textList.add(Component.literal("Structure not formed") + .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))); + return; + } + + // Moth home info + if (mothHomeCount > 0) { + textList.add(Component.literal("Moth Homes: " + mothHomeCount + " (T" + mothHomeTier + ")") + .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN))); + textList.add(Component.literal("Total Moths: " + getTotalMoths()) + .setStyle(Style.EMPTY.withColor(ChatFormatting.AQUA))); + textList.add(Component.literal("Cycle Time: " + (getCycleTimeTicks() / 20) + "s") + .setStyle(Style.EMPTY.withColor(ChatFormatting.YELLOW))); + } else { + textList.add(Component.literal("No Moth Homes installed!") + .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))); + } + + // Distribution mode + textList.add(Component.literal("Mode: " + distributionMode.name()) + .setStyle(Style.EMPTY.withColor(ChatFormatting.WHITE))); + + // Linked partners + int linkedCount = getLinkedPartners().size(); + if (linkedCount > 0) { + textList.add(Component.literal("Linked Drop-Offs: " + linkedCount) + .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN))); + } else { + textList.add(Component.literal("No Drop-Offs linked!") + .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))); + } + + // Capacity info + if (mothHomeCount > 0) { + textList.add(Component + .literal("Capacity: " + getCapacityPerCycle(false) + " items / " + getCapacityPerCycle(true) + + " mB per cycle") + .setStyle(Style.EMPTY.withColor(ChatFormatting.GRAY))); + } + } + + @Override + protected InteractionResult onScrewdriverClick(Player playerIn, InteractionHand hand, Direction gridSide, + BlockHitResult hitResult) { + if (!isRemote()) { + // Cycle through distribution modes + DistributionMode[] modes = DistributionMode.values(); + int nextIndex = (distributionMode.ordinal() + 1) % modes.length; + distributionMode = modes[nextIndex]; + + playerIn.displayClientMessage( + Component.literal("Distribution Mode: " + distributionMode.name()) + .setStyle(Style.EMPTY.withColor(ChatFormatting.AQUA)), + true); + } + return InteractionResult.SUCCESS; + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java index f7248b357..965decbe7 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java @@ -64,6 +64,11 @@ public static void init() { VoidSaltReactor.init(); AtomicReconstructor.init(); DivingBell.init(); + LinkTestStation.init(); // KryosynCrackingChamber.init(); + + // Cargo Moths System + MothCargoStation.init(); + MothCargoDropOff.init(); } } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/CosmicConditions.java b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/CosmicConditions.java index ff6af60f0..3e4de2610 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/CosmicConditions.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/CosmicConditions.java @@ -4,5 +4,9 @@ public class CosmicConditions { public static void register() { TitanCondition.register(); + LinkedPartnerCondition.register(); + LinkedPartnerDimensionCondition.register(); + LinkedPartnerDimensionItemCondition.register(); + LinkedPartnerDimensionFluidCondition.register(); } } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerCondition.java b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerCondition.java new file mode 100644 index 000000000..c48d48249 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerCondition.java @@ -0,0 +1,116 @@ +package com.ghostipedia.cosmiccore.common.recipe.condition; + +import com.ghostipedia.cosmiccore.api.machine.multiblock.LinkedWorkableElectricMultiblockMachine; + +import com.gregtechceu.gtceu.api.machine.trait.RecipeLogic; +import com.gregtechceu.gtceu.api.recipe.GTRecipe; +import com.gregtechceu.gtceu.api.recipe.RecipeCondition; +import com.gregtechceu.gtceu.api.recipe.condition.RecipeConditionType; +import com.gregtechceu.gtceu.api.registry.GTRegistries; + +import net.minecraft.network.chat.Component; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.jetbrains.annotations.NotNull; + +/** + * Recipe condition that requires the machine to have linked partners. + *

+ * Can be configured to require: + * - A minimum number of linked partners + * - At least one partner to be formed (structure valid) + * - At least one partner to be actively working (running a recipe) + */ +public class LinkedPartnerCondition extends RecipeCondition { + + /** Minimum number of linked partners required */ + public int minPartners; + /** If true, at least one partner must have a valid structure */ + public boolean requireFormed; + /** If true, at least one partner must be actively running a recipe */ + public boolean requireWorking; + + public static final Codec CODEC = RecordCodecBuilder + .create(instance -> RecipeCondition.isReverse(instance) + .and(Codec.INT.optionalFieldOf("min_partners", 1).forGetter(val -> val.minPartners)) + .and(Codec.BOOL.optionalFieldOf("require_formed", false).forGetter(val -> val.requireFormed)) + .and(Codec.BOOL.optionalFieldOf("require_working", false).forGetter(val -> val.requireWorking)) + .apply(instance, LinkedPartnerCondition::new)); + + public static RecipeConditionType TYPE; + + public LinkedPartnerCondition(boolean isReverse, int minPartners, boolean requireFormed, boolean requireWorking) { + this.isReverse = isReverse; + this.minPartners = minPartners; + this.requireFormed = requireFormed; + this.requireWorking = requireWorking; + } + + public LinkedPartnerCondition(int minPartners, boolean requireFormed, boolean requireWorking) { + this(false, minPartners, requireFormed, requireWorking); + } + + public LinkedPartnerCondition(int minPartners) { + this(false, minPartners, false, false); + } + + public LinkedPartnerCondition() { + this(1); + } + + public static void register() { + TYPE = GTRegistries.RECIPE_CONDITIONS.register("linked_partner", + new RecipeConditionType<>(LinkedPartnerCondition::new, LinkedPartnerCondition.CODEC)); + } + + @Override + public RecipeConditionType getType() { + return TYPE; + } + + @Override + public Component getTooltips() { + if (requireWorking) { + return Component.translatable("cosmiccore.recipe.condition.linked_partner.working", minPartners); + } else if (requireFormed) { + return Component.translatable("cosmiccore.recipe.condition.linked_partner.formed", minPartners); + } else { + return Component.translatable("cosmiccore.recipe.condition.linked_partner.tooltip", minPartners); + } + } + + @Override + protected boolean testCondition(@NotNull GTRecipe recipe, @NotNull RecipeLogic recipeLogic) { + if (!(recipeLogic.getMachine() instanceof LinkedWorkableElectricMultiblockMachine linkedMachine)) { + return false; + } + + // Check minimum partner count + int partnerCount = linkedMachine.getLinkedPartners().size(); + if (partnerCount < minPartners) { + return false; + } + + // Check if any partner needs to be formed + if (requireFormed) { + if (linkedMachine.countFormedPartners() < 1) { + return false; + } + } + + // Check if any partner needs to be working + if (requireWorking) { + if (!linkedMachine.anyPartnerWorking()) { + return false; + } + } + + return true; + } + + @Override + public RecipeCondition createTemplate() { + return new LinkedPartnerCondition(); + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionCondition.java b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionCondition.java new file mode 100644 index 000000000..817bc53d4 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionCondition.java @@ -0,0 +1,92 @@ +package com.ghostipedia.cosmiccore.common.recipe.condition; + +import com.ghostipedia.cosmiccore.api.machine.multiblock.LinkedWorkableElectricMultiblockMachine; + +import com.gregtechceu.gtceu.api.machine.trait.RecipeLogic; +import com.gregtechceu.gtceu.api.recipe.GTRecipe; +import com.gregtechceu.gtceu.api.recipe.RecipeCondition; +import com.gregtechceu.gtceu.api.recipe.condition.RecipeConditionType; +import com.gregtechceu.gtceu.api.registry.GTRegistries; + +import net.minecraft.core.GlobalPos; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.jetbrains.annotations.NotNull; + +/** + * Recipe condition that requires a linked partner to be in a specific dimension. + *

+ * Use cases: + * - "Requires partner in Sun Orbit" for solar plasma collection + * - "Requires partner in The Deep Below" for mining operations + * - "Requires partner in Moon" for low-gravity processing + */ +public class LinkedPartnerDimensionCondition extends RecipeCondition { + + /** The dimension the partner must be in */ + public ResourceLocation dimension; + + public static final Codec CODEC = RecordCodecBuilder + .create(instance -> RecipeCondition.isReverse(instance) + .and(ResourceLocation.CODEC.fieldOf("dimension").forGetter(val -> val.dimension)) + .apply(instance, LinkedPartnerDimensionCondition::new)); + + public static RecipeConditionType TYPE; + + public LinkedPartnerDimensionCondition(boolean isReverse, ResourceLocation dimension) { + this.isReverse = isReverse; + this.dimension = dimension; + } + + public LinkedPartnerDimensionCondition(ResourceLocation dimension) { + this(false, dimension); + } + + public LinkedPartnerDimensionCondition(String dimension) { + this(false, new ResourceLocation(dimension)); + } + + public LinkedPartnerDimensionCondition() { + this.dimension = new ResourceLocation("minecraft:overworld"); + } + + public static void register() { + TYPE = GTRegistries.RECIPE_CONDITIONS.register("linked_partner_dimension", + new RecipeConditionType<>(LinkedPartnerDimensionCondition::new, LinkedPartnerDimensionCondition.CODEC)); + } + + @Override + public RecipeConditionType getType() { + return TYPE; + } + + @Override + public Component getTooltips() { + return Component.translatable("cosmiccore.recipe.condition.linked_partner_dimension.tooltip", + dimension.toString()); + } + + @Override + protected boolean testCondition(@NotNull GTRecipe recipe, @NotNull RecipeLogic recipeLogic) { + if (!(recipeLogic.getMachine() instanceof LinkedWorkableElectricMultiblockMachine linkedMachine)) { + return false; + } + + // Check if any linked partner is in the required dimension + for (GlobalPos partner : linkedMachine.getLinkedPartners()) { + if (partner.dimension().location().equals(dimension)) { + return true; + } + } + + return false; + } + + @Override + public RecipeCondition createTemplate() { + return new LinkedPartnerDimensionCondition(); + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionFluidCondition.java b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionFluidCondition.java new file mode 100644 index 000000000..91af45cd8 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionFluidCondition.java @@ -0,0 +1,136 @@ +package com.ghostipedia.cosmiccore.common.recipe.condition; + +import com.ghostipedia.cosmiccore.api.machine.multiblock.LinkedWorkableElectricMultiblockMachine; +import com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper; + +import com.gregtechceu.gtceu.api.machine.trait.RecipeLogic; +import com.gregtechceu.gtceu.api.recipe.GTRecipe; +import com.gregtechceu.gtceu.api.recipe.RecipeCondition; +import com.gregtechceu.gtceu.api.recipe.condition.RecipeConditionType; +import com.gregtechceu.gtceu.api.registry.GTRegistries; + +import net.minecraft.core.GlobalPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.material.Fluid; +import net.minecraftforge.fluids.FluidStack; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +/** + * Recipe condition that requires a linked partner in a specific dimension + * to have a specific fluid in its input hatches. + *

+ * Use cases: + * - "Requires partner in Sun Orbit with Solar Plasma" + * - "Requires partner in Deep Below with Molten Core fluid" + */ +public class LinkedPartnerDimensionFluidCondition extends RecipeCondition { + + public ResourceLocation dimension; + public ResourceLocation fluidId; + public int minAmount; + + public static final Codec CODEC = RecordCodecBuilder + .create(instance -> RecipeCondition.isReverse(instance) + .and(ResourceLocation.CODEC.fieldOf("dimension").forGetter(val -> val.dimension)) + .and(ResourceLocation.CODEC.fieldOf("fluid").forGetter(val -> val.fluidId)) + .and(Codec.INT.optionalFieldOf("amount", 1000).forGetter(val -> val.minAmount)) + .apply(instance, LinkedPartnerDimensionFluidCondition::new)); + + public static RecipeConditionType TYPE; + + public LinkedPartnerDimensionFluidCondition(boolean isReverse, ResourceLocation dimension, ResourceLocation fluidId, + int minAmount) { + this.isReverse = isReverse; + this.dimension = dimension; + this.fluidId = fluidId; + this.minAmount = minAmount; + } + + public LinkedPartnerDimensionFluidCondition(ResourceLocation dimension, ResourceLocation fluidId, int minAmount) { + this(false, dimension, fluidId, minAmount); + } + + public LinkedPartnerDimensionFluidCondition(String dimension, Fluid fluid, int minAmount) { + this(false, new ResourceLocation(dimension), BuiltInRegistries.FLUID.getKey(fluid), minAmount); + } + + public LinkedPartnerDimensionFluidCondition(String dimension, Fluid fluid) { + this(dimension, fluid, 1000); + } + + public LinkedPartnerDimensionFluidCondition() { + this.dimension = new ResourceLocation("minecraft:overworld"); + this.fluidId = new ResourceLocation("minecraft:water"); + this.minAmount = 1000; + } + + public static void register() { + TYPE = GTRegistries.RECIPE_CONDITIONS.register("linked_partner_dimension_fluid", + new RecipeConditionType<>(LinkedPartnerDimensionFluidCondition::new, + LinkedPartnerDimensionFluidCondition.CODEC)); + } + + @Override + public RecipeConditionType getType() { + return TYPE; + } + + @Override + public Component getTooltips() { + Fluid fluid = BuiltInRegistries.FLUID.get(fluidId); + String fluidName = new FluidStack(fluid, 1000).getDisplayName().getString(); + return Component.translatable("cosmiccore.recipe.condition.linked_partner_dimension_fluid.tooltip", + minAmount, fluidName, dimension.toString()); + } + + @Override + protected boolean testCondition(@NotNull GTRecipe recipe, @NotNull RecipeLogic recipeLogic) { + if (!(recipeLogic.getMachine() instanceof LinkedWorkableElectricMultiblockMachine linkedMachine)) { + return false; + } + + if (!(linkedMachine.getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + + UUID owner = linkedMachine.getTeamUUID(); + if (owner == null) return false; + + GlobalPos myPos = linkedMachine.getGlobalPos(); + Fluid targetFluid = BuiltInRegistries.FLUID.get(fluidId); + + // Check each partner in the required dimension + for (GlobalPos partner : linkedMachine.getLinkedPartners()) { + if (!partner.dimension().location().equals(dimension)) { + continue; + } + + // Check if this partner has the required fluid + boolean hasFluid = LinkedMultiblockHelper.partnerHasFluid( + serverLevel.getServer(), + owner, + myPos, + partner, + (FluidStack stack) -> stack.getFluid().isSame(targetFluid) && stack.getAmount() >= minAmount); + + if (hasFluid) { + return true; + } + } + + return false; + } + + @Override + public RecipeCondition createTemplate() { + return new LinkedPartnerDimensionFluidCondition(); + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionItemCondition.java b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionItemCondition.java new file mode 100644 index 000000000..6a3f88aea --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/recipe/condition/LinkedPartnerDimensionItemCondition.java @@ -0,0 +1,136 @@ +package com.ghostipedia.cosmiccore.common.recipe.condition; + +import com.ghostipedia.cosmiccore.api.machine.multiblock.LinkedWorkableElectricMultiblockMachine; +import com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper; + +import com.gregtechceu.gtceu.api.machine.trait.RecipeLogic; +import com.gregtechceu.gtceu.api.recipe.GTRecipe; +import com.gregtechceu.gtceu.api.recipe.RecipeCondition; +import com.gregtechceu.gtceu.api.recipe.condition.RecipeConditionType; +import com.gregtechceu.gtceu.api.registry.GTRegistries; + +import net.minecraft.core.GlobalPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +/** + * Recipe condition that requires a linked partner in a specific dimension + * to have a specific item in its input hatches. + *

+ * Use cases: + * - "Requires partner in Sun Orbit with Solar Collector item" + * - "Requires partner in Moon with Helium-3 canister" + */ +public class LinkedPartnerDimensionItemCondition extends RecipeCondition { + + public ResourceLocation dimension; + public ResourceLocation itemId; + public int minCount; + + public static final Codec CODEC = RecordCodecBuilder + .create(instance -> RecipeCondition.isReverse(instance) + .and(ResourceLocation.CODEC.fieldOf("dimension").forGetter(val -> val.dimension)) + .and(ResourceLocation.CODEC.fieldOf("item").forGetter(val -> val.itemId)) + .and(Codec.INT.optionalFieldOf("count", 1).forGetter(val -> val.minCount)) + .apply(instance, LinkedPartnerDimensionItemCondition::new)); + + public static RecipeConditionType TYPE; + + public LinkedPartnerDimensionItemCondition(boolean isReverse, ResourceLocation dimension, ResourceLocation itemId, + int minCount) { + this.isReverse = isReverse; + this.dimension = dimension; + this.itemId = itemId; + this.minCount = minCount; + } + + public LinkedPartnerDimensionItemCondition(ResourceLocation dimension, ResourceLocation itemId, int minCount) { + this(false, dimension, itemId, minCount); + } + + public LinkedPartnerDimensionItemCondition(String dimension, Item item, int minCount) { + this(false, new ResourceLocation(dimension), BuiltInRegistries.ITEM.getKey(item), minCount); + } + + public LinkedPartnerDimensionItemCondition(String dimension, Item item) { + this(dimension, item, 1); + } + + public LinkedPartnerDimensionItemCondition() { + this.dimension = new ResourceLocation("minecraft:overworld"); + this.itemId = new ResourceLocation("minecraft:stone"); + this.minCount = 1; + } + + public static void register() { + TYPE = GTRegistries.RECIPE_CONDITIONS.register("linked_partner_dimension_item", + new RecipeConditionType<>(LinkedPartnerDimensionItemCondition::new, + LinkedPartnerDimensionItemCondition.CODEC)); + } + + @Override + public RecipeConditionType getType() { + return TYPE; + } + + @Override + public Component getTooltips() { + Item item = BuiltInRegistries.ITEM.get(itemId); + String itemName = item.getDescription().getString(); + return Component.translatable("cosmiccore.recipe.condition.linked_partner_dimension_item.tooltip", + minCount, itemName, dimension.toString()); + } + + @Override + protected boolean testCondition(@NotNull GTRecipe recipe, @NotNull RecipeLogic recipeLogic) { + if (!(recipeLogic.getMachine() instanceof LinkedWorkableElectricMultiblockMachine linkedMachine)) { + return false; + } + + if (!(linkedMachine.getLevel() instanceof ServerLevel serverLevel)) { + return false; + } + + UUID owner = linkedMachine.getTeamUUID(); + if (owner == null) return false; + + GlobalPos myPos = linkedMachine.getGlobalPos(); + Item targetItem = BuiltInRegistries.ITEM.get(itemId); + + // Check each partner in the required dimension + for (GlobalPos partner : linkedMachine.getLinkedPartners()) { + if (!partner.dimension().location().equals(dimension)) { + continue; + } + + // Check if this partner has the required item + boolean hasItem = LinkedMultiblockHelper.partnerHasItem( + serverLevel.getServer(), + owner, + myPos, + partner, + (ItemStack stack) -> stack.is(targetItem) && stack.getCount() >= minCount); + + if (hasItem) { + return true; + } + } + + return false; + } + + @Override + public RecipeCondition createTemplate() { + return new LinkedPartnerDimensionItemCondition(); + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/gtbridge/CosmicCoreRecipes.java b/src/main/java/com/ghostipedia/cosmiccore/gtbridge/CosmicCoreRecipes.java index 137167033..fdbecb421 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/gtbridge/CosmicCoreRecipes.java +++ b/src/main/java/com/ghostipedia/cosmiccore/gtbridge/CosmicCoreRecipes.java @@ -1,10 +1,16 @@ package com.ghostipedia.cosmiccore.gtbridge; import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic.LarvaMachine; +import com.ghostipedia.cosmiccore.common.recipe.condition.LinkedPartnerCondition; +import com.ghostipedia.cosmiccore.common.recipe.condition.LinkedPartnerDimensionCondition; +import com.ghostipedia.cosmiccore.common.recipe.condition.LinkedPartnerDimensionFluidCondition; +import com.ghostipedia.cosmiccore.common.recipe.condition.LinkedPartnerDimensionItemCondition; import com.gregtechceu.gtceu.api.GTValues; import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.material.Fluids; import java.util.function.Consumer; @@ -29,6 +35,70 @@ public static void init(Consumer provider) { .save(provider); LarvaMachine.generateTargettingChipRecipes(provider); + + // === Link Test Station Recipes === + // Basic recipe - no partner required (verifies machine works) + LINK_TEST_RECIPES.recipeBuilder("link_test_basic") + .inputItems(Items.IRON_INGOT) + .outputItems(Items.IRON_NUGGET, 9) + .duration(100) + .EUt(GTValues.VA[GTValues.LV]) + .save(provider); + + // Linked recipe - requires at least 1 linked partner + LINK_TEST_RECIPES.recipeBuilder("link_test_linked") + .inputItems(Items.GOLD_INGOT) + .outputItems(Items.DIAMOND) + .duration(200) + .EUt(GTValues.VA[GTValues.MV]) + .addCondition(new LinkedPartnerCondition(1)) + .save(provider); + + // Linked recipe - requires partner to be formed + LINK_TEST_RECIPES.recipeBuilder("link_test_formed_partner") + .inputItems(Items.EMERALD) + .outputItems(Items.NETHER_STAR) + .duration(400) + .EUt(GTValues.VA[GTValues.HV]) + .addCondition(new LinkedPartnerCondition(1, true, false)) + .save(provider); + + // Linked recipe - requires partner in Moon dimension + LINK_TEST_RECIPES.recipeBuilder("link_test_moon_partner") + .inputItems(Items.LAPIS_LAZULI, 4) + .outputItems(Items.ENDER_PEARL) + .duration(200) + .EUt(GTValues.VA[GTValues.MV]) + .addCondition(new LinkedPartnerDimensionCondition("ad_astra:moon")) + .save(provider); + + // Linked recipe - requires partner in Overworld (for testing from other dimensions) + LINK_TEST_RECIPES.recipeBuilder("link_test_overworld_partner") + .inputItems(Items.REDSTONE, 4) + .outputItems(Items.GLOWSTONE_DUST, 4) + .duration(200) + .EUt(GTValues.VA[GTValues.MV]) + .addCondition(new LinkedPartnerDimensionCondition("minecraft:overworld")) + .save(provider); + + // Linked recipe - requires partner in Overworld with diamonds in input + LINK_TEST_RECIPES.recipeBuilder("link_test_dimension_item") + .inputItems(Items.COAL, 8) + .outputItems(Items.DIAMOND) + .duration(400) + .EUt(GTValues.VA[GTValues.HV]) + .addCondition(new LinkedPartnerDimensionItemCondition("minecraft:overworld", Items.DIAMOND, 1)) + .save(provider); + + // Linked recipe - requires partner in Overworld with water in input + LINK_TEST_RECIPES.recipeBuilder("link_test_dimension_fluid") + .inputItems(Items.SPONGE) + .outputItems(Items.WET_SPONGE) + .duration(100) + .EUt(GTValues.VA[GTValues.LV]) + .addCondition(new LinkedPartnerDimensionFluidCondition("minecraft:overworld", Fluids.WATER, 1000)) + .save(provider); + /* * EMBER_TESTER_RECIPES.recipeBuilder("test") * .input(CosmicRecipeCapabilities.EMBER, 100d) diff --git a/src/main/java/com/ghostipedia/cosmiccore/gtbridge/CosmicRecipeTypes.java b/src/main/java/com/ghostipedia/cosmiccore/gtbridge/CosmicRecipeTypes.java index f54a78c55..2a9b8f5df 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/gtbridge/CosmicRecipeTypes.java +++ b/src/main/java/com/ghostipedia/cosmiccore/gtbridge/CosmicRecipeTypes.java @@ -472,6 +472,13 @@ public class CosmicRecipeTypes { // .setSound(GTSoundEntries.CHEMICAL) // TODO - Sounds // .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE, ProgressTexture.FillDirection.LEFT_TO_RIGHT); + // Link Test Station recipe type for testing cross-dimensional linking + public static final GTRecipeType LINK_TEST_RECIPES = GTRecipeTypes + .register("link_test", ELECTRIC) + .setMaxIOSize(2, 2, 0, 0) + .setSound(GTSoundEntries.ASSEMBLER) + .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, ProgressTexture.FillDirection.LEFT_TO_RIGHT); + public static void init() { LASER_ENGRAVER_RECIPES.setMaxIOSize(2, 2, 1, 1); // Oh my God From 40fe68f548cba56aabb146b50dc89f66b6bc6f46 Mon Sep 17 00:00:00 2001 From: Ghostipedia Date: Mon, 19 Jan 2026 20:38:16 -0500 Subject: [PATCH 4/4] Whoops I was on the PR oh well fuck it --- EMI_FAVORITES_PROGRESS.md | 78 -- .../cosmiccore/blockstates/diving_bell.json | 76 ++ .../blockstates/diving_bell_escape_pad.json | 7 + .../blockstates/link_test_station.json | 76 ++ .../blockstates/moth_cargo_drop_off.json | 76 ++ .../blockstates/moth_cargo_station.json | 76 ++ .../cosmiccore/blockstates/moth_home_t1.json | 7 + .../cosmiccore/blockstates/moth_home_t2.json | 7 + .../cosmiccore/blockstates/moth_home_t3.json | 7 + .../cosmiccore/blockstates/moth_home_t4.json | 7 + .../blockstates/moth_station_casing.json | 7 + .../blockstates/star_ladder_research_hub.json | 19 + .../assets/cosmiccore/lang/en_ud.json | 36 + .../assets/cosmiccore/lang/en_us.json | 57 + .../models/block/diving_bell_escape_pad.json | 6 + .../models/block/machine/diving_bell.json | 90 ++ .../block/machine/link_test_station.json | 90 ++ .../block/machine/moth_cargo_drop_off.json | 90 ++ .../block/machine/moth_cargo_station.json | 90 ++ .../machine/star_ladder_research_hub.json | 90 ++ .../cosmiccore/models/block/moth_home_t1.json | 6 + .../cosmiccore/models/block/moth_home_t2.json | 6 + .../cosmiccore/models/block/moth_home_t3.json | 6 + .../cosmiccore/models/block/moth_home_t4.json | 6 + .../models/block/moth_station_casing.json | 6 + .../cosmiccore/models/item/diving_bell.json | 3 + .../models/item/diving_bell_escape_pad.json | 3 + .../models/item/link_test_station.json | 3 + .../models/item/moth_cargo_drop_off.json | 3 + .../models/item/moth_cargo_station.json | 3 + .../cosmiccore/models/item/moth_home_t1.json | 3 + .../cosmiccore/models/item/moth_home_t2.json | 3 + .../cosmiccore/models/item/moth_home_t3.json | 3 + .../cosmiccore/models/item/moth_home_t4.json | 3 + .../models/item/moth_station_casing.json | 3 + .../models/item/star_ladder_research_hub.json | 3 + .../blocks/diving_bell_escape_pad.json | 21 + .../loot_tables/blocks/moth_home_t1.json | 21 + .../loot_tables/blocks/moth_home_t2.json | 21 + .../loot_tables/blocks/moth_home_t3.json | 21 + .../loot_tables/blocks/moth_home_t4.json | 21 + .../blocks/moth_station_casing.json | 21 + .../blocks/mineable/pickaxe_or_wrench.json | 3 +- .../api/data/DebugBlockPattern.java | 10 + .../api/pattern/CosmicPredicates.java | 321 ++++- .../client/ForgeClientEventHandler.java | 11 + .../renderer/RingUpgradePreviewRenderer.java | 316 +++++ .../cosmiccore/common/data/CosmicBlocks.java | 2 + .../common/data/CosmicMachines.java | 1 - .../item/behavior/StructureWriteBehavior.java | 10 + .../multiblock/LinkedMultiblockHelper.java | 197 +-- .../machine/multiblock/multi/DivingBell.java | 7 +- .../machine/multiblock/multi/StarLadder.java | 4 +- .../multi/StarLadderResearchHub.java | 1097 +++++++++++++++++ .../multi/StarLadderResearchHubPatterns.java | 463 +++++++ .../multi/logic/StarLadderMachine.java | 67 + .../logic/StarLadderResearchHubMachine.java | 547 ++++++++ .../multi/modular/MultiblockInit.java | 1 + .../item/CapacityShardBehavior.java | 4 +- .../reflection/item/ScarRemovalBehavior.java | 6 +- .../reflection/ui/ScarSelectionScreen.java | 6 +- .../mixin/accessor/MBPatternAccessor.java | 21 + .../client/PatternPreviewWidgetMixin.java | 52 + .../block/casings/moth/moth_home_t1.png | Bin 0 -> 419 bytes .../block/casings/moth/moth_home_t2.png | Bin 0 -> 419 bytes .../block/casings/moth/moth_home_t3.png | Bin 0 -> 419 bytes .../block/casings/moth/moth_home_t4.png | Bin 0 -> 419 bytes .../casings/solid/moth_station_casing.png | Bin 0 -> 444 bytes .../textures/block/diving_bell_escape_pad.png | Bin 0 -> 419 bytes src/main/resources/cosmiccore.mixins.json | 2 + 70 files changed, 4028 insertions(+), 301 deletions(-) delete mode 100644 EMI_FAVORITES_PROGRESS.md create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/diving_bell.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/diving_bell_escape_pad.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/link_test_station.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/moth_cargo_drop_off.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/moth_cargo_station.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/moth_home_t1.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/moth_home_t2.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/moth_home_t3.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/moth_home_t4.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/moth_station_casing.json create mode 100644 src/generated/resources/assets/cosmiccore/blockstates/star_ladder_research_hub.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/diving_bell_escape_pad.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/machine/diving_bell.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/machine/link_test_station.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/machine/moth_cargo_drop_off.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/machine/moth_cargo_station.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/machine/star_ladder_research_hub.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/moth_home_t1.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/moth_home_t2.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/moth_home_t3.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/moth_home_t4.json create mode 100644 src/generated/resources/assets/cosmiccore/models/block/moth_station_casing.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/diving_bell.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/diving_bell_escape_pad.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/link_test_station.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/moth_cargo_drop_off.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/moth_cargo_station.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/moth_home_t1.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/moth_home_t2.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/moth_home_t3.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/moth_home_t4.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/moth_station_casing.json create mode 100644 src/generated/resources/assets/cosmiccore/models/item/star_ladder_research_hub.json create mode 100644 src/generated/resources/data/cosmiccore/loot_tables/blocks/diving_bell_escape_pad.json create mode 100644 src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t1.json create mode 100644 src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t2.json create mode 100644 src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t3.json create mode 100644 src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t4.json create mode 100644 src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_station_casing.json create mode 100644 src/main/java/com/ghostipedia/cosmiccore/client/renderer/RingUpgradePreviewRenderer.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadderResearchHub.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadderResearchHubPatterns.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/StarLadderMachine.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/StarLadderResearchHubMachine.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/mixin/accessor/MBPatternAccessor.java create mode 100644 src/main/java/com/ghostipedia/cosmiccore/mixin/client/PatternPreviewWidgetMixin.java create mode 100644 src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t1.png create mode 100644 src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t2.png create mode 100644 src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t3.png create mode 100644 src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t4.png create mode 100644 src/main/resources/assets/cosmiccore/textures/block/casings/solid/moth_station_casing.png create mode 100644 src/main/resources/assets/cosmiccore/textures/block/diving_bell_escape_pad.png diff --git a/EMI_FAVORITES_PROGRESS.md b/EMI_FAVORITES_PROGRESS.md deleted file mode 100644 index cde896adb..000000000 --- a/EMI_FAVORITES_PROGRESS.md +++ /dev/null @@ -1,78 +0,0 @@ -# EMI Favorites with Custom Amounts - Progress Summary - -## Goal -Add the ability to pin EMI stacks with custom amounts using CTRL+A, and adjust amounts with CTRL+scroll. - -## What Was Implemented - -### 1. CosmicFavorite Class -**File:** `src/main/java/com/ghostipedia/cosmiccore/integration/emi/CosmicFavorite.java` - -Extended `EmiFavorite` to store custom amounts and render them with a scaled-down overlay: -- Stores `amount` field -- Custom `render()` method that draws the stack without its default amount, then draws a half-scale (0.5f) amount overlay -- `formatCompact()` for K/M/B suffixes (items) and B/KB/MB/BB (fluids in buckets) -- `adjustAmount()` for CTRL+scroll adjustment - -### 2. EmiScreenManagerMixin -**File:** `src/main/java/com/ghostipedia/cosmiccore/mixin/emi/EmiScreenManagerMixin.java` - -Injects into EMI's screen manager: -- **CTRL+A pinning:** Detects favorite keybind + CTRL, gets hovered stack (from sidebar, RecipeScreen, or stack providers), creates CosmicFavorite with amount -- **CTRL+scroll:** Adjusts amount on hovered CosmicFavorite (shift for big steps: 64 for items, 1000 for fluids) -- **Recalculate guard:** Prevents NPE when `currentBase` or its screen is null - -### 3. EmiExclusionAreasMixin -**File:** `src/main/java/com/ghostipedia/cosmiccore/mixin/emi/EmiExclusionAreasMixin.java` - -Guards against NPE in `getExclusion()` when screen is null during reload. - -### 4. RecipeScreenMixin -**File:** `src/main/java/com/ghostipedia/cosmiccore/mixin/emi/RecipeScreenMixin.java` - -Adds public `getHoveredStack()` method to RecipeScreen for detecting what the user is hovering over. - -## Current Status - -### Working -- CTRL+A key detection is working (logs show keyCode=65, ctrl=true, isFavKey=true) -- EmiExclusionAreas guard is in place -- EmiScreenManager.recalculate guard is in place (fixed to use parameterless method signature) - -### Not Yet Tested -- Actual pinning from recipe screens (was crashing before guards were fixed) -- Scaled-down amount rendering -- CTRL+scroll amount adjustment - -## Recent Crashes Fixed - -1. **NPE in EmiExclusionAreas.getExclusion** - Fixed with EmiExclusionAreasMixin guard -2. **NPE in EmiScreenManager.recalculate** - Fixed with guard, but had version mismatch issue -3. **Invalid descriptor on guardRecalculate** - Fixed by removing EmiScreenBase parameter (dev EMI has parameterless `recalculate()`) - -## Files Modified - -- `src/main/java/com/ghostipedia/cosmiccore/integration/emi/CosmicFavorite.java` -- `src/main/java/com/ghostipedia/cosmiccore/mixin/emi/EmiScreenManagerMixin.java` -- `src/main/java/com/ghostipedia/cosmiccore/mixin/emi/EmiExclusionAreasMixin.java` -- `src/main/java/com/ghostipedia/cosmiccore/mixin/emi/RecipeScreenMixin.java` -- `src/main/resources/cosmiccore.mixins.json` (client mixins list) - -## Next Steps - -1. Test that the game launches without crashes -2. Test CTRL+A pinning from recipe screens -3. Test that amounts display with scaled-down rendering -4. Test CTRL+scroll amount adjustment -5. Remove debug logging once everything works - -## Key EMI Classes Referenced - -- `dev.emi.emi.screen.EmiScreenManager` - Main screen management -- `dev.emi.emi.screen.EmiScreenBase` - Wrapper around Minecraft Screen -- `dev.emi.emi.screen.RecipeScreen` - Recipe viewing screen -- `dev.emi.emi.runtime.EmiFavorites` - Favorites management -- `dev.emi.emi.registry.EmiExclusionAreas` - Exclusion zone calculations -- `dev.emi.emi.registry.EmiStackProviders` - Stack detection from screens -- `dev.emi.emi.api.stack.EmiStackInteraction` - Hovered stack info -- `dev.emi.emi.config.EmiConfig.favorite` - Favorite keybind config diff --git a/src/generated/resources/assets/cosmiccore/blockstates/diving_bell.json b/src/generated/resources/assets/cosmiccore/blockstates/diving_bell.json new file mode 100644 index 000000000..e3a97a861 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/diving_bell.json @@ -0,0 +1,76 @@ +{ + "variants": { + "facing=east,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/diving_bell", + "y": 90 + }, + "facing=east,upwards_facing=north": { + "model": "cosmiccore:block/machine/diving_bell", + "y": 90 + }, + "facing=east,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/diving_bell", + "y": 90 + }, + "facing=east,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/diving_bell", + "y": 90 + }, + "facing=north,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/diving_bell" + }, + "facing=north,upwards_facing=north": { + "model": "cosmiccore:block/machine/diving_bell" + }, + "facing=north,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/diving_bell" + }, + "facing=north,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/diving_bell" + }, + "facing=south,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/diving_bell", + "y": 180 + }, + "facing=south,upwards_facing=north": { + "model": "cosmiccore:block/machine/diving_bell", + "y": 180 + }, + "facing=south,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/diving_bell", + "y": 180 + }, + "facing=south,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/diving_bell", + "y": 180 + }, + "facing=west,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/diving_bell", + "y": 270 + }, + "facing=west,upwards_facing=north": { + "model": "cosmiccore:block/machine/diving_bell", + "y": 270 + }, + "facing=west,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/diving_bell", + "y": 270 + }, + "facing=west,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/diving_bell", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/diving_bell_escape_pad.json b/src/generated/resources/assets/cosmiccore/blockstates/diving_bell_escape_pad.json new file mode 100644 index 000000000..170a0143e --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/diving_bell_escape_pad.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "cosmiccore:block/diving_bell_escape_pad" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/link_test_station.json b/src/generated/resources/assets/cosmiccore/blockstates/link_test_station.json new file mode 100644 index 000000000..28b9191d6 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/link_test_station.json @@ -0,0 +1,76 @@ +{ + "variants": { + "facing=east,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/link_test_station", + "y": 90 + }, + "facing=east,upwards_facing=north": { + "model": "cosmiccore:block/machine/link_test_station", + "y": 90 + }, + "facing=east,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/link_test_station", + "y": 90 + }, + "facing=east,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/link_test_station", + "y": 90 + }, + "facing=north,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/link_test_station" + }, + "facing=north,upwards_facing=north": { + "model": "cosmiccore:block/machine/link_test_station" + }, + "facing=north,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/link_test_station" + }, + "facing=north,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/link_test_station" + }, + "facing=south,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/link_test_station", + "y": 180 + }, + "facing=south,upwards_facing=north": { + "model": "cosmiccore:block/machine/link_test_station", + "y": 180 + }, + "facing=south,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/link_test_station", + "y": 180 + }, + "facing=south,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/link_test_station", + "y": 180 + }, + "facing=west,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/link_test_station", + "y": 270 + }, + "facing=west,upwards_facing=north": { + "model": "cosmiccore:block/machine/link_test_station", + "y": 270 + }, + "facing=west,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/link_test_station", + "y": 270 + }, + "facing=west,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/link_test_station", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/moth_cargo_drop_off.json b/src/generated/resources/assets/cosmiccore/blockstates/moth_cargo_drop_off.json new file mode 100644 index 000000000..1a24e6cfc --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/moth_cargo_drop_off.json @@ -0,0 +1,76 @@ +{ + "variants": { + "facing=east,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 90 + }, + "facing=east,upwards_facing=north": { + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 90 + }, + "facing=east,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 90 + }, + "facing=east,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 90 + }, + "facing=north,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/moth_cargo_drop_off" + }, + "facing=north,upwards_facing=north": { + "model": "cosmiccore:block/machine/moth_cargo_drop_off" + }, + "facing=north,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/moth_cargo_drop_off" + }, + "facing=north,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/moth_cargo_drop_off" + }, + "facing=south,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 180 + }, + "facing=south,upwards_facing=north": { + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 180 + }, + "facing=south,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 180 + }, + "facing=south,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 180 + }, + "facing=west,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 270 + }, + "facing=west,upwards_facing=north": { + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 270 + }, + "facing=west,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 270 + }, + "facing=west,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/moth_cargo_drop_off", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/moth_cargo_station.json b/src/generated/resources/assets/cosmiccore/blockstates/moth_cargo_station.json new file mode 100644 index 000000000..a668efed5 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/moth_cargo_station.json @@ -0,0 +1,76 @@ +{ + "variants": { + "facing=east,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 90 + }, + "facing=east,upwards_facing=north": { + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 90 + }, + "facing=east,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 90 + }, + "facing=east,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 90 + }, + "facing=north,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/moth_cargo_station" + }, + "facing=north,upwards_facing=north": { + "model": "cosmiccore:block/machine/moth_cargo_station" + }, + "facing=north,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/moth_cargo_station" + }, + "facing=north,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/moth_cargo_station" + }, + "facing=south,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 180 + }, + "facing=south,upwards_facing=north": { + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 180 + }, + "facing=south,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 180 + }, + "facing=south,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 180 + }, + "facing=west,upwards_facing=east": { + "gtceu:z": 270, + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 270 + }, + "facing=west,upwards_facing=north": { + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 270 + }, + "facing=west,upwards_facing=south": { + "gtceu:z": 180, + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 270 + }, + "facing=west,upwards_facing=west": { + "gtceu:z": 90, + "model": "cosmiccore:block/machine/moth_cargo_station", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t1.json b/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t1.json new file mode 100644 index 000000000..f6eefc61b --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t1.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "cosmiccore:block/moth_home_t1" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t2.json b/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t2.json new file mode 100644 index 000000000..03256da3d --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t2.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "cosmiccore:block/moth_home_t2" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t3.json b/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t3.json new file mode 100644 index 000000000..d9c0370ce --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t3.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "cosmiccore:block/moth_home_t3" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t4.json b/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t4.json new file mode 100644 index 000000000..00287d320 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/moth_home_t4.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "cosmiccore:block/moth_home_t4" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/moth_station_casing.json b/src/generated/resources/assets/cosmiccore/blockstates/moth_station_casing.json new file mode 100644 index 000000000..4ae453039 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/moth_station_casing.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "cosmiccore:block/moth_station_casing" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/blockstates/star_ladder_research_hub.json b/src/generated/resources/assets/cosmiccore/blockstates/star_ladder_research_hub.json new file mode 100644 index 000000000..60017ecf2 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/blockstates/star_ladder_research_hub.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "cosmiccore:block/machine/star_ladder_research_hub", + "y": 90 + }, + "facing=north": { + "model": "cosmiccore:block/machine/star_ladder_research_hub" + }, + "facing=south": { + "model": "cosmiccore:block/machine/star_ladder_research_hub", + "y": 180 + }, + "facing=west": { + "model": "cosmiccore:block/machine/star_ladder_research_hub", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/lang/en_ud.json b/src/generated/resources/assets/cosmiccore/lang/en_ud.json index 99b63e4bd..23e5cabf6 100644 --- a/src/generated/resources/assets/cosmiccore/lang/en_ud.json +++ b/src/generated/resources/assets/cosmiccore/lang/en_ud.json @@ -68,6 +68,8 @@ "block.cosmiccore.dawnforge_eclipsed": "]pǝsdıןɔƎ[ ǝbɹoɟuʍɐᗡ", "block.cosmiccore.dimensional_energy_capacitor": "uoıʇɐʇsqnS ɹǝʍoԀ", "block.cosmiccore.dimensional_energy_interface": "ǝɔɐɟɹǝʇuI ןɐuoısuǝɯıᗡ uoıʇɐʇsqnS ɹǝʍoԀ", + "block.cosmiccore.diving_bell": "ןןǝᗺ buıʌıᗡ", + "block.cosmiccore.diving_bell_escape_pad": "pɐԀ ǝdɐɔsƎ ןןǝᗺ buıʌıᗡ", "block.cosmiccore.dreamers_basin": "uısɐᗺ s,ɹǝɯɐǝɹᗡ", "block.cosmiccore.drone_maintenance_interface": "ǝɔɐɟɹǝʇuI ǝɔuɐuǝʇuıɐW ǝuoɹᗡ", "block.cosmiccore.drone_station": "uoıʇɐʇS ǝuoɹᗡ", @@ -156,6 +158,7 @@ "block.cosmiccore.large_spooling_machine": "ǝuıɥɔɐW buıןoodS ǝbɹɐꞀ", "block.cosmiccore.larva": "]ⱯΛᴚⱯꞀ[ ʎןqɯǝssⱯ buınןɐΛ puɐ uoıʇɐɯɐןɔǝᴚ pıoɹǝʇsⱯ ɔıʇsıboꞀ", "block.cosmiccore.light_dawnstone_casing": "buısɐƆ ǝuoʇsuʍɐᗡ ʇɥbıꞀ", + "block.cosmiccore.link_test_station": "uoıʇɐʇS ʇsǝ⟘ ʞuıꞀ", "block.cosmiccore.living_igniclad_coil_block": "ʞɔoןᗺ ןıoƆ pɐןɔıubI buıʌıꞀ", "block.cosmiccore.livingrock_tiles": "sǝןı⟘ ʞɔoɹbuıʌıꞀ", "block.cosmiccore.lp_steam_bender": "⟘SIXƎ ⟘ON Oᗡ I", @@ -204,6 +207,13 @@ "block.cosmiccore.mantle_bore": "ǝɹoᗺ ǝןʇuɐW", "block.cosmiccore.max_ember_emitter": "ɹǝʇʇıɯƎ ɹǝqɯƎ XⱯW", "block.cosmiccore.max_ember_receptor": "ɹoʇdǝɔǝᴚ ɹǝqɯƎ XⱯW", + "block.cosmiccore.moth_cargo_drop_off": "ɟɟO doɹᗡ obɹɐƆ ɥʇoW", + "block.cosmiccore.moth_cargo_station": "uoıʇɐʇS obɹɐƆ ɥʇoW", + "block.cosmiccore.moth_home_t1": ")Ɩ⟘( ǝɯoH ɥʇoW", + "block.cosmiccore.moth_home_t2": ")ᄅ⟘( ǝɯoH ɥʇoW", + "block.cosmiccore.moth_home_t3": ")Ɛ⟘( ǝɯoH ɥʇoW", + "block.cosmiccore.moth_home_t4": ")ㄣ⟘( ǝɯoH ɥʇoW", + "block.cosmiccore.moth_station_casing": "buısɐƆ uoıʇɐʇS ɥʇoW", "block.cosmiccore.multi_purpose_interstellar_grade_casing": "buısɐƆ ǝpɐɹ⅁ ɹɐןןǝʇsɹǝʇuI ǝsodɹnԀ ıʇןnW", "block.cosmiccore.mv_calx_reactor": "ɹoʇɔɐǝᴚ xןɐƆ ʌW", "block.cosmiccore.mv_electric_apiary": "ʎɹɐıdⱯ uoıʇɔnpoɹԀ ןɐıɹʇsnpuI ΛW", @@ -272,6 +282,7 @@ "block.cosmiccore.soul_stained_steel_aluminium_plated_casing": "buısɐƆ pǝʇɐןԀ ɯnıuıɯnןⱯ ןǝǝʇS pǝuıɐʇS ןnoS", "block.cosmiccore.spirit_crucible": "ǝןqıɔnɹƆ ʇıɹıdS", "block.cosmiccore.star_ladder": "ɹǝppɐꞀ ɹɐʇS", + "block.cosmiccore.star_ladder_research_hub": "qnH ɥɔɹɐǝsǝᴚ ɹǝppɐꞀ ɹɐʇS", "block.cosmiccore.steam_caster": "ɹǝʇsɐƆ ɯɐǝʇS", "block.cosmiccore.steam_ember_emitter": "ɹǝʇʇıɯƎ ɹǝqɯƎ ɯɐǝʇS", "block.cosmiccore.steam_ember_receptor": "ɹoʇdǝɔǝᴚ ɹǝqɯƎ ɯɐǝʇS", @@ -540,6 +551,7 @@ "cosmiccore.conjuct_valkruth.1": "buıɯןǝɥʍɹǝʌO - ןɐΛ9§", "cosmiccore.conjuct_valkruth.2": "uoıʇɐpunoℲ - ɥʇnᴚ9§", "cosmiccore.conjuct_valkruth_emotion.1": "ǝɔuǝbɹǝʌuoƆ - Ɐ˙ᴚ˙Ǝq§", + "cosmiccore.datastick.link_copied": "%s :ʞuıꞀ", "cosmiccore.ember.capacity": "%s 9§:ʎʇıɔɐdɐƆ ɹǝqɯƎɔ§", "cosmiccore.ember.transfer": "%s 9§:ǝʇɐᴚ ɹǝɟsuɐɹ⟘ ɹǝqɯƎɔ§", "cosmiccore.errors.bad_fuel": "ʇıun ɹǝd ןɐʇoʇ ∩Ǝ 0ᄅㄥ> ǝq ʇsnW ʇndʇnO ןǝnℲ \n ¡ʎʇıןɐnὉ ןǝnℲ ʇuǝıɔıɟɟnsuIɐ§", @@ -563,6 +575,23 @@ "cosmiccore.jade.stellar_module.stage": "%s :ǝbɐʇS", "cosmiccore.khoruth.1": "ǝɔɐdS - ɹoɥʞ9§", "cosmiccore.khoruth.2": "uoıʇɐpunoℲ - ɥʇnᴚ9§", + "cosmiccore.link.already_linked": "pǝʞuıן ʎpɐǝɹןɐ ǝɹɐ sǝuıɥɔɐɯ ǝsǝɥ⟘", + "cosmiccore.link.cannot_self_link": "ɟןǝsʇı oʇ ǝuıɥɔɐɯ ɐ ʞuıן ʇouuɐƆ", + "cosmiccore.link.copied": "%s ɯoɹɟ pǝıdoɔ ɐʇɐp ʞuıꞀ", + "cosmiccore.link.different_owner": "sɯɐǝʇ ʇuǝɹǝɟɟıp ʎq pǝuʍo sǝuıɥɔɐɯ ʞuıן ʇouuɐƆ", + "cosmiccore.link.established": "%s ↔ %s :pǝɥsıןqɐʇsǝ ʞuıꞀ", + "cosmiccore.link.incompatible_partner": "ǝdʎʇ sıɥʇ oʇ ʞuıן ʇouuɐɔ ǝuıɥɔɐɯ ɹǝuʇɹɐԀ", + "cosmiccore.link.incompatible_roles": "%s oʇ ʞuıן ʇouuɐɔ %s :sǝןoɹ ʞuıן ǝןqıʇɐdɯoɔuI", + "cosmiccore.link.incompatible_self": "ǝdʎʇ ʇɐɥʇ oʇ ʞuıן ʇouuɐɔ ǝuıɥɔɐɯ sıɥ⟘", + "cosmiccore.link.invalid_data": "ʞɔıʇsɐʇɐp uo ɐʇɐp ʞuıן pıןɐʌuI", + "cosmiccore.link.limit_reached_partner": "ʇıɯıן ʞuıן sʇı pǝɥɔɐǝɹ sɐɥ ǝuıɥɔɐɯ ɹǝuʇɹɐԀ", + "cosmiccore.link.limit_reached_self": "ʇıɯıן ʞuıן sʇı pǝɥɔɐǝɹ sɐɥ ǝuıɥɔɐɯ sıɥ⟘", + "cosmiccore.link.not_linkable": "buıʞuıן ʇɹoddns ʇou sǝop ǝuıɥɔɐɯ ʇǝbɹɐ⟘", + "cosmiccore.link.not_ready": "buıʞuıן ɹoɟ ʎpɐǝɹ ʇou ǝuıɥɔɐW", + "cosmiccore.link.partner_missing": "sʇsıxǝ ɹǝbuoן ou ǝuıɥɔɐɯ ɹǝuʇɹɐԀ", + "cosmiccore.link.partner_not_loaded": "ʞuıן ɥsıןqɐʇsǝ oʇ pǝpɐoן ǝq ʇsnɯ ǝuıɥɔɐɯ ɹǝuʇɹɐԀ", + "cosmiccore.link.partner_offline": "ǝuıןɟɟo ɹǝuʇɹɐd pǝʞuıꞀ", + "cosmiccore.link.too_far": "buıʞuıן ɹoɟ pɐoן-ǝɔɹoɟ oʇ ʎɐʍɐ ɹɐɟ ooʇ sı ɹǝuʇɹɐԀ", "cosmiccore.lore.broken_virtue.0": "ʎןʇɟoS sɹǝppnɥS ʎʇınʇǝdɹǝԀ", "cosmiccore.lore.broken_virtue.1": "˙buoɹʍ ʎɹǝʌ ǝuob sɐɥ buıɥʇǝɯoS", "cosmiccore.lore.shard_huge.0": "˙ʎʇıuɹǝʇǝ ʇsɐd ɯoɹɟ ɹǝʇsnןɔ ǝʌıssɐɯ ʎןןɐɯɹouqɐ uⱯƐ§", @@ -690,6 +719,12 @@ "cosmiccore.omnia_circuit.uxv": "˙ʇınɔɹıƆ ΛX∩ ʎuɐ sɐ sʞɹoM9§", "cosmiccore.omnia_circuit.zpm": "˙ʇınɔɹıƆ WԀZ ʎuɐ sɐ sʞɹoM9§", "cosmiccore.recipe.asteroid_weight_greater_1": "spıoɹǝʇsⱯ ɹǝbɹɐꞀ ɯoɹɟ\nspןǝıʎ ɹǝʇɐǝɹ⅁", + "cosmiccore.recipe.condition.linked_partner.formed": "ǝɹnʇɔnɹʇs pıןɐʌ ɥʇıʍ )s(ɹǝuʇɹɐd pǝʞuıן %s sǝɹınbǝᴚ", + "cosmiccore.recipe.condition.linked_partner.tooltip": ")s(ɹǝuʇɹɐd pǝʞuıן %s sǝɹınbǝᴚ", + "cosmiccore.recipe.condition.linked_partner.working": "buıʞɹoʍ ʎןǝʌıʇɔɐ )s(ɹǝuʇɹɐd pǝʞuıן %s sǝɹınbǝᴚ", + "cosmiccore.recipe.condition.linked_partner_dimension.tooltip": "%s uı ɹǝuʇɹɐd pǝʞuıן sǝɹınbǝᴚ", + "cosmiccore.recipe.condition.linked_partner_dimension_fluid.tooltip": "%s uı ɹǝuʇɹɐd uı %s ᗺɯ%s sǝɹınbǝᴚ", + "cosmiccore.recipe.condition.linked_partner_dimension_item.tooltip": "%s uı ɹǝuʇɹɐd uı %s x%s sǝɹınbǝᴚ", "cosmiccore.recipe.condition.titan.tooltip": "%s :ɹǝı⟘ ɹoʇɔɐǝᴚ uɐʇı⟘ sǝɹınbǝᴚ", "cosmiccore.recipe.ember_in": "%s :ʇnduI ɹǝqɯƎ", "cosmiccore.recipe.ember_out": "%s :ʇndʇnO ɹǝqɯƎ", @@ -700,6 +735,7 @@ "cosmiccore.recipe.soul_out": "%s :ʇndʇnO ןnoS", "cosmiccore.recipe.sterile_in": "%s %s :ɹǝzıןıɹǝʇS", "cosmiccore.recipe.sterile_out": "¿ᴚOᴚᴚƎ", + "cosmiccore.recipe.waiting_for_partner": "ɹǝuʇɹɐd pǝʞuıן ɹoɟ buıʇıɐM", "cosmiccore.roaster.desc": "pǝpnןɔuı ʇou sʍoןןɐɯɥsɹɐW", "cosmiccore.rune_emotion_weak.1": "˙pǝʌɹǝsqo sı uoıʇɔɐǝɹ ⱯᴚƎ ǝʇǝןdɯoɔuı uⱯo§ㄥ§", "cosmiccore.rune_emotion_weak.2": "˙ǝʇɐɹqıʌ oʇ ǝʇɐןs ǝɥʇ ǝsnɐɔ suoıʇɔɐǝɹ ןɐɔıɯǝɥɔ puɐ ןɐuoıʇoɯǝ buoɹʇSo§ㄥ§", diff --git a/src/generated/resources/assets/cosmiccore/lang/en_us.json b/src/generated/resources/assets/cosmiccore/lang/en_us.json index b3bb2689a..2921dbc96 100644 --- a/src/generated/resources/assets/cosmiccore/lang/en_us.json +++ b/src/generated/resources/assets/cosmiccore/lang/en_us.json @@ -68,6 +68,8 @@ "block.cosmiccore.dawnforge_eclipsed": "Dawnforge [Eclipsed]", "block.cosmiccore.dimensional_energy_capacitor": "Power Substation", "block.cosmiccore.dimensional_energy_interface": "Power Substation Dimensional Interface", + "block.cosmiccore.diving_bell": "Diving Bell", + "block.cosmiccore.diving_bell_escape_pad": "Diving Bell Escape Pad", "block.cosmiccore.dreamers_basin": "Dreamer's Basin", "block.cosmiccore.drone_maintenance_interface": "Drone Maintenance Interface", "block.cosmiccore.drone_station": "Drone Station", @@ -156,6 +158,7 @@ "block.cosmiccore.large_spooling_machine": "Large Spooling Machine", "block.cosmiccore.larva": "Logistic Asteroid Reclamation and Valuing Assembly [LARVA]", "block.cosmiccore.light_dawnstone_casing": "Light Dawnstone Casing", + "block.cosmiccore.link_test_station": "Link Test Station", "block.cosmiccore.living_igniclad_coil_block": "Living Igniclad Coil Block", "block.cosmiccore.livingrock_tiles": "Livingrock Tiles", "block.cosmiccore.lp_steam_bender": "I DO NOT EXIST", @@ -204,6 +207,13 @@ "block.cosmiccore.mantle_bore": "Mantle Bore", "block.cosmiccore.max_ember_emitter": "MAX Ember Emitter", "block.cosmiccore.max_ember_receptor": "MAX Ember Receptor", + "block.cosmiccore.moth_cargo_drop_off": "Moth Cargo Drop Off", + "block.cosmiccore.moth_cargo_station": "Moth Cargo Station", + "block.cosmiccore.moth_home_t1": "Moth Home (T1)", + "block.cosmiccore.moth_home_t2": "Moth Home (T2)", + "block.cosmiccore.moth_home_t3": "Moth Home (T3)", + "block.cosmiccore.moth_home_t4": "Moth Home (T4)", + "block.cosmiccore.moth_station_casing": "Moth Station Casing", "block.cosmiccore.multi_purpose_interstellar_grade_casing": "Multi Purpose Interstellar Grade Casing", "block.cosmiccore.mv_calx_reactor": "Mv Calx Reactor", "block.cosmiccore.mv_electric_apiary": "MV Industrial Production Apiary", @@ -272,6 +282,7 @@ "block.cosmiccore.soul_stained_steel_aluminium_plated_casing": "Soul Stained Steel Aluminium Plated Casing", "block.cosmiccore.spirit_crucible": "Spirit Crucible", "block.cosmiccore.star_ladder": "Star Ladder", + "block.cosmiccore.star_ladder_research_hub": "Star Ladder Research Hub", "block.cosmiccore.steam_caster": "Steam Caster", "block.cosmiccore.steam_ember_emitter": "Steam Ember Emitter", "block.cosmiccore.steam_ember_receptor": "Steam Ember Receptor", @@ -540,6 +551,7 @@ "cosmiccore.conjuct_valkruth.1": "§6Val - Overwhelming", "cosmiccore.conjuct_valkruth.2": "§6Ruth - Foundation", "cosmiccore.conjuct_valkruth_emotion.1": "§bE.R.A - Convergence", + "cosmiccore.datastick.link_copied": "Link: %s", "cosmiccore.ember.capacity": "§cEmber Capacity:§6 %s", "cosmiccore.ember.transfer": "§cEmber Transfer Rate:§6 %s", "cosmiccore.errors.bad_fuel": "§aInsufficient Fuel Quality! \n Fuel Output Must be >720 EU total per unit", @@ -563,6 +575,23 @@ "cosmiccore.jade.stellar_module.stage": "Stage: %s", "cosmiccore.khoruth.1": "§6Khor - Space", "cosmiccore.khoruth.2": "§6Ruth - Foundation", + "cosmiccore.link.already_linked": "These machines are already linked", + "cosmiccore.link.cannot_self_link": "Cannot link a machine to itself", + "cosmiccore.link.copied": "Link data copied from %s", + "cosmiccore.link.different_owner": "Cannot link machines owned by different teams", + "cosmiccore.link.established": "Link established: %s ↔ %s", + "cosmiccore.link.incompatible_partner": "Partner machine cannot link to this type", + "cosmiccore.link.incompatible_roles": "Incompatible link roles: %s cannot link to %s", + "cosmiccore.link.incompatible_self": "This machine cannot link to that type", + "cosmiccore.link.invalid_data": "Invalid link data on datastick", + "cosmiccore.link.limit_reached_partner": "Partner machine has reached its link limit", + "cosmiccore.link.limit_reached_self": "This machine has reached its link limit", + "cosmiccore.link.not_linkable": "Target machine does not support linking", + "cosmiccore.link.not_ready": "Machine not ready for linking", + "cosmiccore.link.partner_missing": "Partner machine no longer exists", + "cosmiccore.link.partner_not_loaded": "Partner machine must be loaded to establish link", + "cosmiccore.link.partner_offline": "Linked partner offline", + "cosmiccore.link.too_far": "Partner is too far away to force-load for linking", "cosmiccore.lore.broken_virtue.0": "Perpetuity Shudders Softly", "cosmiccore.lore.broken_virtue.1": "Something has gone very wrong.", "cosmiccore.lore.shard_huge.0": "§3An abnormally massive cluster from past eternity.", @@ -690,6 +719,12 @@ "cosmiccore.omnia_circuit.uxv": "§6Works as any UXV Circuit.", "cosmiccore.omnia_circuit.zpm": "§6Works as any ZPM Circuit.", "cosmiccore.recipe.asteroid_weight_greater_1": "Greater Yields\nfrom Larger Asteroids", + "cosmiccore.recipe.condition.linked_partner.formed": "Requires %s linked partner(s) with valid structure", + "cosmiccore.recipe.condition.linked_partner.tooltip": "Requires %s linked partner(s)", + "cosmiccore.recipe.condition.linked_partner.working": "Requires %s linked partner(s) actively working", + "cosmiccore.recipe.condition.linked_partner_dimension.tooltip": "Requires linked partner in %s", + "cosmiccore.recipe.condition.linked_partner_dimension_fluid.tooltip": "Requires %smB %s in partner in %s", + "cosmiccore.recipe.condition.linked_partner_dimension_item.tooltip": "Requires %sx %s in partner in %s", "cosmiccore.recipe.condition.titan.tooltip": "Requires Titan Reactor Tier: %s", "cosmiccore.recipe.ember_in": "Ember Input: %s", "cosmiccore.recipe.ember_out": "Ember Output: %s", @@ -700,6 +735,7 @@ "cosmiccore.recipe.soul_out": "Soul Output: %s", "cosmiccore.recipe.sterile_in": "Sterilizer: %s %s", "cosmiccore.recipe.sterile_out": "ERROR?", + "cosmiccore.recipe.waiting_for_partner": "Waiting for linked partner", "cosmiccore.roaster.desc": "Marshmallows not included", "cosmiccore.rune_emotion_weak.1": "§7§oAn incomplete ERA reaction is observed.", "cosmiccore.rune_emotion_weak.2": "§7§oStrong emotional and chemical reactions cause the slate to vibrate.", @@ -1452,6 +1488,27 @@ "material.cosmiccore.virtue_meld": "Virtue Meld", "material.cosmiccore.vitrius": "Vitrius", "material.cosmiccore.voidspark": "Voidspark", + "reflection.cosmiccore.bargain.ascension.answer.ready.drawback.0": "-30% movement speed when not flying", + "reflection.cosmiccore.bargain.ascension.answer.ready.drawback.1": "Vulnerable in no-fly zones or enclosed spaces", + "reflection.cosmiccore.bargain.ascension.answer.ready.power.0": "Creative-style flight (toggle with jump while airborne)", + "reflection.cosmiccore.bargain.ascension.answer.ready.power.1": "Fly indefinitely without hunger or stamina cost", + "reflection.cosmiccore.bargain.ascension.answer.ready.power.2": "Full 3D movement control while flying", + "reflection.cosmiccore.bargain.ascension.answer.ready.power.3": "No fall damage while flight is active", + "reflection.cosmiccore.bargain.ascension.answer.ready.response": "Then rise. The ground has no claim on you anymore.", + "reflection.cosmiccore.bargain.ascension.answer.ready.text": "I am ready to fly.", + "reflection.cosmiccore.bargain.ascension.answer.refuse.response": "Served. Like a servant. How long until you realize you were the servant all along?", + "reflection.cosmiccore.bargain.ascension.answer.refuse.text": "The ground has served me well.", + "reflection.cosmiccore.bargain.ascension.description": "Rise above the crawling earth", + "reflection.cosmiccore.bargain.ascension.dialogue.0": "You are bound to the ground. Chained by gravity's petty tyranny.", + "reflection.cosmiccore.bargain.ascension.dialogue.1": "You dream of flight. All mortals do.", + "reflection.cosmiccore.bargain.ascension.dialogue.2": "I can sever those chains. Let you rise.", + "reflection.cosmiccore.bargain.ascension.dialogue.3": "Not gliding. Not falling with style. True flight.", + "reflection.cosmiccore.bargain.ascension.dialogue.4": "The sky will open to you like a door.", + "reflection.cosmiccore.bargain.ascension.dialogue.5": "But the ground... the ground will become alien. Uncomfortable. Wrong.", + "reflection.cosmiccore.bargain.ascension.name": "Ascension", + "reflection.cosmiccore.bargain.ascension.on_accept": "Weight leaves you. The sky opens. You are no longer earth-bound.", + "reflection.cosmiccore.bargain.ascension.on_defy": "Gravity reclaims you. The ground welcomes you back, possessively.", + "reflection.cosmiccore.bargain.ascension.question": "Will you abandon the earth for the sky?", "reflection.cosmiccore.bargain.back.answer.accept.drawback.0": "5 erosion cost per teleport use", "reflection.cosmiccore.bargain.back.answer.accept.drawback.1": "Marker fades after 10 minutes", "reflection.cosmiccore.bargain.back.answer.accept.power.0": "Teleport to death location (once per death)", diff --git a/src/generated/resources/assets/cosmiccore/models/block/diving_bell_escape_pad.json b/src/generated/resources/assets/cosmiccore/models/block/diving_bell_escape_pad.json new file mode 100644 index 000000000..3254ba99b --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/diving_bell_escape_pad.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "cosmiccore:block/diving_bell_escape_pad" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/machine/diving_bell.json b/src/generated/resources/assets/cosmiccore/models/block/machine/diving_bell.json new file mode 100644 index 000000000..7116174fa --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/machine/diving_bell.json @@ -0,0 +1,90 @@ +{ + "parent": "minecraft:block/block", + "loader": "gtceu:machine", + "machine": "cosmiccore:diving_bell", + "texture_overrides": { + "all": "cosmiccore:block/casings/solid/highly_flexible_reinforced_trinavine_casing" + }, + "variants": { + "is_formed=false,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/highly_flexible_reinforced_trinavine_casing", + "overlay_front": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front", + "overlay_front_emissive": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/highly_flexible_reinforced_trinavine_casing", + "overlay_front": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_paused", + "overlay_front_emissive": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_paused_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/highly_flexible_reinforced_trinavine_casing", + "overlay_front": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_active_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/highly_flexible_reinforced_trinavine_casing", + "overlay_front": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/highly_flexible_reinforced_trinavine_casing", + "overlay_front": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front", + "overlay_front_emissive": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/highly_flexible_reinforced_trinavine_casing", + "overlay_front": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_paused", + "overlay_front_emissive": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_paused_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/highly_flexible_reinforced_trinavine_casing", + "overlay_front": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/highly_flexible_reinforced_trinavine_casing", + "overlay_front": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/generator/large_gas_turbine/overlay_front_active_emissive" + } + } + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/machine/link_test_station.json b/src/generated/resources/assets/cosmiccore/models/block/machine/link_test_station.json new file mode 100644 index 000000000..04cf89dc5 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/machine/link_test_station.json @@ -0,0 +1,90 @@ +{ + "parent": "minecraft:block/block", + "loader": "gtceu:machine", + "machine": "cosmiccore:link_test_station", + "texture_overrides": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel" + }, + "variants": { + "is_formed=false,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/machine/moth_cargo_drop_off.json b/src/generated/resources/assets/cosmiccore/models/block/machine/moth_cargo_drop_off.json new file mode 100644 index 000000000..96b65ae3d --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/machine/moth_cargo_drop_off.json @@ -0,0 +1,90 @@ +{ + "parent": "minecraft:block/block", + "loader": "gtceu:machine", + "machine": "cosmiccore:moth_cargo_drop_off", + "texture_overrides": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel" + }, + "variants": { + "is_formed=false,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/machine/moth_cargo_station.json b/src/generated/resources/assets/cosmiccore/models/block/machine/moth_cargo_station.json new file mode 100644 index 000000000..be71977d4 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/machine/moth_cargo_station.json @@ -0,0 +1,90 @@ +{ + "parent": "minecraft:block/block", + "loader": "gtceu:machine", + "machine": "cosmiccore:moth_cargo_station", + "texture_overrides": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel" + }, + "variants": { + "is_formed=false,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_paused_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_solid_steel", + "overlay_front": "gtceu:block/multiblock/implosion_compressor/overlay_front_active", + "overlay_front_emissive": "gtceu:block/multiblock/implosion_compressor/overlay_front_active_emissive" + } + } + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/machine/star_ladder_research_hub.json b/src/generated/resources/assets/cosmiccore/models/block/machine/star_ladder_research_hub.json new file mode 100644 index 000000000..096a7258d --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/machine/star_ladder_research_hub.json @@ -0,0 +1,90 @@ +{ + "parent": "minecraft:block/block", + "loader": "gtceu:machine", + "machine": "cosmiccore:star_ladder_research_hub", + "texture_overrides": { + "all": "cosmiccore:block/casings/solid/superheavy_steel_casing" + }, + "variants": { + "is_formed=false,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/superheavy_steel_casing", + "overlay_top": "cosmiccore:block/multiblock/mantle_bore/overlay_top", + "overlay_top_emissive": "cosmiccore:block/multiblock/mantle_bore/overlay_top_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/superheavy_steel_casing", + "overlay_top": "cosmiccore:block/multiblock/mantle_bore/overlay_top_paused", + "overlay_top_emissive": "cosmiccore:block/multiblock/mantle_bore/overlay_top_paused_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/superheavy_steel_casing", + "overlay_top": "cosmiccore:block/multiblock/mantle_bore/overlay_top_active", + "overlay_top_emissive": "cosmiccore:block/multiblock/mantle_bore/overlay_top_active_emissive" + } + } + }, + "is_formed=false,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/superheavy_steel_casing", + "overlay_top": "cosmiccore:block/multiblock/mantle_bore/overlay_top_active", + "overlay_top_emissive": "cosmiccore:block/multiblock/mantle_bore/overlay_top_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/superheavy_steel_casing", + "overlay_top": "cosmiccore:block/multiblock/mantle_bore/overlay_top", + "overlay_top_emissive": "cosmiccore:block/multiblock/mantle_bore/overlay_top_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/superheavy_steel_casing", + "overlay_top": "cosmiccore:block/multiblock/mantle_bore/overlay_top_paused", + "overlay_top_emissive": "cosmiccore:block/multiblock/mantle_bore/overlay_top_paused_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/superheavy_steel_casing", + "overlay_top": "cosmiccore:block/multiblock/mantle_bore/overlay_top_active", + "overlay_top_emissive": "cosmiccore:block/multiblock/mantle_bore/overlay_top_active_emissive" + } + } + }, + "is_formed=true,recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/template/cube_all/sided", + "textures": { + "all": "cosmiccore:block/casings/solid/superheavy_steel_casing", + "overlay_top": "cosmiccore:block/multiblock/mantle_bore/overlay_top_active", + "overlay_top_emissive": "cosmiccore:block/multiblock/mantle_bore/overlay_top_active_emissive" + } + } + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/moth_home_t1.json b/src/generated/resources/assets/cosmiccore/models/block/moth_home_t1.json new file mode 100644 index 000000000..01a95e183 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/moth_home_t1.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "cosmiccore:block/casings/moth/moth_home_t1" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/moth_home_t2.json b/src/generated/resources/assets/cosmiccore/models/block/moth_home_t2.json new file mode 100644 index 000000000..125c247eb --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/moth_home_t2.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "cosmiccore:block/casings/moth/moth_home_t2" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/moth_home_t3.json b/src/generated/resources/assets/cosmiccore/models/block/moth_home_t3.json new file mode 100644 index 000000000..b340afe8a --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/moth_home_t3.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "cosmiccore:block/casings/moth/moth_home_t3" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/moth_home_t4.json b/src/generated/resources/assets/cosmiccore/models/block/moth_home_t4.json new file mode 100644 index 000000000..7e0f270d2 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/moth_home_t4.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "cosmiccore:block/casings/moth/moth_home_t4" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/block/moth_station_casing.json b/src/generated/resources/assets/cosmiccore/models/block/moth_station_casing.json new file mode 100644 index 000000000..a18105fb7 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/block/moth_station_casing.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "cosmiccore:block/casings/solid/moth_station_casing" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/diving_bell.json b/src/generated/resources/assets/cosmiccore/models/item/diving_bell.json new file mode 100644 index 000000000..cf11e9bdb --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/diving_bell.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/machine/diving_bell" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/diving_bell_escape_pad.json b/src/generated/resources/assets/cosmiccore/models/item/diving_bell_escape_pad.json new file mode 100644 index 000000000..a01178684 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/diving_bell_escape_pad.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/diving_bell_escape_pad" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/link_test_station.json b/src/generated/resources/assets/cosmiccore/models/item/link_test_station.json new file mode 100644 index 000000000..b192604e1 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/link_test_station.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/machine/link_test_station" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/moth_cargo_drop_off.json b/src/generated/resources/assets/cosmiccore/models/item/moth_cargo_drop_off.json new file mode 100644 index 000000000..7af042c03 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/moth_cargo_drop_off.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/machine/moth_cargo_drop_off" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/moth_cargo_station.json b/src/generated/resources/assets/cosmiccore/models/item/moth_cargo_station.json new file mode 100644 index 000000000..a894ef3fb --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/moth_cargo_station.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/machine/moth_cargo_station" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/moth_home_t1.json b/src/generated/resources/assets/cosmiccore/models/item/moth_home_t1.json new file mode 100644 index 000000000..fb4583310 --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/moth_home_t1.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/moth_home_t1" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/moth_home_t2.json b/src/generated/resources/assets/cosmiccore/models/item/moth_home_t2.json new file mode 100644 index 000000000..780bbbfcd --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/moth_home_t2.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/moth_home_t2" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/moth_home_t3.json b/src/generated/resources/assets/cosmiccore/models/item/moth_home_t3.json new file mode 100644 index 000000000..77b5ded2f --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/moth_home_t3.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/moth_home_t3" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/moth_home_t4.json b/src/generated/resources/assets/cosmiccore/models/item/moth_home_t4.json new file mode 100644 index 000000000..e8403c04a --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/moth_home_t4.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/moth_home_t4" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/moth_station_casing.json b/src/generated/resources/assets/cosmiccore/models/item/moth_station_casing.json new file mode 100644 index 000000000..d4e6e313c --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/moth_station_casing.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/moth_station_casing" +} \ No newline at end of file diff --git a/src/generated/resources/assets/cosmiccore/models/item/star_ladder_research_hub.json b/src/generated/resources/assets/cosmiccore/models/item/star_ladder_research_hub.json new file mode 100644 index 000000000..4ecafd7ed --- /dev/null +++ b/src/generated/resources/assets/cosmiccore/models/item/star_ladder_research_hub.json @@ -0,0 +1,3 @@ +{ + "parent": "cosmiccore:block/machine/star_ladder_research_hub" +} \ No newline at end of file diff --git a/src/generated/resources/data/cosmiccore/loot_tables/blocks/diving_bell_escape_pad.json b/src/generated/resources/data/cosmiccore/loot_tables/blocks/diving_bell_escape_pad.json new file mode 100644 index 000000000..a5a2fb8e1 --- /dev/null +++ b/src/generated/resources/data/cosmiccore/loot_tables/blocks/diving_bell_escape_pad.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "cosmiccore:diving_bell_escape_pad" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "cosmiccore:blocks/diving_bell_escape_pad" +} \ No newline at end of file diff --git a/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t1.json b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t1.json new file mode 100644 index 000000000..6208b348d --- /dev/null +++ b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t1.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "cosmiccore:moth_home_t1" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "cosmiccore:blocks/moth_home_t1" +} \ No newline at end of file diff --git a/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t2.json b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t2.json new file mode 100644 index 000000000..de326048b --- /dev/null +++ b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t2.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "cosmiccore:moth_home_t2" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "cosmiccore:blocks/moth_home_t2" +} \ No newline at end of file diff --git a/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t3.json b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t3.json new file mode 100644 index 000000000..34c38a51d --- /dev/null +++ b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t3.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "cosmiccore:moth_home_t3" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "cosmiccore:blocks/moth_home_t3" +} \ No newline at end of file diff --git a/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t4.json b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t4.json new file mode 100644 index 000000000..6b0b0827f --- /dev/null +++ b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_home_t4.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "cosmiccore:moth_home_t4" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "cosmiccore:blocks/moth_home_t4" +} \ No newline at end of file diff --git a/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_station_casing.json b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_station_casing.json new file mode 100644 index 000000000..9296a0687 --- /dev/null +++ b/src/generated/resources/data/cosmiccore/loot_tables/blocks/moth_station_casing.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "cosmiccore:moth_station_casing" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "cosmiccore:blocks/moth_station_casing" +} \ No newline at end of file diff --git a/src/generated/resources/data/gtceu/tags/blocks/mineable/pickaxe_or_wrench.json b/src/generated/resources/data/gtceu/tags/blocks/mineable/pickaxe_or_wrench.json index 7fd7b4e44..ae665ec46 100644 --- a/src/generated/resources/data/gtceu/tags/blocks/mineable/pickaxe_or_wrench.json +++ b/src/generated/resources/data/gtceu/tags/blocks/mineable/pickaxe_or_wrench.json @@ -61,6 +61,7 @@ "cosmiccore:ludicrious_intake", "cosmiccore:ultimate_intake", "cosmiccore:radioactive_filter_casing", - "cosmiccore:zblan_glass" + "cosmiccore:zblan_glass", + "cosmiccore:moth_station_casing" ] } \ No newline at end of file diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/data/DebugBlockPattern.java b/src/main/java/com/ghostipedia/cosmiccore/api/data/DebugBlockPattern.java index 2e3ad66a4..1fb5999c5 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/api/data/DebugBlockPattern.java +++ b/src/main/java/com/ghostipedia/cosmiccore/api/data/DebugBlockPattern.java @@ -8,8 +8,12 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.registries.ForgeRegistries; + import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -19,9 +23,11 @@ public class DebugBlockPattern { public String[][] pattern; public int[][] aisleRepetitions; public Map> symbolMap; + public Map charToBlockMap; public DebugBlockPattern() { symbolMap = new HashMap<>(); + charToBlockMap = new LinkedHashMap<>(); structureDir = new RelativeDirection[] { RelativeDirection.LEFT, RelativeDirection.UP, RelativeDirection.FRONT }; @@ -39,6 +45,7 @@ public DebugBlockPattern( Map map = new HashMap<>(); map.put(Blocks.AIR.defaultBlockState(), ' '); + charToBlockMap.put(' ', ForgeRegistries.BLOCKS.getKey(Blocks.AIR)); char c = 'A'; // auto @@ -52,6 +59,8 @@ public DebugBlockPattern( map.put(state, c); String name = String.valueOf(c); symbolMap.computeIfAbsent(c, key -> new HashSet<>()).add(name); // any + ResourceLocation blockKey = ForgeRegistries.BLOCKS.getKey(state.getBlock()); + charToBlockMap.put(c, blockKey); c++; } builder.append(map.get(state)); @@ -207,6 +216,7 @@ public DebugBlockPattern copy() { } symbolMap.forEach((k, v) -> newPattern.symbolMap.put(k, new HashSet<>(v))); + newPattern.charToBlockMap.putAll(this.charToBlockMap); return newPattern; } diff --git a/src/main/java/com/ghostipedia/cosmiccore/api/pattern/CosmicPredicates.java b/src/main/java/com/ghostipedia/cosmiccore/api/pattern/CosmicPredicates.java index fb240b471..804e53caf 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/api/pattern/CosmicPredicates.java +++ b/src/main/java/com/ghostipedia/cosmiccore/api/pattern/CosmicPredicates.java @@ -4,6 +4,7 @@ import com.ghostipedia.cosmiccore.api.block.IMagnetType; import com.ghostipedia.cosmiccore.api.machine.feature.IStellarModuleReceiver; import com.ghostipedia.cosmiccore.common.block.MagnetBlock; +import com.ghostipedia.cosmiccore.common.data.CosmicBlocks; import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.MothCargoStation; import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; @@ -29,10 +30,25 @@ import java.util.Set; import java.util.function.Supplier; -import static com.gregtechceu.gtceu.common.data.GTBlocks.CASING_STEEL_SOLID; +import static com.gregtechceu.gtceu.common.data.GTBlocks.*; public class CosmicPredicates { + private static Block[] researchHubRingTiers; + + public static Block[] getResearchHubRingTiers() { + if (researchHubRingTiers == null) { + researchHubRingTiers = new Block[] { + CASING_STEEL_SOLID.get(), // T1 + CASING_ALUMINIUM_FROSTPROOF.get(), // T2 + CASING_STAINLESS_CLEAN.get(), // T3 + CASING_TITANIUM_STABLE.get(), // T4 + CASING_TUNGSTENSTEEL_ROBUST.get() // T5 + }; + } + return researchHubRingTiers; + } + public static TraceabilityPredicate magnetCoils() { return new TraceabilityPredicate(blockWorldState -> { var blockState = blockWorldState.getBlockState(); @@ -76,10 +92,6 @@ public static TraceabilityPredicate starLadderModules() { .addTooltips(Component.translatable("gtceu.multiblock.pattern.error.filters")); } - /** - * Predicate for Moth Cargo Station moth homes (Forestry beehives or steel casing). - * Looks up blocks lazily at match time so Forestry blocks are properly resolved. - */ public static TraceabilityPredicate mothHomes() { return new TraceabilityPredicate(blockWorldState -> { var blockState = blockWorldState.getBlockState(); @@ -101,53 +113,302 @@ public static TraceabilityPredicate mothHomes() { }).addTooltips(Component.literal("Forestry Beehive or Steel Casing")); } - /** - * Get a block by ResourceLocation, or vanilla beehive as fallback. - */ private static net.minecraft.world.level.block.state.BlockState getBlockOrFallback(ResourceLocation loc) { Block block = BuiltInRegistries.BLOCK.get(loc); return (block != Blocks.AIR ? block : Blocks.BEEHIVE).defaultBlockState(); } - /** - * Predicate for Stellar Iris module slots. - * Accepts air (empty slot) or a Stellar Module controller (formed or not). - * When a module is found, adds it to the "stellarModules" set in match context. - * The module doesn't need to be formed - the Iris will establish the connection, - * allowing the module to then form using the Iris as its provider. - */ public static TraceabilityPredicate stellarModuleSlot() { return new TraceabilityPredicate(blockWorldState -> { var blockState = blockWorldState.getBlockState(); + if (blockState.isAir()) return true; - // Accept air (empty slot) - if (blockState.isAir()) { - return true; - } - - // Check if this is a stellar module controller var blockEntity = blockWorldState.getTileEntity(); if (blockEntity instanceof IMachineBlockEntity machineBlockEntity) { MetaMachine machine = machineBlockEntity.getMetaMachine(); - - // Must be a multiblock controller that implements IStellarModuleReceiver - if (machine instanceof IMultiController && - machine instanceof IStellarModuleReceiver moduleReceiver) { - - // Add to the set of connected modules (formed or not) - // The Iris will establish the connection during onStructureFormed + if (machine instanceof IMultiController && machine instanceof IStellarModuleReceiver moduleReceiver) { Set modules = blockWorldState.getMatchContext() .getOrCreate("stellarModules", HashSet::new); modules.add(moduleReceiver); return true; } } - - // Invalid block - not air and not a valid module return false; }, () -> new BlockInfo[] { BlockInfo.fromBlockState(Blocks.AIR.defaultBlockState()) }) .addTooltips(Component.translatable("cosmiccore.multiblock.pattern.stellar_module_slot")); } + public static TraceabilityPredicate researchHubRing(int ringIndex) { + return new TraceabilityPredicate(blockWorldState -> { + var blockState = blockWorldState.getBlockState(); + PatternMatchContext ctx = blockWorldState.getMatchContext(); + + if (blockState.isAir()) { + ctx.getOrCreate("ResearchHubRingsWithAir", HashSet::new).add(ringIndex); + return true; + } + + Block requiredBlock = getResearchHubRingTiers()[ringIndex - 1]; + if (blockState.is(requiredBlock)) { + ctx.getOrCreate("ResearchHubRingsWithBlocks", HashSet::new).add(ringIndex); + return true; + } + return false; + }, () -> { + Block requiredBlock = getResearchHubRingTiers()[ringIndex - 1]; + return new BlockInfo[] { + BlockInfo.fromBlockState(requiredBlock.defaultBlockState()), + BlockInfo.fromBlockState(Blocks.AIR.defaultBlockState()) + }; + }).addTooltips(Component.translatable("cosmiccore.multiblock.pattern.research_hub_ring")); + } + + public static int validateResearchHubRings(PatternMatchContext ctx) { + Set ringsWithBlocks = ctx.get("ResearchHubRingsWithBlocks"); + Set ringsWithAir = ctx.get("ResearchHubRingsWithAir"); + + if (ringsWithBlocks == null || ringsWithBlocks.isEmpty()) { + return 0; + } + + int highestCompleteRing = 0; + for (int ring = 1; ring <= 4; ring++) { + boolean hasBlocks = ringsWithBlocks.contains(ring); + boolean hasAir = ringsWithAir != null && ringsWithAir.contains(ring); + + if (hasBlocks && !hasAir) { + if (ring == highestCompleteRing + 1) { + highestCompleteRing = ring; + } else { + return -1; + } + } else if (hasBlocks && hasAir) { + for (int higherRing = ring + 1; higherRing <= 4; higherRing++) { + boolean higherHasBlocks = ringsWithBlocks.contains(higherRing); + boolean higherHasAir = ringsWithAir != null && ringsWithAir.contains(higherRing); + if (higherHasBlocks && !higherHasAir) { + return -1; + } + } + break; + } else { + for (int higherRing = ring + 1; higherRing <= 4; higherRing++) { + if (ringsWithBlocks.contains(higherRing)) { + return -1; + } + } + break; + } + } + + return highestCompleteRing; + } + + public static int getPartialRingIndex(PatternMatchContext ctx) { + Set ringsWithBlocks = ctx.get("ResearchHubRingsWithBlocks"); + Set ringsWithAir = ctx.get("ResearchHubRingsWithAir"); + + if (ringsWithBlocks == null || ringsWithAir == null) { + return 0; + } + + for (int ring = 1; ring <= 4; ring++) { + boolean hasBlocks = ringsWithBlocks.contains(ring); + boolean hasAir = ringsWithAir.contains(ring); + if (hasBlocks && hasAir) { + return ring; + } + } + return 0; + } + + // ===== Star Ladder Research Hub Tier Predicates ===== + // These predicates accept either air OR the tier-specific block, tracking what was found. + // Tier mapping: + // T0: A, B, C, D blocks (core structure) + // T1: E blocks (bichromal_nevramite) + // T2: F, G blocks (oscillating_gilded_pthanterum, highly_flexible_reinforced_trinavine) + // T3: H, I, J blocks (royal_ichorium, multipurpose_interstellar, ultra_powered) + + /** + * Predicate for T0 blocks (A = superheavy_steel). + * Accepts air or the required block, tracking presence in context. + */ + public static TraceabilityPredicate slrhTier0BlockA() { + Block requiredBlock = CosmicBlocks.SUPERHEAVY_STEEL_CASING.get(); + return slrhTierBlock(requiredBlock, 0); + } + + /** + * Predicate for T0 blocks (B = bolted_heavy_frame). + */ + public static TraceabilityPredicate slrhTier0BlockB() { + Block requiredBlock = CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.get(); + return slrhTierBlock(requiredBlock, 0); + } + + /** + * Predicate for T0 blocks (C = somarust). + */ + public static TraceabilityPredicate slrhTier0BlockC() { + Block requiredBlock = CosmicBlocks.SOMARUST_CASING.get(); + return slrhTierBlock(requiredBlock, 0); + } + + /** + * Predicate for T0 blocks (D = soul_muted). + */ + public static TraceabilityPredicate slrhTier0BlockD() { + Block requiredBlock = CosmicBlocks.SOUL_MUTED_CASING.get(); + return slrhTierBlock(requiredBlock, 0); + } + + /** + * Predicate for T1+ blocks (E = bichromal_nevramite). + * Accepts air or the required block, tracking presence in context. + */ + public static TraceabilityPredicate slrhTier1Block() { + Block requiredBlock = CosmicBlocks.BICHROMAL_NEVRAMITE_CASING.get(); + return slrhTierBlock(requiredBlock, 1); + } + + /** + * Predicate for T2+ blocks (F = oscillating_gilded_pthanterum). + */ + public static TraceabilityPredicate slrhTier2BlockF() { + Block requiredBlock = CosmicBlocks.OSCILLATING_GILDED_PTHANTERUM_CASING.get(); + return slrhTierBlock(requiredBlock, 2); + } + + /** + * Predicate for T2+ blocks (G = highly_flexible_reinforced_trinavine). + */ + public static TraceabilityPredicate slrhTier2BlockG() { + Block requiredBlock = CosmicBlocks.HIGHLY_FLEXIBLE_REINFORCED_TRINAVINE_CASING.get(); + return slrhTierBlock(requiredBlock, 2); + } + + /** + * Predicate for T3+ blocks (H = royal_ichorium). + */ + public static TraceabilityPredicate slrhTier3BlockH() { + Block requiredBlock = CosmicBlocks.ROYAL_ICHORIUM_CASING.get(); + return slrhTierBlock(requiredBlock, 3); + } + + /** + * Predicate for T3+ blocks (I = multipurpose_interstellar). + */ + public static TraceabilityPredicate slrhTier3BlockI() { + Block requiredBlock = CosmicBlocks.MULTIPURPOSE_INTERSTELLAR_GRADE_CASING.get(); + return slrhTierBlock(requiredBlock, 3); + } + + /** + * Predicate for T3+ blocks (J = ultra_powered). + */ + public static TraceabilityPredicate slrhTier3BlockJ() { + Block requiredBlock = CosmicBlocks.ULTRA_POWERED_CASING.get(); + return slrhTierBlock(requiredBlock, 3); + } + + private static TraceabilityPredicate slrhTierBlock(Block requiredBlock, int tier) { + return new TraceabilityPredicate(blockWorldState -> { + var blockState = blockWorldState.getBlockState(); + PatternMatchContext ctx = blockWorldState.getMatchContext(); + + if (blockState.isAir()) { + ctx.getOrCreate("SLRHTiersWithAir", HashSet::new).add(tier); + return true; + } + + if (blockState.is(requiredBlock)) { + ctx.getOrCreate("SLRHTiersWithBlocks", HashSet::new).add(tier); + return true; + } + return false; + }, () -> new BlockInfo[] { + BlockInfo.fromBlockState(requiredBlock.defaultBlockState()), + BlockInfo.fromBlockState(Blocks.AIR.defaultBlockState()) + }).addTooltips(Component.translatable("cosmiccore.multiblock.pattern.slrh_tier_block")); + } + + /** + * Validates the SLRH tier configuration and returns the highest complete tier (-1 to 3). + * A tier is "complete" if all positions have blocks (no air). + * Partial tiers are allowed - they just don't count toward the tier level. + * Returns -1 if not even T0 is complete (controller-only state). + * Always allows structure to form - tier just reflects build progress. + */ + public static int validateSLRHTier(PatternMatchContext ctx) { + Set tiersWithBlocks = ctx.get("SLRHTiersWithBlocks"); + Set tiersWithAir = ctx.get("SLRHTiersWithAir"); + + // No blocks at all = pre-T0 (controller only) + if (tiersWithBlocks == null || tiersWithBlocks.isEmpty()) { + return -1; + } + + // Check if T0 (core structure) is complete + boolean t0HasBlocks = tiersWithBlocks.contains(0); + boolean t0HasAir = tiersWithAir != null && tiersWithAir.contains(0); + + if (!t0HasBlocks || t0HasAir) { + // T0 not complete - still in pre-T0 or building T0 + return -1; + } + + // T0 is complete, now check higher tiers + int highestCompleteTier = 0; + for (int tier = 1; tier <= 3; tier++) { + boolean hasBlocks = tiersWithBlocks.contains(tier); + boolean hasAir = tiersWithAir != null && tiersWithAir.contains(tier); + + if (hasBlocks && !hasAir) { + // This tier is complete - but only count it if previous tier was complete + if (tier == highestCompleteTier + 1) { + highestCompleteTier = tier; + } + // If there's a gap, we just stop counting (don't invalidate) + } + // Partial or empty tiers just mean we stop here + } + + return highestCompleteTier; + } + + /** + * Gets the partial tier index (tier currently being built) for SLRH. + * Returns -1 if building T0 (some T0 blocks but not complete). + * Returns 0 if T0 complete but no higher tier being built. + * Returns 1-3 for partial higher tiers. + */ + public static int getSLRHPartialTierIndex(PatternMatchContext ctx) { + Set tiersWithBlocks = ctx.get("SLRHTiersWithBlocks"); + Set tiersWithAir = ctx.get("SLRHTiersWithAir"); + + if (tiersWithAir == null) { + return 0; + } + + // Check T0 first + boolean t0HasBlocks = tiersWithBlocks != null && tiersWithBlocks.contains(0); + boolean t0HasAir = tiersWithAir.contains(0); + if (t0HasAir) { + // Building T0 + return t0HasBlocks ? 0 : -1; + } + + // T0 complete, check higher tiers + for (int tier = 1; tier <= 3; tier++) { + boolean hasBlocks = tiersWithBlocks != null && tiersWithBlocks.contains(tier); + boolean hasAir = tiersWithAir.contains(tier); + if (hasBlocks && hasAir) { + return tier; + } + } + return 0; + } + public static void init() {} } diff --git a/src/main/java/com/ghostipedia/cosmiccore/client/ForgeClientEventHandler.java b/src/main/java/com/ghostipedia/cosmiccore/client/ForgeClientEventHandler.java index e59e4dda2..730b1a7a2 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/client/ForgeClientEventHandler.java +++ b/src/main/java/com/ghostipedia/cosmiccore/client/ForgeClientEventHandler.java @@ -2,12 +2,14 @@ import com.ghostipedia.cosmiccore.CosmicCore; import com.ghostipedia.cosmiccore.CosmicUtils; +import com.ghostipedia.cosmiccore.client.renderer.RingUpgradePreviewRenderer; import com.ghostipedia.cosmiccore.client.renderer.StructureBoundingBox; import net.minecraft.client.renderer.FogRenderer; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RenderLevelStageEvent; import net.minecraftforge.client.event.ViewportEvent; +import net.minecraftforge.event.level.LevelEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -22,6 +24,15 @@ public static void onRenderWorldLast(RenderLevelStageEvent event) { var stage = event.getStage(); if (stage == RenderLevelStageEvent.Stage.AFTER_TRIPWIRE_BLOCKS) { StructureBoundingBox.renderStructureSelect(event.getPoseStack(), event.getCamera()); + RingUpgradePreviewRenderer.renderPreviews(event.getPoseStack(), event.getCamera()); + } + } + + @SubscribeEvent + public static void onWorldUnload(LevelEvent.Unload event) { + // Clear all previews when world unloads to prevent stale data + if (event.getLevel().isClientSide()) { + RingUpgradePreviewRenderer.clearAllPreviews(); } } diff --git a/src/main/java/com/ghostipedia/cosmiccore/client/renderer/RingUpgradePreviewRenderer.java b/src/main/java/com/ghostipedia/cosmiccore/client/renderer/RingUpgradePreviewRenderer.java new file mode 100644 index 000000000..f40142de0 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/client/renderer/RingUpgradePreviewRenderer.java @@ -0,0 +1,316 @@ +package com.ghostipedia.cosmiccore.client.renderer; + +import com.ghostipedia.cosmiccore.common.data.CosmicBlocks; +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.StarLadderResearchHubPatterns; + +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ItemBlockRenderTypes; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.BlockRenderDispatcher; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexFormat; + +import java.util.*; + +public class RingUpgradePreviewRenderer { + + private static final Map ACTIVE_PREVIEWS = new HashMap<>(); + private static final RandomSource RANDOM = RandomSource.create(); + + // Cached delta maps: position relative to controller -> block to place + private static Map DELTA_NOTHING_TO_T0; + private static Map DELTA_T0_TO_T1; + private static Map DELTA_T1_TO_T2; + private static Map DELTA_T2_TO_T3; + private static Map DELTA_T3_TO_T4; + + // Block mapping for unified character scheme + private static Map CHAR_TO_BLOCK; + + private record PreviewData(BlockPos controllerPos, Map deltaBlocks) {} + + static { + initializeBlockMapping(); + computeDeltas(); + } + + private static void initializeBlockMapping() { + CHAR_TO_BLOCK = new HashMap<>(); + CHAR_TO_BLOCK.put('A', CosmicBlocks.SUPERHEAVY_STEEL_CASING.get()); + CHAR_TO_BLOCK.put('B', CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.get()); + CHAR_TO_BLOCK.put('C', CosmicBlocks.SOMARUST_CASING.get()); + CHAR_TO_BLOCK.put('D', CosmicBlocks.SOUL_MUTED_CASING.get()); + CHAR_TO_BLOCK.put('E', CosmicBlocks.BICHROMAL_NEVRAMITE_CASING.get()); + CHAR_TO_BLOCK.put('F', CosmicBlocks.OSCILLATING_GILDED_PTHANTERUM_CASING.get()); + CHAR_TO_BLOCK.put('G', CosmicBlocks.HIGHLY_FLEXIBLE_REINFORCED_TRINAVINE_CASING.get()); + CHAR_TO_BLOCK.put('H', CosmicBlocks.ROYAL_ICHORIUM_CASING.get()); + CHAR_TO_BLOCK.put('I', CosmicBlocks.MULTIPURPOSE_INTERSTELLAR_GRADE_CASING.get()); + CHAR_TO_BLOCK.put('J', CosmicBlocks.ULTRA_POWERED_CASING.get()); + } + + private static void computeDeltas() { + // Parse each tier's pattern + Map t0Blocks = parsePatternRelativeToController(StarLadderResearchHubPatterns.TIER_0, "T0"); + Map t1Blocks = parsePatternRelativeToController(StarLadderResearchHubPatterns.TIER_1, "T1"); + Map t2Blocks = parsePatternRelativeToController(StarLadderResearchHubPatterns.TIER_2, "T2"); + Map t3Blocks = parsePatternRelativeToController(StarLadderResearchHubPatterns.TIER_3, "T3"); + + // Compute deltas between consecutive tiers + // Delta from nothing (controller-only) to T0 is just all T0 blocks + DELTA_NOTHING_TO_T0 = computeDelta(new HashMap<>(), t0Blocks); + DELTA_T0_TO_T1 = computeDelta(t0Blocks, t1Blocks); + DELTA_T1_TO_T2 = computeDelta(t1Blocks, t2Blocks); + DELTA_T2_TO_T3 = computeDelta(t2Blocks, t3Blocks); + // T4 not yet available + DELTA_T3_TO_T4 = new HashMap<>(); + } + + private static Map parsePatternRelativeToController(String[] patternData, String tierName) { + if (patternData == null || patternData.length == 0) return new HashMap<>(); + + List> aisles = new ArrayList<>(); + for (String aisleStr : patternData) { + if (aisleStr.contains("|")) { + aisles.add(Arrays.asList(aisleStr.split("\\|", -1))); + } + } + if (aisles.isEmpty()) return new HashMap<>(); + + // Find controller position (marked with '#') + BlockPos controllerPos = null; + outer: + for (int z = 0; z < aisles.size(); z++) { + List aisle = aisles.get(z); + for (int y = 0; y < aisle.size(); y++) { + String layer = aisle.get(y); + for (int x = 0; x < layer.length(); x++) { + if (layer.charAt(x) == '#') { + controllerPos = new BlockPos(x, y, z); + break outer; + } + } + } + } + if (controllerPos == null) return new HashMap<>(); + + // Extract all blocks relative to controller + Map blocks = new HashMap<>(); + int cx = controllerPos.getX(), cy = controllerPos.getY(), cz = controllerPos.getZ(); + + for (int z = 0; z < aisles.size(); z++) { + List aisle = aisles.get(z); + for (int y = 0; y < aisle.size(); y++) { + String layer = aisle.get(y); + for (int x = 0; x < layer.length(); x++) { + char ch = layer.charAt(x); + if (ch != ' ' && ch != '@' && ch != '#') { + blocks.put(new BlockPos(x - cx, y - cy, z - cz), ch); + } + } + } + } + return blocks; + } + + private static Map computeDelta(Map prevTier, Map nextTier) { + Map delta = new HashMap<>(); + + for (Map.Entry entry : nextTier.entrySet()) { + BlockPos pos = entry.getKey(); + char ch = entry.getValue(); + + // Block is new if it wasn't in previous tier or was a different type + if (!prevTier.containsKey(pos) || !prevTier.get(pos).equals(ch)) { + Block block = CHAR_TO_BLOCK.get(ch); + if (block != null) { + delta.put(pos, block); + } + } + } + + return delta; + } + + public static void enablePreview(BlockPos controllerPos, Direction facing, int currentTier) { + if (currentTier >= 3) return; // T3 is max currently (T4 not available) + + Map delta = getDeltaForTier(currentTier); + if (delta == null || delta.isEmpty()) return; + + // Rotate delta positions based on controller facing + Map rotatedDelta = new HashMap<>(); + for (Map.Entry entry : delta.entrySet()) { + BlockPos relPos = entry.getKey(); + BlockPos rotated = rotateOffset(relPos.getX(), relPos.getY(), relPos.getZ(), facing); + BlockPos worldPos = controllerPos.offset(rotated); + rotatedDelta.put(worldPos, entry.getValue()); + } + + ACTIVE_PREVIEWS.put(controllerPos, new PreviewData(controllerPos, rotatedDelta)); + } + + public static void disablePreview(BlockPos controllerPos) { + ACTIVE_PREVIEWS.remove(controllerPos); + } + + public static void updatePreview(BlockPos controllerPos, Direction facing, int newTier) { + if (ACTIVE_PREVIEWS.containsKey(controllerPos)) { + disablePreview(controllerPos); + enablePreview(controllerPos, facing, newTier); + } + } + + public static void clearAllPreviews() { + ACTIVE_PREVIEWS.clear(); + } + + private static Map getDeltaForTier(int currentTier) { + return switch (currentTier) { + case -1 -> DELTA_NOTHING_TO_T0; + case 0 -> DELTA_T0_TO_T1; + case 1 -> DELTA_T1_TO_T2; + case 2 -> DELTA_T2_TO_T3; + case 3 -> DELTA_T3_TO_T4; + default -> null; + }; + } + + private static BlockPos rotateOffset(int x, int y, int z, Direction facing) { + // Maps pattern coords to world coords based on GTCEu's setActualRelativeOffset + return switch (facing) { + case NORTH -> new BlockPos(-x, y, -z); + case SOUTH -> new BlockPos(x, y, z); + case EAST -> new BlockPos(z, y, -x); + case WEST -> new BlockPos(-z, y, x); + default -> new BlockPos(x, y, z); + }; + } + + public static void renderPreviews(PoseStack poseStack, Camera camera) { + if (ACTIVE_PREVIEWS.isEmpty()) return; + + var mc = Minecraft.getInstance(); + if (mc.level == null) return; + + Vec3 camPos = camera.getPosition(); + BlockRenderDispatcher dispatcher = mc.getBlockRenderer(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + + poseStack.pushPose(); + poseStack.translate(-camPos.x, -camPos.y, -camPos.z); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + + for (PreviewData preview : ACTIVE_PREVIEWS.values()) { + if (!mc.level.isLoaded(preview.controllerPos)) continue; + + for (Map.Entry entry : preview.deltaBlocks.entrySet()) { + BlockPos pos = entry.getKey(); + Block block = entry.getValue(); + + // Only render ghost blocks where there's air + if (!mc.level.isEmptyBlock(pos)) continue; + + BlockState state = block.defaultBlockState(); + renderGhostBlock(poseStack, dispatcher, buffer, tesselator, pos, state); + } + } + + RenderSystem.disableBlend(); + poseStack.popPose(); + } + + private static void renderGhostBlock(PoseStack poseStack, BlockRenderDispatcher dispatcher, + BufferBuilder buffer, Tesselator tesselator, + BlockPos pos, BlockState state) { + poseStack.pushPose(); + poseStack.translate(pos.getX(), pos.getY(), pos.getZ()); + poseStack.translate(0.5, 0.5, 0.5); + poseStack.scale(0.8f, 0.8f, 0.8f); + poseStack.translate(-0.5, -0.5, -0.5); + + for (RenderType renderType : RenderType.chunkBufferLayers()) { + if (!ItemBlockRenderTypes.getRenderLayers(state).contains(renderType)) continue; + renderType.setupRenderState(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); + dispatcher.renderBatched(state, pos, Minecraft.getInstance().level, poseStack, buffer, false, RANDOM); + tesselator.end(); + renderType.clearRenderState(); + } + poseStack.popPose(); + } + + public static int getDeltaBlockCount(int currentTier) { + Map delta = getDeltaForTier(currentTier); + return delta != null ? delta.size() : 0; + } + + public static Map getDeltaBlockCounts(int currentTier) { + Map delta = getDeltaForTier(currentTier); + if (delta == null) return new HashMap<>(); + + Map counts = new HashMap<>(); + for (Block block : delta.values()) { + counts.merge(block, 1, Integer::sum); + } + return counts; + } + + public static Block getRingBlock(int tier) { + Map delta = getDeltaForTier(tier - 1); + if (delta == null || delta.isEmpty()) return null; + + Map counts = new HashMap<>(); + for (Block block : delta.values()) { + counts.merge(block, 1, Integer::sum); + } + return counts.entrySet().stream() + .max(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .orElse(null); + } + + public static int getRingBlockCount(int tier) { + Map delta = getDeltaForTier(tier - 1); + return delta != null ? delta.size() : 0; + } + + public static Set calculateRingPositions(BlockPos controllerPos, Direction facing, int targetTier) { + Map delta = getDeltaForTier(targetTier - 1); + if (delta == null || delta.isEmpty()) return new HashSet<>(); + + Set positions = new HashSet<>(); + for (BlockPos relPos : delta.keySet()) { + BlockPos rotated = rotateOffset(relPos.getX(), relPos.getY(), relPos.getZ(), facing); + positions.add(controllerPos.offset(rotated)); + } + return positions; + } + + public static Map calculateRingPositionsWithBlocks(BlockPos controllerPos, Direction facing, int targetTier) { + Map delta = getDeltaForTier(targetTier - 1); + if (delta == null || delta.isEmpty()) return new HashMap<>(); + + Map positions = new HashMap<>(); + for (Map.Entry entry : delta.entrySet()) { + BlockPos relPos = entry.getKey(); + BlockPos rotated = rotateOffset(relPos.getX(), relPos.getY(), relPos.getZ(), facing); + positions.put(controllerPos.offset(rotated), entry.getValue()); + } + return positions; + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java b/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java index 0c520b2b2..cd2c14d5f 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicBlocks.java @@ -694,6 +694,7 @@ public class CosmicBlocks { public static final BlockEntry DIVING_BELL_ESCAPE_PAD = REGISTRATE .block("diving_bell_escape_pad", DivingBellEscapePad::new) .initialProperties(() -> Blocks.STONE) + .exBlockstate(GTModels.cubeAllModel(CosmicCore.id("block/diving_bell_escape_pad"))) .simpleItem() .register(); @@ -713,6 +714,7 @@ private static BlockEntry createMothHomeBlock(int tier) { .lang("Moth Home (T" + tier + ")") .initialProperties(() -> Blocks.IRON_BLOCK) .properties(p -> p.strength(3.0f, 6.0f)) + .exBlockstate(GTModels.cubeAllModel(CosmicCore.id("block/casings/moth/moth_home_t" + tier))) .simpleItem() .register(); } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicMachines.java b/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicMachines.java index 7a7da3c30..6cf807d16 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicMachines.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/data/CosmicMachines.java @@ -992,7 +992,6 @@ public static void init() { .build(); }); } - GCYMMachines.LARGE_CENTRIFUGE.setPatternFactory(() -> FactoryBlockPattern.start() .aisle("#XXX#", "XXXXX", "#XXX#") .aisle("XXXXX", "XAPAX", "XXXXX") diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/item/behavior/StructureWriteBehavior.java b/src/main/java/com/ghostipedia/cosmiccore/common/item/behavior/StructureWriteBehavior.java index 9c793dec7..eb52cd5ad 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/item/behavior/StructureWriteBehavior.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/item/behavior/StructureWriteBehavior.java @@ -126,6 +126,16 @@ private void exportLog(HeldItemUIFactory.HeldItemHolder playerInventoryHolder) { builder.append(".aisle(\"%s\")\n".formatted(Joiner.on("\", \"").join(strings))); } + // Add legend mapping characters to block resource locations + builder.append("\n// Block Legend:\n"); + blockPattern.charToBlockMap.forEach((character, resourceLocation) -> { + if (character == ' ') { + builder.append("// ' ' (space) - %s\n".formatted(resourceLocation)); + } else { + builder.append("// %c - %s\n".formatted(character, resourceLocation)); + } + }); + GTCEu.LOGGER.info("\n" + builder.toString()); } } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/LinkedMultiblockHelper.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/LinkedMultiblockHelper.java index 3f37c4bbe..b242279f3 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/LinkedMultiblockHelper.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/LinkedMultiblockHelper.java @@ -25,65 +25,18 @@ import java.util.*; -/** - * Utilities for cross-dimensional multiblock access. - * Handles chunk loading with proper lifecycle management. - */ public class LinkedMultiblockHelper { - /** Maximum forced chunks per requesting machine */ public static final int MAX_FORCED_CHUNKS_PER_MACHINE = 4; - - /** - * Tracks active force-load tickets. - * Maps: Requester GlobalPos -> (Target GlobalPos -> Owner BlockPos used for ticket) - *

- * IMPORTANT: We use the requester's position as the ticket owner to ensure - * each requester has independent tickets. This prevents one requester from - * accidentally releasing another's ticket. - *

- * NOTE: Tickets are tracked in memory only. A hard crash while tickets are active - * can leave chunks loaded until restart. Mitigation: tickets are short-lived - * (released after use), and Forge clears orphaned tickets on dimension unload. - */ private static final Map> activeTickets = new HashMap<>(); - // ==================== Role Negotiation ==================== - - /** - * Result of role negotiation between two machines. - */ public record RolePair(LinkRole aRole, LinkRole bRole) {} - /** - * Negotiate effective roles for a link between two machines. - *

- * Rules: - *

    - *
  • PEER + PEER = PEER/PEER (bidirectional)
  • - *
  • PEER adapts to stricter partner: - *
      - *
    • PEER + CONTROLLER → REMOTE/CONTROLLER
    • - *
    • PEER + REMOTE → CONTROLLER/REMOTE
    • - *
    - *
  • - *
  • CONTROLLER + REMOTE = CONTROLLER/REMOTE (asymmetric)
  • - *
  • CONTROLLER + CONTROLLER = incompatible
  • - *
  • REMOTE + REMOTE = incompatible
  • - *
- * - * @param aDeclared Machine A's declared role preference - * @param bDeclared Machine B's declared role preference - * @return Negotiated roles, or null if incompatible - */ @Nullable public static RolePair negotiateRoles(LinkRole aDeclared, LinkRole bDeclared) { - // PEER + PEER = PEER/PEER if (aDeclared == LinkRole.PEER && bDeclared == LinkRole.PEER) { return new RolePair(LinkRole.PEER, LinkRole.PEER); } - - // PEER adapts to stricter partner (downgrade to preserve partner's intent) if (aDeclared == LinkRole.PEER && bDeclared == LinkRole.CONTROLLER) { return new RolePair(LinkRole.REMOTE, LinkRole.CONTROLLER); } @@ -96,27 +49,15 @@ public static RolePair negotiateRoles(LinkRole aDeclared, LinkRole bDeclared) { if (aDeclared == LinkRole.REMOTE && bDeclared == LinkRole.PEER) { return new RolePair(LinkRole.REMOTE, LinkRole.CONTROLLER); } - - // CONTROLLER + REMOTE = valid asymmetric link if (aDeclared == LinkRole.CONTROLLER && bDeclared == LinkRole.REMOTE) { return new RolePair(LinkRole.CONTROLLER, LinkRole.REMOTE); } if (aDeclared == LinkRole.REMOTE && bDeclared == LinkRole.CONTROLLER) { return new RolePair(LinkRole.REMOTE, LinkRole.CONTROLLER); } - - // CONTROLLER + CONTROLLER = incompatible (conflict) - // REMOTE + REMOTE = incompatible (deadlock) return null; } - // ==================== Machine Access ==================== - - /** - * Safely retrieve a machine from any dimension. - * Returns null if dimension doesn't exist or chunk isn't loaded. - * Does NOT force-load the chunk. - */ @Nullable public static MetaMachine getMachine(MinecraftServer server, GlobalPos pos) { ServerLevel level = server.getLevel(pos.dimension()); @@ -129,48 +70,21 @@ public static MetaMachine getMachine(MinecraftServer server, GlobalPos pos) { return MetaMachine.getMachine(level, pos.pos()); } - /** - * Get machine as ILinkedMultiblock if it implements the interface. - */ @Nullable public static ILinkedMultiblock getLinkedMachine(MinecraftServer server, GlobalPos pos) { MetaMachine machine = getMachine(server, pos); - if (machine instanceof ILinkedMultiblock linked) { - return linked; - } - return null; + return machine instanceof ILinkedMultiblock linked ? linked : null; } - /** - * Check if a linked partner is currently accessible (chunk loaded). - */ public static boolean isPartnerOnline(MinecraftServer server, GlobalPos pos) { return getMachine(server, pos) != null; } - // ==================== Chunk Loading ==================== - - /** - * Force-load a partner's chunk for cross-dimensional access. - * Tracks the ticket with proper owner position for later removal. - *

- * Uses the REQUESTER's position as the ticket owner to ensure each - * requester has independent tickets. - * - * @param server The server - * @param requester The machine requesting the load (for ticket tracking) - * @param target The partner machine's position to load - * @return true if successfully loaded (or already loaded), false if at limit or failed - */ public static boolean forceLoadPartnerChunk(MinecraftServer server, GlobalPos requester, GlobalPos target) { - // Check per-machine limit Map existing = activeTickets.getOrDefault(requester, Collections.emptyMap()); - - // Already loaded by this requester? if (existing.containsKey(target)) { return true; } - if (existing.size() >= MAX_FORCED_CHUNKS_PER_MACHINE) { CosmicCore.LOGGER.warn("Machine at {} has reached force-load limit of {}", requester, MAX_FORCED_CHUNKS_PER_MACHINE); @@ -185,20 +99,15 @@ public static boolean forceLoadPartnerChunk(MinecraftServer server, GlobalPos re } ChunkPos chunkPos = new ChunkPos(target.pos()); - - // Use REQUESTER position as the ticket owner (unique per requester) - // This prevents one requester from releasing another's ticket BlockPos ownerPos = requester.pos(); - boolean success = ForgeChunkManager.forceChunk( level, CosmicCore.MOD_ID, ownerPos, chunkPos.x, chunkPos.z, - true, // add - true // ticking - ); + true, + true); if (success) { activeTickets.computeIfAbsent(requester, k -> new HashMap<>()) @@ -213,30 +122,24 @@ public static boolean forceLoadPartnerChunk(MinecraftServer server, GlobalPos re return success; } - /** - * Release a specific force-loaded chunk. - * Uses the same owner position that was used when adding the ticket. - */ public static void releasePartnerChunk(MinecraftServer server, GlobalPos requester, GlobalPos target) { Map tickets = activeTickets.get(requester); if (tickets == null) return; BlockPos ownerPos = tickets.remove(target); - if (ownerPos == null) return; // Wasn't loaded by this requester + if (ownerPos == null) return; ServerLevel level = server.getLevel(target.dimension()); if (level == null) return; ChunkPos chunkPos = new ChunkPos(target.pos()); - - // Use the SAME owner position that was used for add ForgeChunkManager.forceChunk( level, CosmicCore.MOD_ID, ownerPos, chunkPos.x, chunkPos.z, - false, // remove + false, true); CosmicCore.LOGGER.debug("Released chunk {} in {} for machine at {} (owner: {})", @@ -247,10 +150,6 @@ public static void releasePartnerChunk(MinecraftServer server, GlobalPos request } } - /** - * Release ALL force-loaded chunks for a machine. - * MUST be called in onMachineRemoved() to prevent ticket leaks. - */ public static void releaseAllTickets(MinecraftServer server, GlobalPos requester) { Map tickets = activeTickets.remove(requester); if (tickets == null) return; @@ -279,31 +178,19 @@ public static void releaseAllTickets(MinecraftServer server, GlobalPos requester released, requester); } - /** - * Get number of active force-load tickets for a machine. - */ public static int getActiveTicketCount(GlobalPos requester) { return activeTickets.getOrDefault(requester, Collections.emptyMap()).size(); } - // ==================== Link Validation ==================== - - /** - * Validate that both machines still exist and link is valid. - * Does not force-load chunks - returns false if either is unloaded. - */ public static boolean validateLink(MinecraftServer server, UUID owner, GlobalPos a, GlobalPos b) { - // Check SavedData LinkedMultiblockSavedData savedData = LinkedMultiblockSavedData.getOrCreate(server); if (!savedData.isLinked(owner, a, b)) { return false; } - // Check machines exist (if chunks loaded) MetaMachine machineA = getMachine(server, a); MetaMachine machineB = getMachine(server, b); - // If chunk is loaded but machine is gone, link is invalid ServerLevel levelA = server.getLevel(a.dimension()); if (levelA != null && levelA.isLoaded(a.pos()) && machineA == null) { return false; @@ -317,40 +204,16 @@ public static boolean validateLink(MinecraftServer server, UUID owner, GlobalPos return true; } - // ==================== Permission Helpers ==================== - - /** - * Check if requester can query the target (convenience method). - */ public static boolean canQuery(MinecraftServer server, UUID owner, GlobalPos requester, GlobalPos target) { return LinkedMultiblockSavedData.getOrCreate(server).canQuery(owner, requester, target); } - // ==================== Partner Resource Query ==================== - - /** - * Functional interface for querying a partner machine. - */ @FunctionalInterface public interface PartnerQuery { T query(WorkableElectricMultiblockMachine partner); } - /** - * Query a partner machine with temporary chunk loading. - * Loads the partner's chunk if needed, executes the query, then releases. - *

- * IMPORTANT: This method handles short-lived queries. For sustained access, - * use forceLoadPartnerChunk/releasePartnerChunk directly. - * - * @param server The server - * @param owner Team/player UUID for permission check - * @param requester The requesting machine's position - * @param target The partner machine's position - * @param query The query function to execute - * @return Query result, or null if partner unavailable or permission denied - */ @Nullable public static T queryPartner( MinecraftServer server, @@ -358,7 +221,6 @@ public static T queryPartner( GlobalPos requester, GlobalPos target, PartnerQuery query) { - // Permission check if (!canQuery(server, owner, requester, target)) { CosmicCore.LOGGER.debug("Query denied: {} cannot query {}", requester, target); return null; @@ -385,12 +247,6 @@ public static T queryPartner( } } - /** - * Get a partner's item handler capabilities. - * Returns empty list if partner unavailable or permission denied. - * - * @param io IO.IN for input handlers, IO.OUT for output handlers - */ public static List> getPartnerItemHandlers( MinecraftServer server, UUID owner, @@ -402,12 +258,6 @@ public static List> getPartnerItemHandlers( return result != null ? result : Collections.emptyList(); } - /** - * Get a partner's fluid handler capabilities. - * Returns empty list if partner unavailable or permission denied. - * - * @param io IO.IN for input handlers, IO.OUT for output handlers - */ public static List> getPartnerFluidHandlers( MinecraftServer server, UUID owner, @@ -419,12 +269,6 @@ public static List> getPartnerFluidHandlers( return result != null ? result : Collections.emptyList(); } - /** - * Get a partner's energy container capabilities. - * Returns empty list if partner unavailable or permission denied. - * - * @param io IO.IN for input energy, IO.OUT for output energy - */ public static List> getPartnerEnergyHandlers( MinecraftServer server, UUID owner, @@ -436,9 +280,6 @@ public static List> getPartnerEnergyHandlers( return result != null ? result : Collections.emptyList(); } - /** - * Check if a partner has a specific item in any of its input handlers. - */ public static boolean partnerHasItem( MinecraftServer server, UUID owner, @@ -463,9 +304,6 @@ public static boolean partnerHasItem( return result != null && result; } - /** - * Check if a partner has a specific fluid in any of its input handlers. - */ public static boolean partnerHasFluid( MinecraftServer server, UUID owner, @@ -490,10 +328,6 @@ public static boolean partnerHasFluid( return result != null && result; } - /** - * Get total energy stored across all of a partner's energy containers. - * Returns 0 if partner unavailable or permission denied. - */ public static long getPartnerEnergyStored( MinecraftServer server, UUID owner, @@ -514,9 +348,6 @@ public static long getPartnerEnergyStored( return result != null ? result : 0L; } - /** - * Check if partner's multiblock is formed and working. - */ public static boolean isPartnerFormed( MinecraftServer server, UUID owner, @@ -527,9 +358,6 @@ public static boolean isPartnerFormed( return result != null && result; } - /** - * Check if partner is currently running a recipe. - */ public static boolean isPartnerWorking( MinecraftServer server, UUID owner, @@ -541,4 +369,19 @@ public static boolean isPartnerWorking( }); return result != null && result; } + + private static final Map DIMENSION_NAMES = Map.of( + "minecraft:overworld", "Overworld", + "minecraft:the_nether", "The Nether", + "minecraft:the_end", "The End", + "ad_astra:earth_orbit", "Earth Orbit", + "ad_astra:moon", "Moon", + "ad_astra:mars", "Mars", + "ad_astra:venus", "Venus", + "ad_astra:mercury", "Mercury", + "ad_astra:glacio", "Glacio"); + + public static String getDimensionName(net.minecraft.resources.ResourceLocation dim) { + return DIMENSION_NAMES.getOrDefault(dim.toString(), dim.getPath().replace("_", " ")); + } } diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/DivingBell.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/DivingBell.java index 0fac80a63..00a6988e3 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/DivingBell.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/DivingBell.java @@ -13,7 +13,6 @@ import static com.ghostipedia.cosmiccore.api.registries.CosmicRegistration.REGISTRATE; import static com.ghostipedia.cosmiccore.common.data.CosmicBlocks.*; import static com.gregtechceu.gtceu.api.pattern.Predicates.*; -import static com.gregtechceu.gtceu.common.data.models.GTMachineModels.createWorkableCasingMachineModel; public class DivingBell { @@ -39,10 +38,8 @@ public class DivingBell { .or(abilities(PartAbility.MAINTENANCE).setExactLimit(1))) .build()) // spotless:on - .model( - createWorkableCasingMachineModel( - CosmicCore.id("block/casings/solid/reinforced_naquadria_casing"), - GTCEu.id("block/multiblock/generator/large_gas_turbine"))) + .workableCasingModel(CosmicCore.id("block/casings/solid/highly_flexible_reinforced_trinavine_casing"), + GTCEu.id("block/multiblock/generator/large_gas_turbine")) .register(); public static void init() {} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadder.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadder.java index af661526e..11964b2e3 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadder.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadder.java @@ -3,13 +3,13 @@ import com.ghostipedia.cosmiccore.CosmicCore; import com.ghostipedia.cosmiccore.client.renderer.machine.CosmicDynamicRenderHelpers; import com.ghostipedia.cosmiccore.common.data.CosmicBlocks; +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic.StarLadderMachine; import com.ghostipedia.cosmiccore.gtbridge.CosmicRecipeTypes; import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.data.RotationState; import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; import com.gregtechceu.gtceu.api.machine.multiblock.PartAbility; -import com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine; import com.gregtechceu.gtceu.api.pattern.FactoryBlockPattern; import com.gregtechceu.gtceu.api.pattern.Predicates; import com.gregtechceu.gtceu.api.recipe.OverclockingLogic; @@ -27,7 +27,7 @@ public class StarLadder { public final static MultiblockMachineDefinition STAR_LADDER = REGISTRATE - .multiblock("star_ladder", WorkableElectricMultiblockMachine::new) + .multiblock("star_ladder", StarLadderMachine::new) .rotationState(RotationState.NON_Y_AXIS) .recipeType(CosmicRecipeTypes.STAR_LADDER_RESEARCH) .recipeModifier(GTRecipeModifiers.ELECTRIC_OVERCLOCK.apply(OverclockingLogic.NON_PERFECT_OVERCLOCK_SUBTICK)) diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadderResearchHub.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadderResearchHub.java new file mode 100644 index 000000000..91c34f0b5 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadderResearchHub.java @@ -0,0 +1,1097 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi; + +import com.ghostipedia.cosmiccore.CosmicCore; +import com.ghostipedia.cosmiccore.api.pattern.CosmicPredicates; +import com.ghostipedia.cosmiccore.common.data.CosmicBlocks; +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic.StarLadderResearchHubMachine; +import com.ghostipedia.cosmiccore.gtbridge.CosmicRecipeTypes; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.data.RotationState; +import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; +import com.gregtechceu.gtceu.api.machine.multiblock.PartAbility; +import com.gregtechceu.gtceu.api.pattern.BlockPattern; +import com.gregtechceu.gtceu.api.pattern.FactoryBlockPattern; +import com.gregtechceu.gtceu.api.pattern.MultiblockShapeInfo; +import com.gregtechceu.gtceu.api.pattern.TraceabilityPredicate; +import com.gregtechceu.gtceu.common.data.GTBlocks; +import com.gregtechceu.gtceu.common.data.GTMachines; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.block.Blocks; + +import java.util.ArrayList; +import java.util.List; + +import static com.ghostipedia.cosmiccore.api.registries.CosmicRegistration.REGISTRATE; +import static com.gregtechceu.gtceu.api.pattern.Predicates.*; +import static com.gregtechceu.gtceu.common.data.models.GTMachineModels.createWorkableCasingMachineModel; + +/** + * Star Ladder Research Hub multiblock definition. + * + * LEGEND: + * ' ' (space) = Air/ANY + * '@' = Controller area (hatches/buses allowed) + * '#' = Controller position + * A = cosmiccore:superheavy_steel_casing + * B = cosmiccore:bolted_heavy_frame_casing + * C = cosmiccore:somarust_casing + * D = cosmiccore:soul_muted_casing + * E = cosmiccore:bichromal_nevramite_casing (T1+) + * F = cosmiccore:oscillating_gilded_pthanterum_casings (T2+) + * G = cosmiccore:highly_flexible_reinforced_trinavine_casing (T2+) + * H = cosmiccore:royal_ichorium_casing (T3+) + * I = cosmiccore:multi_purpose_interstellar_grade_casing (T3+) + * J = cosmiccore:ultra_powered_casing (T3+) + */ +public class StarLadderResearchHub { + + public final static MultiblockMachineDefinition STAR_LADDER_RESEARCH_HUB = REGISTRATE + .multiblock("star_ladder_research_hub", StarLadderResearchHubMachine::new) + .langValue("Star Ladder Research Hub") + .tooltips( + Component.literal("Remote research station for the Star Ladder"), + Component.literal("Link to a Star Ladder using a datastick"), + Component.literal("Build larger structures to increase tier (T0-T3)"), + Component.literal("Do NOT use the terminal to automatically build"), + Component.literal("Use the Inbuilt multiblock build system (Screwdriver) otherwise you'll experience wrong block placement"), + Component.literal("TODO: ACTUAL LANG KEYS LMAO") + .withStyle(ChatFormatting.RED)) + .rotationState(RotationState.NON_Y_AXIS) + .allowExtendedFacing(false) + .recipeType(CosmicRecipeTypes.STAR_LADDER_RESEARCH) + .appearanceBlock(CosmicBlocks.SUPERHEAVY_STEEL_CASING) + .pattern(definition -> tier0Pattern() + .where(' ', any()) + .where('@', hatchSlot()) + .where('#', controller(blocks(definition.getBlock()))) + .where('A', blocks(CosmicBlocks.SUPERHEAVY_STEEL_CASING.get())) + .where('B', blocks(CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.get())) + .where('C', blocks(CosmicBlocks.SOMARUST_CASING.get())) + .where('D', blocks(CosmicBlocks.SOUL_MUTED_CASING.get())) + .build()) + // Each tier has its own correctly-sized shapeInfo + // The mixin prevents crashes when clicking blocks in previews that don't match the main pattern - disgusting yes i know + .shapeInfos(definition -> { + List shapeInfos = new ArrayList<>(); + + // T0 preview + shapeInfos.add(tier0ShapeInfo(definition) + .where(' ', Blocks.AIR.defaultBlockState()) + .where('@', GTMachines.ENERGY_INPUT_HATCH[3], Direction.NORTH) + .where('#', definition, Direction.NORTH) + .where('A', CosmicBlocks.SUPERHEAVY_STEEL_CASING.getDefaultState()) + .where('B', CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.getDefaultState()) + .where('C', CosmicBlocks.SOMARUST_CASING.getDefaultState()) + .where('D', CosmicBlocks.SOUL_MUTED_CASING.getDefaultState()) + .build()); + + // T1 preview + shapeInfos.add(tier1ShapeInfo(definition) + .where(' ', Blocks.AIR.defaultBlockState()) + .where('@', GTMachines.ENERGY_INPUT_HATCH[4], Direction.NORTH) + .where('#', definition, Direction.NORTH) + .where('A', CosmicBlocks.SUPERHEAVY_STEEL_CASING.getDefaultState()) + .where('B', CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.getDefaultState()) + .where('C', CosmicBlocks.SOMARUST_CASING.getDefaultState()) + .where('D', CosmicBlocks.SOUL_MUTED_CASING.getDefaultState()) + .where('E', CosmicBlocks.BICHROMAL_NEVRAMITE_CASING.getDefaultState()) + .build()); + + // T2 preview + shapeInfos.add(tier2ShapeInfo(definition) + .where(' ', Blocks.AIR.defaultBlockState()) + .where('@', GTMachines.ENERGY_INPUT_HATCH[5], Direction.NORTH) + .where('#', definition, Direction.NORTH) + .where('A', CosmicBlocks.SUPERHEAVY_STEEL_CASING.getDefaultState()) + .where('B', CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.getDefaultState()) + .where('C', CosmicBlocks.SOMARUST_CASING.getDefaultState()) + .where('D', CosmicBlocks.SOUL_MUTED_CASING.getDefaultState()) + .where('E', CosmicBlocks.BICHROMAL_NEVRAMITE_CASING.getDefaultState()) + .where('F', CosmicBlocks.OSCILLATING_GILDED_PTHANTERUM_CASING.getDefaultState()) + .where('G', CosmicBlocks.HIGHLY_FLEXIBLE_REINFORCED_TRINAVINE_CASING.getDefaultState()) + .build()); + + // T3 preview + shapeInfos.add(tier3ShapeInfo(definition) + .where(' ', Blocks.AIR.defaultBlockState()) + .where('@', GTMachines.ENERGY_INPUT_HATCH[6], Direction.NORTH) + .where('#', definition, Direction.NORTH) + .where('A', CosmicBlocks.SUPERHEAVY_STEEL_CASING.getDefaultState()) + .where('B', CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.getDefaultState()) + .where('C', CosmicBlocks.SOMARUST_CASING.getDefaultState()) + .where('D', CosmicBlocks.SOUL_MUTED_CASING.getDefaultState()) + .where('E', CosmicBlocks.BICHROMAL_NEVRAMITE_CASING.getDefaultState()) + .where('F', CosmicBlocks.OSCILLATING_GILDED_PTHANTERUM_CASING.getDefaultState()) + .where('G', CosmicBlocks.HIGHLY_FLEXIBLE_REINFORCED_TRINAVINE_CASING.getDefaultState()) + .where('H', CosmicBlocks.ROYAL_ICHORIUM_CASING.getDefaultState()) + .where('I', CosmicBlocks.MULTIPURPOSE_INTERSTELLAR_GRADE_CASING.getDefaultState()) + .where('J', CosmicBlocks.ULTRA_POWERED_CASING.getDefaultState()) + .build()); + + return shapeInfos; + }) + .model( + createWorkableCasingMachineModel( + CosmicCore.id("block/casings/solid/superheavy_steel_casing"), + CosmicCore.id("block/multiblock/mantle_bore"))) + .register(); + + public static void init() {} + + private static TraceabilityPredicate controllerSlot() { + return blocks(STAR_LADDER_RESEARCH_HUB.getBlock()); + } + + private static TraceabilityPredicate hatchSlot() { + return blocks(CosmicBlocks.SUPERHEAVY_STEEL_CASING.get()) + .or(abilities(PartAbility.INPUT_ENERGY).setMinGlobalLimited(1).setMaxGlobalLimited(2)) + .or(abilities(PartAbility.MAINTENANCE).setExactLimit(1)) + .or(abilities(PartAbility.IMPORT_ITEMS).setMaxGlobalLimited(4)) + .or(abilities(PartAbility.EXPORT_ITEMS).setMaxGlobalLimited(4)) + .or(abilities(PartAbility.IMPORT_FLUIDS).setMaxGlobalLimited(4)) + .or(abilities(PartAbility.EXPORT_FLUIDS).setMaxGlobalLimited(4)); + } + + public static BlockPattern buildT0Pattern() { + return tier0Pattern() + .where(' ', any()) + .where('@', hatchSlot()) + .where('#', controller(controllerSlot())) + .where('A', blocks(CosmicBlocks.SUPERHEAVY_STEEL_CASING.get())) + .where('B', blocks(CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.get())) + .where('C', blocks(CosmicBlocks.SOMARUST_CASING.get())) + .where('D', blocks(CosmicBlocks.SOUL_MUTED_CASING.get())) + .build(); + } + + public static BlockPattern buildT1Pattern() { + return tier1Pattern() + .where(' ', any()) + .where('@', hatchSlot()) + .where('#', controller(controllerSlot())) + .where('A', blocks(CosmicBlocks.SUPERHEAVY_STEEL_CASING.get())) + .where('B', blocks(CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.get())) + .where('C', blocks(CosmicBlocks.SOMARUST_CASING.get())) + .where('D', blocks(CosmicBlocks.SOUL_MUTED_CASING.get())) + .where('E', blocks(CosmicBlocks.BICHROMAL_NEVRAMITE_CASING.get())) + .build(); + } + + public static BlockPattern buildT2Pattern() { + return tier2Pattern() + .where(' ', any()) + .where('@', hatchSlot()) + .where('#', controller(controllerSlot())) + .where('A', blocks(CosmicBlocks.SUPERHEAVY_STEEL_CASING.get())) + .where('B', blocks(CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.get())) + .where('C', blocks(CosmicBlocks.SOMARUST_CASING.get())) + .where('D', blocks(CosmicBlocks.SOUL_MUTED_CASING.get())) + .where('E', blocks(CosmicBlocks.BICHROMAL_NEVRAMITE_CASING.get())) + .where('F', blocks(CosmicBlocks.OSCILLATING_GILDED_PTHANTERUM_CASING.get())) + .where('G', blocks(CosmicBlocks.HIGHLY_FLEXIBLE_REINFORCED_TRINAVINE_CASING.get())) + .build(); + } + + public static BlockPattern buildT3Pattern() { + return tier3Pattern() + .where(' ', any()) + .where('@', hatchSlot()) + .where('#', controller(controllerSlot())) + .where('A', blocks(CosmicBlocks.SUPERHEAVY_STEEL_CASING.get())) + .where('B', blocks(CosmicBlocks.BOLTED_HEAVY_FRAME_CASING.get())) + .where('C', blocks(CosmicBlocks.SOMARUST_CASING.get())) + .where('D', blocks(CosmicBlocks.SOUL_MUTED_CASING.get())) + .where('E', blocks(CosmicBlocks.BICHROMAL_NEVRAMITE_CASING.get())) + .where('F', blocks(CosmicBlocks.OSCILLATING_GILDED_PTHANTERUM_CASING.get())) + .where('G', blocks(CosmicBlocks.HIGHLY_FLEXIBLE_REINFORCED_TRINAVINE_CASING.get())) + .where('H', blocks(CosmicBlocks.ROYAL_ICHORIUM_CASING.get())) + .where('I', blocks(CosmicBlocks.MULTIPURPOSE_INTERSTELLAR_GRADE_CASING.get())) + .where('J', blocks(CosmicBlocks.ULTRA_POWERED_CASING.get())) + .build(); + } + + // spotless:off + + private static FactoryBlockPattern tier0Pattern() { + return FactoryBlockPattern.start() + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AAA AAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AA AA AAAA AAAA AA AA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AA AAAA ACCCA AAAA AA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AABA ACCCA ABAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AABABA ACCCA ABABAA ", " ", " ", " ", " ", " ") + .aisle(" DD DD ", " D D ", " D BAB D ", " AA ABABAD ACCCA DABABA AA ", " D D ", " D D ", " DD DD ", " ", " ") + .aisle(" DD DD ", " ", " BAB ", " A ABABA ACCCA ABABA A ", " ", " ", " DD DD ", " ", " ") + .aisle(" DD DD ", " AAAAA ", " BAB ", " AA ABABA ACCCA ABABA AA ", " ", " AAAAA ", " DD DD ", " ", " ") + .aisle(" D D ", " D AAAAAAAAA D ", " D BBBBB D ", " AA DABABA BBBBB ABABAD AA ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " A ABABBBBBBBBBBBBBABA A ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " A ABABBBBBBBBBBBABA A ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " AA BBBBBBBBBBBBBBB AA ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB ", " CCCCCCBBBBBBBBBBBBBBBBBCCCCCC ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDD@@@@@DDDDDD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", "AAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAA", " CCCCCCBBBBBBBBBBBBBBBBBCCCCCC ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCCC@@#@@CCCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB ", " CCCCCCBBBBBBBBBBBBBBBBBCCCCCC ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDD@@@@@DDDDDD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " AA BBBBBBBBBBBBBBB AA ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " A ABABBBBBBBBBBBABA A ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " A ABABBBBBBBBBBBBBABA A ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" D D ", " D AAAAAAAAA D ", " D BBBBB D ", " AA DABABA BBBBB ABABAD AA ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" DD DD ", " AAAAA ", " BAB ", " AA ABABA ACCCA ABABA AA ", " ", " AAAAA ", " DD DD ", " ", " ") + .aisle(" DD DD ", " ", " BAB ", " A ABABA ACCCA ABABA A ", " ", " ", " DD DD ", " ", " ") + .aisle(" DD DD ", " D D ", " D BAB D ", " AA ABABAD ACCCA DABABA AA ", " D D ", " D D ", " DD DD ", " ", " ") + .aisle(" ", " ", " BAB ", " AABABA ACCCA ABABAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AABA ACCCA ABAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AA AAAA ACCCA AAAA AA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AA AA AAAA AAAA AA AA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AAA AAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + ; + } + + private static MultiblockShapeInfo.ShapeInfoBuilder tier0ShapeInfo(MultiblockMachineDefinition definition) { + return MultiblockShapeInfo.builder() + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AAA AAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AA AA AAAA AAAA AA AA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AA AAAA ACCCA AAAA AA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AABA ACCCA ABAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AABABA ACCCA ABABAA ", " ", " ", " ", " ", " ") + .aisle(" DD DD ", " D D ", " D BAB D ", " AA ABABAD ACCCA DABABA AA ", " D D ", " D D ", " DD DD ", " ", " ") + .aisle(" DD DD ", " ", " BAB ", " A ABABA ACCCA ABABA A ", " ", " ", " DD DD ", " ", " ") + .aisle(" DD DD ", " AAAAA ", " BAB ", " AA ABABA ACCCA ABABA AA ", " ", " AAAAA ", " DD DD ", " ", " ") + .aisle(" D D ", " D AAAAAAAAA D ", " D BBBBB D ", " AA DABABA BBBBB ABABAD AA ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " A ABABBBBBBBBBBBBBABA A ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " A ABABBBBBBBBBBBABA A ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " AA BBBBBBBBBBBBBBB AA ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB ", " CCCCCCBBBBBBBBBBBBBBBBBCCCCCC ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDD@@@@@DDDDDD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", "AAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAA", " CCCCCCBBBBBBBBBBBBBBBBBCCCCCC ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCCC@@#@@CCCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB ", " CCCCCCBBBBBBBBBBBBBBBBBCCCCCC ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDD@@@@@DDDDDD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " AA BBBBBBBBBBBBBBB AA ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " A ABABBBBBBBBBBBABA A ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " A ABABBBBBBBBBBBBBABA A ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" D D ", " D AAAAAAAAA D ", " D BBBBB D ", " AA DABABA BBBBB ABABAD AA ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" DD DD ", " AAAAA ", " BAB ", " AA ABABA ACCCA ABABA AA ", " ", " AAAAA ", " DD DD ", " ", " ") + .aisle(" DD DD ", " ", " BAB ", " A ABABA ACCCA ABABA A ", " ", " ", " DD DD ", " ", " ") + .aisle(" DD DD ", " D D ", " D BAB D ", " AA ABABAD ACCCA DABABA AA ", " D D ", " D D ", " DD DD ", " ", " ") + .aisle(" ", " ", " BAB ", " AABABA ACCCA ABABAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AABA ACCCA ABAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AA AAAA ACCCA AAAA AA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AA AA AAAA AAAA AA AA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " AAA AAA ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " BAB ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " A ", " ", " ", " ", " ", " ", " ") + ; + } + private static FactoryBlockPattern tier1Pattern() { + return FactoryBlockPattern.start() + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCCBABCCDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCACBABCACDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " A A ", " A A ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " A A ", " A A ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " AAA EEE AAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " D BAB D ", " AA AA AAAAEEEAAAA AA AA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " D BAB D ", " AA AAAA ACCCA AAAA AA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " BAB ", " AABA ACCCA ABAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " BAB ", " AABABA ACCCA ABABAA ", " ", " ", " A A ", " ", " ") + .aisle(" DD DD ", " D D ", " D BAB D ", " AA ABABAD ACCCA DABABA AA ", " D D ", " D D ", " DD A A DD ", " ", " ") + .aisle(" DD DD ", " ", " BAB ", " A ABABA ACCCA ABABA A ", " ", " ", " DD A A DD ", " ", " ") + .aisle(" DD DD ", " AAAAA ", " BAB ", " AA ABABA ACCCA ABABA AA ", " ", " AAAAA ", " DD A A DD ", " ", " ") + .aisle(" D D ", " D AAAAAAAAA D ", " D BBBBB D ", " AA DABABA BBBBB ABABAD AA ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " A ABABBBBBBBBBBBBBABA A ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " A ABABBBBBBBBBBBABA A ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AA BBBBBBBBBBBBBBB AA ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " EEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEE ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", "DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD", " EEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEE ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCCC@@#@@CCCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " EEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEE ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AA BBBBBBBBBBBBBBB AA ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " A ABABBBBBBBBBBBABA A ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " A ABABBBBBBBBBBBBBABA A ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" D D ", " D AAAAAAAAA D ", " D BBBBB D ", " AA DABABA BBBBB ABABAD AA ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" DD DD ", " AAAAA ", " BAB ", " AA ABABA ACCCA ABABA AA ", " ", " AAAAA ", " DD A A DD ", " ", " ") + .aisle(" DD DD ", " ", " BAB ", " A ABABA ACCCA ABABA A ", " ", " ", " DD A A DD ", " ", " ") + .aisle(" DD DD ", " D D ", " D BAB D ", " AA ABABAD ACCCA DABABA AA ", " D D ", " D D ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " BAB ", " AABABA ACCCA ABABAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " BAB ", " AABA ACCCA ABAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " D BAB D ", " AA AAAA ACCCA AAAA AA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " D BAB D ", " AA AA AAAAEEEAAAA AA AA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " AAA EEE AAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " A A ", " A A ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " A A ", " A A ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCACBABCACDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCCBABCCDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + ; + } + + private static MultiblockShapeInfo.ShapeInfoBuilder tier1ShapeInfo(MultiblockMachineDefinition definition) { + return MultiblockShapeInfo.builder() + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCCBABCCDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCACBABCACDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " A A ", " A A ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " A A ", " A A ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " AAA EEE AAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " D BAB D ", " AA AA AAAAEEEAAAA AA AA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " D BAB D ", " AA AAAA ACCCA AAAA AA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " BAB ", " AABA ACCCA ABAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " BAB ", " AABABA ACCCA ABABAA ", " ", " ", " A A ", " ", " ") + .aisle(" DD DD ", " D D ", " D BAB D ", " AA ABABAD ACCCA DABABA AA ", " D D ", " D D ", " DD A A DD ", " ", " ") + .aisle(" DD DD ", " ", " BAB ", " A ABABA ACCCA ABABA A ", " ", " ", " DD A A DD ", " ", " ") + .aisle(" DD DD ", " AAAAA ", " BAB ", " AA ABABA ACCCA ABABA AA ", " ", " AAAAA ", " DD A A DD ", " ", " ") + .aisle(" D D ", " D AAAAAAAAA D ", " D BBBBB D ", " AA DABABA BBBBB ABABAD AA ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " A ABABBBBBBBBBBBBBABA A ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " A ABABBBBBBBBBBBABA A ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AA BBBBBBBBBBBBBBB AA ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " EEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEE ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", "DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD", " EEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEE ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCCC@@#@@CCCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " EEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEE ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " A BBBBBBBBBBBBBBB A ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AA BBBBBBBBBBBBBBB AA ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " A ABABBBBBBBBBBBABA A ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " A ABABBBBBBBBBBBBBABA A ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" D D ", " D AAAAAAAAA D ", " D BBBBB D ", " AA DABABA BBBBB ABABAD AA ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" DD DD ", " AAAAA ", " BAB ", " AA ABABA ACCCA ABABA AA ", " ", " AAAAA ", " DD A A DD ", " ", " ") + .aisle(" DD DD ", " ", " BAB ", " A ABABA ACCCA ABABA A ", " ", " ", " DD A A DD ", " ", " ") + .aisle(" DD DD ", " D D ", " D BAB D ", " AA ABABAD ACCCA DABABA AA ", " D D ", " D D ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " BAB ", " AABABA ACCCA ABABAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " BAB ", " AABA ACCCA ABAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " D BAB D ", " AA AAAA ACCCA AAAA AA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " D BAB D ", " AA AA AAAAEEEAAAA AA AA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " AAA EEE AAA ", " ", " ", " A A ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " A A ", " A A ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " A A ", " A A ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEEEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCAACBABCAACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCACBABCACDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCACBABCACD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCCBABCCDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " D ", " ", " ", " ", " ", " ", " ") + ; + } + + private static FactoryBlockPattern tier2Pattern() { + return FactoryBlockPattern.start() + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDD ", " ", " ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDADD ", " ", " ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DBABD ", " ", " ", " ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DDBABDD ", " ", " ", " ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DDCBABCDD ", " ", " ", " ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFFFFF ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " FFFFFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF FFFFF ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " FFFFF FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FF ", " DDCCBABCCDD ", " ", " ", " ", " DDCCBABCCDD ", " FF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFF FFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFF FFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFFFEFFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFFEFFFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFFF E FFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFF E FFFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFF E FFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFF E FFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFF E FFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFF E FFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF EEE FFFFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFFFF EEE FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GG GG ", " FFFF EEE FFFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFFF EEE FFFF ", " GG GG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGA AGGG ", " FFF EEE FFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFF EEE FFF ", " GGGA AGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGAA AAGGG ", " FF FFFEEEFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFEEEFFF FF ", " GGGAA AAGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGGGA AGGGGA ", " FF FFFFFFEEEFFFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFFFEEEFFFFFF FF ", " AGGGGA AGGGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGAGGA AGGAGA ", " FF FFFFFFFFEEEFFFFFFFF FF ", " DDCACBABCACDD ", " ", " ", " ", " DDCACBABCACDD ", " FF FFFFFFFFEEEFFFFFFFF FF ", " AGAGGA AGGAGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFF EEEEE FFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFF EEEEE FFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFFF EEEEE FFFF FFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFF FFFF EEEEE FFFF FFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFF EEEEE FFF FFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFF FFF EEEEE FFF FFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFFF FF EEEEE FF FFFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFFF FF EEEEE FF FFFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " A A ", " AGGAGGA A A AGGAGGA ", " F F FF EEEEE FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF EEEEE FF F F ", " AGGAGGA A A AGGAGGA ", " A A ", " ", " ", " ") + .aisle(" ", " ", " A A ", " A A ", " AGGAGGA AGGAGGA ", " F F FF EEEEE FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF EEEEE FF F F ", " AGGAGGA AGGAGGA ", " A A ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " F F FF AAA EEE AAA FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF AAA EEE AAA FF F F ", " AGGAGGA AGGAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " F F FF AA AA AAAAEEEAAAA AA AA FF F F ", " D BAB D ", " ", " ", " ", " D BAB D ", " F F FF AA AA AAAAEEEAAAA AA AA FF F F ", " AGGAGGA AGGAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAG GAGGA ", " FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF ", " D BAB D ", " ", " ", " ", " D BAB D ", " FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF ", " AGGAG GAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGA AGGA ", " F F FFF AABA ACCCA ABAA FFF F F ", " BAB ", " ", " ", " ", " BAB ", " F F FFF AABA ACCCA ABAA FFF F F ", " AGGA AGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " A A ", " F F FFF AABABA ACCCA ABABAA FFF F F ", " BAB ", " ", " ", " ", " BAB ", " F F FFF AABABA ACCCA ABABAA FFF F F ", " A A ", " ", " A A ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " D D ", " D D ", " F F FFF AA ABABAD ACCCA DABABA AA FFF F F ", " D BAB D ", " D D ", " DD DD ", " D D ", " D BAB D ", " F F FFF AA ABABAD ACCCA DABABA AA FFF F F ", " D D ", " D D ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " ", " ", " F F FFF A ABABA ACCCA ABABA A FFF F F ", " BAB ", " ", " DD DD ", " ", " BAB ", " F F FFF A ABABA ACCCA ABABA A FFF F F ", " ", " ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " AAAAA ", " ", " F F FFFF AA ABABA ACCCA ABABA AA FFFF F F ", " BAB ", " AAAAA ", " DD DD ", " AAAAA ", " BAB ", " F F FFFF AA ABABA ACCCA ABABA AA FFFF F F ", " ", " AAAAA ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " D D D D ", " D AAAAAAAAA D ", " D BBBBB D ", " F F FFF AA DABABA BBBBB ABABAD AA FFF F F ", " D BBBBB D ", " D AAAAAAAAA D ", " D GGGGG D ", " D AAAAAAAAA D ", " D BBBBB D ", " F F FFF AA DABABA BBBBB ABABAD AA FFF F F ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " GGGGGGGGG ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBABA A FFFF F F ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " F F FFFF A ABABBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " CDCDC ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " ", " DCDCDCD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" D D ", " D D ", " DDCDCDCDD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " ", " CCCC CCCC ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " ", " AAAAAAAAADDDDDD DDDDDDAAAAAAAAA ", " AA AAAAAAAAAAAAAAAAAAA AA ", " A BBBBBBBBBBBBBBBBB A ", " EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " ", " CCCCC CCCCC ", " AAAAAAAAA AAAAAAAAA ", " BBBBBBBB BBBBBBBB ", " EEEEEEEEEEEEF EEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBB BBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEE FEEEEEEEEEEEE ", "DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", "DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD", " EEEEEEEEEEEEF EEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEE FEEEEEEEEEEEE ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCCC@@#@@CCCCC ", " ", " ") + .aisle(" ", " ", " AAAAAAAAADDDDDD DDDDDDAAAAAAAAA ", " AA AAAAAAAAAAAAAAAAAAA AA ", " A BBBBBBBBBBBBBBBBB A ", " EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " ", " CCCC CCCC ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" D D ", " D D ", " DDCDCDCDD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " ", " DCDCDCD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " ", " CDCDC ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBABA A FFFF F F ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " F F FFFF A ABABBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " GGGGGGGGG ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " D D D D ", " D AAAAAAAAA D ", " D BBBBB D ", " F F FFF AA DABABA BBBBB ABABAD AA FFF F F ", " D BBBBB D ", " D AAAAAAAAA D ", " D GGGGG D ", " D AAAAAAAAA D ", " D BBBBB D ", " F F FFF AA DABABA BBBBB ABABAD AA FFF F F ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " AAAAA ", " ", " F F FFFF AA ABABA ACCCA ABABA AA FFFF F F ", " BAB ", " AAAAA ", " DD DD ", " AAAAA ", " BAB ", " F F FFFF AA ABABA ACCCA ABABA AA FFFF F F ", " ", " AAAAA ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " ", " ", " F F FFF A ABABA ACCCA ABABA A FFF F F ", " BAB ", " ", " DD DD ", " ", " BAB ", " F F FFF A ABABA ACCCA ABABA A FFF F F ", " ", " ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " D D ", " D D ", " F F FFF AA ABABAD ACCCA DABABA AA FFF F F ", " D BAB D ", " D D ", " DD DD ", " D D ", " D BAB D ", " F F FFF AA ABABAD ACCCA DABABA AA FFF F F ", " D D ", " D D ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " A A ", " ", " A A ", " F F FFF AABABA ACCCA ABABAA FFF F F ", " BAB ", " ", " ", " ", " BAB ", " F F FFF AABABA ACCCA ABABAA FFF F F ", " A A ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGA AGGA ", " F F FFF AABA ACCCA ABAA FFF F F ", " BAB ", " ", " ", " ", " BAB ", " F F FFF AABA ACCCA ABAA FFF F F ", " AGGA AGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAG GAGGA ", " FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF ", " D BAB D ", " ", " ", " ", " D BAB D ", " FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF ", " AGGAG GAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " F F FF AA AA AAAAEEEAAAA AA AA FF F F ", " D BAB D ", " ", " ", " ", " D BAB D ", " F F FF AA AA AAAAEEEAAAA AA AA FF F F ", " AGGAGGA AGGAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " F F FF AAA EEE AAA FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF AAA EEE AAA FF F F ", " AGGAGGA AGGAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " A A ", " AGGAGGA AGGAGGA ", " F F FF EEEEE FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF EEEEE FF F F ", " AGGAGGA AGGAGGA ", " A A ", " A A ", " ", " ") + .aisle(" ", " ", " ", " A A ", " AGGAGGA A A AGGAGGA ", " F F FF EEEEE FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF EEEEE FF F F ", " AGGAGGA A A AGGAGGA ", " A A ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFFF FF EEEEE FF FFFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFFF FF EEEEE FF FFFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFF EEEEE FFF FFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFF FFF EEEEE FFF FFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFFF EEEEE FFFF FFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFF FFFF EEEEE FFFF FFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFF EEEEE FFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFF EEEEE FFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGAGGA AGGAGA ", " FF FFFFFFFFEEEFFFFFFFF FF ", " DDCACBABCACDD ", " ", " ", " ", " DDCACBABCACDD ", " FF FFFFFFFFEEEFFFFFFFF FF ", " AGAGGA AGGAGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGGGA AGGGGA ", " FF FFFFFFEEEFFFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFFFEEEFFFFFF FF ", " AGGGGA AGGGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGAA AAGGG ", " FF FFFEEEFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFEEEFFF FF ", " GGGAA AAGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGA AGGG ", " FFF EEE FFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFF EEE FFF ", " GGGA AGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GG GG ", " FFFF EEE FFFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFFF EEE FFFF ", " GG GG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF EEE FFFFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFFFF EEE FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFF E FFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFF E FFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFF E FFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFF E FFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFFF E FFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFF E FFFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFFFEFFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFFEFFFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFF FFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFF FFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FF ", " DDCCBABCCDD ", " ", " ", " ", " DDCCBABCCDD ", " FF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF FFFFF ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " FFFFF FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFFFFF ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " FFFFFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DDCBABCDD ", " ", " ", " ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DDBABDD ", " ", " ", " ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DBABD ", " ", " ", " ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDADD ", " ", " ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDD ", " ", " ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + ; + } + + private static MultiblockShapeInfo.ShapeInfoBuilder tier2ShapeInfo(MultiblockMachineDefinition definition) { + return MultiblockShapeInfo.builder() + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDD ", " ", " ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDADD ", " ", " ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DBABD ", " ", " ", " ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DDBABDD ", " ", " ", " ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DDCBABCDD ", " ", " ", " ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFFFFF ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " FFFFFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF FFFFF ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " FFFFF FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FF ", " DDCCBABCCDD ", " ", " ", " ", " DDCCBABCCDD ", " FF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFF FFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFF FFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFFFEFFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFFEFFFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFFF E FFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFF E FFFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFF E FFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFF E FFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFF E FFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFF E FFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF EEE FFFFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFFFF EEE FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GG GG ", " FFFF EEE FFFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFFF EEE FFFF ", " GG GG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGA AGGG ", " FFF EEE FFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFF EEE FFF ", " GGGA AGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGAA AAGGG ", " FF FFFEEEFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFEEEFFF FF ", " GGGAA AAGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGGGA AGGGGA ", " FF FFFFFFEEEFFFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFFFEEEFFFFFF FF ", " AGGGGA AGGGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGAGGA AGGAGA ", " FF FFFFFFFFEEEFFFFFFFF FF ", " DDCACBABCACDD ", " ", " ", " ", " DDCACBABCACDD ", " FF FFFFFFFFEEEFFFFFFFF FF ", " AGAGGA AGGAGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFF EEEEE FFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFF EEEEE FFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFFF EEEEE FFFF FFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFF FFFF EEEEE FFFF FFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFF EEEEE FFF FFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFF FFF EEEEE FFF FFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFFF FF EEEEE FF FFFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFFF FF EEEEE FF FFFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " A A ", " AGGAGGA A A AGGAGGA ", " F F FF EEEEE FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF EEEEE FF F F ", " AGGAGGA A A AGGAGGA ", " A A ", " ", " ", " ") + .aisle(" ", " ", " A A ", " A A ", " AGGAGGA AGGAGGA ", " F F FF EEEEE FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF EEEEE FF F F ", " AGGAGGA AGGAGGA ", " A A ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " F F FF AAA EEE AAA FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF AAA EEE AAA FF F F ", " AGGAGGA AGGAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " F F FF AA AA AAAAEEEAAAA AA AA FF F F ", " D BAB D ", " ", " ", " ", " D BAB D ", " F F FF AA AA AAAAEEEAAAA AA AA FF F F ", " AGGAGGA AGGAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAG GAGGA ", " FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF ", " D BAB D ", " ", " ", " ", " D BAB D ", " FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF ", " AGGAG GAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGA AGGA ", " F F FFF AABA ACCCA ABAA FFF F F ", " BAB ", " ", " ", " ", " BAB ", " F F FFF AABA ACCCA ABAA FFF F F ", " AGGA AGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " A A ", " F F FFF AABABA ACCCA ABABAA FFF F F ", " BAB ", " ", " ", " ", " BAB ", " F F FFF AABABA ACCCA ABABAA FFF F F ", " A A ", " ", " A A ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " D D ", " D D ", " F F FFF AA ABABAD ACCCA DABABA AA FFF F F ", " D BAB D ", " D D ", " DD DD ", " D D ", " D BAB D ", " F F FFF AA ABABAD ACCCA DABABA AA FFF F F ", " D D ", " D D ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " ", " ", " F F FFF A ABABA ACCCA ABABA A FFF F F ", " BAB ", " ", " DD DD ", " ", " BAB ", " F F FFF A ABABA ACCCA ABABA A FFF F F ", " ", " ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " AAAAA ", " ", " F F FFFF AA ABABA ACCCA ABABA AA FFFF F F ", " BAB ", " AAAAA ", " DD DD ", " AAAAA ", " BAB ", " F F FFFF AA ABABA ACCCA ABABA AA FFFF F F ", " ", " AAAAA ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " D D D D ", " D AAAAAAAAA D ", " D BBBBB D ", " F F FFF AA DABABA BBBBB ABABAD AA FFF F F ", " D BBBBB D ", " D AAAAAAAAA D ", " D GGGGG D ", " D AAAAAAAAA D ", " D BBBBB D ", " F F FFF AA DABABA BBBBB ABABAD AA FFF F F ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " GGGGGGGGG ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBABA A FFFF F F ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " F F FFFF A ABABBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " CDCDC ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " ", " DCDCDCD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" D D ", " D D ", " DDCDCDCDD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " ", " CCCC CCCC ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " ", " AAAAAAAAADDDDDD DDDDDDAAAAAAAAA ", " AA AAAAAAAAAAAAAAAAAAA AA ", " A BBBBBBBBBBBBBBBBB A ", " EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " ", " CCCCC CCCCC ", " AAAAAAAAA AAAAAAAAA ", " BBBBBBBB BBBBBBBB ", " EEEEEEEEEEEEF EEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBB BBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEE FEEEEEEEEEEEE ", "DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", "DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD", " EEEEEEEEEEEEF EEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEE FEEEEEEEEEEEE ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCCC@@#@@CCCCC ", " ", " ") + .aisle(" ", " ", " AAAAAAAAADDDDDD DDDDDDAAAAAAAAA ", " AA AAAAAAAAAAAAAAAAAAA AA ", " A BBBBBBBBBBBBBBBBB A ", " EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " ", " CCCC CCCC ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" D D ", " D D ", " DDCDCDCDD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " ", " DCDCDCD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " F F FFFF A BBBBBBBBBBBBBBB A FFFF F F ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " ", " CDCDC ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBABA A FFFF F F ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD ", " F F FFFF A ABABBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " GGGGGGGGG ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " D D D D ", " D AAAAAAAAA D ", " D BBBBB D ", " F F FFF AA DABABA BBBBB ABABAD AA FFF F F ", " D BBBBB D ", " D AAAAAAAAA D ", " D GGGGG D ", " D AAAAAAAAA D ", " D BBBBB D ", " F F FFF AA DABABA BBBBB ABABAD AA FFF F F ", " D BBBBB D ", " D AAAAAAAAA D ", " D D D D ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " AAAAA ", " ", " F F FFFF AA ABABA ACCCA ABABA AA FFFF F F ", " BAB ", " AAAAA ", " DD DD ", " AAAAA ", " BAB ", " F F FFFF AA ABABA ACCCA ABABA AA FFFF F F ", " ", " AAAAA ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " ", " ", " F F FFF A ABABA ACCCA ABABA A FFF F F ", " BAB ", " ", " DD DD ", " ", " BAB ", " F F FFF A ABABA ACCCA ABABA A FFF F F ", " ", " ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " D D ", " D D ", " F F FFF AA ABABAD ACCCA DABABA AA FFF F F ", " D BAB D ", " D D ", " DD DD ", " D D ", " D BAB D ", " F F FFF AA ABABAD ACCCA DABABA AA FFF F F ", " D D ", " D D ", " DD A A DD ", " ", " ") + .aisle(" ", " ", " A A ", " ", " A A ", " F F FFF AABABA ACCCA ABABAA FFF F F ", " BAB ", " ", " ", " ", " BAB ", " F F FFF AABABA ACCCA ABABAA FFF F F ", " A A ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGA AGGA ", " F F FFF AABA ACCCA ABAA FFF F F ", " BAB ", " ", " ", " ", " BAB ", " F F FFF AABA ACCCA ABAA FFF F F ", " AGGA AGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAG GAGGA ", " FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF ", " D BAB D ", " ", " ", " ", " D BAB D ", " FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF ", " AGGAG GAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " F F FF AA AA AAAAEEEAAAA AA AA FF F F ", " D BAB D ", " ", " ", " ", " D BAB D ", " F F FF AA AA AAAAEEEAAAA AA AA FF F F ", " AGGAGGA AGGAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " F F FF AAA EEE AAA FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF AAA EEE AAA FF F F ", " AGGAGGA AGGAGGA ", " ", " A A ", " ", " ") + .aisle(" ", " ", " A A ", " A A ", " AGGAGGA AGGAGGA ", " F F FF EEEEE FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF EEEEE FF F F ", " AGGAGGA AGGAGGA ", " A A ", " A A ", " ", " ") + .aisle(" ", " ", " ", " A A ", " AGGAGGA A A AGGAGGA ", " F F FF EEEEE FF F F ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " F F FF EEEEE FF F F ", " AGGAGGA A A AGGAGGA ", " A A ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFFF FF EEEEE FF FFFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFFF FF EEEEE FF FFFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFF EEEEE FFF FFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFF FFF EEEEE FFF FFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFFF EEEEE FFFF FFF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FFF FFFF EEEEE FFFF FFF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFF EEEEE FFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFF EEEEE FFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " DCAACBABCAACD ", " ", " ", " ", " DCAACBABCAACD ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGAGGA AGGAGA ", " FF FFFFFFFFEEEFFFFFFFF FF ", " DDCACBABCACDD ", " ", " ", " ", " DDCACBABCACDD ", " FF FFFFFFFFEEEFFFFFFFF FF ", " AGAGGA AGGAGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGGGA AGGGGA ", " FF FFFFFFEEEFFFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFFFEEEFFFFFF FF ", " AGGGGA AGGGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGAA AAGGG ", " FF FFFEEEFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFEEEFFF FF ", " GGGAA AAGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGA AGGG ", " FFF EEE FFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFF EEE FFF ", " GGGA AGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GG GG ", " FFFF EEE FFFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFFF EEE FFFF ", " GG GG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF EEE FFFFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFFFF EEE FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFF E FFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFF E FFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFF E FFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFF E FFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFFF E FFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFF E FFFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FFFFFEFFFFF FF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FF FFFFFEFFFFF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFF FFF ", " DCACBABCACD ", " ", " ", " ", " DCACBABCACD ", " FFF FFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FF FF ", " DDCCBABCCDD ", " ", " ", " ", " DDCCBABCCDD ", " FF FF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF FFFFF ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " FFFFF FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFFFFF ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " FFFFFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCCBABCCD ", " ", " ", " ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DDCBABCDD ", " ", " ", " ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " ", " ", " ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DDBABDD ", " ", " ", " ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " ", " ", " ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DBABD ", " ", " ", " ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDADD ", " ", " ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDD ", " ", " ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " ", " ", " D ", " ", " ", " ", " ", " ", " ") + ; + } + + private static FactoryBlockPattern tier3Pattern() { + return FactoryBlockPattern.start() + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDD ", " ", " H ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDADD ", " ", " H ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DDBABDD ", " I I ", " IHI ", " I I ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DDCBABCDD ", " I I ", " IHI ", " I I ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFFFFF ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " FFFFFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFIIIIIIIIIFFFFF ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " FFFFFIIIIIIIIIFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIIIIIIIIIIIIIIIFF ", " IIIIDDCCBABCCDDIIII ", " IIII I I IIII ", " IIII IHI IIII ", " IIII I I IIII ", " IIIIDDCCBABCCDDIIII ", " FFIIIIIIIIIIIIIIIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFIIIIIIIIIIIIIIIIIIIIIIIFFF ", " IIIIIIDCACBABCACDIIIIII ", " IIIIII I I IIIIII ", " IIIIII IHI IIIIII ", " IIIIII I I IIIIII ", " IIIIIIDCACBABCACDIIIIII ", " FFFIIIIIIIIIIIIIIIIIIIIIIIFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF ", " IIIIIIIIDCACBABCACDIIIIIIII ", " IIIIIIII I I IIIIIIII ", " IIIIIIII IHI IIIIIIII ", " IIIIIIII I I IIIIIIII ", " IIIIIIIIDCACBABCACDIIIIIIII ", " FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIFFFFF E FFFFFIIIIIFF ", " IIIII DCACBABCACD IIIII ", " IIIII I I IIIII ", " IIIII IHI IIIII ", " IIIII I I IIIII ", " IIIII DCACBABCACD IIIII ", " FFIIIIIFFFFF E FFFFFIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIFFFF E FFFFIIIFF ", " III DCACBABCACD III ", " III I I III ", " III IHI III ", " III I I III ", " III DCACBABCACD III ", " FFIIIFFFF E FFFFIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIFFF E FFFIIFF ", " II DCACBABCACD II ", " II I I II ", " II IHI II ", " II I I II ", " II DCACBABCACD II ", " FFIIFFF E FFFIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF EEE FFFFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFFFF EEE FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GG GG ", " FFFF EEE FFFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFFF EEE FFFF ", " GG J GG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGA AGGG ", " FFF EEE FFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFF EEE FFF ", " GGGA J AGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGAA AAGGG ", " FF FFFEEEFFF FF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FF FFFEEEFFF FF ", " GGGAA J AAGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGGGA AGGGGA ", " FF FFFFFFEEEFFFFFF FF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FF FFFFFFEEEFFFFFF FF ", " AGGGGA J AGGGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGAGGA AGGAGA ", " FF FFFFFFFFEEEFFFFFFFF FF ", " DDCACBABCACDD ", " I I ", " IHI ", " I I ", " DDCACBABCACDD ", " FF FFFFFFFFEEEFFFFFFFF FF ", " AGAGGA J AGGAGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " AGGAGGA J AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFF EEEEE FFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFF EEEEE FFFFFF FF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFFF EEEEE FFFF FFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFF FFFF EEEEE FFFF FFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFF EEEEE FFF FFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFF FFF EEEEE FFF FFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFFF FF EEEEE FF FFFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFFF FF EEEEE FF FFFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " A A ", " AGGAGGA A A AGGAGGA ", " FIF FF EEEEE FF FIF ", " I DCAACBABCAACD I ", " I I I I ", " I IHI I ", " I I I I ", " I DCAACBABCAACD I ", " FIF FF EEEEE FF FIF ", " AGGAGGA A A AGGAGGA ", " AJA ", " ", " ", " ") + .aisle(" ", " ", " A A ", " A A ", " AGGAGGA AGGAGGA ", " FIIF FF EEEEE FF FIIF ", " II DCAACBABCAACD II ", " II I I II ", " II IHI II ", " II I I II ", " II DCAACBABCAACD II ", " FIIF FF EEEEE FF FIIF ", " AGGAGGA AGGAGGA ", " A A ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " FIF FF AAA EEE AAA FF FIF ", " I DCAACBABCAACD I ", " I I I I ", " I IHI I ", " I I I I ", " I DCAACBABCAACD I ", " FIF FF AAA EEE AAA FF FIF ", " AGGAGGA AGGAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF ", " II D BAB D II ", " II I I II ", " II IHI II ", " II I I II ", " II D BAB D II ", " FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF ", " AGGAGGA AGGAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAG GAGGA ", " FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF ", " I D BAB D I ", " I I I I ", " I IHI I ", " I I I I ", " I D BAB D I ", " FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF ", " AGGAG GAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGA AGGA ", " FIIF FFF AABA ACCCA ABAA FFF FIIF ", " II BAB II ", " II I I II ", " II IHI II ", " II I I II ", " II BAB II ", " FIIF FFF AABA ACCCA ABAA FFF FIIF ", " AGGA AGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " A A ", " FIIF FFF AABABA ACCCA ABABAA FFF FIIF ", " II BAB II ", " II I I II ", " II IHI II ", " II I I II ", " II BAB II ", " FIIF FFF AABABA ACCCA ABABAA FFF FIIF ", " A A ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " D D ", " D D ", " FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF ", " III D BAB D III ", " III D I I D III ", " III DD IHI DD III ", " III D I I D III ", " III D BAB D III ", " FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF ", " D D ", " D D ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " ", " ", " FIIF FFF A ABABA ACCCA ABABA A FFF FIIF ", " II BAB II ", " II I I II ", " II DD IHI DD II ", " II I I II ", " II BAB II ", " FIIF FFF A ABABA ACCCA ABABA A FFF FIIF ", " ", " ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " AAAAA ", " ", " FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF ", " III BAB III ", " III AAAAA III ", " III DD H DD III ", " III AAAAA III ", " III BAB III ", " FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF ", " ", " AAAAA ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " D D D D ", " D AAAAAAAAA D ", " D BBBBB D ", " FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF ", " III D BBBBB D III ", " III D AAAAAAAAA D III ", " III D GGGGG D III ", " III D AAAAAAAAA D III ", " III D BBBBB D III ", " FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF ", " D BBBBB D ", " D AAAAAAAAA D ", " D DJD D ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF ", " III BBBBBBBBB III ", " III AAAAAAAAAAAAA III ", " III GGGGGGGGG III ", " III AAAAAAAAAAAAA III ", " III BBBBBBBBB III ", " FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF ", " III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III ", " III AAAAAAAAAAAAAAA III ", " III GGGGGGGGGGG III ", " III AAAAAAAAAAAAAAA III ", " III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III ", " FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " CDCDC ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " ", " DCDCDCD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" D D ", " D D ", " DDCDCDCDD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " ", " CCCCCCCCCCC ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " ", " AAAAAAAAADDDDDDCCCCCDDDDDDAAAAAAAAA ", " AA AAAAAAAAAAAAAAAAAAA AA ", " A BBBBBBBBBBBBBBBBB A ", " EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII GGGGGGGGGGGGGGGGG IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " ", " CCCCCCCCCCCCCCC ", " AAAAAAAAA AAAAAAAAA ", " BBBBBBBB BBBBBBBB ", " EEEEEEEEEEEEFIIIEEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBB BBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEEIIIFEEEEEEEEEEEE ", " DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD ", " AAAAAAAAAAAAAAAAAAA ", "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", " AAAAAAAAAAAAAAAAAAA ", " DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD ", " EEEEEEEEEEEEFIIIEEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEEIIIFEEEEEEEEEEEE ", " JJJJJJ BBBBBBBBBBBBBBBBB JJJJJJ ", " JJJJJJ AAAAAAAAAAAAAAAAAAA JJJJJJ ", " JJJJJJJJJJCCCCC@@#@@CCCCCJJJJJJJJJJ ", " ", " ") + .aisle(" ", " ", " AAAAAAAAADDDDDDCCCCCDDDDDDAAAAAAAAA ", " AA AAAAAAAAAAAAAAAAAAA AA ", " A BBBBBBBBBBBBBBBBB A ", " EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII GGGGGGGGGGGGGGGGG IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " ", " CCCCCCCCCCC ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" D D ", " D D ", " DDCDCDCDD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " ", " DCDCDCD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " ", " CDCDC ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF ", " III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III ", " III AAAAAAAAAAAAAAA III ", " III GGGGGGGGGGG III ", " III AAAAAAAAAAAAAAA III ", " III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III ", " FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF ", " III BBBBBBBBB III ", " III AAAAAAAAAAAAA III ", " III GGGGGGGGG III ", " III AAAAAAAAAAAAA III ", " III BBBBBBBBB III ", " FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " D D D D ", " D AAAAAAAAA D ", " D BBBBB D ", " FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF ", " III D BBBBB D III ", " III D AAAAAAAAA D III ", " III D GGGGG D III ", " III D AAAAAAAAA D III ", " III D BBBBB D III ", " FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF ", " D BBBBB D ", " D AAAAAAAAA D ", " D DJD D ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " AAAAA ", " ", " FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF ", " III BAB III ", " III AAAAA III ", " III DD H DD III ", " III AAAAA III ", " III BAB III ", " FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF ", " ", " AAAAA ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " ", " ", " FIIF FFF A ABABA ACCCA ABABA A FFF FIIF ", " II BAB II ", " II I I II ", " II DD IHI DD II ", " II I I II ", " II BAB II ", " FIIF FFF A ABABA ACCCA ABABA A FFF FIIF ", " ", " ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " D D ", " D D ", " FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF ", " III D BAB D III ", " III D I I D III ", " III DD IHI DD III ", " III D I I D III ", " III D BAB D III ", " FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF ", " D D ", " D D ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " A A ", " ", " A A ", " FIIF FFF AABABA ACCCA ABABAA FFF FIIF ", " II BAB II ", " II I I II ", " II IHI II ", " II I I II ", " II BAB II ", " FIIF FFF AABABA ACCCA ABABAA FFF FIIF ", " A A ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGA AGGA ", " FIIF FFF AABA ACCCA ABAA FFF FIIF ", " II BAB II ", " II I I II ", " II IHI II ", " II I I II ", " II BAB II ", " FIIF FFF AABA ACCCA ABAA FFF FIIF ", " AGGA AGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAG GAGGA ", " FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF ", " I D BAB D I ", " I I I I ", " I IHI I ", " I I I I ", " I D BAB D I ", " FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF ", " AGGAG GAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF ", " II D BAB D II ", " II I I II ", " II IHI II ", " II I I II ", " II D BAB D II ", " FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF ", " AGGAGGA AGGAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " FIF FF AAA EEE AAA FF FIF ", " I DCAACBABCAACD I ", " I I I I ", " I IHI I ", " I I I I ", " I DCAACBABCAACD I ", " FIF FF AAA EEE AAA FF FIF ", " AGGAGGA AGGAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " A A ", " AGGAGGA AGGAGGA ", " FIIF FF EEEEE FF FIIF ", " II DCAACBABCAACD II ", " II I I II ", " II IHI II ", " II I I II ", " II DCAACBABCAACD II ", " FIIF FF EEEEE FF FIIF ", " AGGAGGA AGGAGGA ", " A A ", " AJA ", " ", " ") + .aisle(" ", " ", " ", " A A ", " AGGAGGA A A AGGAGGA ", " FIF FF EEEEE FF FIF ", " I DCAACBABCAACD I ", " I I I I ", " I IHI I ", " I I I I ", " I DCAACBABCAACD I ", " FIF FF EEEEE FF FIF ", " AGGAGGA A A AGGAGGA ", " AJA ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFFF FF EEEEE FF FFFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFFF FF EEEEE FF FFFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFF EEEEE FFF FFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFF FFF EEEEE FFF FFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFFF EEEEE FFFF FFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFF FFFF EEEEE FFFF FFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFF EEEEE FFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFF EEEEE FFFFFF FF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " AGGAGGA J AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGAGGA AGGAGA ", " FF FFFFFFFFEEEFFFFFFFF FF ", " DDCACBABCACDD ", " I I ", " IHI ", " I I ", " DDCACBABCACDD ", " FF FFFFFFFFEEEFFFFFFFF FF ", " AGAGGA J AGGAGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGGGA AGGGGA ", " FF FFFFFFEEEFFFFFF FF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FF FFFFFFEEEFFFFFF FF ", " AGGGGA J AGGGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGAA AAGGG ", " FF FFFEEEFFF FF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FF FFFEEEFFF FF ", " GGGAA J AAGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGA AGGG ", " FFF EEE FFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFF EEE FFF ", " GGGA J AGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GG GG ", " FFFF EEE FFFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFFF EEE FFFF ", " GG J GG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF EEE FFFFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFFFF EEE FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIFFF E FFFIIFF ", " II DCACBABCACD II ", " II I I II ", " II IHI II ", " II I I II ", " II DCACBABCACD II ", " FFIIFFF E FFFIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIFFFF E FFFFIIIFF ", " III DCACBABCACD III ", " III I I III ", " III IHI III ", " III I I III ", " III DCACBABCACD III ", " FFIIIFFFF E FFFFIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIFFFFF E FFFFFIIIIIFF ", " IIIII DCACBABCACD IIIII ", " IIIII I I IIIII ", " IIIII IHI IIIII ", " IIIII I I IIIII ", " IIIII DCACBABCACD IIIII ", " FFIIIIIFFFFF E FFFFFIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF ", " IIIIIIIIDCACBABCACDIIIIIIII ", " IIIIIIII I I IIIIIIII ", " IIIIIIII IHI IIIIIIII ", " IIIIIIII I I IIIIIIII ", " IIIIIIIIDCACBABCACDIIIIIIII ", " FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFIIIIIIIIIIIIIIIIIIIIIIIFFF ", " IIIIIIDCACBABCACDIIIIII ", " IIIIII I I IIIIII ", " IIIIII IHI IIIIII ", " IIIIII I I IIIIII ", " IIIIIIDCACBABCACDIIIIII ", " FFFIIIIIIIIIIIIIIIIIIIIIIIFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIIIIIIIIIIIIIIIFF ", " IIIIDDCCBABCCDDIIII ", " IIII I I IIII ", " IIII IHI IIII ", " IIII I I IIII ", " IIIIDDCCBABCCDDIIII ", " FFIIIIIIIIIIIIIIIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFIIIIIIIIIFFFFF ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " FFFFFIIIIIIIIIFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFFFFF ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " FFFFFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DDCBABCDD ", " I I ", " IHI ", " I I ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DDBABDD ", " I I ", " IHI ", " I I ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDADD ", " ", " H ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDD ", " ", " H ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + ; + } + + private static MultiblockShapeInfo.ShapeInfoBuilder tier3ShapeInfo(MultiblockMachineDefinition definition) { + return MultiblockShapeInfo.builder() + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDD ", " ", " H ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDADD ", " ", " H ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DDBABDD ", " I I ", " IHI ", " I I ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DDCBABCDD ", " I I ", " IHI ", " I I ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFFFFF ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " FFFFFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFIIIIIIIIIFFFFF ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " FFFFFIIIIIIIIIFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIIIIIIIIIIIIIIIFF ", " IIIIDDCCBABCCDDIIII ", " IIII I I IIII ", " IIII IHI IIII ", " IIII I I IIII ", " IIIIDDCCBABCCDDIIII ", " FFIIIIIIIIIIIIIIIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFIIIIIIIIIIIIIIIIIIIIIIIFFF ", " IIIIIIDCACBABCACDIIIIII ", " IIIIII I I IIIIII ", " IIIIII IHI IIIIII ", " IIIIII I I IIIIII ", " IIIIIIDCACBABCACDIIIIII ", " FFFIIIIIIIIIIIIIIIIIIIIIIIFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF ", " IIIIIIIIDCACBABCACDIIIIIIII ", " IIIIIIII I I IIIIIIII ", " IIIIIIII IHI IIIIIIII ", " IIIIIIII I I IIIIIIII ", " IIIIIIIIDCACBABCACDIIIIIIII ", " FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIFFFFF E FFFFFIIIIIFF ", " IIIII DCACBABCACD IIIII ", " IIIII I I IIIII ", " IIIII IHI IIIII ", " IIIII I I IIIII ", " IIIII DCACBABCACD IIIII ", " FFIIIIIFFFFF E FFFFFIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIFFFF E FFFFIIIFF ", " III DCACBABCACD III ", " III I I III ", " III IHI III ", " III I I III ", " III DCACBABCACD III ", " FFIIIFFFF E FFFFIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIFFF E FFFIIFF ", " II DCACBABCACD II ", " II I I II ", " II IHI II ", " II I I II ", " II DCACBABCACD II ", " FFIIFFF E FFFIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF EEE FFFFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFFFF EEE FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GG GG ", " FFFF EEE FFFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFFF EEE FFFF ", " GG J GG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGA AGGG ", " FFF EEE FFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFF EEE FFF ", " GGGA J AGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGAA AAGGG ", " FF FFFEEEFFF FF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FF FFFEEEFFF FF ", " GGGAA J AAGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGGGA AGGGGA ", " FF FFFFFFEEEFFFFFF FF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FF FFFFFFEEEFFFFFF FF ", " AGGGGA J AGGGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGAGGA AGGAGA ", " FF FFFFFFFFEEEFFFFFFFF FF ", " DDCACBABCACDD ", " I I ", " IHI ", " I I ", " DDCACBABCACDD ", " FF FFFFFFFFEEEFFFFFFFF FF ", " AGAGGA J AGGAGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " AGGAGGA J AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFF EEEEE FFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFF EEEEE FFFFFF FF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFFF EEEEE FFFF FFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFF FFFF EEEEE FFFF FFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFF EEEEE FFF FFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFF FFF EEEEE FFF FFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFFF FF EEEEE FF FFFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFFF FF EEEEE FF FFFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " A A ", " AGGAGGA A A AGGAGGA ", " FIF FF EEEEE FF FIF ", " I DCAACBABCAACD I ", " I I I I ", " I IHI I ", " I I I I ", " I DCAACBABCAACD I ", " FIF FF EEEEE FF FIF ", " AGGAGGA A A AGGAGGA ", " AJA ", " ", " ", " ") + .aisle(" ", " ", " A A ", " A A ", " AGGAGGA AGGAGGA ", " FIIF FF EEEEE FF FIIF ", " II DCAACBABCAACD II ", " II I I II ", " II IHI II ", " II I I II ", " II DCAACBABCAACD II ", " FIIF FF EEEEE FF FIIF ", " AGGAGGA AGGAGGA ", " A A ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " FIF FF AAA EEE AAA FF FIF ", " I DCAACBABCAACD I ", " I I I I ", " I IHI I ", " I I I I ", " I DCAACBABCAACD I ", " FIF FF AAA EEE AAA FF FIF ", " AGGAGGA AGGAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF ", " II D BAB D II ", " II I I II ", " II IHI II ", " II I I II ", " II D BAB D II ", " FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF ", " AGGAGGA AGGAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAG GAGGA ", " FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF ", " I D BAB D I ", " I I I I ", " I IHI I ", " I I I I ", " I D BAB D I ", " FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF ", " AGGAG GAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGA AGGA ", " FIIF FFF AABA ACCCA ABAA FFF FIIF ", " II BAB II ", " II I I II ", " II IHI II ", " II I I II ", " II BAB II ", " FIIF FFF AABA ACCCA ABAA FFF FIIF ", " AGGA AGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " A A ", " FIIF FFF AABABA ACCCA ABABAA FFF FIIF ", " II BAB II ", " II I I II ", " II IHI II ", " II I I II ", " II BAB II ", " FIIF FFF AABABA ACCCA ABABAA FFF FIIF ", " A A ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " D D ", " D D ", " FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF ", " III D BAB D III ", " III D I I D III ", " III DD IHI DD III ", " III D I I D III ", " III D BAB D III ", " FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF ", " D D ", " D D ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " ", " ", " FIIF FFF A ABABA ACCCA ABABA A FFF FIIF ", " II BAB II ", " II I I II ", " II DD IHI DD II ", " II I I II ", " II BAB II ", " FIIF FFF A ABABA ACCCA ABABA A FFF FIIF ", " ", " ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " AAAAA ", " ", " FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF ", " III BAB III ", " III AAAAA III ", " III DD H DD III ", " III AAAAA III ", " III BAB III ", " FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF ", " ", " AAAAA ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " D D D D ", " D AAAAAAAAA D ", " D BBBBB D ", " FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF ", " III D BBBBB D III ", " III D AAAAAAAAA D III ", " III D GGGGG D III ", " III D AAAAAAAAA D III ", " III D BBBBB D III ", " FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF ", " D BBBBB D ", " D AAAAAAAAA D ", " D DJD D ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF ", " III BBBBBBBBB III ", " III AAAAAAAAAAAAA III ", " III GGGGGGGGG III ", " III AAAAAAAAAAAAA III ", " III BBBBBBBBB III ", " FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF ", " III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III ", " III AAAAAAAAAAAAAAA III ", " III GGGGGGGGGGG III ", " III AAAAAAAAAAAAAAA III ", " III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III ", " FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " CDCDC ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " ", " DCDCDCD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" D D ", " D D ", " DDCDCDCDD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " ", " CCCCCCCCCCC ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" ", " ", " AAAAAAAAADDDDDDCCCCCDDDDDDAAAAAAAAA ", " AA AAAAAAAAAAAAAAAAAAA AA ", " A BBBBBBBBBBBBBBBBB A ", " EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII GGGGGGGGGGGGGGGGG IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " ", " CCCCCCCCCCCCCCC ", " AAAAAAAAA AAAAAAAAA ", " BBBBBBBB BBBBBBBB ", " EEEEEEEEEEEEFIIIEEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBB BBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEEIIIFEEEEEEEEEEEE ", " DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD ", " AAAAAAAAAAAAAAAAAAA ", "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", " AAAAAAAAAAAAAAAAAAA ", " DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD ", " EEEEEEEEEEEEFIIIEEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEEIIIFEEEEEEEEEEEE ", " JJJJJJ BBBBBBBBBBBBBBBBB JJJJJJ ", " JJJJJJ AAAAAAAAAAAAAAAAAAA JJJJJJ ", " JJJJJJJJJJCCCCC@@#@@CCCCCJJJJJJJJJJ ", " ", " ") + .aisle(" ", " ", " AAAAAAAAADDDDDDCCCCCDDDDDDAAAAAAAAA ", " AA AAAAAAAAAAAAAAAAAAA AA ", " A BBBBBBBBBBBBBBBBB A ", " EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII GGGGGGGGGGGGGGGGG IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII ", " DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD ", " EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE ", " A BBBBBBBBBBBBBBBBB A ", " AA AAAAAAAAAAAAAAAAAAA AA ", " AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA ", " ", " ") + .aisle(" ", " ", " CCCCCCCCCCC ", " AAAAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBBBB ", " FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD ", " FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF ", " BBBBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAAAA ", " CCCC@@@CCCC ", " ", " ") + .aisle(" D D ", " D D ", " DDCDCDCDD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DDCDCDCDD ", " D D ", " D D ") + .aisle(" ", " ", " DCDCDCD ", " AAAAAAAAAAAAAAAAA ", " BBBBBBBBBBBBBBB ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " AAAAAAAAAAAAAAAAA ", " GGGGGGGGGGGGGGG ", " AAAAAAAAAAAAAAAAA ", " DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD ", " FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF ", " BBBBBBBBBBBBBBB ", " AAAAAAAAAAAAAAAAA ", " DCDCDCD ", " ", " ") + .aisle(" ", " ", " CDCDC ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBBBB ", " FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " AAAAAAAAAAAAAAA ", " GGGGGGGGGGGGG ", " AAAAAAAAAAAAAAA ", " DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD ", " FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF ", " BBBBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " CDCDC ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAAAA ", " BBBBBBBBBBB ", " FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF ", " III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III ", " III AAAAAAAAAAAAAAA III ", " III GGGGGGGGGGG III ", " III AAAAAAAAAAAAAAA III ", " III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III ", " FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF ", " BBBBBBBBBBB ", " AAAAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " DCD ", " AAAAAAAAAAAAA ", " BBBBBBBBB ", " FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF ", " III BBBBBBBBB III ", " III AAAAAAAAAAAAA III ", " III GGGGGGGGG III ", " III AAAAAAAAAAAAA III ", " III BBBBBBBBB III ", " FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF ", " BBBBBBBBB ", " AAAAAAAAAAAAA ", " DCD ", " ", " ") + .aisle(" ", " ", " D D D D ", " D AAAAAAAAA D ", " D BBBBB D ", " FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF ", " III D BBBBB D III ", " III D AAAAAAAAA D III ", " III D GGGGG D III ", " III D AAAAAAAAA D III ", " III D BBBBB D III ", " FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF ", " D BBBBB D ", " D AAAAAAAAA D ", " D DJD D ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " AAAAA ", " ", " FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF ", " III BAB III ", " III AAAAA III ", " III DD H DD III ", " III AAAAA III ", " III BAB III ", " FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF ", " ", " AAAAA ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " ", " ", " FIIF FFF A ABABA ACCCA ABABA A FFF FIIF ", " II BAB II ", " II I I II ", " II DD IHI DD II ", " II I I II ", " II BAB II ", " FIIF FFF A ABABA ACCCA ABABA A FFF FIIF ", " ", " ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " DD A A DD ", " D D ", " D D ", " FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF ", " III D BAB D III ", " III D I I D III ", " III DD IHI DD III ", " III D I I D III ", " III D BAB D III ", " FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF ", " D D ", " D D ", " DD AJA DD ", " ", " ") + .aisle(" ", " ", " A A ", " ", " A A ", " FIIF FFF AABABA ACCCA ABABAA FFF FIIF ", " II BAB II ", " II I I II ", " II IHI II ", " II I I II ", " II BAB II ", " FIIF FFF AABABA ACCCA ABABAA FFF FIIF ", " A A ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGA AGGA ", " FIIF FFF AABA ACCCA ABAA FFF FIIF ", " II BAB II ", " II I I II ", " II IHI II ", " II I I II ", " II BAB II ", " FIIF FFF AABA ACCCA ABAA FFF FIIF ", " AGGA AGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAG GAGGA ", " FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF ", " I D BAB D I ", " I I I I ", " I IHI I ", " I I I I ", " I D BAB D I ", " FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF ", " AGGAG GAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF ", " II D BAB D II ", " II I I II ", " II IHI II ", " II I I II ", " II D BAB D II ", " FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF ", " AGGAGGA AGGAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " ", " AGGAGGA AGGAGGA ", " FIF FF AAA EEE AAA FF FIF ", " I DCAACBABCAACD I ", " I I I I ", " I IHI I ", " I I I I ", " I DCAACBABCAACD I ", " FIF FF AAA EEE AAA FF FIF ", " AGGAGGA AGGAGGA ", " ", " AJA ", " ", " ") + .aisle(" ", " ", " A A ", " A A ", " AGGAGGA AGGAGGA ", " FIIF FF EEEEE FF FIIF ", " II DCAACBABCAACD II ", " II I I II ", " II IHI II ", " II I I II ", " II DCAACBABCAACD II ", " FIIF FF EEEEE FF FIIF ", " AGGAGGA AGGAGGA ", " A A ", " AJA ", " ", " ") + .aisle(" ", " ", " ", " A A ", " AGGAGGA A A AGGAGGA ", " FIF FF EEEEE FF FIF ", " I DCAACBABCAACD I ", " I I I I ", " I IHI I ", " I I I I ", " I DCAACBABCAACD I ", " FIF FF EEEEE FF FIF ", " AGGAGGA A A AGGAGGA ", " AJA ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFFF FF EEEEE FF FFFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFFF FF EEEEE FF FFFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFF EEEEE FFF FFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFF FFF EEEEE FFF FFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FFF FFFF EEEEE FFFF FFF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FFF FFFF EEEEE FFFF FFF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFF EEEEE FFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFF EEEEE FFFFFF FF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFFFFF EEEEE FFFFFFFFF FF ", " AGGAGGA AGGAGGA ", " J ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGAGGA AGGAGGA ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " DCAACBABCAACD ", " I I ", " IHI ", " I I ", " DCAACBABCAACD ", " FF FFFFFFFFFFEEEFFFFFFFFFF FF ", " AGGAGGA J AGGAGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGAGGA AGGAGA ", " FF FFFFFFFFEEEFFFFFFFF FF ", " DDCACBABCACDD ", " I I ", " IHI ", " I I ", " DDCACBABCACDD ", " FF FFFFFFFFEEEFFFFFFFF FF ", " AGAGGA J AGGAGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " AGGGGA AGGGGA ", " FF FFFFFFEEEFFFFFF FF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FF FFFFFFEEEFFFFFF FF ", " AGGGGA J AGGGGA ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGAA AAGGG ", " FF FFFEEEFFF FF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FF FFFEEEFFF FF ", " GGGAA J AAGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GGGA AGGG ", " FFF EEE FFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFF EEE FFF ", " GGGA J AGGG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " GG GG ", " FFFF EEE FFFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFFF EEE FFFF ", " GG J GG ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFF EEE FFFFF ", " DCACBABCACD ", " I I ", " IHI ", " I I ", " DCACBABCACD ", " FFFFF EEE FFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIFFF E FFFIIFF ", " II DCACBABCACD II ", " II I I II ", " II IHI II ", " II I I II ", " II DCACBABCACD II ", " FFIIFFF E FFFIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIFFFF E FFFFIIIFF ", " III DCACBABCACD III ", " III I I III ", " III IHI III ", " III I I III ", " III DCACBABCACD III ", " FFIIIFFFF E FFFFIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIFFFFF E FFFFFIIIIIFF ", " IIIII DCACBABCACD IIIII ", " IIIII I I IIIII ", " IIIII IHI IIIII ", " IIIII I I IIIII ", " IIIII DCACBABCACD IIIII ", " FFIIIIIFFFFF E FFFFFIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF ", " IIIIIIIIDCACBABCACDIIIIIIII ", " IIIIIIII I I IIIIIIII ", " IIIIIIII IHI IIIIIIII ", " IIIIIIII I I IIIIIIII ", " IIIIIIIIDCACBABCACDIIIIIIII ", " FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFIIIIIIIIIIIIIIIIIIIIIIIFFF ", " IIIIIIDCACBABCACDIIIIII ", " IIIIII I I IIIIII ", " IIIIII IHI IIIIII ", " IIIIII I I IIIIII ", " IIIIIIDCACBABCACDIIIIII ", " FFFIIIIIIIIIIIIIIIIIIIIIIIFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFIIIIIIIIIIIIIIIIIIIFF ", " IIIIDDCCBABCCDDIIII ", " IIII I I IIII ", " IIII IHI IIII ", " IIII I I IIII ", " IIIIDDCCBABCCDDIIII ", " FFIIIIIIIIIIIIIIIIIIIFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFIIIIIIIIIFFFFF ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " FFFFFIIIIIIIIIFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " FFFFFFFFF ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " FFFFFFFFF ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCCBABCCD ", " I I ", " IHI ", " I I ", " DCCBABCCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DDCBABCDD ", " I I ", " IHI ", " I I ", " DDCBABCDD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " EEE ", " DCBABCD ", " I I ", " IHI ", " I I ", " DCBABCD ", " EEE ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DDBABDD ", " I I ", " IHI ", " I I ", " DDBABDD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " E ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " E ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DBABD ", " I I ", " IHI ", " I I ", " DBABD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDADD ", " ", " H ", " ", " DDADD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DAD ", " ", " H ", " ", " DAD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " DDD ", " ", " H ", " ", " DDD ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " D ", " ", " H ", " ", " D ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + .aisle(" ", " ", " ", " ", " ", " ", " ", " ", " H ", " ", " ", " ", " ", " ", " ", " ", " ") + ; + } + + // spotless:on +} \ No newline at end of file diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadderResearchHubPatterns.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadderResearchHubPatterns.java new file mode 100644 index 000000000..ad1a1fe0f --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/StarLadderResearchHubPatterns.java @@ -0,0 +1,463 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi; + +/** + * Star Ladder Research Hub structure patterns for tiers T0-T3. + * Characters have been normalized across all tiers for consistency. + * Each string in the array is one aisle, with layers separated by '|'. + * + * UNIFIED LEGEND (applies to all tiers): + * ' ' (space) = Air/ANY + * '@' = Controller marker (orange_wool from export) + * '#' = Controller position (red_wool from export) + * A = cosmiccore:superheavy_steel_casing + * B = cosmiccore:bolted_heavy_frame_casing + * C = cosmiccore:somarust_casing + * D = cosmiccore:soul_muted_casing + * E = cosmiccore:bichromal_nevramite_casing (T1+) + * F = cosmiccore:oscillating_gilded_pthanterum_casings (T2+) + * G = cosmiccore:highly_flexible_reinforced_trinavine_casing (T2+) + * H = cosmiccore:royal_ichorium_casing (T3+) + * I = cosmiccore:multi_purpose_interstellar_grade_casing (T3+) + * J = cosmiccore:ultra_powered_casing (T3+) + */ +public class StarLadderResearchHubPatterns { + + // spotless:off + + public static final String[] TIER_0 = { + " | | A | | | | | | ", + " | | A | | | | | | ", + " | | A | | | | | | ", + " | | BAB | | | | | | ", + " | | BAB | AAA AAA | | | | | ", + " | | BAB | AA AA AAAA AAAA AA AA | | | | | ", + " | | BAB | AA AAAA ACCCA AAAA AA | | | | | ", + " | | BAB | AABA ACCCA ABAA | | | | | ", + " | | BAB | AABABA ACCCA ABABAA | | | | | ", + " DD DD | D D | D BAB D | AA ABABAD ACCCA DABABA AA | D D | D D | DD DD | | ", + " DD DD | | BAB | A ABABA ACCCA ABABA A | | | DD DD | | ", + " DD DD | AAAAA | BAB | AA ABABA ACCCA ABABA AA | | AAAAA | DD DD | | ", + " D D | D AAAAAAAAA D | D BBBBB D | AA DABABA BBBBB ABABAD AA | D BBBBB D | D AAAAAAAAA D | D D D D | | ", + " | AAAAAAAAAAAAA | BBBBBBBBB | A ABABBBBBBBBBBBBBABA A | BBBBBBBBB | AAAAAAAAAAAAA | DCD | | ", + " | AAAAAAAAAAAAAAA | BBBBBBBBBBB | A ABABBBBBBBBBBBABA A | BBBBBBBBBBB | AAAAAAAAAAAAAAA | DCD | | ", + " | AAAAAAAAAAAAAAA | BBBBBBBBBBBBB | AA BBBBBBBBBBBBBBB AA | BBBBBBBBBBBBB | AAAAAAAAAAAAAAA | CDCDC | | ", + " | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | A BBBBBBBBBBBBBBB A | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DCDCDCD | | ", + " | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | A BBBBBBBBBBBBBBB A | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DDCDCDCDD | D D | D D ", + " | AAAAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBBBB | AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCC@@@CCCC | | ", + " | AAAAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB | CCCCCCBBBBBBBBBBBBBBBBBCCCCCC | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | DDDDDD@@@@@DDDDDD | | ", + " | AAAAAAAAAAAAAAAAAAA |AAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAA| CCCCCCBBBBBBBBBBBBBBBBBCCCCCC | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCCC@@#@@CCCCC | | ", + " | AAAAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB | CCCCCCBBBBBBBBBBBBBBBBBCCCCCC | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | DDDDDD@@@@@DDDDDD | | ", + " | AAAAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBBBB | AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCC@@@CCCC | | ", + " | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | A BBBBBBBBBBBBBBB A | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DDCDCDCDD | D D | D D ", + " | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | A BBBBBBBBBBBBBBB A | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DCDCDCD | | ", + " | AAAAAAAAAAAAAAA | BBBBBBBBBBBBB | AA BBBBBBBBBBBBBBB AA | BBBBBBBBBBBBB | AAAAAAAAAAAAAAA | CDCDC | | ", + " | AAAAAAAAAAAAAAA | BBBBBBBBBBB | A ABABBBBBBBBBBBABA A | BBBBBBBBBBB | AAAAAAAAAAAAAAA | DCD | | ", + " | AAAAAAAAAAAAA | BBBBBBBBB | A ABABBBBBBBBBBBBBABA A | BBBBBBBBB | AAAAAAAAAAAAA | DCD | | ", + " D D | D AAAAAAAAA D | D BBBBB D | AA DABABA BBBBB ABABAD AA | D BBBBB D | D AAAAAAAAA D | D D D D | | ", + " DD DD | AAAAA | BAB | AA ABABA ACCCA ABABA AA | | AAAAA | DD DD | | ", + " DD DD | | BAB | A ABABA ACCCA ABABA A | | | DD DD | | ", + " DD DD | D D | D BAB D | AA ABABAD ACCCA DABABA AA | D D | D D | DD DD | | ", + " | | BAB | AABABA ACCCA ABABAA | | | | | ", + " | | BAB | AABA ACCCA ABAA | | | | | ", + " | | BAB | AA AAAA ACCCA AAAA AA | | | | | ", + " | | BAB | AA AA AAAA AAAA AA AA | | | | | ", + " | | BAB | AAA AAA | | | | | ", + " | | BAB | | | | | | ", + " | | A | | | | | | ", + " | | A | | | | | | " + }; + + public static final String[] TIER_1 = { + " | | D | | | | | | ", + " | | D | | | | | | ", + " | | D | | | | | | ", + " | | D | | | | | | ", + " | | DDD | | | | | | ", + " | | DAD | | | | | | ", + " | | DAD | | | | | | ", + " | | DAD | | | | | | ", + " | | DDADD | | | | | | ", + " | | DBABD | | | | | | ", + " | | DBABD | E | | | | | ", + " | | DBABD | E | | | | | ", + " | | DBABD | E | | | | | ", + " | | DDBABDD | E | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DDCBABCDD | EEE | | | | | ", + " | | DCCBABCCD | EEE | | | | | ", + " | | DCCBABCCD | E | | | | | ", + " | | DCCBABCCD | | | | | | ", + " | | DCCBABCCD | | | | | | ", + " | | DDCCBABCCDD | | | | | | ", + " | | DCACBABCACD | | | | | | ", + " | | DCACBABCACD | E | | | | | ", + " | | DCACBABCACD | E | | | | | ", + " | | DCACBABCACD | E | | | | | ", + " | | DCACBABCACD | E | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DDCACBABCACDD | EEE | | | | | ", + " | | DCAACBABCAACD | EEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | A A | A A | | | ", + " | | DCAACBABCAACD | EEEEE | | A A | A A | | ", + " | | DCAACBABCAACD | AAA EEE AAA | | | A A | | ", + " | | D BAB D | AA AA AAAAEEEAAAA AA AA | | | A A | | ", + " | | D BAB D | AA AAAA ACCCA AAAA AA | | | A A | | ", + " | | BAB | AABA ACCCA ABAA | | | A A | | ", + " | | BAB | AABABA ACCCA ABABAA | | | A A | | ", + " DD DD | D D | D BAB D | AA ABABAD ACCCA DABABA AA | D D | D D | DD A A DD | | ", + " DD DD | | BAB | A ABABA ACCCA ABABA A | | | DD A A DD | | ", + " DD DD | AAAAA | BAB | AA ABABA ACCCA ABABA AA | | AAAAA | DD A A DD | | ", + " D D | D AAAAAAAAA D | D BBBBB D | AA DABABA BBBBB ABABAD AA | D BBBBB D | D AAAAAAAAA D | D D D D | | ", + " | AAAAAAAAAAAAA | BBBBBBBBB | A ABABBBBBBBBBBBBBABA A | BBBBBBBBB | AAAAAAAAAAAAA | DCD | | ", + " | AAAAAAAAAAAAAAA | DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD | A ABABBBBBBBBBBBABA A | BBBBBBBBBBB | AAAAAAAAAAAAAAA | DCD | | ", + " | AAAAAAAAAAAAAAA | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | AA BBBBBBBBBBBBBBB AA | BBBBBBBBBBBBB | AAAAAAAAAAAAAAA | CDCDC | | ", + " | AAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | A BBBBBBBBBBBBBBB A | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DCDCDCD | | ", + " | AAAAAAAAAAAAAAAAA | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | A BBBBBBBBBBBBBBB A | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DDCDCDCDD | D D | D D ", + " | AAAAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | EEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEE | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCC@@@CCCC | | ", + " | AAAAAAAAAAAAAAAAAAA | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | EEEEEEE EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE EEEEEEE | A BBBBBBBBBBBBBBBBB A | AA AAAAAAAAAAAAAAAAAAA AA | AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA | | ", + " | AAAAAAAAAAAAAAAAAAA |DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD| EEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEE | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCCC@@#@@CCCCC | | ", + " | AAAAAAAAAAAAAAAAAAA | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | EEEEEEE EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE EEEEEEE | A BBBBBBBBBBBBBBBBB A | AA AAAAAAAAAAAAAAAAAAA AA | AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA | | ", + " | AAAAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | EEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEE | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCC@@@CCCC | | ", + " | AAAAAAAAAAAAAAAAA | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | A BBBBBBBBBBBBBBB A | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DDCDCDCDD | D D | D D ", + " | AAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | A BBBBBBBBBBBBBBB A | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DCDCDCD | | ", + " | AAAAAAAAAAAAAAA | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | AA BBBBBBBBBBBBBBB AA | BBBBBBBBBBBBB | AAAAAAAAAAAAAAA | CDCDC | | ", + " | AAAAAAAAAAAAAAA | DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD | A ABABBBBBBBBBBBABA A | BBBBBBBBBBB | AAAAAAAAAAAAAAA | DCD | | ", + " | AAAAAAAAAAAAA | BBBBBBBBB | A ABABBBBBBBBBBBBBABA A | BBBBBBBBB | AAAAAAAAAAAAA | DCD | | ", + " D D | D AAAAAAAAA D | D BBBBB D | AA DABABA BBBBB ABABAD AA | D BBBBB D | D AAAAAAAAA D | D D D D | | ", + " DD DD | AAAAA | BAB | AA ABABA ACCCA ABABA AA | | AAAAA | DD A A DD | | ", + " DD DD | | BAB | A ABABA ACCCA ABABA A | | | DD A A DD | | ", + " DD DD | D D | D BAB D | AA ABABAD ACCCA DABABA AA | D D | D D | DD A A DD | | ", + " | | BAB | AABABA ACCCA ABABAA | | | A A | | ", + " | | BAB | AABA ACCCA ABAA | | | A A | | ", + " | | D BAB D | AA AAAA ACCCA AAAA AA | | | A A | | ", + " | | D BAB D | AA AA AAAAEEEAAAA AA AA | | | A A | | ", + " | | DCAACBABCAACD | AAA EEE AAA | | | A A | | ", + " | | DCAACBABCAACD | EEEEE | | A A | A A | | ", + " | | DCAACBABCAACD | EEEEE | A A | A A | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEEEE | | | | | ", + " | | DCAACBABCAACD | EEE | | | | | ", + " | | DDCACBABCACDD | EEE | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DCACBABCACD | EEE | | | | | ", + " | | DCACBABCACD | E | | | | | ", + " | | DCACBABCACD | E | | | | | ", + " | | DCACBABCACD | E | | | | | ", + " | | DCACBABCACD | E | | | | | ", + " | | DCACBABCACD | | | | | | ", + " | | DDCCBABCCDD | | | | | | ", + " | | DCCBABCCD | | | | | | ", + " | | DCCBABCCD | | | | | | ", + " | | DCCBABCCD | E | | | | | ", + " | | DCCBABCCD | EEE | | | | | ", + " | | DDCBABCDD | EEE | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DCBABCD | EEE | | | | | ", + " | | DDBABDD | E | | | | | ", + " | | DBABD | E | | | | | ", + " | | DBABD | E | | | | | ", + " | | DBABD | E | | | | | ", + " | | DBABD | | | | | | ", + " | | DDADD | | | | | | ", + " | | DAD | | | | | | ", + " | | DAD | | | | | | ", + " | | DAD | | | | | | ", + " | | DDD | | | | | | ", + " | | D | | | | | | ", + " | | D | | | | | | ", + " | | D | | | | | | ", + " | | D | | | | | | " + }; + + public static final String[] TIER_2 = { + " | | | | | | D | | | | D | | | | | | ", + " | | | | | | D | | | | D | | | | | | ", + " | | | | | | D | | | | D | | | | | | ", + " | | | | | | D | | | | D | | | | | | ", + " | | | | | | DDD | | | | DDD | | | | | | ", + " | | | | | | DAD | | | | DAD | | | | | | ", + " | | | | | | DAD | | | | DAD | | | | | | ", + " | | | | | | DAD | | | | DAD | | | | | | ", + " | | | | | | DDADD | | | | DDADD | | | | | | ", + " | | | | | | DBABD | | | | DBABD | | | | | | ", + " | | | | | E | DBABD | | | | DBABD | E | | | | | ", + " | | | | | E | DBABD | | | | DBABD | E | | | | | ", + " | | | | | E | DBABD | | | | DBABD | E | | | | | ", + " | | | | | E | DDBABDD | | | | DDBABDD | E | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DDCBABCDD | | | | DDCBABCDD | EEE | | | | | ", + " | | | | | EEE | DCCBABCCD | | | | DCCBABCCD | EEE | | | | | ", + " | | | | | E | DCCBABCCD | | | | DCCBABCCD | E | | | | | ", + " | | | | | FFFFFFFFF | DCCBABCCD | | | | DCCBABCCD | FFFFFFFFF | | | | | ", + " | | | | | FFFFF FFFFF | DCCBABCCD | | | | DCCBABCCD | FFFFF FFFFF | | | | | ", + " | | | | | FF FF | DDCCBABCCDD | | | | DDCCBABCCDD | FF FF | | | | | ", + " | | | | | FFF FFF | DCACBABCACD | | | | DCACBABCACD | FFF FFF | | | | | ", + " | | | | | FF FFFFFEFFFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFFFEFFFFF FF | | | | | ", + " | | | | | FF FFFFF E FFFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFFF E FFFFF FF | | | | | ", + " | | | | | FF FFFF E FFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFF E FFFF FF | | | | | ", + " | | | | | FF FFF E FFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFF E FFF FF | | | | | ", + " | | | | | FFFFF EEE FFFFF | DCACBABCACD | | | | DCACBABCACD | FFFFF EEE FFFFF | | | | | ", + " | | | | GG GG | FFFF EEE FFFF | DCACBABCACD | | | | DCACBABCACD | FFFF EEE FFFF | GG GG | | | | ", + " | | | | GGGA AGGG | FFF EEE FFF | DCACBABCACD | | | | DCACBABCACD | FFF EEE FFF | GGGA AGGG | | | | ", + " | | | | GGGAA AAGGG | FF FFFEEEFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFEEEFFF FF | GGGAA AAGGG | | | | ", + " | | | | AGGGGA AGGGGA | FF FFFFFFEEEFFFFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFFFFEEEFFFFFF FF | AGGGGA AGGGGA | | | | ", + " | | | | AGAGGA AGGAGA | FF FFFFFFFFEEEFFFFFFFF FF | DDCACBABCACDD | | | | DDCACBABCACDD | FF FFFFFFFFEEEFFFFFFFF FF | AGAGGA AGGAGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFFFFFFEEEFFFFFFFFFF FF | DCAACBABCAACD | | | | DCAACBABCAACD | FF FFFFFFFFFFEEEFFFFFFFFFF FF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFFFFF EEEEE FFFFFFFFF FF | DCAACBABCAACD | | | | DCAACBABCAACD | FF FFFFFFFFF EEEEE FFFFFFFFF FF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFF EEEEE FFFFFF FF | DCAACBABCAACD | | | | DCAACBABCAACD | FF FFFFFF EEEEE FFFFFF FF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FFF FFFF EEEEE FFFF FFF | DCAACBABCAACD | | | | DCAACBABCAACD | FFF FFFF EEEEE FFFF FFF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FFF FFF EEEEE FFF FFF | DCAACBABCAACD | | | | DCAACBABCAACD | FFF FFF EEEEE FFF FFF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FFFF FF EEEEE FF FFFF | DCAACBABCAACD | | | | DCAACBABCAACD | FFFF FF EEEEE FF FFFF | AGGAGGA AGGAGGA | | | | ", + " | | | A A | AGGAGGA A A AGGAGGA | F F FF EEEEE FF F F | DCAACBABCAACD | | | | DCAACBABCAACD | F F FF EEEEE FF F F | AGGAGGA A A AGGAGGA | A A | | | ", + " | | A A | A A | AGGAGGA AGGAGGA | F F FF EEEEE FF F F | DCAACBABCAACD | | | | DCAACBABCAACD | F F FF EEEEE FF F F | AGGAGGA AGGAGGA | A A | A A | | ", + " | | A A | | AGGAGGA AGGAGGA | F F FF AAA EEE AAA FF F F | DCAACBABCAACD | | | | DCAACBABCAACD | F F FF AAA EEE AAA FF F F | AGGAGGA AGGAGGA | | A A | | ", + " | | A A | | AGGAGGA AGGAGGA | F F FF AA AA AAAAEEEAAAA AA AA FF F F | D BAB D | | | | D BAB D | F F FF AA AA AAAAEEEAAAA AA AA FF F F | AGGAGGA AGGAGGA | | A A | | ", + " | | A A | | AGGAG GAGGA | FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF | D BAB D | | | | D BAB D | FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF | AGGAG GAGGA | | A A | | ", + " | | A A | | AGGA AGGA | F F FFF AABA ACCCA ABAA FFF F F | BAB | | | | BAB | F F FFF AABA ACCCA ABAA FFF F F | AGGA AGGA | | A A | | ", + " | | A A | | A A | F F FFF AABABA ACCCA ABABAA FFF F F | BAB | | | | BAB | F F FFF AABABA ACCCA ABABAA FFF F F | A A | | A A | | ", + " | | DD A A DD | D D | D D | F F FFF AA ABABAD ACCCA DABABA AA FFF F F | D BAB D | D D | DD DD | D D | D BAB D | F F FFF AA ABABAD ACCCA DABABA AA FFF F F | D D | D D | DD A A DD | | ", + " | | DD A A DD | | | F F FFF A ABABA ACCCA ABABA A FFF F F | BAB | | DD DD | | BAB | F F FFF A ABABA ACCCA ABABA A FFF F F | | | DD A A DD | | ", + " | | DD A A DD | AAAAA | | F F FFFF AA ABABA ACCCA ABABA AA FFFF F F | BAB | AAAAA | DD DD | AAAAA | BAB | F F FFFF AA ABABA ACCCA ABABA AA FFFF F F | | AAAAA | DD A A DD | | ", + " | | D D D D | D AAAAAAAAA D | D BBBBB D | F F FFF AA DABABA BBBBB ABABAD AA FFF F F | D BBBBB D | D AAAAAAAAA D | D GGGGG D | D AAAAAAAAA D | D BBBBB D | F F FFF AA DABABA BBBBB ABABAD AA FFF F F | D BBBBB D | D AAAAAAAAA D | D D D D | | ", + " | | DCD | AAAAAAAAAAAAA | BBBBBBBBB | F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F | BBBBBBBBB | AAAAAAAAAAAAA | GGGGGGGGG | AAAAAAAAAAAAA | BBBBBBBBB | F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F | BBBBBBBBB | AAAAAAAAAAAAA | DCD | | ", + " | | DCD | AAAAAAAAAAAAAAA | BBBBBBBBBBB | F F FFFF A ABABBBBBBBBBBBABA A FFFF F F | DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD | AAAAAAAAAAAAAAA | GGGGGGGGGGG | AAAAAAAAAAAAAAA | DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD | F F FFFF A ABABBBBBBBBBBBABA A FFFF F F | BBBBBBBBBBB | AAAAAAAAAAAAAAA | DCD | | ", + " | | CDCDC | AAAAAAAAAAAAAAA | BBBBBBBBBBBBB | F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | AAAAAAAAAAAAAAA | GGGGGGGGGGGGG | AAAAAAAAAAAAAAA | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F | BBBBBBBBBBBBB | AAAAAAAAAAAAAAA | CDCDC | | ", + " | | DCDCDCD | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | F F FFFF A BBBBBBBBBBBBBBB A FFFF F F | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | AAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | F F FFFF A BBBBBBBBBBBBBBB A FFFF F F | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DCDCDCD | | ", + " D D | D D | DDCDCDCDD | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | F F FFFF A BBBBBBBBBBBBBBB A FFFF F F | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | AAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAA | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | F F FFFF A BBBBBBBBBBBBBBB A FFFF F F | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DDCDCDCDD | D D | D D ", + " | | CCCC CCCC | AAAAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBBBB | F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | AAAAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCC@@@CCCC | | ", + " | | AAAAAAAAADDDDDD DDDDDDAAAAAAAAA | AA AAAAAAAAAAAAAAAAAAA AA | A BBBBBBBBBBBBBBBBB A | EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | AAAAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAAAA | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE | A BBBBBBBBBBBBBBBBB A | AA AAAAAAAAAAAAAAAAAAA AA | AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA | | ", + " | | CCCCC CCCCC | AAAAAAAAA AAAAAAAAA | BBBBBBBB BBBBBBBB | EEEEEEEEEEEEF EEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBB BBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEE FEEEEEEEEEEEE |DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD| AAAAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAAAA |DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD| EEEEEEEEEEEEF EEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEE FEEEEEEEEEEEE | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCCC@@#@@CCCCC | | ", + " | | AAAAAAAAADDDDDD DDDDDDAAAAAAAAA | AA AAAAAAAAAAAAAAAAAAA AA | A BBBBBBBBBBBBBBBBB A | EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | AAAAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAAAA | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | EEEEEEE F F EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE F F EEEEEEE | A BBBBBBBBBBBBBBBBB A | AA AAAAAAAAAAAAAAAAAAA AA | AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA | | ", + " | | CCCC CCCC | AAAAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBBBB | F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | AAAAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | F F FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF F F | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCC@@@CCCC | | ", + " D D | D D | DDCDCDCDD | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | F F FFFF A BBBBBBBBBBBBBBB A FFFF F F | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | AAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAA | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | F F FFFF A BBBBBBBBBBBBBBB A FFFF F F | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DDCDCDCDD | D D | D D ", + " | | DCDCDCD | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | F F FFFF A BBBBBBBBBBBBBBB A FFFF F F | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | AAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | F F FFFF A BBBBBBBBBBBBBBB A FFFF F F | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DCDCDCD | | ", + " | | CDCDC | AAAAAAAAAAAAAAA | BBBBBBBBBBBBB | F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | AAAAAAAAAAAAAAA | GGGGGGGGGGGGG | AAAAAAAAAAAAAAA | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | F F FFFF AA BBBBBBBBBBBBBBB AA FFFF F F | BBBBBBBBBBBBB | AAAAAAAAAAAAAAA | CDCDC | | ", + " | | DCD | AAAAAAAAAAAAAAA | BBBBBBBBBBB | F F FFFF A ABABBBBBBBBBBBABA A FFFF F F | DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD | AAAAAAAAAAAAAAA | GGGGGGGGGGG | AAAAAAAAAAAAAAA | DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD | F F FFFF A ABABBBBBBBBBBBABA A FFFF F F | BBBBBBBBBBB | AAAAAAAAAAAAAAA | DCD | | ", + " | | DCD | AAAAAAAAAAAAA | BBBBBBBBB | F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F | BBBBBBBBB | AAAAAAAAAAAAA | GGGGGGGGG | AAAAAAAAAAAAA | BBBBBBBBB | F F FFFF A ABABBBBBBBBBBBBBABA A FFFF F F | BBBBBBBBB | AAAAAAAAAAAAA | DCD | | ", + " | | D D D D | D AAAAAAAAA D | D BBBBB D | F F FFF AA DABABA BBBBB ABABAD AA FFF F F | D BBBBB D | D AAAAAAAAA D | D GGGGG D | D AAAAAAAAA D | D BBBBB D | F F FFF AA DABABA BBBBB ABABAD AA FFF F F | D BBBBB D | D AAAAAAAAA D | D D D D | | ", + " | | DD A A DD | AAAAA | | F F FFFF AA ABABA ACCCA ABABA AA FFFF F F | BAB | AAAAA | DD DD | AAAAA | BAB | F F FFFF AA ABABA ACCCA ABABA AA FFFF F F | | AAAAA | DD A A DD | | ", + " | | DD A A DD | | | F F FFF A ABABA ACCCA ABABA A FFF F F | BAB | | DD DD | | BAB | F F FFF A ABABA ACCCA ABABA A FFF F F | | | DD A A DD | | ", + " | | DD A A DD | D D | D D | F F FFF AA ABABAD ACCCA DABABA AA FFF F F | D BAB D | D D | DD DD | D D | D BAB D | F F FFF AA ABABAD ACCCA DABABA AA FFF F F | D D | D D | DD A A DD | | ", + " | | A A | | A A | F F FFF AABABA ACCCA ABABAA FFF F F | BAB | | | | BAB | F F FFF AABABA ACCCA ABABAA FFF F F | A A | | A A | | ", + " | | A A | | AGGA AGGA | F F FFF AABA ACCCA ABAA FFF F F | BAB | | | | BAB | F F FFF AABA ACCCA ABAA FFF F F | AGGA AGGA | | A A | | ", + " | | A A | | AGGAG GAGGA | FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF | D BAB D | | | | D BAB D | FF FF FFF AA AAAA ACCCA AAAA AA FFF FF FF | AGGAG GAGGA | | A A | | ", + " | | A A | | AGGAGGA AGGAGGA | F F FF AA AA AAAAEEEAAAA AA AA FF F F | D BAB D | | | | D BAB D | F F FF AA AA AAAAEEEAAAA AA AA FF F F | AGGAGGA AGGAGGA | | A A | | ", + " | | A A | | AGGAGGA AGGAGGA | F F FF AAA EEE AAA FF F F | DCAACBABCAACD | | | | DCAACBABCAACD | F F FF AAA EEE AAA FF F F | AGGAGGA AGGAGGA | | A A | | ", + " | | A A | A A | AGGAGGA AGGAGGA | F F FF EEEEE FF F F | DCAACBABCAACD | | | | DCAACBABCAACD | F F FF EEEEE FF F F | AGGAGGA AGGAGGA | A A | A A | | ", + " | | | A A | AGGAGGA A A AGGAGGA | F F FF EEEEE FF F F | DCAACBABCAACD | | | | DCAACBABCAACD | F F FF EEEEE FF F F | AGGAGGA A A AGGAGGA | A A | | | ", + " | | | | AGGAGGA AGGAGGA | FFFF FF EEEEE FF FFFF | DCAACBABCAACD | | | | DCAACBABCAACD | FFFF FF EEEEE FF FFFF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FFF FFF EEEEE FFF FFF | DCAACBABCAACD | | | | DCAACBABCAACD | FFF FFF EEEEE FFF FFF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FFF FFFF EEEEE FFFF FFF | DCAACBABCAACD | | | | DCAACBABCAACD | FFF FFFF EEEEE FFFF FFF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFF EEEEE FFFFFF FF | DCAACBABCAACD | | | | DCAACBABCAACD | FF FFFFFF EEEEE FFFFFF FF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFFFFF EEEEE FFFFFFFFF FF | DCAACBABCAACD | | | | DCAACBABCAACD | FF FFFFFFFFF EEEEE FFFFFFFFF FF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFFFFFFEEEFFFFFFFFFF FF | DCAACBABCAACD | | | | DCAACBABCAACD | FF FFFFFFFFFFEEEFFFFFFFFFF FF | AGGAGGA AGGAGGA | | | | ", + " | | | | AGAGGA AGGAGA | FF FFFFFFFFEEEFFFFFFFF FF | DDCACBABCACDD | | | | DDCACBABCACDD | FF FFFFFFFFEEEFFFFFFFF FF | AGAGGA AGGAGA | | | | ", + " | | | | AGGGGA AGGGGA | FF FFFFFFEEEFFFFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFFFFEEEFFFFFF FF | AGGGGA AGGGGA | | | | ", + " | | | | GGGAA AAGGG | FF FFFEEEFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFEEEFFF FF | GGGAA AAGGG | | | | ", + " | | | | GGGA AGGG | FFF EEE FFF | DCACBABCACD | | | | DCACBABCACD | FFF EEE FFF | GGGA AGGG | | | | ", + " | | | | GG GG | FFFF EEE FFFF | DCACBABCACD | | | | DCACBABCACD | FFFF EEE FFFF | GG GG | | | | ", + " | | | | | FFFFF EEE FFFFF | DCACBABCACD | | | | DCACBABCACD | FFFFF EEE FFFFF | | | | | ", + " | | | | | FF FFF E FFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFF E FFF FF | | | | | ", + " | | | | | FF FFFF E FFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFF E FFFF FF | | | | | ", + " | | | | | FF FFFFF E FFFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFFF E FFFFF FF | | | | | ", + " | | | | | FF FFFFFEFFFFF FF | DCACBABCACD | | | | DCACBABCACD | FF FFFFFEFFFFF FF | | | | | ", + " | | | | | FFF FFF | DCACBABCACD | | | | DCACBABCACD | FFF FFF | | | | | ", + " | | | | | FF FF | DDCCBABCCDD | | | | DDCCBABCCDD | FF FF | | | | | ", + " | | | | | FFFFF FFFFF | DCCBABCCD | | | | DCCBABCCD | FFFFF FFFFF | | | | | ", + " | | | | | FFFFFFFFF | DCCBABCCD | | | | DCCBABCCD | FFFFFFFFF | | | | | ", + " | | | | | E | DCCBABCCD | | | | DCCBABCCD | E | | | | | ", + " | | | | | EEE | DCCBABCCD | | | | DCCBABCCD | EEE | | | | | ", + " | | | | | EEE | DDCBABCDD | | | | DDCBABCDD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | | | | DCBABCD | EEE | | | | | ", + " | | | | | E | DDBABDD | | | | DDBABDD | E | | | | | ", + " | | | | | E | DBABD | | | | DBABD | E | | | | | ", + " | | | | | E | DBABD | | | | DBABD | E | | | | | ", + " | | | | | E | DBABD | | | | DBABD | E | | | | | ", + " | | | | | | DBABD | | | | DBABD | | | | | | ", + " | | | | | | DDADD | | | | DDADD | | | | | | ", + " | | | | | | DAD | | | | DAD | | | | | | ", + " | | | | | | DAD | | | | DAD | | | | | | ", + " | | | | | | DAD | | | | DAD | | | | | | ", + " | | | | | | DDD | | | | DDD | | | | | | ", + " | | | | | | D | | | | D | | | | | | ", + " | | | | | | D | | | | D | | | | | | ", + " | | | | | | D | | | | D | | | | | | ", + " | | | | | | D | | | | D | | | | | | " + }; + + public static final String[] TIER_3 = { + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | D | | H | | D | | | | | | ", + " | | | | | | D | | H | | D | | | | | | ", + " | | | | | | D | | H | | D | | | | | | ", + " | | | | | | D | | H | | D | | | | | | ", + " | | | | | | DDD | | H | | DDD | | | | | | ", + " | | | | | | DAD | | H | | DAD | | | | | | ", + " | | | | | | DAD | | H | | DAD | | | | | | ", + " | | | | | | DAD | | H | | DAD | | | | | | ", + " | | | | | | DDADD | | H | | DDADD | | | | | | ", + " | | | | | | DBABD | I I | IHI | I I | DBABD | | | | | | ", + " | | | | | E | DBABD | I I | IHI | I I | DBABD | E | | | | | ", + " | | | | | E | DBABD | I I | IHI | I I | DBABD | E | | | | | ", + " | | | | | E | DBABD | I I | IHI | I I | DBABD | E | | | | | ", + " | | | | | E | DDBABDD | I I | IHI | I I | DDBABDD | E | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DDCBABCDD | I I | IHI | I I | DDCBABCDD | EEE | | | | | ", + " | | | | | EEE | DCCBABCCD | I I | IHI | I I | DCCBABCCD | EEE | | | | | ", + " | | | | | E | DCCBABCCD | I I | IHI | I I | DCCBABCCD | E | | | | | ", + " | | | | | FFFFFFFFF | DCCBABCCD | I I | IHI | I I | DCCBABCCD | FFFFFFFFF | | | | | ", + " | | | | | FFFFFIIIIIIIIIFFFFF | DCCBABCCD | I I | IHI | I I | DCCBABCCD | FFFFFIIIIIIIIIFFFFF | | | | | ", + " | | | | | FFIIIIIIIIIIIIIIIIIIIFF | IIIIDDCCBABCCDDIIII | IIII I I IIII | IIII IHI IIII | IIII I I IIII | IIIIDDCCBABCCDDIIII | FFIIIIIIIIIIIIIIIIIIIFF | | | | | ", + " | | | | | FFFIIIIIIIIIIIIIIIIIIIIIIIFFF | IIIIIIDCACBABCACDIIIIII | IIIIII I I IIIIII | IIIIII IHI IIIIII | IIIIII I I IIIIII | IIIIIIDCACBABCACDIIIIII | FFFIIIIIIIIIIIIIIIIIIIIIIIFFF | | | | | ", + " | | | | | FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF | IIIIIIIIDCACBABCACDIIIIIIII | IIIIIIII I I IIIIIIII | IIIIIIII IHI IIIIIIII | IIIIIIII I I IIIIIIII | IIIIIIIIDCACBABCACDIIIIIIII | FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF | | | | | ", + " | | | | | FFIIIIIFFFFF E FFFFFIIIIIFF | IIIII DCACBABCACD IIIII | IIIII I I IIIII | IIIII IHI IIIII | IIIII I I IIIII | IIIII DCACBABCACD IIIII | FFIIIIIFFFFF E FFFFFIIIIIFF | | | | | ", + " | | | | | FFIIIFFFF E FFFFIIIFF | III DCACBABCACD III | III I I III | III IHI III | III I I III | III DCACBABCACD III | FFIIIFFFF E FFFFIIIFF | | | | | ", + " | | | | | FFIIFFF E FFFIIFF | II DCACBABCACD II | II I I II | II IHI II | II I I II | II DCACBABCACD II | FFIIFFF E FFFIIFF | | | | | ", + " | | | | | FFFFF EEE FFFFF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FFFFF EEE FFFFF | | | | | ", + " | | | | GG GG | FFFF EEE FFFF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FFFF EEE FFFF | GG J GG | | | | ", + " | | | | GGGA AGGG | FFF EEE FFF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FFF EEE FFF | GGGA J AGGG | | | | ", + " | | | | GGGAA AAGGG | FF FFFEEEFFF FF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FF FFFEEEFFF FF | GGGAA J AAGGG | | | | ", + " | | | | AGGGGA AGGGGA | FF FFFFFFEEEFFFFFF FF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FF FFFFFFEEEFFFFFF FF | AGGGGA J AGGGGA | | | | ", + " | | | | AGAGGA AGGAGA | FF FFFFFFFFEEEFFFFFFFF FF | DDCACBABCACDD | I I | IHI | I I | DDCACBABCACDD | FF FFFFFFFFEEEFFFFFFFF FF | AGAGGA J AGGAGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFFFFFFEEEFFFFFFFFFF FF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FF FFFFFFFFFFEEEFFFFFFFFFF FF | AGGAGGA J AGGAGGA | | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFFFFF EEEEE FFFFFFFFF FF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FF FFFFFFFFF EEEEE FFFFFFFFF FF | AGGAGGA AGGAGGA | J | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFF EEEEE FFFFFF FF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FF FFFFFF EEEEE FFFFFF FF | AGGAGGA AGGAGGA | J | | | ", + " | | | | AGGAGGA AGGAGGA | FFF FFFF EEEEE FFFF FFF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FFF FFFF EEEEE FFFF FFF | AGGAGGA AGGAGGA | J | | | ", + " | | | | AGGAGGA AGGAGGA | FFF FFF EEEEE FFF FFF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FFF FFF EEEEE FFF FFF | AGGAGGA AGGAGGA | J | | | ", + " | | | | AGGAGGA AGGAGGA | FFFF FF EEEEE FF FFFF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FFFF FF EEEEE FF FFFF | AGGAGGA AGGAGGA | J | | | ", + " | | | A A | AGGAGGA A A AGGAGGA | FIF FF EEEEE FF FIF | I DCAACBABCAACD I | I I I I | I IHI I | I I I I | I DCAACBABCAACD I | FIF FF EEEEE FF FIF | AGGAGGA A A AGGAGGA | AJA | | | ", + " | | A A | A A | AGGAGGA AGGAGGA | FIIF FF EEEEE FF FIIF | II DCAACBABCAACD II | II I I II | II IHI II | II I I II | II DCAACBABCAACD II | FIIF FF EEEEE FF FIIF | AGGAGGA AGGAGGA | A A | AJA | | ", + " | | A A | | AGGAGGA AGGAGGA | FIF FF AAA EEE AAA FF FIF | I DCAACBABCAACD I | I I I I | I IHI I | I I I I | I DCAACBABCAACD I | FIF FF AAA EEE AAA FF FIF | AGGAGGA AGGAGGA | | AJA | | ", + " | | A A | | AGGAGGA AGGAGGA | FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF | II D BAB D II | II I I II | II IHI II | II I I II | II D BAB D II | FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF | AGGAGGA AGGAGGA | | AJA | | ", + " | | A A | | AGGAG GAGGA | FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF | I D BAB D I | I I I I | I IHI I | I I I I | I D BAB D I | FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF | AGGAG GAGGA | | AJA | | ", + " | | A A | | AGGA AGGA | FIIF FFF AABA ACCCA ABAA FFF FIIF | II BAB II | II I I II | II IHI II | II I I II | II BAB II | FIIF FFF AABA ACCCA ABAA FFF FIIF | AGGA AGGA | | AJA | | ", + " | | A A | | A A | FIIF FFF AABABA ACCCA ABABAA FFF FIIF | II BAB II | II I I II | II IHI II | II I I II | II BAB II | FIIF FFF AABABA ACCCA ABABAA FFF FIIF | A A | | AJA | | ", + " | | DD A A DD | D D | D D | FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF | III D BAB D III | III D I I D III | III DD IHI DD III | III D I I D III | III D BAB D III | FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF | D D | D D | DD AJA DD | | ", + " | | DD A A DD | | | FIIF FFF A ABABA ACCCA ABABA A FFF FIIF | II BAB II | II I I II | II DD IHI DD II | II I I II | II BAB II | FIIF FFF A ABABA ACCCA ABABA A FFF FIIF | | | DD AJA DD | | ", + " | | DD A A DD | AAAAA | | FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF | III BAB III | III AAAAA III | III DD H DD III | III AAAAA III | III BAB III | FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF | | AAAAA | DD AJA DD | | ", + " | | D D D D | D AAAAAAAAA D | D BBBBB D | FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF | III D BBBBB D III | III D AAAAAAAAA D III | III D GGGGG D III | III D AAAAAAAAA D III | III D BBBBB D III | FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF | D BBBBB D | D AAAAAAAAA D | D DJD D | | ", + " | | DCD | AAAAAAAAAAAAA | BBBBBBBBB | FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF | III BBBBBBBBB III | III AAAAAAAAAAAAA III | III GGGGGGGGG III | III AAAAAAAAAAAAA III | III BBBBBBBBB III | FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF | BBBBBBBBB | AAAAAAAAAAAAA | DCD | | ", + " | | DCD | AAAAAAAAAAAAAAA | BBBBBBBBBBB | FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF | III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III | III AAAAAAAAAAAAAAA III | III GGGGGGGGGGG III | III AAAAAAAAAAAAAAA III | III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III | FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF | BBBBBBBBBBB | AAAAAAAAAAAAAAA | DCD | | ", + " | | CDCDC | AAAAAAAAAAAAAAA | BBBBBBBBBBBBB | FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | AAAAAAAAAAAAAAA | GGGGGGGGGGGGG | AAAAAAAAAAAAAAA | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF | BBBBBBBBBBBBB | AAAAAAAAAAAAAAA | CDCDC | | ", + " | | DCDCDCD | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | AAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DCDCDCD | | ", + " D D | D D | DDCDCDCDD | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | AAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAA | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DDCDCDCDD | D D | D D ", + " | | CCCCCCCCCCC | AAAAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBBBB | FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | AAAAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCC@@@CCCC | | ", + " | | AAAAAAAAADDDDDDCCCCCDDDDDDAAAAAAAAA | AA AAAAAAAAAAAAAAAAAAA AA | A BBBBBBBBBBBBBBBBB A | EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII GGGGGGGGGGGGGGGGG IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE | A BBBBBBBBBBBBBBBBB A | AA AAAAAAAAAAAAAAAAAAA AA | AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA | | ", + " | | CCCCCCCCCCCCCCC | AAAAAAAAA AAAAAAAAA | BBBBBBBB BBBBBBBB | EEEEEEEEEEEEFIIIEEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBB BBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEEIIIFEEEEEEEEEEEE | DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD | AAAAAAAAAAAAAAAAAAA |HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH| AAAAAAAAAAAAAAAAAAA | DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD | EEEEEEEEEEEEFIIIEEEEEEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEEEEEEIIIFEEEEEEEEEEEE | JJJJJJ BBBBBBBBBBBBBBBBB JJJJJJ | JJJJJJ AAAAAAAAAAAAAAAAAAA JJJJJJ | JJJJJJJJJJCCCCC@@#@@CCCCCJJJJJJJJJJ | | ", + " | | AAAAAAAAADDDDDDCCCCCDDDDDDAAAAAAAAA | AA AAAAAAAAAAAAAAAAAAA AA | A BBBBBBBBBBBBBBBBB A | EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII GGGGGGGGGGGGGGGGG IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII | DDDDDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBDDDDD | EEEEEEE FIIIF EEEEEEEEEEEEEEEECCCCCCBBBBBBBBBBBBBBBBBCCCCCCEEEEEEEEEEEEEEEE FIIIF EEEEEEE | A BBBBBBBBBBBBBBBBB A | AA AAAAAAAAAAAAAAAAAAA AA | AAAAAAAAADDDDDD@@@@@DDDDDDAAAAAAAAA | | ", + " | | CCCCCCCCCCC | AAAAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBBBB | FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | AAAAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC BBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDD | FIIIF FFFFEEEEEEE AAAAAAABBBBBBBBBBBBBBBBBAAAAAAA EEEEEEEFFFF FIIIF | BBBBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAAAA | CCCC@@@CCCC | | ", + " D D | D D | DDCDCDCDD | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | AAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAA | DDDDDDDCCCCCAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAACCCCCDDDDDDD | FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DDCDCDCDD | D D | D D ", + " | | DCDCDCD | AAAAAAAAAAAAAAAAA | BBBBBBBBBBBBBBB | FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | AAAAAAAAAAAAAAAAA | GGGGGGGGGGGGGGG | AAAAAAAAAAAAAAAAA | DDDDDDCCCCCCCCCCCAAAAAAAAA BBBBBBBBBBBBBBB AAAAAAAAACCCCCCCCCCCDDDDDD | FIIIF FFFF A BBBBBBBBBBBBBBB A FFFF FIIIF | BBBBBBBBBBBBBBB | AAAAAAAAAAAAAAAAA | DCDCDCD | | ", + " | | CDCDC | AAAAAAAAAAAAAAA | BBBBBBBBBBBBB | FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | AAAAAAAAAAAAAAA | GGGGGGGGGGGGG | AAAAAAAAAAAAAAA | DDDDDDDDDDDDCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCDDDDDDDDDDDD | FIIF FFFF AA BBBBBBBBBBBBBBB AA FFFF FIIF | BBBBBBBBBBBBB | AAAAAAAAAAAAAAA | CDCDC | | ", + " | | DCD | AAAAAAAAAAAAAAA | BBBBBBBBBBB | FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF | III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III | III AAAAAAAAAAAAAAA III | III GGGGGGGGGGG III | III AAAAAAAAAAAAAAA III | III DDDDDDDDDDDD BBBBBBBBBBB DDDDDDDDDDDD III | FIIIF FFFF A ABABBBBBBBBBBBABA A FFFF FIIIF | BBBBBBBBBBB | AAAAAAAAAAAAAAA | DCD | | ", + " | | DCD | AAAAAAAAAAAAA | BBBBBBBBB | FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF | III BBBBBBBBB III | III AAAAAAAAAAAAA III | III GGGGGGGGG III | III AAAAAAAAAAAAA III | III BBBBBBBBB III | FIIIF FFFF A ABABBBBBBBBBBBBBABA A FFFF FIIIF | BBBBBBBBB | AAAAAAAAAAAAA | DCD | | ", + " | | D D D D | D AAAAAAAAA D | D BBBBB D | FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF | III D BBBBB D III | III D AAAAAAAAA D III | III D GGGGG D III | III D AAAAAAAAA D III | III D BBBBB D III | FIIIF FFF AA DABABA BBBBB ABABAD AA FFF FIIIF | D BBBBB D | D AAAAAAAAA D | D DJD D | | ", + " | | DD A A DD | AAAAA | | FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF | III BAB III | III AAAAA III | III DD H DD III | III AAAAA III | III BAB III | FIIIF FFFF AA ABABA ACCCA ABABA AA FFFF FIIIF | | AAAAA | DD AJA DD | | ", + " | | DD A A DD | | | FIIF FFF A ABABA ACCCA ABABA A FFF FIIF | II BAB II | II I I II | II DD IHI DD II | II I I II | II BAB II | FIIF FFF A ABABA ACCCA ABABA A FFF FIIF | | | DD AJA DD | | ", + " | | DD A A DD | D D | D D | FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF | III D BAB D III | III D I I D III | III DD IHI DD III | III D I I D III | III D BAB D III | FIIIF FFF AA ABABAD ACCCA DABABA AA FFF FIIIF | D D | D D | DD AJA DD | | ", + " | | A A | | A A | FIIF FFF AABABA ACCCA ABABAA FFF FIIF | II BAB II | II I I II | II IHI II | II I I II | II BAB II | FIIF FFF AABABA ACCCA ABABAA FFF FIIF | A A | | AJA | | ", + " | | A A | | AGGA AGGA | FIIF FFF AABA ACCCA ABAA FFF FIIF | II BAB II | II I I II | II IHI II | II I I II | II BAB II | FIIF FFF AABA ACCCA ABAA FFF FIIF | AGGA AGGA | | AJA | | ", + " | | A A | | AGGAG GAGGA | FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF | I D BAB D I | I I I I | I IHI I | I I I I | I D BAB D I | FFIFF FFF AA AAAA ACCCA AAAA AA FFF FFIFF | AGGAG GAGGA | | AJA | | ", + " | | A A | | AGGAGGA AGGAGGA | FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF | II D BAB D II | II I I II | II IHI II | II I I II | II D BAB D II | FIIF FF AA AA AAAAEEEAAAA AA AA FF FIIF | AGGAGGA AGGAGGA | | AJA | | ", + " | | A A | | AGGAGGA AGGAGGA | FIF FF AAA EEE AAA FF FIF | I DCAACBABCAACD I | I I I I | I IHI I | I I I I | I DCAACBABCAACD I | FIF FF AAA EEE AAA FF FIF | AGGAGGA AGGAGGA | | AJA | | ", + " | | A A | A A | AGGAGGA AGGAGGA | FIIF FF EEEEE FF FIIF | II DCAACBABCAACD II | II I I II | II IHI II | II I I II | II DCAACBABCAACD II | FIIF FF EEEEE FF FIIF | AGGAGGA AGGAGGA | A A | AJA | | ", + " | | | A A | AGGAGGA A A AGGAGGA | FIF FF EEEEE FF FIF | I DCAACBABCAACD I | I I I I | I IHI I | I I I I | I DCAACBABCAACD I | FIF FF EEEEE FF FIF | AGGAGGA A A AGGAGGA | AJA | | | ", + " | | | | AGGAGGA AGGAGGA | FFFF FF EEEEE FF FFFF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FFFF FF EEEEE FF FFFF | AGGAGGA AGGAGGA | J | | | ", + " | | | | AGGAGGA AGGAGGA | FFF FFF EEEEE FFF FFF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FFF FFF EEEEE FFF FFF | AGGAGGA AGGAGGA | J | | | ", + " | | | | AGGAGGA AGGAGGA | FFF FFFF EEEEE FFFF FFF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FFF FFFF EEEEE FFFF FFF | AGGAGGA AGGAGGA | J | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFF EEEEE FFFFFF FF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FF FFFFFF EEEEE FFFFFF FF | AGGAGGA AGGAGGA | J | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFFFFF EEEEE FFFFFFFFF FF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FF FFFFFFFFF EEEEE FFFFFFFFF FF | AGGAGGA AGGAGGA | J | | | ", + " | | | | AGGAGGA AGGAGGA | FF FFFFFFFFFFEEEFFFFFFFFFF FF | DCAACBABCAACD | I I | IHI | I I | DCAACBABCAACD | FF FFFFFFFFFFEEEFFFFFFFFFF FF | AGGAGGA J AGGAGGA | | | | ", + " | | | | AGAGGA AGGAGA | FF FFFFFFFFEEEFFFFFFFF FF | DDCACBABCACDD | I I | IHI | I I | DDCACBABCACDD | FF FFFFFFFFEEEFFFFFFFF FF | AGAGGA J AGGAGA | | | | ", + " | | | | AGGGGA AGGGGA | FF FFFFFFEEEFFFFFF FF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FF FFFFFFEEEFFFFFF FF | AGGGGA J AGGGGA | | | | ", + " | | | | GGGAA AAGGG | FF FFFEEEFFF FF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FF FFFEEEFFF FF | GGGAA J AAGGG | | | | ", + " | | | | GGGA AGGG | FFF EEE FFF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FFF EEE FFF | GGGA J AGGG | | | | ", + " | | | | GG GG | FFFF EEE FFFF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FFFF EEE FFFF | GG J GG | | | | ", + " | | | | | FFFFF EEE FFFFF | DCACBABCACD | I I | IHI | I I | DCACBABCACD | FFFFF EEE FFFFF | | | | | ", + " | | | | | FFIIFFF E FFFIIFF | II DCACBABCACD II | II I I II | II IHI II | II I I II | II DCACBABCACD II | FFIIFFF E FFFIIFF | | | | | ", + " | | | | | FFIIIFFFF E FFFFIIIFF | III DCACBABCACD III | III I I III | III IHI III | III I I III | III DCACBABCACD III | FFIIIFFFF E FFFFIIIFF | | | | | ", + " | | | | | FFIIIIIFFFFF E FFFFFIIIIIFF | IIIII DCACBABCACD IIIII | IIIII I I IIIII | IIIII IHI IIIII | IIIII I I IIIII | IIIII DCACBABCACD IIIII | FFIIIIIFFFFF E FFFFFIIIIIFF | | | | | ", + " | | | | | FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF | IIIIIIIIDCACBABCACDIIIIIIII | IIIIIIII I I IIIIIIII | IIIIIIII IHI IIIIIIII | IIIIIIII I I IIIIIIII | IIIIIIIIDCACBABCACDIIIIIIII | FFIIIIIIIIFFFFFEFFFFFIIIIIIIIFF | | | | | ", + " | | | | | FFFIIIIIIIIIIIIIIIIIIIIIIIFFF | IIIIIIDCACBABCACDIIIIII | IIIIII I I IIIIII | IIIIII IHI IIIIII | IIIIII I I IIIIII | IIIIIIDCACBABCACDIIIIII | FFFIIIIIIIIIIIIIIIIIIIIIIIFFF | | | | | ", + " | | | | | FFIIIIIIIIIIIIIIIIIIIFF | IIIIDDCCBABCCDDIIII | IIII I I IIII | IIII IHI IIII | IIII I I IIII | IIIIDDCCBABCCDDIIII | FFIIIIIIIIIIIIIIIIIIIFF | | | | | ", + " | | | | | FFFFFIIIIIIIIIFFFFF | DCCBABCCD | I I | IHI | I I | DCCBABCCD | FFFFFIIIIIIIIIFFFFF | | | | | ", + " | | | | | FFFFFFFFF | DCCBABCCD | I I | IHI | I I | DCCBABCCD | FFFFFFFFF | | | | | ", + " | | | | | E | DCCBABCCD | I I | IHI | I I | DCCBABCCD | E | | | | | ", + " | | | | | EEE | DCCBABCCD | I I | IHI | I I | DCCBABCCD | EEE | | | | | ", + " | | | | | EEE | DDCBABCDD | I I | IHI | I I | DDCBABCDD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | EEE | DCBABCD | I I | IHI | I I | DCBABCD | EEE | | | | | ", + " | | | | | E | DDBABDD | I I | IHI | I I | DDBABDD | E | | | | | ", + " | | | | | E | DBABD | I I | IHI | I I | DBABD | E | | | | | ", + " | | | | | E | DBABD | I I | IHI | I I | DBABD | E | | | | | ", + " | | | | | E | DBABD | I I | IHI | I I | DBABD | E | | | | | ", + " | | | | | | DBABD | I I | IHI | I I | DBABD | | | | | | ", + " | | | | | | DDADD | | H | | DDADD | | | | | | ", + " | | | | | | DAD | | H | | DAD | | | | | | ", + " | | | | | | DAD | | H | | DAD | | | | | | ", + " | | | | | | DAD | | H | | DAD | | | | | | ", + " | | | | | | DDD | | H | | DDD | | | | | | ", + " | | | | | | D | | H | | D | | | | | | ", + " | | | | | | D | | H | | D | | | | | | ", + " | | | | | | D | | H | | D | | | | | | ", + " | | | | | | D | | H | | D | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | ", + " | | | | | | | | H | | | | | | | | " + }; + + public static final String[] TIER_4 = { + // T4 not yet available + }; + + // spotless:on +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/StarLadderMachine.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/StarLadderMachine.java new file mode 100644 index 000000000..ad00810d3 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/StarLadderMachine.java @@ -0,0 +1,67 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic; + +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock; +import com.ghostipedia.cosmiccore.api.machine.multiblock.LinkedWorkableElectricMultiblockMachine; +import com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper; + +import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; + +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.GlobalPos; +import net.minecraft.network.chat.Component; + +import java.util.List; + +public class StarLadderMachine extends LinkedWorkableElectricMultiblockMachine { + + protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( + StarLadderMachine.class, + LinkedWorkableElectricMultiblockMachine.MANAGED_FIELD_HOLDER); + + public StarLadderMachine(IMachineBlockEntity holder, Object... args) { + super(holder, args); + } + + @Override + public ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + @Override + public LinkRole getLinkRole() { + return LinkRole.CONTROLLER; + } + + @Override + public int getMaxPartners() { + return 1; + } + + @Override + public boolean canLinkTo(GlobalPos partner, ILinkedMultiblock partnerMachine) { + return partnerMachine instanceof StarLadderResearchHubMachine; + } + + @Override + public void addDisplayText(List textList) { + super.addDisplayText(textList); + if (!isFormed()) return; + + GlobalPos hub = getLinkedPartners().stream().findFirst().orElse(null); + if (hub == null) { + textList.add(Component.literal("No linked Research Hub").withStyle(ChatFormatting.GRAY)); + return; + } + + boolean online = getPartnerMachine(hub) != null; + textList.add(Component.literal("Research Hub: " + (online ? "Online" : "Offline")) + .withStyle(online ? ChatFormatting.GREEN : ChatFormatting.RED)); + textList.add(Component.literal(" " + LinkedMultiblockHelper.getDimensionName(hub.dimension().location())) + .withStyle(ChatFormatting.GRAY)); + textList.add(Component.literal(" [%d, %d, %d]".formatted( + hub.pos().getX(), hub.pos().getY(), hub.pos().getZ())) + .withStyle(ChatFormatting.GRAY)); + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/StarLadderResearchHubMachine.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/StarLadderResearchHubMachine.java new file mode 100644 index 000000000..651c9323c --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/logic/StarLadderResearchHubMachine.java @@ -0,0 +1,547 @@ +package com.ghostipedia.cosmiccore.common.machine.multiblock.multi.logic; + +import com.ghostipedia.cosmiccore.api.capability.ILinkedMultiblock; +import com.ghostipedia.cosmiccore.api.machine.multiblock.LinkedWorkableElectricMultiblockMachine; +import com.ghostipedia.cosmiccore.client.renderer.RingUpgradePreviewRenderer; +import com.ghostipedia.cosmiccore.common.machine.multiblock.LinkedMultiblockHelper; +import com.ghostipedia.cosmiccore.common.machine.multiblock.multi.StarLadderResearchHub; + +import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity; +import com.gregtechceu.gtceu.api.pattern.BlockPattern; + +import com.lowdragmc.lowdraglib.syncdata.annotation.DescSynced; +import com.lowdragmc.lowdraglib.syncdata.annotation.Persisted; +import com.lowdragmc.lowdraglib.syncdata.annotation.UpdateListener; +import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.GlobalPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import lombok.Getter; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class StarLadderResearchHubMachine extends LinkedWorkableElectricMultiblockMachine { + + protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( + StarLadderResearchHubMachine.class, + LinkedWorkableElectricMultiblockMachine.MANAGED_FIELD_HOLDER); + + private static final ResourceKey REQUIRED_DIMENSION = ResourceKey.create( + Registries.DIMENSION, new ResourceLocation("ad_astra", "earth_orbit")); + + @Persisted + @DescSynced + @UpdateListener(methodName = "onRingTierSynced") + @Getter + private int ringTier = 0; + + @Persisted + @DescSynced + @UpdateListener(methodName = "onRingPreviewSynced") + @Getter + private boolean ringPreviewEnabled = false; + + @Persisted + @DescSynced + @Getter + private int partialRingIndex = 0; + + public StarLadderResearchHubMachine(IMachineBlockEntity holder, Object... args) { + super(holder, args); + } + + @OnlyIn(Dist.CLIENT) + @SuppressWarnings("unused") + protected void onRingPreviewSynced(boolean newValue, boolean oldValue) { + if (newValue) { + RingUpgradePreviewRenderer.enablePreview(getPos(), getFrontFacing(), ringTier); + } else { + RingUpgradePreviewRenderer.disablePreview(getPos()); + } + } + + @OnlyIn(Dist.CLIENT) + @SuppressWarnings("unused") + protected void onRingTierSynced(int newValue, int oldValue) { + if (ringPreviewEnabled) { + RingUpgradePreviewRenderer.updatePreview(getPos(), getFrontFacing(), newValue); + } + } + + public void toggleRingPreview() { + // ringTier is 0-3, max tier is 3 + if (!isFormed() || ringTier >= 3) { + ringPreviewEnabled = false; + return; + } + ringPreviewEnabled = !ringPreviewEnabled; + } + + public boolean canUpgrade() { + // Can upgrade from 0 (to build T1) up through 2 (to build T3) + return isFormed() && ringTier < 3; + } + + public Block getNextRingBlock() { + if (!canUpgrade()) return null; + return RingUpgradePreviewRenderer.getRingBlock(ringTier + 1); + } + + public int getNextRingBlockCount() { + if (!canUpgrade()) return 0; + return RingUpgradePreviewRenderer.getRingBlockCount(ringTier + 1); + } + + public Set getNextRingPositions() { + if (!canUpgrade()) return Set.of(); + return RingUpgradePreviewRenderer.calculateRingPositions(getPos(), getFrontFacing(), ringTier + 1); + } + + public Map getNextRingPositionsWithBlocks() { + if (!canUpgrade()) return Map.of(); + return RingUpgradePreviewRenderer.calculateRingPositionsWithBlocks(getPos(), getFrontFacing(), ringTier + 1); + } + + public int autoBuildNextRing(Player player) { + if (!canUpgrade() || getLevel() == null) return 0; + + Map positionsWithBlocks = getNextRingPositionsWithBlocks(); + if (positionsWithBlocks.isEmpty()) return 0; + + boolean isCreative = player.isCreative(); + int placed = 0; + + for (Map.Entry entry : positionsWithBlocks.entrySet()) { + BlockPos pos = entry.getKey(); + Block targetBlock = entry.getValue(); + + if (!getLevel().isEmptyBlock(pos)) continue; + + if (!isCreative) { + // Find and consume a matching block from player inventory + ItemStack consumed = consumeBlockFromInventory(player, targetBlock); + if (consumed.isEmpty()) continue; // Try next position, might have different block type + } + + // Place the block + getLevel().setBlock(pos, targetBlock.defaultBlockState(), Block.UPDATE_ALL); + + // Play place sound + SoundType soundType = targetBlock.defaultBlockState().getSoundType(); + getLevel().playSound(null, pos, soundType.getPlaceSound(), SoundSource.BLOCKS, + (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F); + + placed++; + } + + return placed; + } + + private ItemStack consumeBlockFromInventory(Player player, Block targetBlock) { + for (int i = 0; i < player.getInventory().getContainerSize(); i++) { + ItemStack stack = player.getInventory().getItem(i); + if (stack.isEmpty()) continue; + + if (stack.getItem() instanceof BlockItem blockItem) { + if (blockItem.getBlock() == targetBlock) { + ItemStack consumed = stack.split(1); + if (stack.isEmpty()) { + player.getInventory().setItem(i, ItemStack.EMPTY); + } + return consumed; + } + } + } + return ItemStack.EMPTY; + } + + public int countBlocksInInventory(Player player, Block targetBlock) { + int count = 0; + for (int i = 0; i < player.getInventory().getContainerSize(); i++) { + ItemStack stack = player.getInventory().getItem(i); + if (stack.isEmpty()) continue; + + if (stack.getItem() instanceof BlockItem blockItem) { + if (blockItem.getBlock() == targetBlock) { + count += stack.getCount(); + } + } + } + return count; + } + + public int countEmptyRingPositions() { + if (!canUpgrade() || getLevel() == null) return 0; + + Map positions = getNextRingPositionsWithBlocks(); + int empty = 0; + for (BlockPos pos : positions.keySet()) { + if (getLevel().isEmptyBlock(pos)) { + empty++; + } + } + return empty; + } + + public Map countEmptyRingPositionsByBlock() { + if (!canUpgrade() || getLevel() == null) return Map.of(); + + Map positions = getNextRingPositionsWithBlocks(); + Map counts = new java.util.HashMap<>(); + for (Map.Entry entry : positions.entrySet()) { + if (getLevel().isEmptyBlock(entry.getKey())) { + counts.merge(entry.getValue(), 1, Integer::sum); + } + } + return counts; + } + + public Map getNextRingBlockCounts() { + if (!canUpgrade()) return Map.of(); + return RingUpgradePreviewRenderer.getDeltaBlockCounts(ringTier); + } + + @Override + public ManagedFieldHolder getFieldHolder() { + return MANAGED_FIELD_HOLDER; + } + + @Override + public void onStructureFormed() { + super.onStructureFormed(); + + // Use the tier from the pattern that matched during checkPattern() + this.ringTier = Math.max(0, lastMatchedTier); + this.partialRingIndex = 0; // No partial rings with strict pattern matching + + // Disable preview if max tier reached (T3 is max) + if (ringPreviewEnabled && ringTier >= 3) { + ringPreviewEnabled = false; + } + } + + @Override + public void onStructureInvalid() { + super.onStructureInvalid(); + this.ringTier = 0; + this.partialRingIndex = 0; + this.ringPreviewEnabled = false; + } + + @Override + protected InteractionResult onScrewdriverClick(Player playerIn, InteractionHand hand, + Direction gridSide, BlockHitResult hitResult) { + if (!isRemote()) { + if (playerIn.isShiftKeyDown()) { + // Shift+Screwdriver = Auto-build + return handleAutoBuild(playerIn); + } else { + // Normal Screwdriver = Toggle preview + return handlePreviewToggle(playerIn); + } + } + return super.onScrewdriverClick(playerIn, hand, gridSide, hitResult); + } + + @Override + protected InteractionResult onSoftMalletClick(Player playerIn, InteractionHand hand, + Direction gridSide, BlockHitResult hitResult) { + if (!isRemote()) { + if (playerIn.isShiftKeyDown()) { + // Shift+Soft Mallet = Debug auto-build T0 only (creative mode only) + return handleDebugBuildT0(playerIn); + } + } + return super.onSoftMalletClick(playerIn, hand, gridSide, hitResult); + } + + private InteractionResult handlePreviewToggle(Player player) { + if (canUpgrade()) { + toggleRingPreview(); + + if (ringPreviewEnabled) { + int totalCount = getNextRingBlockCount(); + player.displayClientMessage( + Component.literal("Ring Preview: ON") + .withStyle(ChatFormatting.GREEN) + .append(Component.literal(" (" + totalCount + " blocks needed)") + .withStyle(ChatFormatting.GRAY)), + true); + } else { + player.displayClientMessage( + Component.literal("Ring Preview: OFF").withStyle(ChatFormatting.RED), + true); + } + return InteractionResult.SUCCESS; + } else if (ringTier >= 3) { + player.displayClientMessage( + Component.literal("Maximum tier reached!").withStyle(ChatFormatting.GOLD), + true); + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + private InteractionResult handleAutoBuild(Player player) { + if (!canUpgrade()) { + if (ringTier >= 3) { + player.displayClientMessage( + Component.literal("Maximum tier reached!").withStyle(ChatFormatting.GOLD), + true); + } + return InteractionResult.SUCCESS; + } + + int needed = countEmptyRingPositions(); + + if (needed == 0) { + player.displayClientMessage( + Component.literal("Ring already complete! Break and rebuild structure to upgrade tier.") + .withStyle(ChatFormatting.YELLOW), + true); + return InteractionResult.SUCCESS; + } + + // Check if player has any of the required blocks (skip for creative) + if (!player.isCreative()) { + Map neededByBlock = countEmptyRingPositionsByBlock(); + boolean hasAnyBlocks = false; + for (Block block : neededByBlock.keySet()) { + if (countBlocksInInventory(player, block) > 0) { + hasAnyBlocks = true; + break; + } + } + + if (!hasAnyBlocks) { + player.displayClientMessage( + Component.literal("Missing blocks for next tier!").withStyle(ChatFormatting.RED), + true); + return InteractionResult.SUCCESS; + } + } + + int placed = autoBuildNextRing(player); + int remaining = countEmptyRingPositions(); + + if (remaining == 0) { + player.displayClientMessage( + Component.literal("Ring complete! ").withStyle(ChatFormatting.GREEN) + .append(Component.literal("Placed " + placed + " blocks.") + .withStyle(ChatFormatting.WHITE)), + true); + // Play a completion sound + getLevel().playSound(null, getPos(), SoundEvents.PLAYER_LEVELUP, SoundSource.BLOCKS, 0.5F, 1.0F); + + // Force structure re-evaluation by invalidating first, then rechecking + // This ensures onStructureFormed() is called again with the new tier + onStructureInvalid(); + getMultiblockState().setError(null); + if (checkPattern()) { + onStructureFormed(); + } + } else { + player.displayClientMessage( + Component.literal("Placed " + placed + " blocks. ").withStyle(ChatFormatting.YELLOW) + .append(Component.literal(remaining + " positions remaining.") + .withStyle(ChatFormatting.GRAY)), + true); + } + + return InteractionResult.SUCCESS; + } + + /** + * Auto-build T0 structure for easier testing. + * Only works in creative mode. Use Shift+Soft Mallet to trigger. + */ + private InteractionResult handleDebugBuildT0(Player player) { + if (!player.isCreative()) { + player.displayClientMessage( + Component.literal("Creative mode only!").withStyle(ChatFormatting.RED), + true); + return InteractionResult.SUCCESS; + } + + if (getLevel() == null) return InteractionResult.PASS; + + Map t0Positions = RingUpgradePreviewRenderer.calculateRingPositionsWithBlocks( + getPos(), getFrontFacing(), 0); + + if (t0Positions.isEmpty()) { + player.displayClientMessage( + Component.literal("No T0 positions found!").withStyle(ChatFormatting.RED), + true); + return InteractionResult.SUCCESS; + } + + int placed = 0; + int skipped = 0; + for (Map.Entry entry : t0Positions.entrySet()) { + BlockPos pos = entry.getKey(); + Block targetBlock = entry.getValue(); + + if (!getLevel().isEmptyBlock(pos)) { + skipped++; + continue; + } + + getLevel().setBlock(pos, targetBlock.defaultBlockState(), Block.UPDATE_ALL); + + SoundType soundType = targetBlock.defaultBlockState().getSoundType(); + getLevel().playSound(null, pos, soundType.getPlaceSound(), SoundSource.BLOCKS, + (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F); + + placed++; + } + + player.displayClientMessage( + Component.literal("Built T0: ").withStyle(ChatFormatting.LIGHT_PURPLE) + .append(Component.literal(placed + " blocks placed, " + skipped + " skipped") + .withStyle(ChatFormatting.WHITE)), + true); + + getLevel().playSound(null, getPos(), SoundEvents.PLAYER_LEVELUP, SoundSource.BLOCKS, 0.5F, 1.2F); + + // Force structure re-evaluation + onStructureInvalid(); + getMultiblockState().setError(null); + if (checkPattern()) { + onStructureFormed(); + } + + return InteractionResult.SUCCESS; + } + + // Cache of tier patterns for checkPattern - built lazily + private static BlockPattern[] tierPatterns; + + // Tracks which tier pattern matched during the last successful checkPattern() + // This is used by onStructureFormed() to set the correct tier + private int lastMatchedTier = -1; + + private static BlockPattern[] getTierPatterns() { + if (tierPatterns == null) { + tierPatterns = new BlockPattern[] { + StarLadderResearchHub.buildT0Pattern(), + StarLadderResearchHub.buildT1Pattern(), + StarLadderResearchHub.buildT2Pattern(), + StarLadderResearchHub.buildT3Pattern() + }; + } + return tierPatterns; + } + + @Override + public boolean checkPattern() { + // TODO: Re-enable dimension check after testing + // Check dimension requirement + // if (getLevel() != null && !getLevel().dimension().equals(REQUIRED_DIMENSION)) { + // return false; + // } + + // Try each tier pattern from T3 to T0 (highest first) + // The highest tier that matches determines the structure's tier + var state = getMultiblockState(); + + for (int tier = 3; tier >= 0; tier--) { + BlockPattern pattern = getTierPatterns()[tier]; + if (pattern.checkPatternAt(state, false)) { + lastMatchedTier = tier; + return true; + } + state.setError(null); + } + + lastMatchedTier = -1; + return false; + } + + @Override + public LinkRole getLinkRole() { + return LinkRole.REMOTE; + } + + @Override + public int getMaxPartners() { + return 1; + } + + @Override + public boolean canLinkTo(GlobalPos partner, ILinkedMultiblock partnerMachine) { + return partnerMachine instanceof StarLadderMachine; + } + + @Override + public void addDisplayText(List textList) { + super.addDisplayText(textList); + if (!isFormed()) return; + + // Display tier (0-3) + textList.add(Component.literal("Tier: T" + ringTier) + .withStyle(ChatFormatting.AQUA)); + + // Display upgrade info if not at max tier + if (canUpgrade()) { + // Show what's needed for next tier + Map neededByBlock = countEmptyRingPositionsByBlock(); + if (!neededByBlock.isEmpty()) { + textList.add(Component.literal("Next Tier (T" + (ringTier + 1) + ") Needs:") + .withStyle(ChatFormatting.GRAY)); + for (Map.Entry entry : neededByBlock.entrySet()) { + textList.add(Component.literal(" " + entry.getValue() + "x ") + .withStyle(ChatFormatting.WHITE) + .append(entry.getKey().getName().copy().withStyle(ChatFormatting.YELLOW))); + } + } + + if (ringPreviewEnabled) { + textList.add(Component.literal(" [Preview: ON]") + .withStyle(ChatFormatting.GREEN)); + } else { + textList.add(Component.literal(" [Screwdriver: preview | Shift: build]") + .withStyle(ChatFormatting.DARK_GRAY)); + } + } else if (ringTier >= 3) { + textList.add(Component.literal("Maximum tier reached!") + .withStyle(ChatFormatting.GOLD)); + } + + GlobalPos ladder = getLinkedPartners().stream().findFirst().orElse(null); + if (ladder == null) { + textList.add(Component.literal("Not linked to Star Ladder").withStyle(ChatFormatting.GRAY)); + return; + } + + boolean online = getPartnerMachine(ladder) != null; + textList.add(Component.literal("Star Ladder: " + (online ? "Online" : "Offline")) + .withStyle(online ? ChatFormatting.GREEN : ChatFormatting.RED)); + textList.add(Component.literal(" " + LinkedMultiblockHelper.getDimensionName(ladder.dimension().location())) + .withStyle(ChatFormatting.GRAY)); + textList.add(Component.literal(" [%d, %d, %d]".formatted( + ladder.pos().getX(), ladder.pos().getY(), ladder.pos().getZ())) + .withStyle(ChatFormatting.GRAY)); + } +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java index 5d347434b..06a75602f 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/machine/multiblock/multi/modular/MultiblockInit.java @@ -67,6 +67,7 @@ public static void init() { DivingBell.init(); LinkTestStation.init(); StarLadder.init(); + StarLadderResearchHub.init(); // KryosynCrackingChamber.init(); // Cargo Moths System diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/reflection/item/CapacityShardBehavior.java b/src/main/java/com/ghostipedia/cosmiccore/common/reflection/item/CapacityShardBehavior.java index 6df58a751..f11d558f0 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/reflection/item/CapacityShardBehavior.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/reflection/item/CapacityShardBehavior.java @@ -43,7 +43,9 @@ public InteractionResultHolder use(Item item, Level level, Player pla return ReflectionCapability.get(player).map(reflection -> { if (!reflection.hasAwakened()) { player.displayClientMessage( - Component.literal("The shard feels... dormant. Perhaps after you've seen yourself in the mirror.") + Component + .literal( + "The shard feels... dormant. Perhaps after you've seen yourself in the mirror.") .withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC), true); return InteractionResultHolder.fail(stack); diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/reflection/item/ScarRemovalBehavior.java b/src/main/java/com/ghostipedia/cosmiccore/common/reflection/item/ScarRemovalBehavior.java index 4492c5aff..617a5ed14 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/reflection/item/ScarRemovalBehavior.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/reflection/item/ScarRemovalBehavior.java @@ -32,8 +32,7 @@ */ public class ScarRemovalBehavior implements IInteractionItem, IAddInformation { - public ScarRemovalBehavior() { - } + public ScarRemovalBehavior() {} @Override public InteractionResultHolder use(Item item, Level level, Player player, InteractionHand hand) { @@ -51,7 +50,8 @@ public InteractionResultHolder use(Item item, Level level, Player pla return ReflectionCapability.get(player).map(reflection -> { if (!reflection.hasAwakened()) { player.displayClientMessage( - Component.literal("The cluster feels... dormant. Perhaps after you've seen yourself in the mirror.") + Component.literal( + "The cluster feels... dormant. Perhaps after you've seen yourself in the mirror.") .withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC), true); return InteractionResultHolder.fail(stack); diff --git a/src/main/java/com/ghostipedia/cosmiccore/common/reflection/ui/ScarSelectionScreen.java b/src/main/java/com/ghostipedia/cosmiccore/common/reflection/ui/ScarSelectionScreen.java index cc445582e..24176de40 100644 --- a/src/main/java/com/ghostipedia/cosmiccore/common/reflection/ui/ScarSelectionScreen.java +++ b/src/main/java/com/ghostipedia/cosmiccore/common/reflection/ui/ScarSelectionScreen.java @@ -84,7 +84,8 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTi graphics.drawCenteredString(font, title, width / 2, listStartY - 30, withAlpha(0xFFBB99DD, 1f)); // Subtitle - graphics.drawCenteredString(font, "Choose which scar to heal", width / 2, listStartY - 16, withAlpha(0xFF888888, 1f)); + graphics.drawCenteredString(font, "Choose which scar to heal", width / 2, listStartY - 16, + withAlpha(0xFF888888, 1f)); // Entries hoveredIndex = -1; @@ -98,8 +99,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTi int x = (width - ENTRY_WIDTH) / 2; int y = listStartY + i * (ENTRY_HEIGHT + ENTRY_SPACING); - boolean hovered = mouseX >= x && mouseX < x + ENTRY_WIDTH - && mouseY >= y && mouseY < y + ENTRY_HEIGHT; + boolean hovered = mouseX >= x && mouseX < x + ENTRY_WIDTH && mouseY >= y && mouseY < y + ENTRY_HEIGHT; if (hovered) hoveredIndex = entryIndex; renderEntry(graphics, entry, x, y, hovered); diff --git a/src/main/java/com/ghostipedia/cosmiccore/mixin/accessor/MBPatternAccessor.java b/src/main/java/com/ghostipedia/cosmiccore/mixin/accessor/MBPatternAccessor.java new file mode 100644 index 000000000..4a9f5a16b --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/mixin/accessor/MBPatternAccessor.java @@ -0,0 +1,21 @@ +package com.ghostipedia.cosmiccore.mixin.accessor; + +import com.gregtechceu.gtceu.api.gui.widget.PatternPreviewWidget; +import com.gregtechceu.gtceu.api.pattern.TraceabilityPredicate; + +import net.minecraft.core.BlockPos; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +/** + * Accessor mixin to access package-private fields in MBPattern inner class. + */ +@Mixin(value = PatternPreviewWidget.MBPattern.class, remap = false) +public interface MBPatternAccessor { + + @Accessor("predicateMap") + Map cosmiccore$getPredicateMap(); +} diff --git a/src/main/java/com/ghostipedia/cosmiccore/mixin/client/PatternPreviewWidgetMixin.java b/src/main/java/com/ghostipedia/cosmiccore/mixin/client/PatternPreviewWidgetMixin.java new file mode 100644 index 000000000..f6c006ab7 --- /dev/null +++ b/src/main/java/com/ghostipedia/cosmiccore/mixin/client/PatternPreviewWidgetMixin.java @@ -0,0 +1,52 @@ +package com.ghostipedia.cosmiccore.mixin.client; + +import com.ghostipedia.cosmiccore.mixin.accessor.MBPatternAccessor; +import com.gregtechceu.gtceu.api.gui.widget.PatternPreviewWidget; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Mixin to fix NPE crash when clicking on blocks in EMI multiblock preview + * when the pattern uses predicates that accept air (for tiered multiblocks). + * + * The crash occurs because patterns[index].predicateMap is null when the + * pattern's checkPatternAt() fails during preview generation (which happens + * when tier-detecting predicates accept air instead of actual blocks). + */ +@Mixin(value = PatternPreviewWidget.class, remap = false) +public abstract class PatternPreviewWidgetMixin { + + @Shadow + @Final + public PatternPreviewWidget.MBPattern[] patterns; + + @Shadow + private int index; + + /** + * Inject at the head of onPosSelected to bail out early if predicateMap is null. + * This prevents the NPE crash while still allowing the preview to render. + */ + @Inject(method = "onPosSelected", at = @At("HEAD"), cancellable = true) + private void cosmiccore$preventNullPredicateMapCrash(BlockPos pos, Direction facing, CallbackInfo ci) { + if (patterns != null && index >= 0 && index < patterns.length) { + PatternPreviewWidget.MBPattern pattern = patterns[index]; + if (pattern != null) { + // Use accessor to check if predicateMap is null + var predicateMap = ((MBPatternAccessor) (Object) pattern).cosmiccore$getPredicateMap(); + if (predicateMap == null) { + // predicateMap is null, bail out to prevent NPE + ci.cancel(); + } + } + } + } +} diff --git a/src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t1.png b/src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t1.png new file mode 100644 index 0000000000000000000000000000000000000000..274c1bd88604eb474f573e9b4f6b8ffebdcc6dc9 GIT binary patch literal 419 zcmV;U0bKrxP)_2X_yN8G z37XQ&nJ~+!xu?+GnK@@3yK8B!H`jI55$n3DU-*1JW_`I_lml`c$0|t@GTv^tU0_vJ zJAq*s=s!)9s_R-g*l4X)Q52+DmL&y1*s?6OZJQxuzbp$?m!>J%9Cm^L2b8lcGbG>l z=?|f%X~-W%5s3jdKKJ{b1mJwX-zPEy{@3fJuGgyyg5X=hx~|kON;_soR&L|@d>TJQ z7&cE+_A4_p`FKJYCU`as7#t7H^SoW~k&Nteu(ou{fiYPIbLp~LM_vr>8JOpJx0ijR z+As{i0_p(SGiQ<~BLN{Bi?(gafOCF49=jk(9>>wdo@THt%jq7-vH|wXcS0@T9duns zHw>2qL=NIPpj^bkeiA@gd7hJ@?|aib59$}V6${TuNMFS literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t2.png b/src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t2.png new file mode 100644 index 0000000000000000000000000000000000000000..274c1bd88604eb474f573e9b4f6b8ffebdcc6dc9 GIT binary patch literal 419 zcmV;U0bKrxP)_2X_yN8G z37XQ&nJ~+!xu?+GnK@@3yK8B!H`jI55$n3DU-*1JW_`I_lml`c$0|t@GTv^tU0_vJ zJAq*s=s!)9s_R-g*l4X)Q52+DmL&y1*s?6OZJQxuzbp$?m!>J%9Cm^L2b8lcGbG>l z=?|f%X~-W%5s3jdKKJ{b1mJwX-zPEy{@3fJuGgyyg5X=hx~|kON;_soR&L|@d>TJQ z7&cE+_A4_p`FKJYCU`as7#t7H^SoW~k&Nteu(ou{fiYPIbLp~LM_vr>8JOpJx0ijR z+As{i0_p(SGiQ<~BLN{Bi?(gafOCF49=jk(9>>wdo@THt%jq7-vH|wXcS0@T9duns zHw>2qL=NIPpj^bkeiA@gd7hJ@?|aib59$}V6${TuNMFS literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t3.png b/src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t3.png new file mode 100644 index 0000000000000000000000000000000000000000..274c1bd88604eb474f573e9b4f6b8ffebdcc6dc9 GIT binary patch literal 419 zcmV;U0bKrxP)_2X_yN8G z37XQ&nJ~+!xu?+GnK@@3yK8B!H`jI55$n3DU-*1JW_`I_lml`c$0|t@GTv^tU0_vJ zJAq*s=s!)9s_R-g*l4X)Q52+DmL&y1*s?6OZJQxuzbp$?m!>J%9Cm^L2b8lcGbG>l z=?|f%X~-W%5s3jdKKJ{b1mJwX-zPEy{@3fJuGgyyg5X=hx~|kON;_soR&L|@d>TJQ z7&cE+_A4_p`FKJYCU`as7#t7H^SoW~k&Nteu(ou{fiYPIbLp~LM_vr>8JOpJx0ijR z+As{i0_p(SGiQ<~BLN{Bi?(gafOCF49=jk(9>>wdo@THt%jq7-vH|wXcS0@T9duns zHw>2qL=NIPpj^bkeiA@gd7hJ@?|aib59$}V6${TuNMFS literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t4.png b/src/main/resources/assets/cosmiccore/textures/block/casings/moth/moth_home_t4.png new file mode 100644 index 0000000000000000000000000000000000000000..274c1bd88604eb474f573e9b4f6b8ffebdcc6dc9 GIT binary patch literal 419 zcmV;U0bKrxP)_2X_yN8G z37XQ&nJ~+!xu?+GnK@@3yK8B!H`jI55$n3DU-*1JW_`I_lml`c$0|t@GTv^tU0_vJ zJAq*s=s!)9s_R-g*l4X)Q52+DmL&y1*s?6OZJQxuzbp$?m!>J%9Cm^L2b8lcGbG>l z=?|f%X~-W%5s3jdKKJ{b1mJwX-zPEy{@3fJuGgyyg5X=hx~|kON;_soR&L|@d>TJQ z7&cE+_A4_p`FKJYCU`as7#t7H^SoW~k&Nteu(ou{fiYPIbLp~LM_vr>8JOpJx0ijR z+As{i0_p(SGiQ<~BLN{Bi?(gafOCF49=jk(9>>wdo@THt%jq7-vH|wXcS0@T9duns zHw>2qL=NIPpj^bkeiA@gd7hJ@?|aib59$}V6${TuNMFS literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/cosmiccore/textures/block/casings/solid/moth_station_casing.png b/src/main/resources/assets/cosmiccore/textures/block/casings/solid/moth_station_casing.png new file mode 100644 index 0000000000000000000000000000000000000000..1d0ac9c6ce4ea400a509f934270096387ebb6c32 GIT binary patch literal 444 zcmV;t0YmFFcd|PX@~L{vPgA`?Qe)X7k+{pzhFMoo%kQ3 z$W+VFfOQOoHoB;dtuqYXO$o_;@AT#7WW8R0Ip=WB@mFP8k|YUfn(pn12+lcT=Nwg4 zp|!?Z8xHh+&tkE_dylo2IF7@fF^0`%Ls1k%0j)J65;`5C`K#4xH4p$;YY`D1?(YCl zN&yf>YC_}z@ZNKAc}3GShzRkts#1#Eo9nRt_!vkwuUp=mPXHdD${As3AtE!xL*_$L z171HvK4u=z>~_1MKv|Z+@8FJ^i}Tajx~^+fk|cXr|1kr!ZHta3x{|<4~ zF>{fh(f1w^A&Mg4XNb$?5)r|Bf7CqHn9Puaafpvk!%(A0@%-{O`#oTxe~!s;V%?&@@e`Jl^wmyZs@G0=h8 mG>#N)+mh$`47=;P;Q0pTMa_ul;Mc|g0000d~ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/cosmiccore/textures/block/diving_bell_escape_pad.png b/src/main/resources/assets/cosmiccore/textures/block/diving_bell_escape_pad.png new file mode 100644 index 0000000000000000000000000000000000000000..274c1bd88604eb474f573e9b4f6b8ffebdcc6dc9 GIT binary patch literal 419 zcmV;U0bKrxP)_2X_yN8G z37XQ&nJ~+!xu?+GnK@@3yK8B!H`jI55$n3DU-*1JW_`I_lml`c$0|t@GTv^tU0_vJ zJAq*s=s!)9s_R-g*l4X)Q52+DmL&y1*s?6OZJQxuzbp$?m!>J%9Cm^L2b8lcGbG>l z=?|f%X~-W%5s3jdKKJ{b1mJwX-zPEy{@3fJuGgyyg5X=hx~|kON;_soR&L|@d>TJQ z7&cE+_A4_p`FKJYCU`as7#t7H^SoW~k&Nteu(ou{fiYPIbLp~LM_vr>8JOpJx0ijR z+As{i0_p(SGiQ<~BLN{Bi?(gafOCF49=jk(9>>wdo@THt%jq7-vH|wXcS0@T9duns zHw>2qL=NIPpj^bkeiA@gd7hJ@?|aib59$}V6${TuNMFS literal 0 HcmV?d00001 diff --git a/src/main/resources/cosmiccore.mixins.json b/src/main/resources/cosmiccore.mixins.json index ff9102442..9ff9de07c 100644 --- a/src/main/resources/cosmiccore.mixins.json +++ b/src/main/resources/cosmiccore.mixins.json @@ -7,8 +7,10 @@ "plugin": "com.ghostipedia.cosmiccore.mixin.CosmicCoreMixinPlugin", "client": [ "StarKillerMixin", + "accessor.MBPatternAccessor", "client.AdAstraPlanetsScreenMixin", "client.MinecraftMixin", + "client.PatternPreviewWidgetMixin", "client.SoundManagerMixin", "embers.EmberDialBlockMixin", "emi.EmiApiMixin",