From d1ba26311bdf10855a729e031e95ca57d8c6fc1e Mon Sep 17 00:00:00 2001 From: shartte Date: Mon, 10 Aug 2020 11:29:07 +0200 Subject: [PATCH] One Spatial Dimension (#4570) * Ported the "one spatial dimension" concept from pre-1.15 * Apply lighting updates after moving chunks. Mark world data as dirty when changing it. * Consolidated naming of spatial storage world/dimension. Use server light manager to update lighting info in chunk. * Save last transition information, and add ae2 spatial command to interact with spatial storage. * Formatting fixes. * Improved docs for the origin generation. Inverted condition on when the x/z axis are flipped. Also added a helper to map a plot to a region file * Remove TransitionResult since it's only used in lieu of a boolean. Rework transition to make the checks BEFORE allocating a new cell. * Removed unused imports. Co-authored-by: yueh --- .../api/implementations/TransitionResult.java | 38 -- .../items/ISpatialStorageCell.java | 13 +- .../appeng/api/storage/ISpatialDimension.java | 51 --- .../block/AEBaseBlockItemChargeable.java | 2 - .../bootstrap/ItemDefinitionBuilder.java | 4 - src/main/java/appeng/core/Registration.java | 13 +- .../java/appeng/hooks/DynamicDimensions.java | 14 - .../appeng/items/misc/CrystalSeedItem.java | 2 - .../items/storage/SpatialStorageCellItem.java | 121 ++++--- .../tools/powered/ColorApplicatorItem.java | 2 - .../appeng/mixins/MinecraftServerMixin.java | 105 ------ .../mixins/spatial/DimensionOptionMixin.java | 38 ++ .../{ => spatial}/DimensionTypeMixin.java | 11 +- .../{ => spatial}/SkyPropertiesMixin.java | 10 +- .../mixins/{ => spatial}/SkyRenderMixin.java | 6 +- .../IdentityAnnihilationPlanePart.java | 4 - .../handlers/GrinderRecipeSerializer.java | 1 - .../handlers/InscriberRecipeSerializer.java | 1 - src/main/java/appeng/server/Commands.java | 4 +- .../subcommands/SpatialStorageCommand.java | 327 ++++++++++++++++++ src/main/java/appeng/spatial/CachedPlane.java | 30 +- .../spatial/SpatialDimensionExtraData.java | 70 ---- .../spatial/SpatialDimensionManager.java | 201 ----------- ...ellBiome.java => SpatialStorageBiome.java} | 9 +- ...java => SpatialStorageChunkGenerator.java} | 13 +- .../spatial/SpatialStorageDimensionIds.java | 57 +++ ...eHelper.java => SpatialStorageHelper.java} | 12 +- .../appeng/spatial/SpatialStoragePlot.java | 189 ++++++++++ .../spatial/SpatialStoragePlotManager.java | 118 +++++++ ....java => SpatialStorageSkyProperties.java} | 5 +- .../spatial/SpatialStorageWorldData.java | 110 ++++++ .../java/appeng/spatial/TransitionInfo.java | 69 ++++ .../tile/spatial/SpatialIOPortTileEntity.java | 13 +- .../meteorite/MeteoriteStructurePiece.java | 6 + .../resources/appliedenergistics2.mixins.json | 8 +- .../appliedenergistics2/spatial_storage.json | 6 + 36 files changed, 1073 insertions(+), 610 deletions(-) delete mode 100644 src/api/java/appeng/api/implementations/TransitionResult.java delete mode 100644 src/api/java/appeng/api/storage/ISpatialDimension.java delete mode 100644 src/main/java/appeng/hooks/DynamicDimensions.java delete mode 100644 src/main/java/appeng/mixins/MinecraftServerMixin.java create mode 100644 src/main/java/appeng/mixins/spatial/DimensionOptionMixin.java rename src/main/java/appeng/mixins/{ => spatial}/DimensionTypeMixin.java (68%) rename src/main/java/appeng/mixins/{ => spatial}/SkyPropertiesMixin.java (71%) rename src/main/java/appeng/mixins/{ => spatial}/SkyRenderMixin.java (84%) create mode 100644 src/main/java/appeng/server/subcommands/SpatialStorageCommand.java delete mode 100644 src/main/java/appeng/spatial/SpatialDimensionExtraData.java delete mode 100644 src/main/java/appeng/spatial/SpatialDimensionManager.java rename src/main/java/appeng/spatial/{StorageCellBiome.java => SpatialStorageBiome.java} (88%) rename src/main/java/appeng/spatial/{StorageChunkGenerator.java => SpatialStorageChunkGenerator.java} (90%) create mode 100644 src/main/java/appeng/spatial/SpatialStorageDimensionIds.java rename src/main/java/appeng/spatial/{StorageHelper.java => SpatialStorageHelper.java} (96%) create mode 100644 src/main/java/appeng/spatial/SpatialStoragePlot.java create mode 100644 src/main/java/appeng/spatial/SpatialStoragePlotManager.java rename src/main/java/appeng/spatial/{StorageSkyProperties.java => SpatialStorageSkyProperties.java} (88%) create mode 100644 src/main/java/appeng/spatial/SpatialStorageWorldData.java create mode 100644 src/main/java/appeng/spatial/TransitionInfo.java create mode 100644 src/main/resources/data/minecraft/dimension/appliedenergistics2/spatial_storage.json diff --git a/src/api/java/appeng/api/implementations/TransitionResult.java b/src/api/java/appeng/api/implementations/TransitionResult.java deleted file mode 100644 index 209524a49..000000000 --- a/src/api/java/appeng/api/implementations/TransitionResult.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2013 AlgorithmX2 - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package appeng.api.implementations; - -/** - * Defines the result of performing a transition from the world into a storage - * cell, if its possible, and what the energy usage is. - */ -public class TransitionResult { - public final boolean success; - public final double energyUsage; - - public TransitionResult(final boolean success, final double power) { - this.success = success; - this.energyUsage = power; - } -} diff --git a/src/api/java/appeng/api/implementations/items/ISpatialStorageCell.java b/src/api/java/appeng/api/implementations/items/ISpatialStorageCell.java index 58d98fd7f..62279b1f2 100644 --- a/src/api/java/appeng/api/implementations/items/ISpatialStorageCell.java +++ b/src/api/java/appeng/api/implementations/items/ISpatialStorageCell.java @@ -25,11 +25,8 @@ package appeng.api.implementations.items; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.util.RegistryKey; -import net.minecraft.world.World; import net.minecraft.world.server.ServerWorld; -import appeng.api.implementations.TransitionResult; import appeng.api.util.WorldCoord; /** @@ -52,13 +49,13 @@ public interface ISpatialStorageCell { int getMaxStoredDim(ItemStack is); /** - * get the currently stored Dimension id. + * get the currently stored spatial storage plot id. * * @param is spatial storage cell * - * @return dimension id or -1 + * @return plot id or -1 */ - RegistryKey getStoredDimension(ItemStack is); + int getAllocatedPlotId(ItemStack is); /** * Perform a spatial swap with the contents of the cell, and the world. @@ -69,7 +66,7 @@ public interface ISpatialStorageCell { * @param max max coord * @param playerId owner of current grid or -1 * - * @return result of transition + * @return success of transition */ - TransitionResult doSpatialTransition(ItemStack is, ServerWorld w, WorldCoord min, WorldCoord max, int playerId); + boolean doSpatialTransition(ItemStack is, ServerWorld w, WorldCoord min, WorldCoord max, int playerId); } \ No newline at end of file diff --git a/src/api/java/appeng/api/storage/ISpatialDimension.java b/src/api/java/appeng/api/storage/ISpatialDimension.java deleted file mode 100644 index b3e3ca2aa..000000000 --- a/src/api/java/appeng/api/storage/ISpatialDimension.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2017, AlgorithmX2, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -package appeng.api.storage; - -import java.util.List; - -import javax.annotation.Nullable; - -import net.minecraft.util.RegistryKey; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.world.World; -import net.minecraft.world.server.ServerWorld; - -public interface ISpatialDimension { - ServerWorld getWorld(RegistryKey cellDim); - - @Nullable - RegistryKey createNewCellDimension(BlockPos capacity); - - void deleteCellDimension(RegistryKey cellDim); - - boolean isCellDimension(RegistryKey cellDim); - - BlockPos getCellDimensionOrigin(RegistryKey cellDim); - - BlockPos getCellDimensionSize(RegistryKey cellDim); - - /** - * Adds a user-facing tooltip that describes this dimension so that a player can - * keep storage cells apart. - */ - void addCellDimensionTooltip(RegistryKey cellDim, List lines); - -} diff --git a/src/main/java/appeng/block/AEBaseBlockItemChargeable.java b/src/main/java/appeng/block/AEBaseBlockItemChargeable.java index 79927fa14..dade7caec 100644 --- a/src/main/java/appeng/block/AEBaseBlockItemChargeable.java +++ b/src/main/java/appeng/block/AEBaseBlockItemChargeable.java @@ -23,10 +23,8 @@ import java.util.List; import net.minecraft.block.Block; import net.minecraft.client.util.ITooltipFlag; -import net.minecraft.item.ItemModelsProperties; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; -import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TranslationTextComponent; import net.minecraft.world.World; diff --git a/src/main/java/appeng/bootstrap/ItemDefinitionBuilder.java b/src/main/java/appeng/bootstrap/ItemDefinitionBuilder.java index d7ee453a1..cd4f37a6d 100644 --- a/src/main/java/appeng/bootstrap/ItemDefinitionBuilder.java +++ b/src/main/java/appeng/bootstrap/ItemDefinitionBuilder.java @@ -30,14 +30,10 @@ import net.minecraft.block.DispenserBlock; import net.minecraft.dispenser.IDispenseItemBehavior; import net.minecraft.item.Item; import net.minecraft.item.ItemGroup; -import net.minecraft.item.ItemModelsProperties; -import net.minecraft.util.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import appeng.api.features.AEFeature; -import appeng.block.AEBaseBlockItemChargeable; -import appeng.bootstrap.components.IClientSetupComponent; import appeng.bootstrap.components.IInitComponent; import appeng.bootstrap.components.IItemRegistrationComponent; import appeng.core.AEItemGroup; diff --git a/src/main/java/appeng/core/Registration.java b/src/main/java/appeng/core/Registration.java index cdced766c..882e56df1 100644 --- a/src/main/java/appeng/core/Registration.java +++ b/src/main/java/appeng/core/Registration.java @@ -220,13 +220,15 @@ import appeng.recipes.game.FacadeRecipe; import appeng.recipes.handlers.GrinderRecipeSerializer; import appeng.recipes.handlers.InscriberRecipeSerializer; import appeng.server.AECommand; -import appeng.spatial.StorageCellBiome; -import appeng.spatial.StorageChunkGenerator; +import appeng.spatial.SpatialStorageBiome; +import appeng.spatial.SpatialStorageChunkGenerator; +import appeng.spatial.SpatialStorageDimensionIds; import appeng.tile.AEBaseTileEntity; import appeng.tile.crafting.MolecularAssemblerRenderer; import appeng.worldgen.ChargedQuartzOreConfig; import appeng.worldgen.ChargedQuartzOreFeature; import appeng.worldgen.meteorite.MeteoriteStructure; +import appeng.worldgen.meteorite.MeteoriteStructurePiece; final class Registration { @@ -680,6 +682,8 @@ final class Registration { new ResourceLocation(dimension)); } + MeteoriteStructurePiece.register(); + ForgeRegistries.BIOMES.forEach(b -> { addMeteoriteWorldGen(b); addQuartzWorldGen(b); @@ -739,11 +743,12 @@ final class Registration { MeteoriteStructure.INSTANCE.setRegistryName(MeteoriteStructure.ID), GenerationStage.Decoration.TOP_LAYER_MODIFICATION); - Registry.register(Registry.CHUNK_GENERATOR_CODEC, AppEng.makeId("storage"), StorageChunkGenerator.CODEC); + Registry.register(Registry.CHUNK_GENERATOR_CODEC, SpatialStorageDimensionIds.CHUNK_GENERATOR_ID, + SpatialStorageChunkGenerator.CODEC); } public void registerBiomes(RegistryEvent.Register evt) { - evt.getRegistry().register(StorageCellBiome.INSTANCE.setRegistryName(AppEng.makeId("storage"))); + evt.getRegistry().register(SpatialStorageBiome.INSTANCE.setRegistryName(SpatialStorageDimensionIds.BIOME_ID)); } @OnlyIn(Dist.CLIENT) diff --git a/src/main/java/appeng/hooks/DynamicDimensions.java b/src/main/java/appeng/hooks/DynamicDimensions.java deleted file mode 100644 index 92e4b8097..000000000 --- a/src/main/java/appeng/hooks/DynamicDimensions.java +++ /dev/null @@ -1,14 +0,0 @@ -package appeng.hooks; - -import net.minecraft.util.RegistryKey; -import net.minecraft.world.DimensionType; -import net.minecraft.world.World; -import net.minecraft.world.gen.ChunkGenerator; -import net.minecraft.world.server.ServerWorld; - -public interface DynamicDimensions { - - ServerWorld addWorld(RegistryKey worldId, RegistryKey dimensionTypeId, - ChunkGenerator chunkGenerator); - -} diff --git a/src/main/java/appeng/items/misc/CrystalSeedItem.java b/src/main/java/appeng/items/misc/CrystalSeedItem.java index 5be256996..1d8b6a3c5 100644 --- a/src/main/java/appeng/items/misc/CrystalSeedItem.java +++ b/src/main/java/appeng/items/misc/CrystalSeedItem.java @@ -29,12 +29,10 @@ import net.minecraft.block.material.Material; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.Entity; import net.minecraft.item.ItemGroup; -import net.minecraft.item.ItemModelsProperties; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; import net.minecraft.util.IItemProvider; import net.minecraft.util.NonNullList; -import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.StringTextComponent; diff --git a/src/main/java/appeng/items/storage/SpatialStorageCellItem.java b/src/main/java/appeng/items/storage/SpatialStorageCellItem.java index 68cba4bdd..530b43b60 100644 --- a/src/main/java/appeng/items/storage/SpatialStorageCellItem.java +++ b/src/main/java/appeng/items/storage/SpatialStorageCellItem.java @@ -18,35 +18,40 @@ package appeng.items.storage; +import java.time.Instant; import java.util.List; +import java.util.Locale; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; -import net.minecraft.util.RegistryKey; -import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.registry.Registry; import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import net.minecraft.world.server.ServerWorld; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.util.Constants; -import appeng.api.implementations.TransitionResult; import appeng.api.implementations.items.ISpatialStorageCell; -import appeng.api.storage.ISpatialDimension; import appeng.api.util.WorldCoord; import appeng.core.AELog; import appeng.core.localization.GuiText; import appeng.items.AEBaseItem; -import appeng.spatial.SpatialDimensionManager; -import appeng.spatial.StorageHelper; +import appeng.spatial.SpatialStorageHelper; +import appeng.spatial.SpatialStoragePlot; +import appeng.spatial.SpatialStoragePlotManager; +import appeng.spatial.TransitionInfo; public class SpatialStorageCellItem extends AEBaseItem implements ISpatialStorageCell { - private static final String TAG_DIMENSION_ID = "dimension_id"; + private static final String TAG_PLOT_ID = "plot_id"; + + /** + * This is only stored in the itemstack to display in the tooltip on the + * client-side. + */ + private static final String TAG_PLOT_SIZE = "plot_size"; private final int maxRegion; @@ -59,18 +64,22 @@ public class SpatialStorageCellItem extends AEBaseItem implements ISpatialStorag @Override public void addInformation(final ItemStack stack, final World world, final List lines, final ITooltipFlag advancedTooltips) { - final RegistryKey worldId = this.getStoredDimension(stack); - if (worldId == null) { + int plotId = this.getAllocatedPlotId(stack); + if (plotId == -1) { lines.add(GuiText.Unformatted.text().deepCopy().mergeStyle(TextFormatting.ITALIC)); lines.add(GuiText.SpatialCapacity.text(maxRegion, maxRegion, maxRegion)); - } else { - SpatialDimensionManager.INSTANCE.addCellDimensionTooltip(worldId, lines); + return; } - if (advancedTooltips.isAdvanced()) { - if (worldId != null && worldId.func_240901_a_() != null) { - lines.add(new StringTextComponent("Dimension: " + worldId.func_240901_a_())); - } + // Add a serial number to allows players to keep different cells apart + // Try to make this a little more flavorful. + String serialNumber = String.format(Locale.ROOT, "SP-%04d", plotId); + lines.add(GuiText.SerialNumber.text(serialNumber)); + + CompoundNBT tag = stack.getTag(); + if (tag != null && tag.contains(TAG_PLOT_SIZE, Constants.NBT.TAG_LONG)) { + BlockPos size = BlockPos.fromLong(tag.getLong(TAG_PLOT_SIZE)); + lines.add(GuiText.StoredSize.text(size.getX(), size.getY(), size.getZ())); } } @@ -85,70 +94,80 @@ public class SpatialStorageCellItem extends AEBaseItem implements ISpatialStorag } @Override - public RegistryKey getStoredDimension(final ItemStack is) { + public int getAllocatedPlotId(final ItemStack is) { final CompoundNBT c = is.getTag(); - if (c != null && c.contains(TAG_DIMENSION_ID)) { + if (c != null && c.contains(TAG_PLOT_ID)) { try { - ResourceLocation worldId = new ResourceLocation(c.getString(TAG_DIMENSION_ID)); - return RegistryKey.func_240903_a_(Registry.WORLD_KEY, worldId); + int plotId = c.getInt(TAG_PLOT_ID); + if (SpatialStoragePlotManager.INSTANCE.getPlot(plotId) == null) { + return -1; + } + return plotId; } catch (Exception e) { - AELog.warn("Failed to retrieve storage cell dimension.", e); + AELog.warn("Failed to retrieve spatial storage dimension: %s", e); } } - return null; + return -1; } @Override - public TransitionResult doSpatialTransition(final ItemStack is, final ServerWorld w, final WorldCoord min, + public boolean doSpatialTransition(final ItemStack is, final ServerWorld w, final WorldCoord min, final WorldCoord max, int playerId) { final int targetX = max.x - min.x - 1; final int targetY = max.y - min.y - 1; final int targetZ = max.z - min.z - 1; final int maxSize = this.getMaxStoredDim(is); + if (targetX > maxSize && targetY > maxSize && targetZ > maxSize) { + AELog.info( + "Failing spatial transition because the transfer area (%dx%dx%d) exceeds the cell capacity (%s).", + targetX, targetY, targetZ, maxSize); + return false; + } final BlockPos targetSize = new BlockPos(targetX, targetY, targetZ); - ISpatialDimension manager = SpatialDimensionManager.INSTANCE; + SpatialStoragePlotManager manager = SpatialStoragePlotManager.INSTANCE; - RegistryKey worldId = this.getStoredDimension(is); - if (worldId == null || manager.getWorld(worldId) == null) { - worldId = manager.createNewCellDimension(targetSize); + SpatialStoragePlot plot = SpatialStoragePlotManager.INSTANCE.getPlot(this.getAllocatedPlotId(is)); + if (plot != null) { + // Check that the existing plot has the right size + if (!plot.getSize().equals(targetSize)) { + AELog.info( + "Failing spatial transition because the transfer area (%dx%dx%d) does not match the spatial storage plot's size (%s).", + targetX, targetY, targetZ, plot.getSize()); + return false; + } + } else { + // Otherwise allocate a new one + plot = manager.allocatePlot(targetSize, playerId); } - if (worldId == null) { - // Failed to create the dimension - return new TransitionResult(false, 0); - } + // Store some information about this transition in the plot + TransitionInfo info = new TransitionInfo(w.func_234923_W_().func_240901_a_(), min.getBlockPos(), + max.getBlockPos(), Instant.now()); + manager.setLastTransition(plot.getId(), info); try { - if (manager.isCellDimension(worldId)) { - ServerWorld cellWorld = manager.getWorld(worldId); + ServerWorld cellWorld = manager.getWorld(); - BlockPos scale = manager.getCellDimensionSize(worldId); + BlockPos offset = plot.getOrigin(); - if (scale.equals(targetSize)) { - if (targetX <= maxSize && targetY <= maxSize && targetZ <= maxSize) { - BlockPos offset = manager.getCellDimensionOrigin(worldId); + this.setStoredDimension(is, plot.getId(), plot.getSize()); + SpatialStorageHelper.getInstance().swapRegions(w, min.x + 1, min.y + 1, min.z + 1, cellWorld, offset.getX(), + offset.getY(), offset.getZ(), targetX - 1, targetY - 1, targetZ - 1); - this.setStoredDimension(is, worldId); - StorageHelper.getInstance().swapRegions(w, min.x + 1, min.y + 1, min.z + 1, cellWorld, - offset.getX(), offset.getY(), offset.getZ(), targetX - 1, targetY - 1, targetZ - 1); - - return new TransitionResult(true, 0); - } - } - } - return new TransitionResult(false, 0); + return true; } finally { // clean up newly created dimensions that failed transfer - if (manager.isCellDimension(worldId) && this.getStoredDimension(is) == null) { - manager.deleteCellDimension(worldId); + if (this.getAllocatedPlotId(is) == -1) { + manager.freePlot(plot.getId(), true); } } } - private void setStoredDimension(final ItemStack is, RegistryKey worldId) { + public void setStoredDimension(final ItemStack is, int plotId, BlockPos size) { final CompoundNBT c = is.getOrCreateTag(); - c.putString(TAG_DIMENSION_ID, worldId.func_240901_a_().toString()); + c.putInt(TAG_PLOT_ID, plotId); + c.putLong(TAG_PLOT_SIZE, size.toLong()); } } diff --git a/src/main/java/appeng/items/tools/powered/ColorApplicatorItem.java b/src/main/java/appeng/items/tools/powered/ColorApplicatorItem.java index 01f9406f9..d0e0c28ac 100644 --- a/src/main/java/appeng/items/tools/powered/ColorApplicatorItem.java +++ b/src/main/java/appeng/items/tools/powered/ColorApplicatorItem.java @@ -32,7 +32,6 @@ import net.minecraft.block.BlockState; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; -import net.minecraft.item.ItemModelsProperties; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUseContext; import net.minecraft.item.SnowballItem; @@ -69,7 +68,6 @@ import appeng.block.networking.CableBusBlock; import appeng.block.paint.PaintSplotchesBlock; import appeng.core.AEConfig; import appeng.core.Api; -import appeng.core.AppEng; import appeng.core.localization.GuiText; import appeng.helpers.IMouseWheelItem; import appeng.hooks.IBlockTool; diff --git a/src/main/java/appeng/mixins/MinecraftServerMixin.java b/src/main/java/appeng/mixins/MinecraftServerMixin.java deleted file mode 100644 index f2d28b821..000000000 --- a/src/main/java/appeng/mixins/MinecraftServerMixin.java +++ /dev/null @@ -1,105 +0,0 @@ -package appeng.mixins; - -import java.util.Map; -import java.util.concurrent.Executor; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -import net.minecraft.server.IDynamicRegistries; -import net.minecraft.server.MinecraftServer; -import net.minecraft.util.RegistryKey; -import net.minecraft.util.registry.Registry; -import net.minecraft.world.Dimension; -import net.minecraft.world.DimensionType; -import net.minecraft.world.World; -import net.minecraft.world.border.IBorderListener; -import net.minecraft.world.border.WorldBorder; -import net.minecraft.world.chunk.listener.IChunkStatusListener; -import net.minecraft.world.gen.ChunkGenerator; -import net.minecraft.world.gen.settings.DimensionGeneratorSettings; -import net.minecraft.world.server.ServerWorld; -import net.minecraft.world.storage.DerivedWorldInfo; -import net.minecraft.world.storage.IServerConfiguration; -import net.minecraft.world.storage.IServerWorldInfo; -import net.minecraft.world.storage.SaveFormat; - -import appeng.hooks.DynamicDimensions; - -@Mixin(MinecraftServer.class) -public class MinecraftServerMixin implements DynamicDimensions { - - @Shadow() - private IDynamicRegistries.Impl field_240767_f_; - - @Shadow - private Map, ServerWorld> worlds; - - @Shadow - private IServerConfiguration field_240768_i_; - - @Shadow - private Executor backgroundExecutor; - - @Shadow - private SaveFormat.LevelSave anvilConverterForAnvilFile; - - public ServerWorld addWorld(RegistryKey worldId, RegistryKey dimensionTypeId, - ChunkGenerator chunkGenerator) { - Preconditions.checkArgument(!worlds.containsKey(worldId), "World with id %s already exists.", - worldId.func_240901_a_()); - - // Each worlds must have a corresponding dimension options, otherwise they will - // not be re-created on load - DimensionGeneratorSettings generatorOptions = this.field_240768_i_.getDimensionGeneratorSettings(); - Preconditions.checkArgument(!generatorOptions.func_236224_e_().containsKey(worldId.func_240901_a_()), - "Dimension config with id %s already exists.", worldId.func_240901_a_()); - - DimensionType dimensionType = field_240767_f_.func_230520_a_().getValueForKey(dimensionTypeId); - Preconditions.checkArgument(dimensionType != null, "dimensionTypeId %s is not registered", dimensionTypeId); - - // Create dimension options with the given world-id and chunk generator - Dimension dimensionOptions = new Dimension( - () -> field_240767_f_.func_230520_a_().getValueForKey(dimensionTypeId), chunkGenerator); - - IServerWorldInfo serverWorldProperties = this.field_240768_i_.func_230407_G_(); - - // This sucks a little, but during startup, the server will use the same - // listener for every world, - // but it is ultimately only stored in the chunk storage. So we retrieve it from - // the overworld to - // mirror the original code as much as possible - ServerWorld overworld = worlds.get(ServerWorld.field_234918_g_); - Preconditions.checkState(overworld != null, "Overworld does not exist!"); - IChunkStatusListener worldGenerationProgressListener = ((ThreadedAnvilChunkStorageAccessor) overworld - .getChunkProvider().chunkManager).getWorldGenerationProgressListener(); - - // Inherit seed + WorldBorder from overworld - long seed = overworld.getSeed(); - WorldBorder worldBorder = overworld.getWorldBorder(); - - MinecraftServer self = (MinecraftServer) (Object) this; - DerivedWorldInfo unmodifiableLevelProperties = new DerivedWorldInfo(this.field_240768_i_, - serverWorldProperties); - ServerWorld world = new ServerWorld(self, this.backgroundExecutor, this.anvilConverterForAnvilFile, - unmodifiableLevelProperties, worldId, dimensionTypeId, dimensionType, worldGenerationProgressListener, - chunkGenerator, false, seed, ImmutableList.of(), false); - worldBorder.addListener(new IBorderListener.Impl(world.getWorldBorder())); - this.worlds.put(worldId, world); - - // Adding it here will cause the world to be re-created on server restart - RegistryKey dimOptKey = RegistryKey.func_240903_a_(Registry.DIMENSION_KEY, worldId.func_240901_a_()); - generatorOptions.func_236224_e_().register(dimOptKey, dimensionOptions); - generatorOptions.func_236224_e_().func_239662_d_(dimOptKey); // Otherwise it wont be saved - // Ensure the save properties are saved, or the world will potentially be lost - // on restart - this.anvilConverterForAnvilFile.func_237288_a_(this.field_240767_f_, this.field_240768_i_, - self.getPlayerList().getHostPlayerData()); - - return world; - } - -} diff --git a/src/main/java/appeng/mixins/spatial/DimensionOptionMixin.java b/src/main/java/appeng/mixins/spatial/DimensionOptionMixin.java new file mode 100644 index 000000000..2ef594d69 --- /dev/null +++ b/src/main/java/appeng/mixins/spatial/DimensionOptionMixin.java @@ -0,0 +1,38 @@ +package appeng.mixins.spatial; + +import java.util.List; +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import net.minecraft.util.RegistryKey; +import net.minecraft.world.Dimension; + +import appeng.spatial.SpatialStorageDimensionIds; + +/** + * This Mixin tries to hide the screen that warns users about experimental + * features being used, but only if AE2's dimension is the only added dimension. + */ +@Mixin(Dimension.class) +public class DimensionOptionMixin { + + /** + * This injects right after the method makes a local copy of all present + * dimensions, and modifies the list by removing our mod-provided dimension from + * it. + *

+ * This means Vanilla will perform it's check whether it should display an + * experimental warning without considering our dimension. + */ + @ModifyVariable(method = "func_236060_a_", at = @At(value = "INVOKE_ASSIGN", ordinal = 0, target = "Lcom/google/common/collect/Lists;newArrayList(Ljava/lang/Iterable;)Ljava/util/ArrayList;", remap = false), allow = 1) + private static List, Dimension>> overrideExperimentalCheck( + List, Dimension>> dimensions) { + // this only removes our dimension from the check + dimensions.removeIf(e -> e.getKey() == SpatialStorageDimensionIds.DIMENSION_ID); + return dimensions; + } + +} \ No newline at end of file diff --git a/src/main/java/appeng/mixins/DimensionTypeMixin.java b/src/main/java/appeng/mixins/spatial/DimensionTypeMixin.java similarity index 68% rename from src/main/java/appeng/mixins/DimensionTypeMixin.java rename to src/main/java/appeng/mixins/spatial/DimensionTypeMixin.java index 26fe8b0a3..fe751d076 100644 --- a/src/main/java/appeng/mixins/DimensionTypeMixin.java +++ b/src/main/java/appeng/mixins/spatial/DimensionTypeMixin.java @@ -1,4 +1,4 @@ -package appeng.mixins; +package appeng.mixins.spatial; import java.util.OptionalLong; @@ -11,15 +11,20 @@ import net.minecraft.server.IDynamicRegistries; import net.minecraft.tags.BlockTags; import net.minecraft.world.DimensionType; -import appeng.spatial.SpatialDimensionManager; +import appeng.spatial.SpatialStorageDimensionIds; +/** + * Adds the storage cell world dimension type as a built-in dimension type. This + * can be registered as a JSON file as well, but doing so will trigger an + * experimental feature warning when the world is being loaded. + */ @Mixin(value = DimensionType.class) public class DimensionTypeMixin { @Inject(method = "func_236027_a_", at = @At("TAIL")) private static void addRegistryDefaults(IDynamicRegistries.Impl registryTracker, CallbackInfoReturnable cir) { - registryTracker.func_239774_a_(SpatialDimensionManager.STORAGE_DIMENSION_TYPE, + registryTracker.func_239774_a_(SpatialStorageDimensionIds.DIMENSION_TYPE_ID, new DimensionType(OptionalLong.of(12000), false, false, false, false, false, false, true, false, false, 256, BlockTags.INFINIBURN_OVERWORLD.getName(), 1.0f)); diff --git a/src/main/java/appeng/mixins/SkyPropertiesMixin.java b/src/main/java/appeng/mixins/spatial/SkyPropertiesMixin.java similarity index 71% rename from src/main/java/appeng/mixins/SkyPropertiesMixin.java rename to src/main/java/appeng/mixins/spatial/SkyPropertiesMixin.java index 42c293a1b..3213b5689 100644 --- a/src/main/java/appeng/mixins/SkyPropertiesMixin.java +++ b/src/main/java/appeng/mixins/spatial/SkyPropertiesMixin.java @@ -1,4 +1,4 @@ -package appeng.mixins; +package appeng.mixins.spatial; import java.util.Optional; @@ -11,8 +11,8 @@ import net.minecraft.client.world.DimensionRenderInfo; import net.minecraft.util.RegistryKey; import net.minecraft.world.DimensionType; -import appeng.spatial.SpatialDimensionManager; -import appeng.spatial.StorageSkyProperties; +import appeng.spatial.SpatialStorageDimensionIds; +import appeng.spatial.SpatialStorageSkyProperties; @Mixin(DimensionRenderInfo.class) public class SkyPropertiesMixin { @@ -20,8 +20,8 @@ public class SkyPropertiesMixin { @Inject(method = "func_239215_a_", at = @At("HEAD"), cancellable = true) private static void byDimensionType(Optional> optional, CallbackInfoReturnable ci) { - if (optional.orElse(null) == SpatialDimensionManager.STORAGE_DIMENSION_TYPE) { - ci.setReturnValue(StorageSkyProperties.INSTANCE); + if (optional.orElse(null) == SpatialStorageDimensionIds.DIMENSION_TYPE_ID) { + ci.setReturnValue(SpatialStorageSkyProperties.INSTANCE); } } diff --git a/src/main/java/appeng/mixins/SkyRenderMixin.java b/src/main/java/appeng/mixins/spatial/SkyRenderMixin.java similarity index 84% rename from src/main/java/appeng/mixins/SkyRenderMixin.java rename to src/main/java/appeng/mixins/spatial/SkyRenderMixin.java index d8b178181..2b7382c63 100644 --- a/src/main/java/appeng/mixins/SkyRenderMixin.java +++ b/src/main/java/appeng/mixins/spatial/SkyRenderMixin.java @@ -1,4 +1,4 @@ -package appeng.mixins; +package appeng.mixins.spatial; import com.mojang.blaze3d.matrix.MatrixStack; @@ -12,7 +12,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.WorldRenderer; import appeng.client.render.SpatialSkyRender; -import appeng.spatial.SpatialDimensionManager; +import appeng.spatial.SpatialStorageDimensionIds; @Mixin(value = WorldRenderer.class) public class SkyRenderMixin { @@ -23,7 +23,7 @@ public class SkyRenderMixin { @SuppressWarnings("ConstantConditions") @Inject(method = "renderSky(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V", at = @At("HEAD"), cancellable = true) public void renderSky(MatrixStack matrices, float tickDelta, CallbackInfo ci) { - if (mc.world.func_234922_V_() == SpatialDimensionManager.STORAGE_DIMENSION_TYPE) { + if (mc.world.func_234922_V_() == SpatialStorageDimensionIds.DIMENSION_TYPE_ID) { SpatialSkyRender.getInstance().render(matrices); ci.cancel(); } diff --git a/src/main/java/appeng/parts/automation/IdentityAnnihilationPlanePart.java b/src/main/java/appeng/parts/automation/IdentityAnnihilationPlanePart.java index 27d457465..cf2ee5ec2 100644 --- a/src/main/java/appeng/parts/automation/IdentityAnnihilationPlanePart.java +++ b/src/main/java/appeng/parts/automation/IdentityAnnihilationPlanePart.java @@ -28,15 +28,11 @@ import net.minecraft.block.BlockState; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.enchantment.Enchantments; import net.minecraft.item.ItemStack; -import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.server.ServerWorld; import net.minecraftforge.client.model.data.IModelData; -import appeng.api.parts.IPart; -import appeng.api.parts.IPartHost; import appeng.api.parts.IPartModel; -import appeng.api.util.AEPartLocation; import appeng.items.parts.PartModels; public class IdentityAnnihilationPlanePart extends AnnihilationPlanePart { diff --git a/src/main/java/appeng/recipes/handlers/GrinderRecipeSerializer.java b/src/main/java/appeng/recipes/handlers/GrinderRecipeSerializer.java index 434dd71b3..5945b377a 100644 --- a/src/main/java/appeng/recipes/handlers/GrinderRecipeSerializer.java +++ b/src/main/java/appeng/recipes/handlers/GrinderRecipeSerializer.java @@ -20,7 +20,6 @@ import net.minecraft.util.ResourceLocation; import net.minecraftforge.registries.ForgeRegistryEntry; import appeng.core.AEConfig; -import appeng.core.AppEng; import appeng.core.sync.BasePacket; public class GrinderRecipeSerializer extends ForgeRegistryEntry> diff --git a/src/main/java/appeng/recipes/handlers/InscriberRecipeSerializer.java b/src/main/java/appeng/recipes/handlers/InscriberRecipeSerializer.java index 8e46d9072..ccc507059 100644 --- a/src/main/java/appeng/recipes/handlers/InscriberRecipeSerializer.java +++ b/src/main/java/appeng/recipes/handlers/InscriberRecipeSerializer.java @@ -14,7 +14,6 @@ import net.minecraft.util.ResourceLocation; import net.minecraftforge.registries.ForgeRegistryEntry; import appeng.api.features.InscriberProcessType; -import appeng.core.AppEng; import appeng.core.sync.BasePacket; public class InscriberRecipeSerializer extends ForgeRegistryEntry> diff --git a/src/main/java/appeng/server/Commands.java b/src/main/java/appeng/server/Commands.java index 8b5997d7c..a2bd40714 100644 --- a/src/main/java/appeng/server/Commands.java +++ b/src/main/java/appeng/server/Commands.java @@ -19,13 +19,15 @@ package appeng.server; import appeng.server.subcommands.ChunkLogger; +import appeng.server.subcommands.SpatialStorageCommand; import appeng.server.subcommands.Supporters; import appeng.server.subcommands.TestMeteoritesCommand; import appeng.server.subcommands.TestOreGenCommand; public enum Commands { Chunklogger(4, new ChunkLogger(), false), Supporters(0, new Supporters(), false), - TestOreGen(4, new TestOreGenCommand(), true), TestMeteorites(4, new TestMeteoritesCommand(), true); + TestOreGen(4, new TestOreGenCommand(), true), TestMeteorites(4, new TestMeteoritesCommand(), true), + Spatial(4, new SpatialStorageCommand(), false); public final int level; public final ISubCommand command; diff --git a/src/main/java/appeng/server/subcommands/SpatialStorageCommand.java b/src/main/java/appeng/server/subcommands/SpatialStorageCommand.java new file mode 100644 index 000000000..56adf70a4 --- /dev/null +++ b/src/main/java/appeng/server/subcommands/SpatialStorageCommand.java @@ -0,0 +1,327 @@ +package appeng.server.subcommands; + +import static net.minecraft.command.Commands.literal; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.function.UnaryOperator; + +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import net.minecraft.command.CommandException; +import net.minecraft.command.CommandSource; +import net.minecraft.command.Commands; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.IFormattableTextComponent; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.event.ClickEvent; +import net.minecraft.util.text.event.HoverEvent; +import net.minecraftforge.fml.server.ServerLifecycleHooks; + +import appeng.core.Api; +import appeng.core.worlddata.WorldData; +import appeng.items.storage.SpatialStorageCellItem; +import appeng.server.ISubCommand; +import appeng.spatial.SpatialStorageDimensionIds; +import appeng.spatial.SpatialStoragePlot; +import appeng.spatial.SpatialStoragePlotManager; +import appeng.spatial.TransitionInfo; + +/** + * This admin command allows management of spatial storage plots. + */ +public class SpatialStorageCommand implements ISubCommand { + + @Override + public void addArguments(LiteralArgumentBuilder builder) { + // Shows info about a given plot or about the plot the player is currently in + builder.then(literal("info").executes(ctx -> { + showPlotInfo(ctx.getSource(), getCurrentPlot(ctx.getSource())); + return 1; + }).then(Commands.argument("plotId", IntegerArgumentType.integer(1)).executes(ctx -> { + int plotId = IntegerArgumentType.getInteger(ctx, "plotId"); + showPlotInfo(ctx.getSource(), getPlot(plotId)); + return 1; + }))); + + // Teleport into the plot + builder.then(literal("tp").then(Commands.argument("plotId", IntegerArgumentType.integer(1)).executes(ctx -> { + int plotId = IntegerArgumentType.getInteger(ctx, "plotId"); + teleportToPlot(ctx.getSource(), plotId); + return 1; + }))); + + // Teleport from the current plot back to the source of its content, or do the + // same for a given plot id + builder.then(literal("tpback").executes(ctx -> { + teleportBack(ctx.getSource()); + return 1; + }).then(Commands.argument("plotId", IntegerArgumentType.integer(1)).executes(ctx -> { + int plotId = IntegerArgumentType.getInteger(ctx, "plotId"); + teleportBack(ctx.getSource(), getPlot(plotId)); + return 1; + }))); + + // Creates a storage cell for the given plot id and gives it to the player + builder.then( + literal("givecell").then(Commands.argument("plotId", IntegerArgumentType.integer(1)).executes(ctx -> { + int plotId = IntegerArgumentType.getInteger(ctx, "plotId"); + giveCell(ctx.getSource(), plotId); + return 1; + }))); + } + + /** + * If the player is currently within a spatial storage plot, teleports them back + * to the last source of transition. + */ + private void teleportBack(CommandSource source) { + + if (source.getWorld().func_234923_W_() != SpatialStorageDimensionIds.WORLD_ID) { + throw new CommandException(new StringTextComponent("Must be within the spatial storage world.")); + } + + BlockPos playerPos = new BlockPos(source.getPos()); + int x = playerPos.getX(); + int z = playerPos.getZ(); + + // This is slow, but for an admin-command it's acceptable + + for (SpatialStoragePlot plot : SpatialStoragePlotManager.INSTANCE.getPlots()) { + BlockPos origin = plot.getOrigin(); + BlockPos size = plot.getSize(); + if (x >= origin.getX() && x <= origin.getX() + size.getX() && z >= origin.getZ() + && z <= origin.getZ() + size.getZ()) { + teleportBack(source, plot); + return; + } + } + + throw new CommandException(ITextComponent.func_241827_a_("Couldn't find a plot for the current position.")); + + } + + /** + * Teleports back from the given plot. + */ + private void teleportBack(CommandSource source, SpatialStoragePlot plot) { + TransitionInfo lastTransition = plot.getLastTransition(); + if (lastTransition == null) { + throw new CommandException( + ITextComponent.func_241827_a_("This plot doesn't have a last known transition.")); + } + + String command = getTeleportCommand(lastTransition.getWorldId(), lastTransition.getMin().add(0, 1, 0)); + runCommandFor(source, command); + } + + /** + * Shows detailed information about a spatial storage plot. + */ + private static void showPlotInfo(CommandSource source, SpatialStoragePlot plot) { + + sendKeyValuePair(source, "Plot ID", String.valueOf(plot.getId())); + // Show the owner of the spatial storage plot + int playerId = plot.getOwner(); + if (playerId != -1) { + UUID profileId = WorldData.instance().playerData().getProfileId(playerId); + + if (profileId == null) { + sendKeyValuePair(source, "Owner", "Unknown AE2 player (" + playerId + ")"); + } else { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + ServerPlayerEntity player = server.getPlayerList().getPlayerByUUID(profileId); + if (player == null) { + sendKeyValuePair(source, "Owner", "Unknown Minecraft profile (" + profileId + ")"); + } else { + sendKeyValuePair(source, "Owner", player.getDisplayName()); + } + } + } else { + sendKeyValuePair(source, "Owner", "Unknown"); + } + + sendKeyValuePair(source, "Size", formatBlockPos(plot.getSize(), "x")); + + // Show the plot's origin and make it clickable to teleport directly to it + String teleportToPlotCommand = getTeleportCommand(SpatialStorageDimensionIds.WORLD_ID.func_240901_a_(), + plot.getOrigin()); + sendKeyValuePair(source, "Origin", new StringTextComponent(formatBlockPos(plot.getOrigin(), ",")) + .modifyStyle(makeCommandLink(teleportToPlotCommand, "Teleport into plot"))); + + sendKeyValuePair(source, "Region file:", plot.getRegionFilename()); + + // Show information about what was last transfered into the plot (with a + // clickable link to the source) + TransitionInfo lastTransition = plot.getLastTransition(); + if (lastTransition != null) { + source.sendFeedback(new StringTextComponent("Last Transition:").mergeStyle(TextFormatting.UNDERLINE, + TextFormatting.BOLD), true); + + String sourceWorldId = lastTransition.getWorldId().toString(); + IFormattableTextComponent sourceLink = new StringTextComponent( + sourceWorldId + " - " + formatBlockPos(lastTransition.getMin(), ",") + " to " + + formatBlockPos(lastTransition.getMax(), ",")); + String tpCommand = getTeleportCommand(lastTransition.getWorldId(), lastTransition.getMin().add(0, 1, 0)); + sourceLink.modifyStyle(makeCommandLink(tpCommand, "Click to teleport")); + + sendKeyValuePair(source, "Source", sourceLink); + sendKeyValuePair(source, "When", lastTransition.getTimestamp().toString()); + } else { + source.sendFeedback(new StringTextComponent("Last Transition unknown"), true); + } + + } + + private static void teleportToPlot(CommandSource source, int plotId) { + SpatialStoragePlot plot = getPlot(plotId); + + String teleportCommand = getTeleportCommand(SpatialStorageDimensionIds.WORLD_ID.func_240901_a_(), + plot.getOrigin()); + + runCommandFor(source, teleportCommand); + } + + private void giveCell(CommandSource source, int plotId) throws CommandSyntaxException { + ServerPlayerEntity player = source.asPlayer(); + + SpatialStoragePlot plot = getPlot(plotId); + + ItemStack cell; + int longestSide = getLongestSide(plot.getSize()); + if (longestSide <= 2) { + cell = Api.instance().definitions().items().spatialCell2().stack(1); + } else if (longestSide <= 16) { + cell = Api.instance().definitions().items().spatialCell16().stack(1); + } else { + cell = Api.instance().definitions().items().spatialCell128().stack(1); + } + + if (!(cell.getItem() instanceof SpatialStorageCellItem)) { + throw new CommandException( + ITextComponent.func_241827_a_("Storage cell items don't implement the storage cell interface!")); + } + + SpatialStorageCellItem spatialCellItem = (SpatialStorageCellItem) cell.getItem(); + spatialCellItem.setStoredDimension(cell, plotId, plot.getSize()); + + player.addItemStackToInventory(cell); + } + + private static int getLongestSide(BlockPos size) { + return Math.max(size.getX(), Math.max(size.getY(), size.getZ())); + } + + @Override + public void call(MinecraftServer srv, CommandContext ctx, CommandSource sender) { + + List plots = new ArrayList<>(SpatialStoragePlotManager.INSTANCE.getPlots()); + + // Prints the least recently used plots + plots.sort(Comparator.comparing((SpatialStoragePlot plot) -> { + TransitionInfo lastTransition = plot.getLastTransition(); + if (lastTransition != null) { + return lastTransition.getTimestamp(); + } else { + return Instant.MIN; + } + }).reversed()); + + for (int i = 0; i < Math.min(5, plots.size()); i++) { + SpatialStoragePlot plot = plots.get(i); + String size = formatBlockPos(plot.getSize(), "x"); + BlockPos originPos = plot.getOrigin(); + String origin = formatBlockPos(originPos, ","); + + ITextComponent infoLink = new StringTextComponent("Plot #" + plot.getId()) + .modifyStyle(makeCommandLink("/ae2 spatial info " + plot.getId(), "Click to show details")); + ITextComponent tpLink = new StringTextComponent("Origin: " + origin) + .modifyStyle(makeCommandLink("/ae2 spatial tp " + plot.getId(), "Click to teleport into plot")); + + IFormattableTextComponent message = new StringTextComponent("").append(infoLink) + .appendString(" Size: " + size + " ").append(tpLink); + + sender.sendFeedback(message, true); + } + + } + + private static String formatBlockPos(BlockPos size, String separator) { + return size.getX() + separator + size.getY() + separator + size.getZ(); + } + + private static UnaryOperator