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 <yueh@users.noreply.github.com>
This commit is contained in:
@@ -34,9 +34,10 @@ import net.minecraft.world.ITickList;
|
||||
import net.minecraft.world.NextTickListEntry;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.lighting.WorldLightManager;
|
||||
import net.minecraft.world.server.ServerTickList;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraft.world.server.ServerWorldLightManager;
|
||||
|
||||
import appeng.api.movable.IMovableHandler;
|
||||
import appeng.api.movable.IMovableRegistry;
|
||||
@@ -300,12 +301,17 @@ public class CachedPlane {
|
||||
|
||||
private void updateChunks() {
|
||||
|
||||
WorldLightManager lightManager = world.getLightManager();
|
||||
|
||||
// update shit..
|
||||
for (int x = 0; x < this.cx_size; x++) {
|
||||
for (int z = 0; z < this.cz_size; z++) {
|
||||
final Chunk c = this.myChunks[x][z];
|
||||
// FIXME: Light shit
|
||||
c.markDirty();
|
||||
if (lightManager instanceof ServerWorldLightManager) {
|
||||
ServerWorldLightManager serverLightManager = (ServerWorldLightManager) lightManager;
|
||||
for (int x = 0; x < this.cx_size; x++) {
|
||||
for (int z = 0; z < this.cz_size; z++) {
|
||||
final Chunk c = this.myChunks[x][z];
|
||||
serverLightManager.lightChunk(c, false);
|
||||
c.markDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,17 +323,14 @@ public class CachedPlane {
|
||||
|
||||
WorldData.instance().compassData().service().updateArea(this.getWorld(), c);
|
||||
|
||||
// FIXME this was sending chunks to players...
|
||||
SChunkDataPacket cdp = new SChunkDataPacket(c, verticalBits, false);
|
||||
((ServerChunkProvider) world.getChunkProvider()).chunkManager.getTrackingPlayers(c.getPos(), false)
|
||||
world.getChunkProvider().chunkManager.getTrackingPlayers(c.getPos(), false)
|
||||
.forEach(spe -> spe.connection.sendPacket(cdp));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME check if this makes any sense at all to send changes to players asap
|
||||
ServerChunkProvider serverChunkProvider = (ServerChunkProvider) world.getChunkProvider();
|
||||
serverChunkProvider.tick(() -> false);
|
||||
world.getChunkProvider().tick(() -> false);
|
||||
}
|
||||
|
||||
List<WorldCoord> getUpdates() {
|
||||
@@ -340,7 +343,6 @@ public class CachedPlane {
|
||||
|
||||
private static class BlockStorageData {
|
||||
public BlockState state;
|
||||
public int light;
|
||||
}
|
||||
|
||||
private class Column {
|
||||
@@ -373,8 +375,6 @@ public class CachedPlane {
|
||||
final ChunkSection[] storage = this.c.getSections();
|
||||
final ChunkSection extendedBlockStorage = storage[y >> 4];
|
||||
extendedBlockStorage.setBlockState(this.x, y & 15, this.z, data.state);
|
||||
// FIXME extendedBlockStorage.setBlockLight( this.x, y & 15, this.z, data.light
|
||||
// );
|
||||
}
|
||||
|
||||
private void fillData(final int y, BlockStorageData data) {
|
||||
@@ -382,8 +382,6 @@ public class CachedPlane {
|
||||
final ChunkSection extendedblockstorage = storage[y >> 4];
|
||||
|
||||
data.state = extendedblockstorage.getBlockState(this.x, y & 15, this.z);
|
||||
// FIXME data.light = extendedblockstorage.getBlockLight( this.x, y & 15, this.z
|
||||
// );
|
||||
}
|
||||
|
||||
private boolean doNotSkip(final int y) {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package appeng.spatial;
|
||||
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.NBTUtil;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.storage.WorldSavedData;
|
||||
|
||||
/**
|
||||
* Helps with encoding and decoding the extra data we attach to each created
|
||||
* storage dimension world as persistent state.
|
||||
*/
|
||||
public final class SpatialDimensionExtraData extends WorldSavedData {
|
||||
|
||||
/**
|
||||
* ID of this data when it is attached to a world.
|
||||
*/
|
||||
public static final String ID = "ae2_spatial_info";
|
||||
|
||||
// Used to allow forward compatibility
|
||||
private static final int CURRENT_FORMAT = 1;
|
||||
|
||||
private static final String TAG_FORMAT = "format";
|
||||
|
||||
private static final String TAG_SIZE = "size";
|
||||
|
||||
/**
|
||||
* The storage size of this dimension. This is dicateted by the pylon structure
|
||||
* size used to perform the first transfer into this dimension. Once it's set,
|
||||
* it cannot be changed anymore.
|
||||
*/
|
||||
private BlockPos size = BlockPos.ZERO;
|
||||
|
||||
public SpatialDimensionExtraData() {
|
||||
super(ID);
|
||||
}
|
||||
|
||||
public SpatialDimensionExtraData(BlockPos size) {
|
||||
super(ID);
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public BlockPos getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(BlockPos size) {
|
||||
this.size = size;
|
||||
setDirty(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(CompoundNBT tag) {
|
||||
int version = tag.getInt(TAG_FORMAT);
|
||||
if (version != CURRENT_FORMAT) {
|
||||
// Currently no new format has been defined, as such anything but the current
|
||||
// version is invalid
|
||||
throw new IllegalStateException("Invalid AE2 spatial info version: " + version);
|
||||
}
|
||||
|
||||
size = NBTUtil.readBlockPos(tag.getCompound(TAG_SIZE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundNBT write(CompoundNBT tag) {
|
||||
tag.putInt(TAG_FORMAT, CURRENT_FORMAT);
|
||||
tag.put(TAG_SIZE, NBTUtil.writeBlockPos(size));
|
||||
return tag;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,201 +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 <http://www.gnu.org/licenses/lgpl>.
|
||||
*/
|
||||
|
||||
package appeng.spatial;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
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.world.DimensionType;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.fml.server.ServerLifecycleHooks;
|
||||
|
||||
import appeng.api.storage.ISpatialDimension;
|
||||
import appeng.core.AELog;
|
||||
import appeng.core.AppEng;
|
||||
import appeng.core.localization.GuiText;
|
||||
import appeng.hooks.DynamicDimensions;
|
||||
|
||||
public final class SpatialDimensionManager implements ISpatialDimension {
|
||||
|
||||
// A region file is 512x512 blocks (32x32 chunks),
|
||||
// to avoid creating the 4 regions around 0,0,0,
|
||||
// we move the origin to the middle of region 0,0
|
||||
public static final BlockPos REGION_CENTER = new BlockPos(512 / 2, 64, 512 / 2);
|
||||
|
||||
public static final RegistryKey<DimensionType> STORAGE_DIMENSION_TYPE = RegistryKey
|
||||
.func_240903_a_(Registry.DIMENSION_TYPE_KEY, AppEng.makeId("storage_cell"));
|
||||
|
||||
public static final ISpatialDimension INSTANCE = new SpatialDimensionManager();
|
||||
|
||||
private static final String DIM_ID_PREFIX = "spatial_";
|
||||
|
||||
private SpatialDimensionManager() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerWorld getWorld(RegistryKey<World> cellDim) {
|
||||
MinecraftServer server = getServer();
|
||||
return server.getWorld(cellDim);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistryKey<World> createNewCellDimension(BlockPos size) {
|
||||
ResourceLocation dimKey = findFreeDimensionId();
|
||||
AELog.info("Allocating storage cell dimension '%s'", dimKey);
|
||||
|
||||
DynamicDimensions dynamicDimensions = (DynamicDimensions) getServer();
|
||||
|
||||
RegistryKey<World> worldId = RegistryKey.func_240903_a_(Registry.WORLD_KEY, dimKey);
|
||||
|
||||
ServerWorld world = dynamicDimensions.addWorld(worldId, STORAGE_DIMENSION_TYPE, StorageChunkGenerator.INSTANCE);
|
||||
|
||||
SpatialDimensionExtraData spatialExtraData = world.getSavedData().getOrCreate(SpatialDimensionExtraData::new,
|
||||
SpatialDimensionExtraData.ID);
|
||||
spatialExtraData.setSize(size);
|
||||
|
||||
return worldId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries finding the next free storage cell dimension ID based on the currently
|
||||
* registered storage cell dimensions.
|
||||
*/
|
||||
private ResourceLocation findFreeDimensionId() {
|
||||
int maxId = 0;
|
||||
for (RegistryKey<World> worldId : getServer().func_240770_D_()) {
|
||||
ResourceLocation regName = worldId.func_240901_a_();
|
||||
if (regName == null || !AppEng.MOD_ID.equals(regName.getNamespace())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String path = regName.getPath();
|
||||
if (!path.startsWith(DIM_ID_PREFIX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
String numericIdPart = path.substring(DIM_ID_PREFIX.length());
|
||||
maxId = Math.max(Integer.parseUnsignedInt(numericIdPart), maxId);
|
||||
} catch (NumberFormatException e) {
|
||||
AELog.warn("Unparsable storage cell dimension id '%s'", path, e);
|
||||
}
|
||||
}
|
||||
|
||||
++maxId;
|
||||
|
||||
return AppEng.makeId(DIM_ID_PREFIX + maxId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteCellDimension(RegistryKey<World> worldId) {
|
||||
AELog.info("Unregistering storage cell dimension %s", worldId.func_240901_a_());
|
||||
MinecraftServer server = getServer();
|
||||
// FIXME FABRIC ServerWorld world = DimensionManager.getWorld(server, worldId, false, false);
|
||||
// FIXME FABRIC if (world != null) {
|
||||
// FIXME FABRIC DimensionManager.unloadWorld(world);
|
||||
// FIXME FABRIC }
|
||||
// FIXME FABRIC DimensionManager.unloadWorlds(server, true);
|
||||
// FIXME FABRIC DimensionManager.unregisterDimension(worldId.getId());
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellDimension(RegistryKey<World> worldId) {
|
||||
ResourceLocation id = worldId.func_240901_a_();
|
||||
if (!id.getNamespace().equals(AppEng.MOD_ID)) {
|
||||
return false; // World belongs to a different mod
|
||||
}
|
||||
if (!id.getPath().startsWith(DIM_ID_PREFIX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the world has the right dimension type
|
||||
ServerWorld world = getServer().getWorld(worldId);
|
||||
if (world == null) {
|
||||
return false; // Non-existent world
|
||||
}
|
||||
|
||||
return world.func_234922_V_().equals(STORAGE_DIMENSION_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos getCellDimensionOrigin(RegistryKey<World> worldId) {
|
||||
return REGION_CENTER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos getCellDimensionSize(RegistryKey<World> worldId) {
|
||||
SpatialDimensionExtraData extraData = getExtraData(worldId);
|
||||
return extraData != null ? extraData.getSize() : BlockPos.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCellDimensionTooltip(RegistryKey<World> worldId, List<ITextComponent> lines) {
|
||||
// Check if the cell dimension type is even registered
|
||||
ResourceLocation registryName = worldId.func_240901_a_();
|
||||
if (registryName == null || !AppEng.MOD_ID.equals(registryName.getNamespace())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!registryName.getPath().startsWith(DIM_ID_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the actual stored size
|
||||
BlockPos size = SpatialDimensionManager.INSTANCE.getCellDimensionSize(worldId);
|
||||
lines.add(GuiText.StoredSize.text(size.getX(), size.getY(), size.getZ()));
|
||||
|
||||
// Add a serial number to allows players to keep different cells apart
|
||||
int dimId;
|
||||
try {
|
||||
String numericIdPart = registryName.getPath().substring(DIM_ID_PREFIX.length());
|
||||
dimId = Integer.parseUnsignedInt(numericIdPart);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to make this a little more flavorful.
|
||||
String serialNumber = String.format(Locale.ROOT, "SP-%04d", dimId);
|
||||
lines.add(GuiText.SerialNumber.text(serialNumber));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SpatialDimensionExtraData getExtraData(RegistryKey<World> worldId) {
|
||||
ServerWorld world = getWorld(worldId);
|
||||
if (world == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return world.getSavedData().get(SpatialDimensionExtraData::new, SpatialDimensionExtraData.ID);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ServerLifecycleHooks.getCurrentServer();
|
||||
}
|
||||
|
||||
}
|
||||
+6
-3
@@ -25,11 +25,14 @@ import net.minecraft.world.gen.surfacebuilders.SurfaceBuilder;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
public class StorageCellBiome extends Biome {
|
||||
/**
|
||||
* The single biome used within the spatial storage world.
|
||||
*/
|
||||
public class SpatialStorageBiome extends Biome {
|
||||
|
||||
public static final StorageCellBiome INSTANCE = new StorageCellBiome();
|
||||
public static final SpatialStorageBiome INSTANCE = new SpatialStorageBiome();
|
||||
|
||||
public StorageCellBiome() {
|
||||
public SpatialStorageBiome() {
|
||||
super(new Biome.Builder()
|
||||
.surfaceBuilder(
|
||||
new ConfiguredSurfaceBuilder<>(SurfaceBuilder.NOPE, SurfaceBuilder.STONE_STONE_GRAVEL_CONFIG))
|
||||
+8
-5
@@ -43,18 +43,21 @@ import net.minecraft.world.gen.settings.DimensionStructuresSettings;
|
||||
|
||||
import appeng.core.Api;
|
||||
|
||||
public class StorageChunkGenerator extends ChunkGenerator {
|
||||
/**
|
||||
* Chunk generator the spatial storage world.
|
||||
*/
|
||||
public class SpatialStorageChunkGenerator extends ChunkGenerator {
|
||||
|
||||
private final Blockreader columnSample;
|
||||
|
||||
public static final StorageChunkGenerator INSTANCE = new StorageChunkGenerator();
|
||||
public static final SpatialStorageChunkGenerator INSTANCE = new SpatialStorageChunkGenerator();
|
||||
|
||||
public static final Codec<StorageChunkGenerator> CODEC = RecordCodecBuilder
|
||||
public static final Codec<SpatialStorageChunkGenerator> CODEC = RecordCodecBuilder
|
||||
.create((instance) -> instance.stable(INSTANCE));
|
||||
|
||||
private final BlockState defaultBlockState;
|
||||
|
||||
private StorageChunkGenerator() {
|
||||
private SpatialStorageChunkGenerator() {
|
||||
super(createBiomeProvider(), createSettings());
|
||||
this.defaultBlockState = Api.instance().definitions().blocks().matrixFrame().block().getDefaultState();
|
||||
|
||||
@@ -71,7 +74,7 @@ public class StorageChunkGenerator extends ChunkGenerator {
|
||||
}
|
||||
|
||||
private static BiomeProvider createBiomeProvider() {
|
||||
return new SingleBiomeProvider(StorageCellBiome.INSTANCE);
|
||||
return new SingleBiomeProvider(SpatialStorageBiome.INSTANCE);
|
||||
}
|
||||
|
||||
private static DimensionStructuresSettings createSettings() {
|
||||
@@ -0,0 +1,57 @@
|
||||
package appeng.spatial;
|
||||
|
||||
import net.minecraft.util.RegistryKey;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.Dimension;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import appeng.core.AppEng;
|
||||
import appeng.mixins.spatial.DimensionTypeMixin;
|
||||
|
||||
/**
|
||||
* IDs for the spatial storage world related dimension objects.
|
||||
*/
|
||||
public final class SpatialStorageDimensionIds {
|
||||
|
||||
/**
|
||||
* ID of the {@link DimensionType} used for the spatial storage world.
|
||||
* <p>
|
||||
* This is defined in {@link DimensionTypeMixin}.
|
||||
*/
|
||||
public static final RegistryKey<DimensionType> DIMENSION_TYPE_ID = RegistryKey
|
||||
.func_240903_a_(Registry.DIMENSION_TYPE_KEY, AppEng.makeId("spatial_storage"));
|
||||
|
||||
/**
|
||||
* ID of the {@link net.minecraft.world.gen.ChunkGenerator} used for the spatial
|
||||
* storage world.
|
||||
*/
|
||||
public static final ResourceLocation CHUNK_GENERATOR_ID = AppEng.makeId("spatial_storage");
|
||||
|
||||
/**
|
||||
* ID of the {@link net.minecraft.world.biome.Biome} used for the spatial
|
||||
* storage world.
|
||||
*/
|
||||
public static final ResourceLocation BIOME_ID = AppEng.makeId("spatial_storage");
|
||||
|
||||
/**
|
||||
* ID of the {@link Dimension} used for the spatial storage dimension.
|
||||
* <p>
|
||||
* This is defined in
|
||||
* <code>data/minecraft/dimension/appliedenergistics2/spatial_storage.json</code>
|
||||
*/
|
||||
public static final RegistryKey<Dimension> DIMENSION_ID = RegistryKey.func_240903_a_(Registry.DIMENSION_KEY,
|
||||
AppEng.makeId("spatial_storage"));
|
||||
|
||||
/**
|
||||
* ID of the {@link World} that is instantiated from the dimension/dimension
|
||||
* type.
|
||||
*/
|
||||
public static final RegistryKey<World> WORLD_ID = RegistryKey.func_240903_a_(Registry.WORLD_KEY,
|
||||
AppEng.makeId("spatial_storage"));
|
||||
|
||||
private SpatialStorageDimensionIds() {
|
||||
}
|
||||
|
||||
}
|
||||
+6
-6
@@ -39,13 +39,13 @@ import appeng.api.util.WorldCoord;
|
||||
import appeng.core.Api;
|
||||
import appeng.core.AppEng;
|
||||
|
||||
public class StorageHelper {
|
||||
public class SpatialStorageHelper {
|
||||
|
||||
private static StorageHelper instance;
|
||||
private static SpatialStorageHelper instance;
|
||||
|
||||
public static StorageHelper getInstance() {
|
||||
public static SpatialStorageHelper getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new StorageHelper();
|
||||
instance = new SpatialStorageHelper();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class StorageHelper {
|
||||
|
||||
try {
|
||||
oldWorld = (ServerWorld) entity.world;
|
||||
newWorld = (ServerWorld) link.dim;
|
||||
newWorld = link.dim;
|
||||
} catch (final Throwable e) {
|
||||
return entity;
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public class StorageHelper {
|
||||
ChunkStatus.FULL, true);
|
||||
|
||||
if (entity instanceof ServerPlayerEntity
|
||||
&& link.dim.func_234922_V_() == SpatialDimensionManager.STORAGE_DIMENSION_TYPE) {
|
||||
&& link.dim.func_234922_V_() == SpatialStorageDimensionIds.DIMENSION_TYPE_ID) {
|
||||
AppEng.instance().getAdvancementTriggers().getSpatialExplorer().trigger((ServerPlayerEntity) entity);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package appeng.spatial;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.NBTUtil;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
|
||||
/**
|
||||
* A plot inside the storage cell world that is assigned to a specific storage
|
||||
* cell.
|
||||
*/
|
||||
public class SpatialStoragePlot {
|
||||
|
||||
private static final String TAG_ID = "id";
|
||||
|
||||
private static final String TAG_SIZE = "size";
|
||||
|
||||
private static final String TAG_OWNER = "owner";
|
||||
|
||||
private static final String TAG_LAST_TRANSITION = "last_transition";
|
||||
|
||||
private static final int REGION_SIZE = 512;
|
||||
|
||||
public static final int MAX_SIZE = 128;
|
||||
|
||||
/**
|
||||
* Id of the plot.
|
||||
*/
|
||||
private final int id;
|
||||
|
||||
/**
|
||||
* The storage size of this dimension. This is dictated by the pylon structure
|
||||
* size used to perform the first transfer into this dimension. Once it's set,
|
||||
* it cannot be changed anymore.
|
||||
*/
|
||||
private final BlockPos size;
|
||||
|
||||
/**
|
||||
* AE2 player id of who primarily owned the network when the storage plot was
|
||||
* allocated.
|
||||
*/
|
||||
private final int owner;
|
||||
|
||||
/**
|
||||
* Information about the last transition into this plot.
|
||||
*/
|
||||
@Nullable
|
||||
private TransitionInfo lastTransition;
|
||||
|
||||
public SpatialStoragePlot(int id, BlockPos size, int owner) {
|
||||
this.id = id;
|
||||
this.size = size;
|
||||
this.owner = owner;
|
||||
if (size.getX() < 1 || size.getY() < 1 || size.getZ() < 1) {
|
||||
throw new IllegalArgumentException("Plot size " + size + " is smaller than minimum size.");
|
||||
}
|
||||
if (size.getX() > MAX_SIZE || size.getY() > MAX_SIZE || size.getZ() > MAX_SIZE) {
|
||||
throw new IllegalArgumentException("Plot size " + size + " exceeds maximum size of " + MAX_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The size in blocks of the plot in the storage dimension.
|
||||
*
|
||||
* @see #getOrigin()
|
||||
*/
|
||||
public BlockPos getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AE player id of the owner of this plot, or -1 if unknown.
|
||||
*
|
||||
* @see appeng.core.worlddata.IWorldPlayerData
|
||||
*/
|
||||
public int getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the last transition into this plot (if any).
|
||||
*/
|
||||
@Nullable
|
||||
public TransitionInfo getLastTransition() {
|
||||
return lastTransition;
|
||||
}
|
||||
|
||||
void setLastTransition(TransitionInfo info) {
|
||||
this.lastTransition = info;
|
||||
}
|
||||
|
||||
/**
|
||||
* The origin of this plot within the spatial storage dimension.
|
||||
* <p>
|
||||
* To map an integer to a specific position, it uses the following algorithm.
|
||||
*
|
||||
* The 2 least significant bits determine the sign for the x and z axis. Every
|
||||
* other pack of 2 bits locate the plot within a quadrant of a increasing area
|
||||
* by the bit position.
|
||||
*
|
||||
* The first 2 bits after the sign address a quadrant within 1024x1024 blocks
|
||||
* (or 4 region files)
|
||||
*
|
||||
* Every further will continue to double both x and z values. E.g. 2048x2048 for
|
||||
* the 3rd pack and 4096x4096 for the 4th.
|
||||
*
|
||||
* @see #getSize()
|
||||
*/
|
||||
public BlockPos getOrigin() {
|
||||
int signBits = id & 0b11;
|
||||
int offsetBits = id >> 2;
|
||||
int offsetScale = 1;
|
||||
int posx = REGION_SIZE / 2;
|
||||
int posz = REGION_SIZE / 2;
|
||||
|
||||
// find quadrant
|
||||
while (offsetBits != 0) {
|
||||
posx += REGION_SIZE * offsetScale * (offsetBits & 0b01);
|
||||
posz += REGION_SIZE * offsetScale * (offsetBits >> 1 & 0b01);
|
||||
|
||||
offsetBits >>= 2;
|
||||
offsetScale <<= 1;
|
||||
}
|
||||
|
||||
// mirror in one of 4 directions
|
||||
// First flip the z axis on every increment and then every two the x axis
|
||||
if ((signBits & 0b01) != 0) {
|
||||
posz *= -1;
|
||||
}
|
||||
if ((signBits & 0b10) != 0) {
|
||||
posx *= -1;
|
||||
}
|
||||
|
||||
// offset from cell center
|
||||
//
|
||||
// This is to ensure a 128x128x128 block plot (or 8x8 chunks) is centered within
|
||||
// the region.
|
||||
// This provides 12 chunks around it, so the default chunk loading radius on
|
||||
// servers of 10 will prevent adjacent regions to be generated. As well as a 24
|
||||
// chunks to the next plot.
|
||||
posx -= 64;
|
||||
posz -= 64;
|
||||
|
||||
return new BlockPos(posx, 64, posz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename of the region in the world save containing this plot.
|
||||
*/
|
||||
public String getRegionFilename() {
|
||||
BlockPos origin = this.getOrigin();
|
||||
ChunkPos originChunk = new ChunkPos(origin);
|
||||
|
||||
return String.format(Locale.ROOT, "r.%d.%d.mca", originChunk.getRegionCoordX(), originChunk.getRegionCoordZ());
|
||||
}
|
||||
|
||||
public CompoundNBT toTag() {
|
||||
CompoundNBT tag = new CompoundNBT();
|
||||
tag.putInt(TAG_ID, id);
|
||||
tag.put(TAG_SIZE, NBTUtil.writeBlockPos(size));
|
||||
tag.putInt(TAG_OWNER, owner);
|
||||
if (lastTransition != null) {
|
||||
tag.put(TAG_LAST_TRANSITION, lastTransition.toTag());
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static SpatialStoragePlot fromTag(CompoundNBT tag) {
|
||||
int id = tag.getInt(TAG_ID);
|
||||
BlockPos size = NBTUtil.readBlockPos(tag.getCompound(TAG_SIZE));
|
||||
int ownerId = tag.getInt(TAG_OWNER);
|
||||
SpatialStoragePlot plot = new SpatialStoragePlot(id, size, ownerId);
|
||||
|
||||
if (tag.contains(TAG_LAST_TRANSITION, Constants.NBT.TAG_COMPOUND)) {
|
||||
plot.lastTransition = TransitionInfo.fromTag(tag.getCompound(TAG_LAST_TRANSITION));
|
||||
}
|
||||
return plot;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/lgpl>.
|
||||
*/
|
||||
|
||||
package appeng.spatial;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.fml.server.ServerLifecycleHooks;
|
||||
|
||||
import appeng.core.AELog;
|
||||
import appeng.core.Api;
|
||||
|
||||
/**
|
||||
* Allocates and manages plots for spatial storage in the spatial storage world.
|
||||
*/
|
||||
public final class SpatialStoragePlotManager {
|
||||
|
||||
public static final SpatialStoragePlotManager INSTANCE = new SpatialStoragePlotManager();
|
||||
|
||||
private SpatialStoragePlotManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world used to store spatial storage cell's content.
|
||||
*/
|
||||
public ServerWorld getWorld() {
|
||||
MinecraftServer server = getServer();
|
||||
ServerWorld world = server.getWorld(SpatialStorageDimensionIds.WORLD_ID);
|
||||
if (world == null) {
|
||||
throw new IllegalStateException("The storage cell world is missing.");
|
||||
}
|
||||
return world;
|
||||
}
|
||||
|
||||
private SpatialStorageWorldData getWorldData() {
|
||||
return getWorld().getChunkProvider().getSavedData().getOrCreate(SpatialStorageWorldData::new,
|
||||
SpatialStorageWorldData.ID);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SpatialStoragePlot getPlot(int plotId) {
|
||||
if (plotId == -1) {
|
||||
return null;
|
||||
}
|
||||
return getWorldData().getPlotById(plotId);
|
||||
}
|
||||
|
||||
public SpatialStoragePlot allocatePlot(BlockPos size, int ownerId) {
|
||||
SpatialStoragePlot plot = getWorldData().allocatePlot(size, ownerId);
|
||||
AELog.info("Allocating storage cell plot %d with size %s for %d", plot.getId(), size, ownerId);
|
||||
return plot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last source for a spatial storage transition into the given plot.
|
||||
* This is used to allow the server-admin commands to know where the content of
|
||||
* a given plot came from.
|
||||
*/
|
||||
public void setLastTransition(int plotId, TransitionInfo info) {
|
||||
getWorldData().setLastTransition(plotId, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable list of all plots.
|
||||
*/
|
||||
public List<SpatialStoragePlot> getPlots() {
|
||||
return getWorldData().getPlots();
|
||||
}
|
||||
|
||||
public void freePlot(int plotId, boolean resetBlocks) {
|
||||
SpatialStoragePlot plot = getPlot(plotId);
|
||||
if (plot == null) {
|
||||
return; // Already removed apparently
|
||||
}
|
||||
|
||||
if (resetBlocks) {
|
||||
BlockPos from = plot.getOrigin();
|
||||
BlockPos to = from.add(plot.getSize()).add(-1, -1, -1);
|
||||
|
||||
AELog.info("Clearing spatial storage plot %s (%s -> %s)", plotId, from, to);
|
||||
|
||||
// This is slow, but it should usually be just an admin-command
|
||||
ServerWorld world = getWorld();
|
||||
BlockState matrixFrame = Api.instance().definitions().blocks().matrixFrame().block().getDefaultState();
|
||||
for (BlockPos blockPos : BlockPos.getAllInBoxMutable(from, to)) {
|
||||
world.setBlockState(blockPos, matrixFrame);
|
||||
}
|
||||
}
|
||||
|
||||
getWorldData().removePlot(plotId);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ServerLifecycleHooks.getCurrentServer();
|
||||
}
|
||||
|
||||
}
|
||||
+4
-1
@@ -7,8 +7,11 @@ import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
/**
|
||||
* Defines properties for how the sky in the spatial storage world is rendered.
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class StorageSkyProperties {
|
||||
public class SpatialStorageSkyProperties {
|
||||
|
||||
// See the fabric version of this to get any idea what its doing
|
||||
public static final DimensionRenderInfo INSTANCE = new DimensionRenderInfo(Float.NaN /* disables clouds */, false,
|
||||
@@ -0,0 +1,110 @@
|
||||
package appeng.spatial;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.INBT;
|
||||
import net.minecraft.nbt.ListNBT;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.storage.WorldSavedData;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import appeng.core.AELog;
|
||||
|
||||
/**
|
||||
* Extra data attached to the spatial storage world.
|
||||
*/
|
||||
public class SpatialStorageWorldData extends WorldSavedData {
|
||||
|
||||
/**
|
||||
* ID of this data when it is attached to a world.
|
||||
*/
|
||||
public static final String ID = "ae2_spatial_storage";
|
||||
|
||||
// Used to allow forward compatibility
|
||||
private static final int CURRENT_FORMAT = 2;
|
||||
|
||||
private static final String TAG_FORMAT = "format";
|
||||
|
||||
private static final String TAG_PLOTS = "plots";
|
||||
|
||||
private final Int2ObjectOpenHashMap<SpatialStoragePlot> plots = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public SpatialStorageWorldData() {
|
||||
super(ID);
|
||||
}
|
||||
|
||||
public SpatialStoragePlot getPlotById(int id) {
|
||||
return plots.get(id);
|
||||
}
|
||||
|
||||
public List<SpatialStoragePlot> getPlots() {
|
||||
return ImmutableList.copyOf(plots.values());
|
||||
}
|
||||
|
||||
public SpatialStoragePlot allocatePlot(BlockPos size, int owner) {
|
||||
|
||||
int nextId = 1;
|
||||
for (int id : plots.keySet()) {
|
||||
if (id >= nextId) {
|
||||
nextId = id + 1;
|
||||
}
|
||||
}
|
||||
|
||||
SpatialStoragePlot plot = new SpatialStoragePlot(nextId, size, owner);
|
||||
plots.put(nextId, plot);
|
||||
markDirty();
|
||||
return plot;
|
||||
}
|
||||
|
||||
public void removePlot(int plotId) {
|
||||
plots.remove(plotId);
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public void setLastTransition(int plotId, TransitionInfo info) {
|
||||
SpatialStoragePlot plot = plots.get(plotId);
|
||||
if (plot != null) {
|
||||
plot.setLastTransition(info);
|
||||
}
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(CompoundNBT tag) {
|
||||
int version = tag.getInt(TAG_FORMAT);
|
||||
if (version != CURRENT_FORMAT) {
|
||||
// Currently no new format has been defined, as such anything but the current
|
||||
// version is invalid
|
||||
throw new IllegalStateException("Invalid AE2 spatial info version: " + version);
|
||||
}
|
||||
|
||||
ListNBT plotsTag = tag.getList(TAG_PLOTS, Constants.NBT.TAG_COMPOUND);
|
||||
for (INBT plotTag : plotsTag) {
|
||||
SpatialStoragePlot plot = SpatialStoragePlot.fromTag((CompoundNBT) plotTag);
|
||||
|
||||
if (plots.containsKey(plot.getId())) {
|
||||
AELog.warn("Overwriting duplicate plot id %s", plot.getId());
|
||||
}
|
||||
plots.put(plot.getId(), plot);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundNBT write(CompoundNBT tag) {
|
||||
tag.putInt(TAG_FORMAT, CURRENT_FORMAT);
|
||||
|
||||
ListNBT plotTags = new ListNBT();
|
||||
for (SpatialStoragePlot plot : plots.values()) {
|
||||
plotTags.add(plot.toTag());
|
||||
}
|
||||
tag.put(TAG_PLOTS, plotTags);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package appeng.spatial;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.NBTUtil;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
/**
|
||||
* Defines the source world and area of a transition into the spatial storage
|
||||
* plot.
|
||||
*/
|
||||
public final class TransitionInfo {
|
||||
|
||||
public static final String TAG_WORLD_ID = "world_id";
|
||||
public static final String TAG_MIN = "min";
|
||||
public static final String TAG_MAX = "max";
|
||||
public static final String TAG_TIMESTAMP = "timestamp";
|
||||
|
||||
private final ResourceLocation worldId;
|
||||
|
||||
private final BlockPos min;
|
||||
|
||||
private final BlockPos max;
|
||||
|
||||
private final Instant timestamp;
|
||||
|
||||
public TransitionInfo(ResourceLocation worldId, BlockPos min, BlockPos max, Instant timestamp) {
|
||||
this.worldId = worldId;
|
||||
this.min = min.toImmutable();
|
||||
this.max = max.toImmutable();
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public ResourceLocation getWorldId() {
|
||||
return worldId;
|
||||
}
|
||||
|
||||
public BlockPos getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public BlockPos getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public Instant getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public CompoundNBT toTag() {
|
||||
CompoundNBT tag = new CompoundNBT();
|
||||
tag.putString(TAG_WORLD_ID, worldId.toString());
|
||||
tag.put(TAG_MIN, NBTUtil.writeBlockPos(min));
|
||||
tag.put(TAG_MAX, NBTUtil.writeBlockPos(max));
|
||||
tag.putLong(TAG_TIMESTAMP, timestamp.toEpochMilli());
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static TransitionInfo fromTag(CompoundNBT tag) {
|
||||
ResourceLocation worldId = new ResourceLocation(tag.getString(TAG_WORLD_ID));
|
||||
BlockPos min = NBTUtil.readBlockPos(tag.getCompound(TAG_MIN));
|
||||
BlockPos max = NBTUtil.readBlockPos(tag.getCompound(TAG_MAX));
|
||||
Instant timestamp = Instant.ofEpochMilli(tag.getLong(TAG_TIMESTAMP));
|
||||
return new TransitionInfo(worldId, min, max, timestamp);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user