You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何制作Mod覆盖Minecraft重生机制并替换ServerPlayerEntity?

自定义Minecraft玩家重生点解决方案

问题核心

直接继承ServerPlayerEntity无法让服务器自动使用你的子类,因为Minecraft的玩家实体由核心逻辑创建,不支持直接替换。下面提供两种可行方案:


方案1:使用Mixin注入(推荐)

Mixin可以直接修改原有ServerPlayerEntity的setSpawnPoint方法逻辑,无需创建子类,兼容性更好。

代码实现

@Mixin(ServerPlayerEntity.class)
public abstract class ServerPlayerSpawnMixin {
    @Inject(method = "setSpawnPoint", at = @At("HEAD"), cancellable = true)
    private void overrideSpawnPoint(RegistryKey<World> dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, CallbackInfo ci) {
        ServerPlayerEntity player = (ServerPlayerEntity) (Object) this;
        
        // 仅当传入的pos为null时执行自定义逻辑(对应默认重生场景)
        if (pos == null) {
            BlockPos newPos = getCustomSpawnPos(player);
            if (newPos != null) {
                // 调用原生方法设置自定义重生点
                player.setSpawnPoint(World.OVERWORLD, newPos, 0f, false, sendMessage);
                ci.cancel(); // 取消原生逻辑执行,避免冲突
            }
        }
    }

    private BlockPos getCustomSpawnPos(ServerPlayerEntity player) {
        // 根据玩家名匹配预设坐标,可替换为配置文件/随机逻辑
        PlayerPositions posEnum;
        try {
            posEnum = PlayerPositions.valueOf(player.getEntityName());
        } catch (IllegalArgumentException e) {
            // 处理未匹配到的玩家,返回世界出生点或默认位置
            return player.getServer().getWorld(World.OVERWORLD).getSpawnPos();
        }
        
        World world = player.getServer().getWorld(World.OVERWORLD);
        for (int y = world.getTopY(); y > world.getBottomY(); y--) {
            BlockPos checkPos = new BlockPos(posEnum.x, y, posEnum.z);
            if (!world.isSpaceEmpty(new Box(checkPos))) {
                return checkPos.down();
            }
        }
        // 未找到安全位置时返回世界出生点
        return world.getSpawnPos();
    }

    private enum PlayerPositions {
        Player1(-70711, -70711),
        Player2(70711, -70711),
        Player3(-70711, 70711),
        Player4(70711, 70711);

        final int x, z;

        PlayerPositions(int x, int z) {
            this.x = x;
            this.z = z;
        }
    }
}

Mixin配置

在你的Mod资源目录下创建mixins.[你的ModID].json,添加以下内容:

{
  "compatibilityLevel": "JAVA_17",
  "server": [
    "com.your.package.path.ServerPlayerSpawnMixin"
  ]
}

方案2:注册自定义玩家实体(Forge专属)

如果坚持使用子类替换,需要注册自定义实体类型并拦截玩家创建逻辑:

1. 注册自定义玩家实体

// 注册自定义实体类型
public static final RegistryObject<EntityType<Server100KMPlayerEntity>> CUSTOM_PLAYER = 
    DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, "yourmodid")
        .register("custom_player", () -> EntityType.Builder.of(Server100KMPlayerEntity::new, EntityCategory.MISC)
            .sized(0.6F, 1.8F)
            .build("custom_player"));

2. 替换玩家实体(风险较高)

订阅PlayerEvent.PlayerLoggedInEvent,在玩家首次登录时替换实体:

@SubscribeEvent
public void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
    ServerPlayerEntity oldPlayer = (ServerPlayerEntity) event.getPlayer();
    MinecraftServer server = oldPlayer.getServer();
    if (server == null) return;

    // 创建自定义玩家实体
    Server100KMPlayerEntity newPlayer = new Server100KMPlayerEntity(server, oldPlayer.world, oldPlayer.getGameProfile());
    // 复制原有玩家数据
    newPlayer.copyFrom(oldPlayer);
    
    // 替换实体
    oldPlayer.world.removeEntity(oldPlayer, Entity.RemovalReason.DISCARDED);
    oldPlayer.world.spawnEntity(newPlayer);
}

注意:这种方式容易引发兼容性问题,不推荐用于多人服务器。


优化建议

  • 移除硬编码的玩家名枚举,改用配置文件存储玩家-坐标映射,或根据玩家UUID生成随机方向的100000格坐标:
    // 随机生成100000格距离的坐标示例
    double angle = Math.random() * 2 * Math.PI;
    int x = (int) (100000 * Math.cos(angle));
    int z = (int) (100000 * Math.sin(angle));
    
  • 完善坐标检测逻辑,避免返回null导致重生失败
  • 处理玩家首次加入的场景,确保登录时直接传送到自定义位置

内容的提问来源于stack exchange,提问作者CaseyB

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 08:57:24