275 lines
9.4 KiB
Java
275 lines
9.4 KiB
Java
package net.minecraft.world.level.block.entity;
|
|
|
|
import com.mojang.logging.LogUtils;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.HolderLookup.Provider;
|
|
import net.minecraft.core.registries.Registries;
|
|
import net.minecraft.data.worldgen.features.EndFeatures;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.NbtUtils;
|
|
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.world.level.BlockGetter;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.chunk.LevelChunk;
|
|
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
|
|
import net.minecraft.world.level.levelgen.feature.Feature;
|
|
import net.minecraft.world.level.levelgen.feature.configurations.EndGatewayConfiguration;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final int SPAWN_TIME = 200;
|
|
private static final int COOLDOWN_TIME = 40;
|
|
private static final int ATTENTION_INTERVAL = 2400;
|
|
private static final int EVENT_COOLDOWN = 1;
|
|
private static final int GATEWAY_HEIGHT_ABOVE_SURFACE = 10;
|
|
private long age;
|
|
private int teleportCooldown;
|
|
@Nullable
|
|
private BlockPos exitPortal;
|
|
private boolean exactTeleport;
|
|
|
|
public TheEndGatewayBlockEntity(BlockPos blockPos, BlockState blockState) {
|
|
super(BlockEntityType.END_GATEWAY, blockPos, blockState);
|
|
}
|
|
|
|
@Override
|
|
protected void saveAdditional(CompoundTag tag, Provider registries) {
|
|
super.saveAdditional(tag, registries);
|
|
tag.putLong("Age", this.age);
|
|
if (this.exitPortal != null) {
|
|
tag.put("exit_portal", NbtUtils.writeBlockPos(this.exitPortal));
|
|
}
|
|
|
|
if (this.exactTeleport) {
|
|
tag.putBoolean("ExactTeleport", true);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void loadAdditional(CompoundTag tag, Provider registries) {
|
|
super.loadAdditional(tag, registries);
|
|
this.age = tag.getLong("Age");
|
|
NbtUtils.readBlockPos(tag, "exit_portal").filter(Level::isInSpawnableBounds).ifPresent(blockPos -> this.exitPortal = blockPos);
|
|
this.exactTeleport = tag.getBoolean("ExactTeleport");
|
|
}
|
|
|
|
public static void beamAnimationTick(Level level, BlockPos pos, BlockState state, TheEndGatewayBlockEntity blockEntity) {
|
|
blockEntity.age++;
|
|
if (blockEntity.isCoolingDown()) {
|
|
blockEntity.teleportCooldown--;
|
|
}
|
|
}
|
|
|
|
public static void portalTick(Level level, BlockPos pos, BlockState state, TheEndGatewayBlockEntity blockEntity) {
|
|
boolean bl = blockEntity.isSpawning();
|
|
boolean bl2 = blockEntity.isCoolingDown();
|
|
blockEntity.age++;
|
|
if (bl2) {
|
|
blockEntity.teleportCooldown--;
|
|
} else if (blockEntity.age % 2400L == 0L) {
|
|
triggerCooldown(level, pos, state, blockEntity);
|
|
}
|
|
|
|
if (bl != blockEntity.isSpawning() || bl2 != blockEntity.isCoolingDown()) {
|
|
setChanged(level, pos, state);
|
|
}
|
|
}
|
|
|
|
public boolean isSpawning() {
|
|
return this.age < 200L;
|
|
}
|
|
|
|
public boolean isCoolingDown() {
|
|
return this.teleportCooldown > 0;
|
|
}
|
|
|
|
public float getSpawnPercent(float partialTicks) {
|
|
return Mth.clamp(((float)this.age + partialTicks) / 200.0F, 0.0F, 1.0F);
|
|
}
|
|
|
|
public float getCooldownPercent(float partialTicks) {
|
|
return 1.0F - Mth.clamp((this.teleportCooldown - partialTicks) / 40.0F, 0.0F, 1.0F);
|
|
}
|
|
|
|
public ClientboundBlockEntityDataPacket getUpdatePacket() {
|
|
return ClientboundBlockEntityDataPacket.create(this);
|
|
}
|
|
|
|
@Override
|
|
public CompoundTag getUpdateTag(Provider registries) {
|
|
return this.saveCustomOnly(registries);
|
|
}
|
|
|
|
public static void triggerCooldown(Level level, BlockPos pos, BlockState state, TheEndGatewayBlockEntity blockEntity) {
|
|
if (!level.isClientSide) {
|
|
blockEntity.teleportCooldown = 40;
|
|
level.blockEvent(pos, state.getBlock(), 1, 0);
|
|
setChanged(level, pos, state);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean triggerEvent(int id, int type) {
|
|
if (id == 1) {
|
|
this.teleportCooldown = 40;
|
|
return true;
|
|
} else {
|
|
return super.triggerEvent(id, type);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public Vec3 getPortalPosition(ServerLevel level, BlockPos pos) {
|
|
if (this.exitPortal == null && level.dimension() == Level.END) {
|
|
BlockPos blockPos = findOrCreateValidTeleportPos(level, pos);
|
|
blockPos = blockPos.above(10);
|
|
LOGGER.debug("Creating portal at {}", blockPos);
|
|
spawnGatewayPortal(level, blockPos, EndGatewayConfiguration.knownExit(pos, false));
|
|
this.setExitPosition(blockPos, this.exactTeleport);
|
|
}
|
|
|
|
if (this.exitPortal != null) {
|
|
BlockPos blockPos = this.exactTeleport ? this.exitPortal : findExitPosition(level, this.exitPortal);
|
|
return blockPos.getBottomCenter();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static BlockPos findExitPosition(Level level, BlockPos pos) {
|
|
BlockPos blockPos = findTallestBlock(level, pos.offset(0, 2, 0), 5, false);
|
|
LOGGER.debug("Best exit position for portal at {} is {}", pos, blockPos);
|
|
return blockPos.above();
|
|
}
|
|
|
|
private static BlockPos findOrCreateValidTeleportPos(ServerLevel level, BlockPos pos) {
|
|
Vec3 vec3 = findExitPortalXZPosTentative(level, pos);
|
|
LevelChunk levelChunk = getChunk(level, vec3);
|
|
BlockPos blockPos = findValidSpawnInChunk(levelChunk);
|
|
if (blockPos == null) {
|
|
BlockPos blockPos2 = BlockPos.containing(vec3.x + 0.5, 75.0, vec3.z + 0.5);
|
|
LOGGER.debug("Failed to find a suitable block to teleport to, spawning an island on {}", blockPos2);
|
|
level.registryAccess()
|
|
.lookup(Registries.CONFIGURED_FEATURE)
|
|
.flatMap(registry -> registry.get(EndFeatures.END_ISLAND))
|
|
.ifPresent(
|
|
reference -> ((ConfiguredFeature)reference.value())
|
|
.place(level, level.getChunkSource().getGenerator(), RandomSource.create(blockPos2.asLong()), blockPos2)
|
|
);
|
|
blockPos = blockPos2;
|
|
} else {
|
|
LOGGER.debug("Found suitable block to teleport to: {}", blockPos);
|
|
}
|
|
|
|
return findTallestBlock(level, blockPos, 16, true);
|
|
}
|
|
|
|
private static Vec3 findExitPortalXZPosTentative(ServerLevel level, BlockPos pos) {
|
|
Vec3 vec3 = new Vec3(pos.getX(), 0.0, pos.getZ()).normalize();
|
|
int i = 1024;
|
|
Vec3 vec32 = vec3.scale(1024.0);
|
|
|
|
for (int j = 16; !isChunkEmpty(level, vec32) && j-- > 0; vec32 = vec32.add(vec3.scale(-16.0))) {
|
|
LOGGER.debug("Skipping backwards past nonempty chunk at {}", vec32);
|
|
}
|
|
|
|
for (int var6 = 16; isChunkEmpty(level, vec32) && var6-- > 0; vec32 = vec32.add(vec3.scale(16.0))) {
|
|
LOGGER.debug("Skipping forward past empty chunk at {}", vec32);
|
|
}
|
|
|
|
LOGGER.debug("Found chunk at {}", vec32);
|
|
return vec32;
|
|
}
|
|
|
|
private static boolean isChunkEmpty(ServerLevel level, Vec3 pos) {
|
|
return getChunk(level, pos).getHighestFilledSectionIndex() == -1;
|
|
}
|
|
|
|
private static BlockPos findTallestBlock(BlockGetter level, BlockPos pos, int radius, boolean allowBedrock) {
|
|
BlockPos blockPos = null;
|
|
|
|
for (int i = -radius; i <= radius; i++) {
|
|
for (int j = -radius; j <= radius; j++) {
|
|
if (i != 0 || j != 0 || allowBedrock) {
|
|
for (int k = level.getMaxY(); k > (blockPos == null ? level.getMinY() : blockPos.getY()); k--) {
|
|
BlockPos blockPos2 = new BlockPos(pos.getX() + i, k, pos.getZ() + j);
|
|
BlockState blockState = level.getBlockState(blockPos2);
|
|
if (blockState.isCollisionShapeFullBlock(level, blockPos2) && (allowBedrock || !blockState.is(Blocks.BEDROCK))) {
|
|
blockPos = blockPos2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return blockPos == null ? pos : blockPos;
|
|
}
|
|
|
|
private static LevelChunk getChunk(Level level, Vec3 pos) {
|
|
return level.getChunk(Mth.floor(pos.x / 16.0), Mth.floor(pos.z / 16.0));
|
|
}
|
|
|
|
@Nullable
|
|
private static BlockPos findValidSpawnInChunk(LevelChunk chunk) {
|
|
ChunkPos chunkPos = chunk.getPos();
|
|
BlockPos blockPos = new BlockPos(chunkPos.getMinBlockX(), 30, chunkPos.getMinBlockZ());
|
|
int i = chunk.getHighestSectionPosition() + 16 - 1;
|
|
BlockPos blockPos2 = new BlockPos(chunkPos.getMaxBlockX(), i, chunkPos.getMaxBlockZ());
|
|
BlockPos blockPos3 = null;
|
|
double d = 0.0;
|
|
|
|
for (BlockPos blockPos4 : BlockPos.betweenClosed(blockPos, blockPos2)) {
|
|
BlockState blockState = chunk.getBlockState(blockPos4);
|
|
BlockPos blockPos5 = blockPos4.above();
|
|
BlockPos blockPos6 = blockPos4.above(2);
|
|
if (blockState.is(Blocks.END_STONE)
|
|
&& !chunk.getBlockState(blockPos5).isCollisionShapeFullBlock(chunk, blockPos5)
|
|
&& !chunk.getBlockState(blockPos6).isCollisionShapeFullBlock(chunk, blockPos6)) {
|
|
double e = blockPos4.distToCenterSqr(0.0, 0.0, 0.0);
|
|
if (blockPos3 == null || e < d) {
|
|
blockPos3 = blockPos4;
|
|
d = e;
|
|
}
|
|
}
|
|
}
|
|
|
|
return blockPos3;
|
|
}
|
|
|
|
private static void spawnGatewayPortal(ServerLevel level, BlockPos pos, EndGatewayConfiguration config) {
|
|
Feature.END_GATEWAY.place(config, level, level.getChunkSource().getGenerator(), RandomSource.create(), pos);
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldRenderFace(Direction face) {
|
|
return Block.shouldRenderFace(this.getBlockState(), this.level.getBlockState(this.getBlockPos().relative(face)), face);
|
|
}
|
|
|
|
public int getParticleAmount() {
|
|
int i = 0;
|
|
|
|
for (Direction direction : Direction.values()) {
|
|
i += this.shouldRenderFace(direction) ? 1 : 0;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
public void setExitPosition(BlockPos exitPortal, boolean exactTeleport) {
|
|
this.exactTeleport = exactTeleport;
|
|
this.exitPortal = exitPortal;
|
|
this.setChanged();
|
|
}
|
|
}
|