如何制作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
相关产品推荐
相关产品推荐

