PlayerSpawnFinder.java

net.minecraft.server.level.PlayerSpawnFinder

信息

  • 全限定名:net.minecraft.server.level.PlayerSpawnFinder
  • 类型:public class
  • 包:net.minecraft.server.level
  • 源码路径:src/main/java/net/minecraft/server/level/PlayerSpawnFinder.java
  • 起始行号:L28
  • 职责:

    TODO

字段/常量

  • PLAYER_DIMENSIONS

    • 类型: EntityDimensions
    • 修饰符: private static final
    • 源码定位: L29
    • 说明:

      TODO

  • ABSOLUTE_MAX_ATTEMPTS

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L30
    • 说明:

      TODO

  • level

    • 类型: ServerLevel
    • 修饰符: private final
    • 源码定位: L31
    • 说明:

      TODO

  • spawnSuggestion

    • 类型: BlockPos
    • 修饰符: private final
    • 源码定位: L32
    • 说明:

      TODO

  • radius

    • 类型: int
    • 修饰符: private final
    • 源码定位: L33
    • 说明:

      TODO

  • candidateCount

    • 类型: int
    • 修饰符: private final
    • 源码定位: L34
    • 说明:

      TODO

  • coprime

    • 类型: int
    • 修饰符: private final
    • 源码定位: L35
    • 说明:

      TODO

  • offset

    • 类型: int
    • 修饰符: private final
    • 源码定位: L36
    • 说明:

      TODO

  • nextCandidateIndex

    • 类型: int
    • 修饰符: private
    • 源码定位: L37
    • 说明:

      TODO

  • finishedFuture

    • 类型: CompletableFuture<Vec3>
    • 修饰符: private final
    • 源码定位: L38
    • 说明:

      TODO

内部类/嵌套类型

构造器

private PlayerSpawnFinder(ServerLevel level, BlockPos spawnSuggestion, int radius) @ L40

  • 构造器名:PlayerSpawnFinder
  • 源码定位:L40
  • 修饰符:private

参数:

  • level: ServerLevel
  • spawnSuggestion: BlockPos
  • radius: int

说明:

TODO

方法

下面的方法块按源码顺序生成。

public static CompletableFuture<Vec3> findSpawn(ServerLevel level, BlockPos spawnSuggestion) @ L50

  • 方法名:findSpawn
  • 源码定位:L50
  • 返回类型:CompletableFuture
  • 修饰符:public static

参数:

  • level: ServerLevel
  • spawnSuggestion: BlockPos

说明:

TODO

private void scheduleNext() @ L70

  • 方法名:scheduleNext
  • 源码定位:L70
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private static Vec3 fixupSpawnHeight(CollisionGetter level, BlockPos spawnPos) @ L89

  • 方法名:fixupSpawnHeight
  • 源码定位:L89
  • 返回类型:Vec3
  • 修饰符:private static

参数:

  • level: CollisionGetter
  • spawnPos: BlockPos

说明:

TODO

private static boolean noCollisionNoLiquid(CollisionGetter level, BlockPos pos) @ L106

  • 方法名:noCollisionNoLiquid
  • 源码定位:L106
  • 返回类型:boolean
  • 修饰符:private static

参数:

  • level: CollisionGetter
  • pos: BlockPos

说明:

TODO

private static int getCoprime(int possibleOrigins) @ L110

  • 方法名:getCoprime
  • 源码定位:L110
  • 返回类型:int
  • 修饰符:private static

参数:

  • possibleOrigins: int

说明:

TODO

private void scheduleCandidate(int candidateX, int candidateZ, int candidateIndex, Supplier<Optional<Vec3>> candidateChecker) @ L114

  • 方法名:scheduleCandidate
  • 源码定位:L114
  • 返回类型:void
  • 修饰符:private

参数:

  • candidateX: int
  • candidateZ: int
  • candidateIndex: int
  • candidateChecker: Supplier<Optional>

说明:

TODO

protected static BlockPos getOverworldRespawnPos(ServerLevel level, int x, int z) @ L148

  • 方法名:getOverworldRespawnPos
  • 源码定位:L148
  • 返回类型:BlockPos
  • 修饰符:protected static

参数:

  • level: ServerLevel
  • x: int
  • z: int

说明:

TODO

public static BlockPos getSpawnPosInChunk(ServerLevel level, ChunkPos chunkPos) @ L178

  • 方法名:getSpawnPosInChunk
  • 源码定位:L178
  • 返回类型:BlockPos
  • 修饰符:public static

