Working on Spatial Stuff.

This commit is contained in:
Sebastian Hartte
2020-07-17 22:38:27 +02:00
parent 0c5c23e9ff
commit 15d4583c56
35 changed files with 860 additions and 693 deletions
@@ -0,0 +1,408 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2015, 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 appeng.api.AEApi;
import appeng.api.movable.IMovableHandler;
import appeng.api.movable.IMovableRegistry;
import appeng.api.util.AEPartLocation;
import appeng.api.util.WorldCoord;
import appeng.core.AELog;
import appeng.core.worlddata.WorldData;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket;
import net.minecraft.server.world.ServerChunkManager;
import net.minecraft.server.world.ServerTickScheduler;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Tickable;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ScheduledTick;
import net.minecraft.world.TickScheduler;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.WorldChunk;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class CachedPlane {
private final int x_size;
private final int z_size;
private final int cx_size;
private final int cz_size;
private final int x_offset;
private final int y_offset;
private final int z_offset;
private final int y_size;
private final WorldChunk[][] myChunks;
private final Column[][] myColumns;
private final List<BlockEntity> tiles = new ArrayList<>();
private final List<ScheduledTick<Block>> ticks = new ArrayList<>();
private final World world;
private final IMovableRegistry reg = AEApi.instance().registries().movable();
private final List<WorldCoord> updates = new ArrayList<>();
private int verticalBits;
private final BlockState matrixBlockState;
public CachedPlane(final World w, final int minX, final int minY, final int minZ, final int maxX, final int maxY,
final int maxZ) {
Block matrixFrameBlock = AEApi.instance().definitions().blocks().matrixFrame().maybeBlock().orElse(null);
if (matrixFrameBlock != null) {
this.matrixBlockState = matrixFrameBlock.getDefaultState();
} else {
this.matrixBlockState = null;
}
this.world = w;
this.x_size = maxX - minX + 1;
this.y_size = maxY - minY + 1;
this.z_size = maxZ - minZ + 1;
this.x_offset = minX;
this.y_offset = minY;
this.z_offset = minZ;
final int minCX = minX >> 4;
final int minCY = minY >> 4;
final int minCZ = minZ >> 4;
final int maxCX = maxX >> 4;
final int maxCY = maxY >> 4;
final int maxCZ = maxZ >> 4;
this.cx_size = maxCX - minCX + 1;
final int cy_size = maxCY - minCY + 1;
this.cz_size = maxCZ - minCZ + 1;
this.myChunks = new WorldChunk[this.cx_size][this.cz_size];
this.myColumns = new Column[this.x_size][this.z_size];
this.verticalBits = 0;
for (int cy = 0; cy < cy_size; cy++) {
this.verticalBits |= 1 << (minCY + cy);
}
for (int x = 0; x < this.x_size; x++) {
for (int z = 0; z < this.z_size; z++) {
this.myColumns[x][z] = new Column(w.getChunk((minX + x) >> 4, (minZ + z) >> 4), (minX + x) & 0xF,
(minZ + z) & 0xF, minCY, cy_size);
}
}
final IMovableRegistry mr = AEApi.instance().registries().movable();
for (int cx = 0; cx < this.cx_size; cx++) {
for (int cz = 0; cz < this.cz_size; cz++) {
final List<BlockPos> deadTiles = new ArrayList<>();
final WorldChunk c = w.getChunk(minCX + cx, minCZ + cz);
this.myChunks[cx][cz] = c;
Set<BlockPos> blockEntityPositions = c.getBlockEntityPositions();
for (BlockPos tePOS : blockEntityPositions) {
final BlockEntity te = c.getBlockEntity(tePOS);
if (te == null) {
continue;
}
if (tePOS.getX() >= minX && tePOS.getX() <= maxX && tePOS.getY() >= minY && tePOS.getY() <= maxY
&& tePOS.getZ() >= minZ && tePOS.getZ() <= maxZ) {
if (mr.askToMove(te)) {
this.tiles.add(te);
deadTiles.add(tePOS);
} else {
final BlockStorageData details = new BlockStorageData();
this.myColumns[tePOS.getX() - minX][tePOS.getZ() - minZ].fillData(tePOS.getY(), details);
// don't skip air, just let the code replace it...
if (details.state.isAir()) {
w.removeBlock(tePOS, false);
} else {
this.myColumns[tePOS.getX() - minX][tePOS.getZ() - minZ].setSkip(tePOS.getY());
}
}
}
}
for (final BlockPos cp : deadTiles) {
c.removeBlockEntity(cp);
}
final long gameTime = this.getWorld().getTime();
final TickScheduler<Block> pendingBlockTicks = this.getWorld().getBlockTickScheduler();
if (pendingBlockTicks instanceof ServerTickScheduler) {
List<ScheduledTick<Block>> pending = ((ServerTickScheduler<Block>) pendingBlockTicks)
.getScheduledTicksInChunk(c.getPos(), false, true);
for (final ScheduledTick<Block> entry : pending) {
final BlockPos tePOS = entry.pos;
if (tePOS.getX() >= minX && tePOS.getX() <= maxX && tePOS.getY() >= minY && tePOS.getY() <= maxY
&& tePOS.getZ() >= minZ && tePOS.getZ() <= maxZ) {
this.ticks.add(new ScheduledTick<>(tePOS, entry.getObject(),
entry.time - gameTime, entry.priority));
}
}
}
}
}
for (final BlockEntity te : this.tiles) {
try {
this.getWorld().blockEntities.remove(te);
if (te instanceof Tickable) {
this.getWorld().tickingBlockEntities.remove(te);
}
} catch (final Exception e) {
AELog.debug(e);
}
}
}
private IMovableHandler getHandler(final BlockEntity te) {
final IMovableRegistry mr = AEApi.instance().registries().movable();
return mr.getHandler(te);
}
void swap(final CachedPlane dst) {
final IMovableRegistry mr = AEApi.instance().registries().movable();
if (dst.x_size == this.x_size && dst.y_size == this.y_size && dst.z_size == this.z_size) {
AELog.info("Block Copy Scale: " + this.x_size + ", " + this.y_size + ", " + this.z_size);
long startTime = System.nanoTime();
final BlockStorageData aD = new BlockStorageData();
final BlockStorageData bD = new BlockStorageData();
for (int x = 0; x < this.x_size; x++) {
for (int z = 0; z < this.z_size; z++) {
final Column a = this.myColumns[x][z];
final Column b = dst.myColumns[x][z];
for (int y = 0; y < this.y_size; y++) {
final int src_y = y + this.y_offset;
final int dst_y = y + dst.y_offset;
if (a.doNotSkip(src_y) && b.doNotSkip(dst_y)) {
a.fillData(src_y, aD);
b.fillData(dst_y, bD);
a.setBlockState(src_y, bD);
b.setBlockState(dst_y, aD);
} else {
this.markForUpdate(x + this.x_offset, src_y, z + this.z_offset);
dst.markForUpdate(x + dst.x_offset, dst_y, z + dst.z_offset);
}
}
}
}
long endTime = System.nanoTime();
long duration = endTime - startTime;
AELog.info("Block Copy Time: " + duration);
for (final BlockEntity te : this.tiles) {
final BlockPos tePOS = te.getPos();
dst.addTile(tePOS.getX() - this.x_offset, tePOS.getY() - this.y_offset, tePOS.getZ() - this.z_offset,
te, this, mr);
}
for (final BlockEntity te : dst.tiles) {
final BlockPos tePOS = te.getPos();
this.addTile(tePOS.getX() - dst.x_offset, tePOS.getY() - dst.y_offset, tePOS.getZ() - dst.z_offset, te,
dst, mr);
}
for (final ScheduledTick<Block> entry : this.ticks) {
final BlockPos tePOS = entry.pos;
dst.addTick(tePOS.getX() - this.x_offset, tePOS.getY() - this.y_offset, tePOS.getZ() - this.z_offset,
entry);
}
for (final ScheduledTick<Block> entry : dst.ticks) {
final BlockPos tePOS = entry.pos;
this.addTick(tePOS.getX() - dst.x_offset, tePOS.getY() - dst.y_offset, tePOS.getZ() - dst.z_offset,
entry);
}
startTime = System.nanoTime();
this.updateChunks();
dst.updateChunks();
endTime = System.nanoTime();
duration = endTime - startTime;
AELog.info("Update Time: " + duration);
}
}
private void markForUpdate(final int x, final int y, final int z) {
this.updates.add(new WorldCoord(x, y, z));
for (final AEPartLocation d : AEPartLocation.SIDE_LOCATIONS) {
this.updates.add(new WorldCoord(x + d.xOffset, y + d.yOffset, z + d.zOffset));
}
}
private void addTick(final int x, final int y, final int z, final ScheduledTick<Block> entry) {
BlockPos where = new BlockPos(x + this.x_offset, y + this.y_offset, z + this.z_offset);
this.world.getBlockTickScheduler().schedule(where, entry.getObject(), (int) entry.time,
entry.priority);
}
private void addTile(final int x, final int y, final int z, final BlockEntity te,
final CachedPlane alternateDestination, final IMovableRegistry mr) {
try {
final Column c = this.myColumns[x][z];
if (c.doNotSkip(y + this.y_offset) || alternateDestination == null) {
final IMovableHandler handler = this.getHandler(te);
try {
handler.moveTile(te, this.world,
new BlockPos(x + this.x_offset, y + this.y_offset, z + this.z_offset));
} catch (final Throwable e) {
AELog.debug(e);
final BlockPos pos = new BlockPos(x, y, z);
// attempt recovery...
c.c.setBlockEntity(pos, te);
this.world.updateListeners(pos, this.world.getBlockState(pos), this.world.getBlockState(pos), z);
}
mr.doneMoving(te);
} else {
alternateDestination.addTile(x, y, z, te, null, mr);
}
} catch (final Throwable e) {
AELog.debug(e);
}
}
private void updateChunks() {
// update shit..
for (int x = 0; x < this.cx_size; x++) {
for (int z = 0; z < this.cz_size; z++) {
final WorldChunk c = this.myChunks[x][z];
// FIXME: Light shit
c.markDirty();
}
}
// send shit...
for (int x = 0; x < this.cx_size; x++) {
for (int z = 0; z < this.cz_size; z++) {
final WorldChunk c = this.myChunks[x][z];
WorldData.instance().compassData().service().updateArea((ServerWorld) this.getWorld(), c);
// FIXME this was sending chunks to players...
ChunkDataS2CPacket cdp = new ChunkDataS2CPacket(c, verticalBits, false);
((ServerChunkManager) world.getChunkManager()).threadedAnvilChunkStorage.getPlayersWatchingChunk(c.getPos(), false)
.forEach(spe -> spe.networkHandler.sendPacket(cdp));
}
}
// FIXME check if this makes any sense at all to send changes to players asap
ServerChunkManager serverChunkProvider = (ServerChunkManager) world.getChunkManager();
serverChunkProvider.tick(() -> false);
}
List<WorldCoord> getUpdates() {
return this.updates;
}
World getWorld() {
return this.world;
}
private static class BlockStorageData {
public BlockState state;
public int light;
}
private class Column {
private final int x;
private final int z;
private final Chunk c;
private List<Integer> skipThese = null;
public Column(final Chunk chunk, final int x, final int z, final int chunkY, final int chunkHeight) {
this.x = x;
this.z = z;
this.c = chunk;
final ChunkSection[] storage = this.c.getSectionArray();
// make sure storage exists before hand...
for (int ay = 0; ay < chunkHeight; ay++) {
final int by = (ay + chunkY);
ChunkSection extendedblockstorage = storage[by];
if (extendedblockstorage == null) {
extendedblockstorage = storage[by] = new ChunkSection(by << 4);
}
}
}
private void setBlockState(final int y, BlockStorageData data) {
if (data.state == CachedPlane.this.matrixBlockState) {
data.state = Blocks.AIR.getDefaultState();
}
final ChunkSection[] storage = this.c.getSectionArray();
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) {
final ChunkSection[] storage = this.c.getSectionArray();
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) {
final ChunkSection[] storage = this.c.getSectionArray();
final ChunkSection extendedblockstorage = storage[y >> 4];
if (CachedPlane.this.reg
.isBlacklisted(extendedblockstorage.getBlockState(this.x, y & 15, this.z).getBlock())) {
return false;
}
return this.skipThese == null || !this.skipThese.contains(y);
}
private void setSkip(final int yCoord) {
if (this.skipThese == null) {
this.skipThese = new ArrayList<>();
}
this.skipThese.add(yCoord);
}
}
}
@@ -0,0 +1,26 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, 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 net.minecraft.util.math.BlockPos;
public interface ISpatialVisitor {
void visit(BlockPos pos);
}
@@ -0,0 +1,70 @@
package appeng.spatial;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtHelper;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.PersistentState;
/**
* Helps with encoding and decoding the extra data we attach to each created
* storage dimension world as persistent state.
*/
public final class SpatialDimensionExtraData extends PersistentState {
/**
* 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.ORIGIN;
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 fromTag(CompoundTag 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 = NbtHelper.toBlockPos(tag.getCompound(TAG_SIZE));
}
@Override
public CompoundTag toTag(CompoundTag tag) {
tag.putInt(TAG_FORMAT, CURRENT_FORMAT);
tag.put(TAG_SIZE, NbtHelper.fromBlockPos(size));
return tag;
}
}
@@ -0,0 +1,196 @@
/*
* 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 appeng.api.storage.ISpatialDimension;
import appeng.core.AELog;
import appeng.core.AppEng;
import appeng.core.localization.GuiText;
import appeng.hooks.DynamicDimensions;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionType;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Locale;
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.of(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) {
Identifier dimKey = findFreeDimensionId();
AELog.info("Allocating storage cell dimension '%s'", dimKey);
DynamicDimensions dynamicDimensions = (DynamicDimensions) getServer();
RegistryKey<World> worldId = RegistryKey.of(Registry.DIMENSION, dimKey);
ServerWorld world = dynamicDimensions.addWorld(worldId, STORAGE_DIMENSION_TYPE, StorageChunkGenerator.INSTANCE);
SpatialDimensionExtraData spatialExtraData = world.getPersistentStateManager().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 Identifier findFreeDimensionId() {
int maxId = 0;
for (RegistryKey<World> worldId : getServer().getWorldRegistryKeys()) {
Identifier regName = worldId.getValue();
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 new Identifier(AppEng.MOD_ID, DIM_ID_PREFIX + maxId);
}
@Override
public void deleteCellDimension(RegistryKey<World> worldId) {
AELog.info("Unregistering storage cell dimension %s", worldId.getValue());
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) {
Identifier id = worldId.getValue();
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.getDimensionRegistryKey().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.ORIGIN;
}
@Override
public void addCellDimensionTooltip(RegistryKey<World> worldId, List<Text> lines) {
// Check if the cell dimension type is even registered
Identifier registryName = worldId.getValue();
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.getPersistentStateManager().get(SpatialDimensionExtraData::new, SpatialDimensionExtraData.ID);
}
private static MinecraftServer getServer() {
return AppEng.instance().getServer();
}
}
@@ -0,0 +1,51 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, 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 net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeEffects;
import net.minecraft.world.gen.surfacebuilder.ConfiguredSurfaceBuilder;
import net.minecraft.world.gen.surfacebuilder.SurfaceBuilder;
public class StorageCellBiome extends Biome {
public static final StorageCellBiome INSTANCE = new StorageCellBiome();
public StorageCellBiome() {
super(new Biome.Settings().surfaceBuilder(new ConfiguredSurfaceBuilder<>(SurfaceBuilder.NOPE, SurfaceBuilder.STONE_CONFIG))
.precipitation(Precipitation.NONE).category(Category.NONE).depth(0).scale(1)
// Copied from the vanilla void biome
.temperature(0.5F).downfall(0.5F)
.effects(new BiomeEffects.Builder()
.waterColor(4159204)
.waterFogColor(329011)
.fogColor(0)
.build())
.parent(null));
}
@Override
@Environment(EnvType.CLIENT)
public int getSkyColor() {
return 0x111111;
}
}
@@ -0,0 +1,132 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, 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 appeng.api.AEApi;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
import net.minecraft.world.ChunkRegion;
import net.minecraft.world.Heightmap;
import net.minecraft.world.WorldAccess;
import net.minecraft.world.biome.source.BiomeAccess;
import net.minecraft.world.biome.source.BiomeSource;
import net.minecraft.world.biome.source.FixedBiomeSource;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.gen.GenerationStep;
import net.minecraft.world.gen.StructureAccessor;
import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.world.gen.chunk.StructuresConfig;
import net.minecraft.world.gen.chunk.VerticalBlockSample;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
public class StorageChunkGenerator extends ChunkGenerator {
private final VerticalBlockSample columnSample;
public static final StorageChunkGenerator INSTANCE = new StorageChunkGenerator();
public static final Codec<StorageChunkGenerator> CODEC = RecordCodecBuilder.create((instance) ->
instance.stable(INSTANCE));
private final BlockState defaultBlockState;
private StorageChunkGenerator() {
super(createBiomeProvider(), createSettings());
this.defaultBlockState = AEApi.instance().definitions().blocks().matrixFrame().block().getDefaultState();
// Vertical sample is mostly used for Feature generation, for those purposes we're all filled with matrix blocks
BlockState[] columnSample = new BlockState[256];
Arrays.fill(columnSample, this.defaultBlockState);
this.columnSample = new VerticalBlockSample(columnSample);
}
@Override
protected Codec<? extends ChunkGenerator> method_28506() {
return CODEC;
}
private static BiomeSource createBiomeProvider() {
return new FixedBiomeSource(StorageCellBiome.INSTANCE);
}
private static StructuresConfig createSettings() {
return new StructuresConfig(Optional.empty(), Collections.emptyMap());
}
@Override
public void buildSurface(ChunkRegion region, Chunk chunk) {
this.fillChunk(chunk);
chunk.setShouldSave(false);
}
private void fillChunk(Chunk chunk) {
BlockPos.Mutable mutPos = new BlockPos.Mutable();
for (int cx = 0; cx < 16; cx++) {
mutPos.setX(cx);
for (int cz = 0; cz < 16; cz++) {
// FIXME: It's likely a bad idea to fill Y in the inner-loop given the storage
// layout of chunks
mutPos.setZ(cz);
for (int cy = 0; cy < 256; cy++) {
mutPos.setY(cy);
chunk.setBlockState(mutPos, defaultBlockState, false);
}
}
}
}
@Override
public int getSeaLevel() {
return 0;
}
@Override
public ChunkGenerator withSeed(long seed) {
return this;
}
@Override
public void populateNoise(WorldAccess world, StructureAccessor accessor, Chunk chunk) {
}
@Override
public BlockView getColumnSample(int x, int z) {
return columnSample;
}
@Override
public int getHeight(int p_222529_1_, int p_222529_2_, Heightmap.Type heightmapType) {
return 0;
}
@Override
public void generateFeatures(ChunkRegion region, StructureAccessor accessor) {
}
@Override
public void carve(long seed, BiomeAccess access, Chunk chunk, GenerationStep.Carver carver) {
}
}
@@ -0,0 +1,244 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2015, 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 appeng.api.AEApi;
import appeng.api.util.WorldCoord;
import appeng.core.AppEng;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.dimension.DimensionType;
import java.util.ArrayList;
import java.util.List;
public class StorageHelper {
private static StorageHelper instance;
public static StorageHelper getInstance() {
if (instance == null) {
instance = new StorageHelper();
}
return instance;
}
/**
* Mostly from dimensional doors.. which mostly got it form X-Comp.
*
* @param entity to be teleported entity
* @param link destination
*
* @return teleported entity
*/
private Entity teleportEntity(Entity entity, final TelDestination link) {
final ServerWorld oldWorld;
final ServerWorld newWorld;
try {
oldWorld = (ServerWorld) entity.world;
newWorld = link.dim;
} catch (final Throwable e) {
return entity;
}
if (oldWorld == null) {
return entity;
}
if (newWorld == null) {
return entity;
}
if (newWorld == oldWorld) {
return entity;
}
// Are we riding something? Teleport it instead.
if (entity.hasVehicle()) {
return this.teleportEntity(entity.getVehicle(), link);
}
// Is something riding us? Handle it first.
final List<Entity> passengers = entity.getPassengerList();
final List<Entity> passengersOnOtherSide = new ArrayList<>(passengers.size());
for (Entity passenger : passengers) {
passenger.stopRiding();
passengersOnOtherSide.add(this.teleportEntity(passenger, link));
}
// We keep track of all so we can remount them on the other side.
// load the chunk!
newWorld.getChunkManager().getChunk(MathHelper.floor(link.x) >> 4, MathHelper.floor(link.z) >> 4,
ChunkStatus.FULL, true);
if (entity instanceof ServerPlayerEntity && link.dim.getDimensionRegistryKey().equals(SpatialDimensionManager.STORAGE_DIMENSION_TYPE)) {
AppEng.instance().getAdvancementTriggers().getSpatialExplorer().trigger((ServerPlayerEntity) entity);
}
// FIXME FABRIC new METeleporter(link)
float yaw = entity.yaw;
entity = entity.changeDimension(link.dim);
entity.yaw = yaw;
entity.refreshPositionAfterTeleport(link.x, link.y, link.z);
entity.setVelocity(0, 0, 0);
if (!passengersOnOtherSide.isEmpty()) {
for (Entity passanger : passengersOnOtherSide) {
passanger.startRiding(entity, true);
}
}
return entity;
}
private void transverseEdges(final int minX, final int minY, final int minZ, final int maxX, final int maxY,
final int maxZ, final ISpatialVisitor visitor) {
for (int y = minY; y < maxY; y++) {
for (int z = minZ; z < maxZ; z++) {
visitor.visit(new BlockPos(minX, y, z));
visitor.visit(new BlockPos(maxX, y, z));
}
}
for (int x = minX; x < maxX; x++) {
for (int z = minZ; z < maxZ; z++) {
visitor.visit(new BlockPos(x, minY, z));
visitor.visit(new BlockPos(x, maxY, z));
}
}
for (int x = minX; x < maxX; x++) {
for (int y = minY; y < maxY; y++) {
visitor.visit(new BlockPos(x, y, minZ));
visitor.visit(new BlockPos(x, y, maxZ));
}
}
}
public void swapRegions(final ServerWorld srcWorld, final int srcX, final int srcY, final int srcZ, final ServerWorld dstWorld,
final int dstX, final int dstY, final int dstZ, final int scaleX, final int scaleY, final int scaleZ) {
AEApi.instance().definitions().blocks().matrixFrame().maybeBlock()
.ifPresent(matrixFrameBlock -> this.transverseEdges(dstX - 1, dstY - 1, dstZ - 1, dstX + scaleX + 1,
dstY + scaleY + 1, dstZ + scaleZ + 1,
new WrapInMatrixFrame(matrixFrameBlock.getDefaultState(), dstWorld)));
final Box srcBox = new Box(srcX, srcY, srcZ, srcX + scaleX + 1, srcY + scaleY + 1,
srcZ + scaleZ + 1);
final Box dstBox = new Box(dstX, dstY, dstZ, dstX + scaleX + 1, dstY + scaleY + 1,
dstZ + scaleZ + 1);
final CachedPlane cDst = new CachedPlane(dstWorld, dstX, dstY, dstZ, dstX + scaleX, dstY + scaleY,
dstZ + scaleZ);
final CachedPlane cSrc = new CachedPlane(srcWorld, srcX, srcY, srcZ, srcX + scaleX, srcY + scaleY,
srcZ + scaleZ);
// do nearly all the work... swaps blocks, tiles, and block ticks
cSrc.swap(cDst);
final List<Entity> srcE = srcWorld.getEntitiesIncludingUngeneratedChunks(Entity.class, srcBox);
final List<Entity> dstE = dstWorld.getEntitiesIncludingUngeneratedChunks(Entity.class, dstBox);
for (final Entity e : dstE) {
this.teleportEntity(e, new TelDestination(srcWorld, srcBox, e.getX(), e.getY(), e.getZ(),
-dstX + srcX, -dstY + srcY, -dstZ + srcZ));
}
for (final Entity e : srcE) {
this.teleportEntity(e, new TelDestination(dstWorld, dstBox, e.getX(), e.getY(), e.getZ(),
-srcX + dstX, -srcY + dstY, -srcZ + dstZ));
}
for (final WorldCoord wc : cDst.getUpdates()) {
cSrc.getWorld().updateNeighborsAlways(wc.getPos(), Blocks.AIR);
}
for (final WorldCoord wc : cSrc.getUpdates()) {
cSrc.getWorld().updateNeighborsAlways(wc.getPos(), Blocks.AIR);
}
this.transverseEdges(srcX - 1, srcY - 1, srcZ - 1, srcX + scaleX + 1, srcY + scaleY + 1, srcZ + scaleZ + 1,
new TriggerUpdates(srcWorld));
this.transverseEdges(dstX - 1, dstY - 1, dstZ - 1, dstX + scaleX + 1, dstY + scaleY + 1, dstZ + scaleZ + 1,
new TriggerUpdates(dstWorld));
this.transverseEdges(srcX, srcY, srcZ, srcX + scaleX, srcY + scaleY, srcZ + scaleZ,
new TriggerUpdates(srcWorld));
this.transverseEdges(dstX, dstY, dstZ, dstX + scaleX, dstY + scaleY, dstZ + scaleZ,
new TriggerUpdates(dstWorld));
}
private static class TriggerUpdates implements ISpatialVisitor {
private final World dst;
public TriggerUpdates(final World dst2) {
this.dst = dst2;
}
@Override
public void visit(final BlockPos pos) {
final BlockState state = this.dst.getBlockState(pos);
final Block blk = state.getBlock();
blk.neighborUpdate(state, this.dst, pos, blk, pos, false);
}
}
private static class WrapInMatrixFrame implements ISpatialVisitor {
private final World dst;
private final BlockState state;
public WrapInMatrixFrame(final BlockState state, final World dst2) {
this.dst = dst2;
this.state = state;
}
@Override
public void visit(final BlockPos pos) {
this.dst.setBlockState(pos, this.state);
}
}
private static class TelDestination {
private final ServerWorld dim;
private final double x;
private final double y;
private final double z;
TelDestination(final ServerWorld dimension, final Box srcBox, final double x, final double y,
final double z, final int tileX, final int tileY, final int tileZ) {
this.dim = dimension;
this.x = Math.min(srcBox.maxX - 0.5, Math.max(srcBox.minX + 0.5, x + tileX));
this.y = Math.min(srcBox.maxY - 0.5, Math.max(srcBox.minY + 0.5, y + tileY));
this.z = Math.min(srcBox.maxZ - 0.5, Math.max(srcBox.minZ + 0.5, z + tileZ));
}
}
}