diff --git a/iclibs/Nukkit-MOT-SNAPSHOT.jar b/iclibs/Nukkit-MOT-SNAPSHOT.jar index 34d593dd..95876f1e 100644 Binary files a/iclibs/Nukkit-MOT-SNAPSHOT.jar and b/iclibs/Nukkit-MOT-SNAPSHOT.jar differ diff --git a/src/main/java/com/reider745/InnerCoreServer.java b/src/main/java/com/reider745/InnerCoreServer.java index 70f446b4..4487b3f8 100644 --- a/src/main/java/com/reider745/InnerCoreServer.java +++ b/src/main/java/com/reider745/InnerCoreServer.java @@ -12,6 +12,7 @@ import com.reider745.event.EventListener; import com.reider745.event.InnerCorePlugin; import com.reider745.hooks.BiomesHooks; import com.reider745.hooks.SnowfallEverywhere; +import com.reider745.hooks.bugfix.DimensionsFix; import com.reider745.item.CustomItem; import com.reider745.item.ItemMethod; @@ -318,6 +319,7 @@ public class InnerCoreServer { pluginManager.registerEvents(new SnowfallEverywhere(), plugin); } + DimensionsFix.init(); CustomBlock.init(); CustomItem.postInit(); NativeWorkbench.init(); diff --git a/src/main/java/com/reider745/Main.java b/src/main/java/com/reider745/Main.java index ba41866a..84ef67f4 100644 --- a/src/main/java/com/reider745/Main.java +++ b/src/main/java/com/reider745/Main.java @@ -7,6 +7,7 @@ import java.util.Arrays; import com.reider745.api.hooks.JarEditor; import com.reider745.hooks.*; +import com.reider745.hooks.bugfix.DimensionsFix; import com.reider745.hooks.bugfix.EntityItemHooks; import com.reider745.network.InnerCorePacket; @@ -87,6 +88,7 @@ public class Main { // bug fix loader.registerHooksInitializationForClass(EntityItemHooks.class); + loader.registerHooksInitializationForClass(DimensionsFix.class); loader.init(); loader.run("cn.nukkit.Nukkit", args); diff --git a/src/main/java/com/reider745/hooks/bugfix/DimensionsFix.java b/src/main/java/com/reider745/hooks/bugfix/DimensionsFix.java new file mode 100644 index 00000000..072d9958 --- /dev/null +++ b/src/main/java/com/reider745/hooks/bugfix/DimensionsFix.java @@ -0,0 +1,230 @@ +package com.reider745.hooks.bugfix; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.entity.data.ByteEntityData; +import cn.nukkit.entity.data.property.EntityProperty; +import cn.nukkit.event.player.PlayerLoginEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.trim.TrimFactory; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.*; +import cn.nukkit.network.protocol.types.ContainerIds; +import cn.nukkit.network.protocol.types.ExperimentData; +import cn.nukkit.network.protocol.types.GameType; +import cn.nukkit.utils.TextFormat; +import cn.nukkit.utils.Utils; +import com.reider745.InnerCoreServer; +import com.reider745.api.hooks.HookClass; +import com.reider745.api.hooks.annotation.Hooks; +import com.reider745.api.hooks.annotation.Inject; +import com.reider745.network.StartGamePacketFix; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.util.List; + +@Hooks +public class DimensionsFix implements HookClass { + private static int getClientFriendlyGamemode(Player self, int gamemode) { + gamemode &= 0x03; + if (gamemode == Player.SPECTATOR) { + //1.19.30+使用真正的旁观模式 + if (self.getServer().useClientSpectator && self.protocol >= ProtocolInfo.v1_19_30) { + return GameType.SPECTATOR.ordinal(); + } + return Player.CREATIVE; + } + return gamemode; + } + + private static List experiments; + + public static void init() { + final int protocol = InnerCoreServer.PROTOCOL; + + List experiments = new ObjectArrayList<>(); + //TODO Multiversion 当新版本删除部分实验性玩法时,这里也需要加上判断 + if (Server.getInstance().enableExperimentMode) { + experiments.add(new ExperimentData("data_driven_items", true)); + experiments.add(new ExperimentData("experimental_custom_ui", true)); + experiments.add(new ExperimentData("upcoming_creator_features", true)); + experiments.add(new ExperimentData("experimental_molang_features", true)); + if (protocol >= ProtocolInfo.v1_20_0_23) { + experiments.add(new ExperimentData("cameras", true)); + if (protocol >= ProtocolInfo.v1_20_10_21 && protocol < ProtocolInfo.v1_20_30_24) { + experiments.add(new ExperimentData("short_sneaking", true)); + } + } + } + DimensionsFix.experiments = experiments; + } + + @Inject(className = "cn.nukkit.Player") + public static void completeLoginSequence(Player self){ + final Server server = self.getServer(); + + if (self.loggedIn) { + server.getLogger().debug("(BUG) Tried to call completeLoginSequence but player is already logged in"); + return; + } + + PlayerLoginEvent ev; + server.getPluginManager().callEvent(ev = new PlayerLoginEvent(self, "Plugin reason")); + if (ev.isCancelled()) { + self.close(self.getLeaveMessage(), ev.getKickMessage()); + return; + } + + StartGamePacketFix startGamePacket = new StartGamePacketFix(); + startGamePacket.entityUniqueId = self.getId(); + startGamePacket.entityRuntimeId = self.getId(); + startGamePacket.playerGamemode = getClientFriendlyGamemode(self, self.gamemode); + startGamePacket.x = (float) self.x; + startGamePacket.y = (float) self.y; + startGamePacket.z = (float) self.z; + startGamePacket.yaw = (float) self.yaw; + startGamePacket.pitch = (float) self.pitch; + startGamePacket.dimensionFix = self.level.getDimension(); + startGamePacket.generator = (byte) ((self.level.getDimension() + 1) & 0xff); //0 旧世界, 1 主世界, 2 下界, 3末地 + startGamePacket.worldGamemode = startGamePacket.playerGamemode; + startGamePacket.difficulty = server.getDifficulty(); + startGamePacket.spawnX = (int) self.x; + startGamePacket.spawnY = (int) self.y; + startGamePacket.spawnZ = (int) self.z; + startGamePacket.commandsEnabled = self.isEnableClientCommand(); + startGamePacket.experiments.addAll(experiments); + startGamePacket.gameRules = self.getLevel().getGameRules(); + startGamePacket.worldName = self.getServer().getNetwork().getName(); + startGamePacket.version = self.getLoginChainData().getGameVersion(); + startGamePacket.vanillaVersion = Utils.getVersionByProtocol(self.protocol); + if (self.getLevel().isRaining()) { + startGamePacket.rainLevel = self.getLevel().getRainTime(); + if (self.getLevel().isThundering()) { + startGamePacket.lightningLevel = self.getLevel().getThunderTime(); + } + } + startGamePacket.isMovementServerAuthoritative = self.isMovementServerAuthoritative(); + startGamePacket.isServerAuthoritativeBlockBreaking = self.isServerAuthoritativeBlockBreaking(); + startGamePacket.playerPropertyData = EntityProperty.getPlayerPropertyCache(); + self.forceDataPacket(startGamePacket, null); + + self.loggedIn = true; + server.getLogger().info(self.getServer().getLanguage().translateString("nukkit.player.logIn", + TextFormat.AQUA + self.getDisplayName() + TextFormat.WHITE, + self.getAddress(), + String.valueOf(self.getPort()), + self.protocol + " (" + Utils.getVersionByProtocol(self.protocol) + ")")); + + self.setDataFlag(Player.DATA_FLAGS, Player.DATA_FLAG_CAN_CLIMB, true, false); + self.setDataFlag(Player.DATA_FLAGS, Player.DATA_FLAG_CAN_SHOW_NAMETAG, true, false); + self.setDataProperty(new ByteEntityData(Player.DATA_ALWAYS_SHOW_NAMETAG, 1), false); + + try { + if (self.protocol >= ProtocolInfo.v1_8_0) { + if (self.protocol >= ProtocolInfo.v1_12_0) { + if (self.protocol >= ProtocolInfo.v1_16_100) { + if (self.protocol >= ProtocolInfo.v1_17_0) { + //注册实体属性 + for (SyncEntityPropertyPacket pk : EntityProperty.getPacketCache()) { + self.dataPacket(pk); + } + } + ItemComponentPacket itemComponentPacket = new ItemComponentPacket(); + if (server.enableExperimentMode && !Item.getCustomItemDefinition().isEmpty()) { + Int2ObjectOpenHashMap entries = new Int2ObjectOpenHashMap<>(); + int i = 0; + for (var entry : Item.getCustomItemDefinition().entrySet()) { + try { + CompoundTag data = entry.getValue().getNbt(self.protocol); + data.putShort("minecraft:identifier", i); + entries.put(i, new ItemComponentPacket.Entry(entry.getKey(), data)); + i++; + } catch (Exception e) { + server.getLogger().error("ItemComponentPacket encoding error", e); + } + } + itemComponentPacket.setEntries(entries.values().toArray(ItemComponentPacket.Entry.EMPTY_ARRAY)); + } + self.dataPacket(itemComponentPacket); + } + self.dataPacket(new BiomeDefinitionListPacket()); + } + self.dataPacket(new AvailableEntityIdentifiersPacket()); + } + + if (self.protocol >= ProtocolInfo.v1_16_100) { + SetSpawnPositionPacket pk = new SetSpawnPositionPacket(); + pk.spawnType = SetSpawnPositionPacket.TYPE_PLAYER_SPAWN; + pk.x = (int) self.x; + pk.y = (int) self.y; + pk.z = (int) self.z; + pk.dimension = self.level.getDimension(); + self.dataPacket(pk); + } + self.getLevel().sendTime(self); + + SetDifficultyPacket difficultyPacket = new SetDifficultyPacket(); + difficultyPacket.difficulty = server.getDifficulty(); + self.dataPacket(difficultyPacket); + + SetCommandsEnabledPacket commandsPacket = new SetCommandsEnabledPacket(); + commandsPacket.enabled = self.isEnableClientCommand(); + self.dataPacket(commandsPacket); + + self.getAdventureSettings().update(); + + GameRulesChangedPacket gameRulesPK = new GameRulesChangedPacket(); + gameRulesPK.gameRulesMap = self.getLevel().getGameRules().getGameRules(); + self.dataPacket(gameRulesPK); + + server.sendFullPlayerListData(self); + self.sendAttributes(); + + if (self.protocol < ProtocolInfo.v1_16_0 && self.gamemode == Player.SPECTATOR) { + InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); + inventoryContentPacket.inventoryId = ContainerIds.CREATIVE; + self.dataPacket(inventoryContentPacket); + } else { + self.getInventory().sendCreativeContents(); + } + self.sendAllInventories(); + self.getInventory().sendHeldItemIfNotAir(self); + + // BDS sends armor trim templates and materials before the CraftingDataPacket + if (self.protocol >= ProtocolInfo.v1_19_80) { + TrimDataPacket trimDataPacket = new TrimDataPacket(); + trimDataPacket.getMaterials().addAll(TrimFactory.trimMaterials); + trimDataPacket.getPatterns().addAll(TrimFactory.trimPatterns); + self.dataPacket(trimDataPacket); + } + + server.sendRecipeList(self); + + if (self.isEnableClientCommand()) { + self.sendCommandData(); + } + + self.sendPotionEffects(self); + + if (self.isSpectator()) { + self.setDataFlag(Player.DATA_FLAGS, Player.DATA_FLAG_SILENT, true); + self.setDataFlag(Player.DATA_FLAGS, Player.DATA_FLAG_HAS_COLLISION, false); + } + self.sendData(self, self.getDataProperties().clone()); + + if (!server.checkOpMovement && self.isOp()) { + self.setCheckMovement(false); + } + + if (self.isOp() || self.hasPermission("nukkit.textcolor")) { + self.setRemoveFormat(false); + } + + server.onPlayerCompleteLoginSequence(self); + } catch (Exception e) { + self.close("", "Internal Server Error"); + server.getLogger().logException(e); + } + } +} diff --git a/src/main/java/com/reider745/network/StartGamePacketFix.java b/src/main/java/com/reider745/network/StartGamePacketFix.java new file mode 100644 index 00000000..09915069 --- /dev/null +++ b/src/main/java/com/reider745/network/StartGamePacketFix.java @@ -0,0 +1,212 @@ +package com.reider745.network; + +import cn.nukkit.Server; +import cn.nukkit.item.RuntimeItems; +import cn.nukkit.level.GlobalBlockPalette; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.network.protocol.ProtocolInfo; +import cn.nukkit.network.protocol.StartGamePacket; +import cn.nukkit.network.protocol.types.ExperimentData; + +import java.io.IOException; +import java.util.UUID; + +public class StartGamePacketFix extends StartGamePacket { + public int dimensionFix; + + @Override + public void encode() { + this.reset(); + this.putEntityUniqueId(this.entityUniqueId); + this.putEntityRuntimeId(this.entityRuntimeId); + this.putVarInt(this.playerGamemode); + this.putVector3f(this.x, this.y, this.z); + this.putLFloat(this.yaw); + this.putLFloat(this.pitch); + + /* Level settings start */ + if (protocol >= ProtocolInfo.v1_18_30) { + this.putLLong(this.seed); + }else { + this.putVarInt(this.seed); + } + if (protocol >= 407) { + this.putLShort(0x00); // SpawnBiomeType - Default + this.putString(protocol >= ProtocolInfo.v1_16_100 ? "plains" : ""); // UserDefinedBiomeName + } + this.putVarInt(this.dimensionFix); + this.putVarInt(this.generator); + this.putVarInt(this.worldGamemode); + this.putVarInt(this.difficulty); + this.putBlockVector3(this.spawnX, this.spawnY, this.spawnZ); + this.putBoolean(this.hasAchievementsDisabled); + if (protocol >= ProtocolInfo.v1_19_10) { + this.putBoolean(this.worldEditor); + if (protocol >= ProtocolInfo.v1_19_80) { + this.putBoolean(this.createdInEditor); + this.putBoolean(this.exportedFromEditor); + } + } + this.putVarInt(this.dayCycleStopTime); + if (protocol >= 388) { + this.putVarInt(this.eduEditionOffer); + } else { + this.putBoolean(this.eduMode); + } + if (protocol > 224) { + this.putBoolean(this.hasEduFeaturesEnabled); + if (protocol >= 407) { + this.putString(""); // Education Edition Product ID + } + } + this.putLFloat(this.rainLevel); + this.putLFloat(this.lightningLevel); + if (protocol >= 332) { + this.putBoolean(this.hasConfirmedPlatformLockedContent); + } + this.putBoolean(this.multiplayerGame); + this.putBoolean(this.broadcastToLAN); + if (protocol >= 332) { + this.putVarInt(this.xblBroadcastIntent); + this.putVarInt(this.platformBroadcastIntent); + } else { + this.putBoolean(this.broadcastToXboxLive); + } + this.putBoolean(this.commandsEnabled); + this.putBoolean(this.isTexturePacksRequired); + this.putGameRules(protocol, gameRules); + if (protocol >= ProtocolInfo.v1_16_100) { + if (Server.getInstance().enableExperimentMode && !this.experiments.isEmpty()) { + this.putLInt(this.experiments.size()); // Experiment count + for (ExperimentData experiment : this.experiments) { + this.putString(experiment.getName()); + this.putBoolean(experiment.isEnabled()); + } + this.putBoolean(true); // Were experiments previously toggled + } else { + this.putLInt(0); // Experiment count + this.putBoolean(false); // Were experiments previously toggled + } + } + this.putBoolean(this.bonusChest); + if (protocol > 201) { + this.putBoolean(this.hasStartWithMapEnabled); + } + if (protocol < 332) { + this.putBoolean(this.trustPlayers); + } + this.putVarInt(this.permissionLevel); + if (protocol < 332) { + this.putVarInt(this.gamePublish); + } + if (protocol >= 201) { + this.putLInt(this.serverChunkTickRange); + } + if (protocol >= 223 && protocol < 332) { + this.putBoolean(this.broadcastToPlatform); + this.putVarInt(this.platformBroadcastMode); + this.putBoolean(this.xblBroadcastIntentOld); + } + if (protocol > 224) { + this.putBoolean(this.hasLockedBehaviorPack); + this.putBoolean(this.hasLockedResourcePack); + this.putBoolean(this.isFromLockedWorldTemplate); + } + if (protocol >= 291) { + this.putBoolean(this.isUsingMsaGamertagsOnly); + if (protocol >= 313) { + this.putBoolean(this.isFromWorldTemplate); + this.putBoolean(this.isWorldTemplateOptionLocked); + if (protocol >= 361) { + this.putBoolean(this.isOnlySpawningV1Villagers); + if (protocol >= ProtocolInfo.v1_13_0) { + if (protocol >= ProtocolInfo.v1_19_20) { + this.putBoolean(this.isDisablingPersonas); + this.putBoolean(this.isDisablingCustomSkins); + if (protocol >= ProtocolInfo.v1_19_60) { + this.putBoolean(this.emoteChatMuted); + } + } + this.putString(this.vanillaVersion); + } + } + } + if (protocol >= ProtocolInfo.v1_16_0) { + this.putLInt(protocol >= ProtocolInfo.v1_16_100 ? 16 : 0); // Limited world width + this.putLInt(protocol >= ProtocolInfo.v1_16_100 ? 16 : 0); // Limited world height + this.putBoolean(false); // Nether type + if (protocol >= ProtocolInfo.v1_17_30) { // EduSharedUriResource + this.putString(""); // buttonName + this.putString(""); // linkUri + } + this.putBoolean(/*Server.getInstance().enableExperimentMode*/ false); //Force Experimental Gameplay (exclusive to debug clients) + if (protocol >= ProtocolInfo.v1_19_20) { + this.putByte(this.chatRestrictionLevel); + this.putBoolean(this.disablePlayerInteractions); + } + } + } + /* Level settings end */ + + this.putString(this.levelId); + this.putString(this.worldName); + this.putString(this.premiumWorldTemplateId); + this.putBoolean(this.isTrial); + if (protocol >= ProtocolInfo.v1_13_0) { + if (protocol >= ProtocolInfo.v1_16_100) { + if (protocol >= ProtocolInfo.v1_16_210) { + this.putVarInt(this.isMovementServerAuthoritative ? 1 : 0); // 2 - rewind + this.putVarInt(0); // RewindHistorySize + this.putBoolean(this.isServerAuthoritativeBlockBreaking); // isServerAuthoritativeBlockBreaking + } else { + this.putVarInt(this.isMovementServerAuthoritative ? 1 : 0); // 2 - rewind + } + } else { + this.putBoolean(this.isMovementServerAuthoritative); + } + } + this.putLLong(this.currentTick); + this.putVarInt(this.enchantmentSeed); + if (protocol > ProtocolInfo.v1_5_0) { + if (protocol >= ProtocolInfo.v1_16_100) { + this.putUnsignedVarInt(0); // Custom blocks + } else { + this.put(GlobalBlockPalette.getCompiledTable(this.protocol)); + } + if (protocol >= ProtocolInfo.v1_12_0) { + this.put(RuntimeItems.getMapping(protocol).getItemPalette()); + } + this.putString(this.multiplayerCorrelationId); + if (protocol == 354 && version != null && version.startsWith("1.11.4")) { + this.putBoolean(this.isOnlySpawningV1Villagers); + } else if (protocol >= ProtocolInfo.v1_16_0) { + this.putBoolean(false); // isInventoryServerAuthoritative + if (protocol >= ProtocolInfo.v1_16_230_50) { + this.putString(""); // serverEngine + if (protocol >= ProtocolInfo.v1_18_0) { + if (protocol >= ProtocolInfo.v1_19_0_29) { + try { + this.put(NBTIO.writeNetwork(this.playerPropertyData)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + this.putLLong(0L); // BlockRegistryChecksum + if (protocol >= ProtocolInfo.v1_19_0_29) { + this.putUUID(new UUID(0, 0)); // worldTemplateId + if (protocol >= ProtocolInfo.v1_19_20) { + this.putBoolean(this.clientSideGenerationEnabled); + if (protocol >= ProtocolInfo.v1_19_80) { + this.putBoolean(this.blockNetworkIdsHashed); + if (protocol >= ProtocolInfo.v1_20_0_23) { + this.putBoolean(this.networkPermissions.isServerAuthSounds()); + } + } + } + } + } + } + } + } + } +}