参数:

  • level: ServerLevel
  • chunkPos: ChunkPos

说明:

TODO

代码

public class PlayerSpawnFinder {
    private static final EntityDimensions PLAYER_DIMENSIONS = EntityType.PLAYER.getDimensions();
    private static final int ABSOLUTE_MAX_ATTEMPTS = 1024;
    private final ServerLevel level;
    private final BlockPos spawnSuggestion;
    private final int radius;
    private final int candidateCount;
    private final int coprime;
    private final int offset;
    private int nextCandidateIndex;
    private final CompletableFuture<Vec3> finishedFuture = new CompletableFuture<>();
 
    private PlayerSpawnFinder(ServerLevel level, BlockPos spawnSuggestion, int radius) {
        this.level = level;
        this.spawnSuggestion = spawnSuggestion;
        this.radius = radius;
        long squareSide = radius * 2L + 1L;
        this.candidateCount = (int)Math.min(1024L, squareSide * squareSide);
        this.coprime = getCoprime(this.candidateCount);
        this.offset = RandomSource.createThreadLocalInstance().nextInt(this.candidateCount);
    }
 
    public static CompletableFuture<Vec3> findSpawn(ServerLevel level, BlockPos spawnSuggestion) {
        if (level.dimensionType().hasSkyLight() && level.getServer().getWorldData().getGameType() != GameType.ADVENTURE) {
            int radius = Math.max(0, level.getGameRules().get(GameRules.RESPAWN_RADIUS));
            int distToBorder = Mth.floor(level.getWorldBorder().getDistanceToBorder(spawnSuggestion.getX(), spawnSuggestion.getZ()));
            if (distToBorder < radius) {
                radius = distToBorder;
            }
 
            if (distToBorder <= 1) {
                radius = 1;
            }
 
            PlayerSpawnFinder finder = new PlayerSpawnFinder(level, spawnSuggestion, radius);
            finder.scheduleNext();
            return finder.finishedFuture;
        } else {
            return CompletableFuture.completedFuture(fixupSpawnHeight(level, spawnSuggestion));
        }
    }
 
    private void scheduleNext() {
        int candidateIndex = this.nextCandidateIndex++;
        if (candidateIndex < this.candidateCount) {
            int value = (this.offset + this.coprime * candidateIndex) % this.candidateCount;
            int deltaX = value % (this.radius * 2 + 1);
            int deltaZ = value / (this.radius * 2 + 1);
            int targetX = this.spawnSuggestion.getX() + deltaX - this.radius;
            int targetZ = this.spawnSuggestion.getZ() + deltaZ - this.radius;
            this.scheduleCandidate(targetX, targetZ, candidateIndex, () -> {
                BlockPos spawnPos = getOverworldRespawnPos(this.level, targetX, targetZ);
                return spawnPos != null && noCollisionNoLiquid(this.level, spawnPos) ? Optional.of(Vec3.atBottomCenterOf(spawnPos)) : Optional.empty();
            });
        } else {
            this.scheduleCandidate(
                this.spawnSuggestion.getX(), this.spawnSuggestion.getZ(), candidateIndex, () -> Optional.of(fixupSpawnHeight(this.level, this.spawnSuggestion))
            );
        }
    }
 
    private static Vec3 fixupSpawnHeight(CollisionGetter level, BlockPos spawnPos) {
        BlockPos.MutableBlockPos mutablePos = spawnPos.mutable();
 
        while (!noCollisionNoLiquid(level, mutablePos) && mutablePos.getY() < level.getMaxY()) {
            mutablePos.move(Direction.UP);
        }
 
        mutablePos.move(Direction.DOWN);
 
        while (noCollisionNoLiquid(level, mutablePos) && mutablePos.getY() > level.getMinY()) {
            mutablePos.move(Direction.DOWN);
        }
 
        mutablePos.move(Direction.UP);
        return Vec3.atBottomCenterOf(mutablePos);
    }
 
    private static boolean noCollisionNoLiquid(CollisionGetter level, BlockPos pos) {
        return level.noCollision(null, PLAYER_DIMENSIONS.makeBoundingBox(pos.getBottomCenter()), true);
    }
 
    private static int getCoprime(int possibleOrigins) {
        return possibleOrigins <= 16 ? possibleOrigins - 1 : 17;
    }
 
    private void scheduleCandidate(int candidateX, int candidateZ, int candidateIndex, Supplier<Optional<Vec3>> candidateChecker) {
        if (!this.finishedFuture.isDone()) {
            int chunkX = SectionPos.blockToSectionCoord(candidateX);
            int chunkZ = SectionPos.blockToSectionCoord(candidateZ);
            this.level
                .getChunkSource()
                .addTicketAndLoadWithRadius(TicketType.SPAWN_SEARCH, new ChunkPos(chunkX, chunkZ), 0)
                .whenCompleteAsync((ignored, throwable) -> {
                    if (throwable == null) {
                        try {
                            Optional<Vec3> spawnPos = candidateChecker.get();
                            if (spawnPos.isPresent()) {
                                this.finishedFuture.complete(spawnPos.get());
                            } else {
                                this.scheduleNext();
                            }
                        } catch (Throwable var9) {
                            throwable = var9;
                        }
                    }
 
                    if (throwable != null) {
                        CrashReport report = CrashReport.forThrowable(throwable, "Searching for spawn");
                        CrashReportCategory details = report.addCategory("Spawn Lookup");
                        details.setDetail("Origin", this.spawnSuggestion::toString);
                        details.setDetail("Radius", () -> Integer.toString(this.radius));
                        details.setDetail("Candidate", () -> "[" + candidateX + "," + candidateZ + "]");
                        details.setDetail("Progress", () -> candidateIndex + " out of " + this.candidateCount);
                        this.finishedFuture.completeExceptionally(new ReportedException(report));
                    }
                }, this.level.getServer());
        }
    }
 
    protected static @Nullable BlockPos getOverworldRespawnPos(ServerLevel level, int x, int z) {
        boolean caveWorld = level.dimensionType().hasCeiling();
        LevelChunk chunk = level.getChunk(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z));
        int topY = caveWorld ? level.getChunkSource().getGenerator().getSpawnHeight(level) : chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, x & 15, z & 15);
        if (topY < level.getMinY()) {
            return null;
        } else {
            int surface = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, x & 15, z & 15);
            if (surface <= topY && surface > chunk.getHeight(Heightmap.Types.OCEAN_FLOOR, x & 15, z & 15)) {
                return null;
            } else {
                BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
 
                for (int y = topY + 1; y >= level.getMinY(); y--) {
                    pos.set(x, y, z);
                    BlockState blockState = level.getBlockState(pos);
                    if (!blockState.getFluidState().isEmpty()) {
                        break;
                    }
 
                    if (Block.isFaceFull(blockState.getCollisionShape(level, pos), Direction.UP)) {
                        return pos.above().immutable();
                    }
                }
 
                return null;
            }
        }
    }
 
    public static @Nullable BlockPos getSpawnPosInChunk(ServerLevel level, ChunkPos chunkPos) {
        if (SharedConstants.debugVoidTerrain(chunkPos)) {
            return null;
        } else {
            for (int x = chunkPos.getMinBlockX(); x <= chunkPos.getMaxBlockX(); x++) {
                for (int z = chunkPos.getMinBlockZ(); z <= chunkPos.getMaxBlockZ(); z++) {
                    BlockPos validSpawnPosition = getOverworldRespawnPos(level, x, z);
                    if (validSpawnPosition != null) {
                        return validSpawnPosition;
                    }
                }
            }
 
            return null;
        }
    }
}

引用的其他类

  • CrashReport

    • 引用位置: 方法调用
    • 关联成员: CrashReport.forThrowable()
  • ReportedException

    • 引用位置: 构造调用
    • 关联成员: ReportedException()
  • SharedConstants

    • 引用位置: 方法调用
    • 关联成员: SharedConstants.debugVoidTerrain()
  • BlockPos

    • 引用位置: 参数/字段/方法调用/构造调用/返回值
    • 关联成员: BlockPos.MutableBlockPos(), MutableBlockPos()
  • SectionPos

    • 引用位置: 方法调用
    • 关联成员: SectionPos.blockToSectionCoord()
  • ServerLevel

    • 引用位置: 参数/字段
  • Mth

    • 引用位置: 方法调用
    • 关联成员: Mth.floor()
  • RandomSource

    • 引用位置: 方法调用
    • 关联成员: RandomSource.createThreadLocalInstance()
  • EntityDimensions

    • 引用位置: 字段
  • ChunkPos

    • 引用位置: 参数/构造调用
    • 关联成员: ChunkPos()
  • CollisionGetter

    • 引用位置: 参数
  • Block

    • 引用位置: 方法调用
    • 关联成员: Block.isFaceFull()
  • Vec3

    • 引用位置: 参数/字段/方法调用/返回值
    • 关联成员: Vec3.atBottomCenterOf()