Move patches to unapplied

This commit is contained in:
Nassim Jahnke
2024-12-12 12:22:12 +01:00
parent ff75689b08
commit 45ddf764d9
821 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
--- a/net/minecraft/server/Bootstrap.java
+++ b/net/minecraft/server/Bootstrap.java
@@ -17,6 +17,9 @@
import net.minecraft.core.dispenser.DispenseItemBehavior;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.locale.Language;
+import net.minecraft.util.datafix.fixes.BlockStateData;
+import net.minecraft.util.datafix.fixes.ItemIdFix;
+import net.minecraft.util.datafix.fixes.ItemSpawnEggFix;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.attributes.Attribute;
@@ -30,7 +33,8 @@
import net.minecraft.world.level.block.state.BlockBehaviour;
import org.slf4j.Logger;
-@SuppressForbidden(a = "System.out setup")
+@SuppressForbidden(reason = "System.out setup")
+// CraftBukkit end
public class Bootstrap {
public static final PrintStream STDOUT = System.out;
@@ -42,9 +46,27 @@
public static void bootStrap() {
if (!Bootstrap.isBootstrapped) {
+ // CraftBukkit start
+ /*String name = Bootstrap.class.getSimpleName(); // Paper
+ switch (name) {
+ case "DispenserRegistry":
+ break;
+ case "Bootstrap":
+ System.err.println("***************************************************************************");
+ System.err.println("*** WARNING: This server jar may only be used for development purposes. ***");
+ System.err.println("***************************************************************************");
+ break;
+ default:
+ System.err.println("**********************************************************************");
+ System.err.println("*** WARNING: This server jar is unsupported, use at your own risk. ***");
+ System.err.println("**********************************************************************");
+ break;
+ }*/ // Paper
+ // CraftBukkit end
Bootstrap.isBootstrapped = true;
Instant instant = Instant.now();
+ io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping
if (BuiltInRegistries.REGISTRY.keySet().isEmpty()) {
throw new IllegalStateException("Unable to load registries");
} else {
@@ -56,11 +78,77 @@
EntitySelectorOptions.bootStrap();
DispenseItemBehavior.bootStrap();
CauldronInteraction.bootStrap();
- BuiltInRegistries.bootStrap();
+ // Paper start
+ BuiltInRegistries.bootStrap(() -> {
+ });
+ // Paper end
CreativeModeTabs.validate();
Bootstrap.wrapStreams();
Bootstrap.bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis());
}
+ // CraftBukkit start - easier than fixing the decompile
+ BlockStateData.register(1008, "{Name:'minecraft:oak_sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}");
+ BlockStateData.register(1009, "{Name:'minecraft:oak_sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}");
+ BlockStateData.register(1010, "{Name:'minecraft:oak_sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}");
+ BlockStateData.register(1011, "{Name:'minecraft:oak_sign',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'3'}}");
+ BlockStateData.register(1012, "{Name:'minecraft:oak_sign',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'4'}}");
+ BlockStateData.register(1013, "{Name:'minecraft:oak_sign',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'5'}}");
+ BlockStateData.register(1014, "{Name:'minecraft:oak_sign',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'6'}}");
+ BlockStateData.register(1015, "{Name:'minecraft:oak_sign',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'7'}}");
+ BlockStateData.register(1016, "{Name:'minecraft:oak_sign',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'8'}}");
+ BlockStateData.register(1017, "{Name:'minecraft:oak_sign',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'9'}}");
+ BlockStateData.register(1018, "{Name:'minecraft:oak_sign',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'10'}}");
+ BlockStateData.register(1019, "{Name:'minecraft:oak_sign',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'11'}}");
+ BlockStateData.register(1020, "{Name:'minecraft:oak_sign',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'12'}}");
+ BlockStateData.register(1021, "{Name:'minecraft:oak_sign',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'13'}}");
+ BlockStateData.register(1022, "{Name:'minecraft:oak_sign',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'14'}}");
+ BlockStateData.register(1023, "{Name:'minecraft:oak_sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}");
+ ItemIdFix.ITEM_NAMES.put(323, "minecraft:oak_sign");
+
+ BlockStateData.register(1440, "{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}", new String[]{"{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}"});
+
+ ItemIdFix.ITEM_NAMES.put(409, "minecraft:prismarine_shard");
+ ItemIdFix.ITEM_NAMES.put(410, "minecraft:prismarine_crystals");
+ ItemIdFix.ITEM_NAMES.put(411, "minecraft:rabbit");
+ ItemIdFix.ITEM_NAMES.put(412, "minecraft:cooked_rabbit");
+ ItemIdFix.ITEM_NAMES.put(413, "minecraft:rabbit_stew");
+ ItemIdFix.ITEM_NAMES.put(414, "minecraft:rabbit_foot");
+ ItemIdFix.ITEM_NAMES.put(415, "minecraft:rabbit_hide");
+ ItemIdFix.ITEM_NAMES.put(416, "minecraft:armor_stand");
+
+ ItemIdFix.ITEM_NAMES.put(423, "minecraft:mutton");
+ ItemIdFix.ITEM_NAMES.put(424, "minecraft:cooked_mutton");
+ ItemIdFix.ITEM_NAMES.put(425, "minecraft:banner");
+ ItemIdFix.ITEM_NAMES.put(426, "minecraft:end_crystal");
+ ItemIdFix.ITEM_NAMES.put(427, "minecraft:spruce_door");
+ ItemIdFix.ITEM_NAMES.put(428, "minecraft:birch_door");
+ ItemIdFix.ITEM_NAMES.put(429, "minecraft:jungle_door");
+ ItemIdFix.ITEM_NAMES.put(430, "minecraft:acacia_door");
+ ItemIdFix.ITEM_NAMES.put(431, "minecraft:dark_oak_door");
+ ItemIdFix.ITEM_NAMES.put(432, "minecraft:chorus_fruit");
+ ItemIdFix.ITEM_NAMES.put(433, "minecraft:chorus_fruit_popped");
+ ItemIdFix.ITEM_NAMES.put(434, "minecraft:beetroot");
+ ItemIdFix.ITEM_NAMES.put(435, "minecraft:beetroot_seeds");
+ ItemIdFix.ITEM_NAMES.put(436, "minecraft:beetroot_soup");
+ ItemIdFix.ITEM_NAMES.put(437, "minecraft:dragon_breath");
+ ItemIdFix.ITEM_NAMES.put(438, "minecraft:splash_potion");
+ ItemIdFix.ITEM_NAMES.put(439, "minecraft:spectral_arrow");
+ ItemIdFix.ITEM_NAMES.put(440, "minecraft:tipped_arrow");
+ ItemIdFix.ITEM_NAMES.put(441, "minecraft:lingering_potion");
+ ItemIdFix.ITEM_NAMES.put(442, "minecraft:shield");
+ ItemIdFix.ITEM_NAMES.put(443, "minecraft:elytra");
+ ItemIdFix.ITEM_NAMES.put(444, "minecraft:spruce_boat");
+ ItemIdFix.ITEM_NAMES.put(445, "minecraft:birch_boat");
+ ItemIdFix.ITEM_NAMES.put(446, "minecraft:jungle_boat");
+ ItemIdFix.ITEM_NAMES.put(447, "minecraft:acacia_boat");
+ ItemIdFix.ITEM_NAMES.put(448, "minecraft:dark_oak_boat");
+ ItemIdFix.ITEM_NAMES.put(449, "minecraft:totem_of_undying");
+ ItemIdFix.ITEM_NAMES.put(450, "minecraft:shulker_shell");
+ ItemIdFix.ITEM_NAMES.put(452, "minecraft:iron_nugget");
+ ItemIdFix.ITEM_NAMES.put(453, "minecraft:knowledge_book");
+
+ ItemSpawnEggFix.ID_TO_ENTITY[23] = "Arrow";
+ // CraftBukkit end
}
}
}

View File

@@ -0,0 +1,290 @@
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -38,6 +38,7 @@
import net.minecraft.server.dedicated.DedicatedServerProperties;
import net.minecraft.server.dedicated.DedicatedServerSettings;
import net.minecraft.server.level.progress.LoggerChunkProgressListener;
+import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.ServerPacksSource;
import net.minecraft.util.Mth;
@@ -55,22 +56,31 @@
import net.minecraft.world.level.levelgen.WorldOptions;
import net.minecraft.world.level.levelgen.presets.WorldPresets;
import net.minecraft.world.level.storage.LevelDataAndDimensions;
+import net.minecraft.world.level.storage.LevelResource;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.LevelSummary;
import net.minecraft.world.level.storage.PrimaryLevelData;
-import net.minecraft.world.level.storage.WorldData;
import org.slf4j.Logger;
+// CraftBukkit start
+import com.google.common.base.Charsets;
+import java.io.InputStreamReader;
+import java.util.concurrent.atomic.AtomicReference;
+import net.minecraft.SharedConstants;
+import org.bukkit.configuration.file.YamlConfiguration;
+// CraftBukkit end
+
public class Main {
private static final Logger LOGGER = LogUtils.getLogger();
public Main() {}
- @SuppressForbidden(a = "System.out needed before bootstrap")
+ @SuppressForbidden(reason = "System.out needed before bootstrap") // CraftBukkit - decompile error
@DontObfuscate
- public static void main(String[] args) {
+ public static void main(final OptionSet optionset) { // CraftBukkit - replaces main(String[] astring)
SharedConstants.tryDetectVersion();
+ /* CraftBukkit start - Replace everything
OptionParser optionparser = new OptionParser();
OptionSpec<Void> optionspec = optionparser.accepts("nogui");
OptionSpec<Void> optionspec1 = optionparser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits");
@@ -90,50 +100,104 @@
OptionSpec<String> optionspec15 = optionparser.nonOptions();
try {
- OptionSet optionset = optionparser.parse(args);
+ OptionSet optionset = optionparser.parse(astring);
if (optionset.has(optionspec8)) {
optionparser.printHelpOn(System.err);
return;
}
+ */ // CraftBukkit end
- Path path = (Path) optionset.valueOf(optionspec14);
+ try {
+ Path path = (Path) optionset.valueOf("pidFile"); // CraftBukkit
+
if (path != null) {
Main.writePidFile(path);
}
CrashReport.preload();
- if (optionset.has(optionspec13)) {
+ if (optionset.has("jfrProfile")) { // CraftBukkit
JvmProfiler.INSTANCE.start(Environment.SERVER);
}
+ io.papermc.paper.plugin.PluginInitializerManager.load(optionset); // Paper
Bootstrap.bootStrap();
Bootstrap.validate();
Util.startTimerHackThread();
Path path1 = Paths.get("server.properties");
- DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(path1);
+ DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(optionset); // CraftBukkit - CLI argument support
dedicatedserversettings.forceSave();
RegionFileVersion.configure(dedicatedserversettings.getProperties().regionFileComression);
Path path2 = Paths.get("eula.txt");
Eula eula = new Eula(path2);
+ // Paper start - load config files early for access below if needed
+ org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("bukkit-settings"));
+ org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("spigot-settings"));
+ // Paper end - load config files early for access below if needed
- if (optionset.has(optionspec1)) {
+ if (optionset.has("initSettings")) { // CraftBukkit
+ // CraftBukkit start - SPIGOT-5761: Create bukkit.yml and commands.yml if not present
+ File configFile = (File) optionset.valueOf("bukkit-settings");
+ YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configFile);
+ configuration.options().copyDefaults(true);
+ configuration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/bukkit.yml"), Charsets.UTF_8)));
+ configuration.save(configFile);
+
+ File commandFile = (File) optionset.valueOf("commands-settings");
+ YamlConfiguration commandsConfiguration = YamlConfiguration.loadConfiguration(commandFile);
+ commandsConfiguration.options().copyDefaults(true);
+ commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8)));
+ commandsConfiguration.save(commandFile);
+ // CraftBukkit end
Main.LOGGER.info("Initialized '{}' and '{}'", path1.toAbsolutePath(), path2.toAbsolutePath());
return;
}
- if (!eula.hasAgreedToEULA()) {
+ // Spigot Start
+ boolean eulaAgreed = Boolean.getBoolean( "com.mojang.eula.agree" );
+ if ( eulaAgreed )
+ {
+ System.err.println( "You have used the Spigot command line EULA agreement flag." );
+ System.err.println( "By using this setting you are indicating your agreement to Mojang's EULA (https://account.mojang.com/documents/minecraft_eula)." );
+ System.err.println( "If you do not agree to the above EULA please stop your server and remove this flag immediately." );
+ }
+ // Spigot End
+ if (!eula.hasAgreedToEULA() && !eulaAgreed) { // Spigot
Main.LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
return;
}
- File file = new File((String) optionset.valueOf(optionspec9));
- Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file);
- String s = (String) Optional.ofNullable((String) optionset.valueOf(optionspec10)).orElse(dedicatedserversettings.getProperties().levelName);
+ // Paper start - Detect headless JRE
+ String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck();
+ if (awtException != null) {
+ Main.LOGGER.error("You are using a headless JRE distribution.");
+ Main.LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function.");
+ Main.LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install");
+ Main.LOGGER.error("");
+ Main.LOGGER.error(awtException);
+ return;
+ }
+ // Paper end - Detect headless JRE
+
+ org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init
+ // Paper start - fix SPIGOT-5824
+ File file;
+ File userCacheFile = new File(Services.USERID_CACHE_FILE);
+ if (optionset.has("universe")) {
+ file = (File) optionset.valueOf("universe"); // CraftBukkit
+ userCacheFile = new File(file, Services.USERID_CACHE_FILE);
+ } else {
+ file = new File(bukkitConfiguration.getString("settings.world-container", "."));
+ }
+ // Paper end - fix SPIGOT-5824
+ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, userCacheFile, optionset); // Paper - pass OptionSet to load paper config files; override authentication service; fix world-container
+ // CraftBukkit start
+ String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName);
LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath());
- LevelStorageSource.LevelStorageAccess convertable_conversionsession = convertable.validateAndCreateAccess(s);
+ LevelStorageSource.LevelStorageAccess convertable_conversionsession = convertable.validateAndCreateAccess(s, LevelStem.OVERWORLD);
+ // CraftBukkit end
Dynamic dynamic;
if (convertable_conversionsession.hasWorldData()) {
@@ -174,13 +238,31 @@
}
Dynamic<?> dynamic1 = dynamic;
- boolean flag = optionset.has(optionspec7);
+ boolean flag = optionset.has("safeMode"); // CraftBukkit
if (flag) {
Main.LOGGER.warn("Safe mode active, only vanilla datapack will be loaded");
}
PackRepository resourcepackrepository = ServerPacksSource.createPackRepository(convertable_conversionsession);
+ // CraftBukkit start
+ File bukkitDataPackFolder = new File(convertable_conversionsession.getLevelPath(LevelResource.DATAPACK_DIR).toFile(), "bukkit");
+ if (!bukkitDataPackFolder.exists()) {
+ bukkitDataPackFolder.mkdirs();
+ }
+ File mcMeta = new File(bukkitDataPackFolder, "pack.mcmeta");
+ try {
+ com.google.common.io.Files.write("{\n"
+ + " \"pack\": {\n"
+ + " \"description\": \"Data pack for resources provided by Bukkit plugins\",\n"
+ + " \"pack_format\": " + SharedConstants.getCurrentVersion().getPackVersion(PackType.SERVER_DATA) + "\n"
+ + " }\n"
+ + "}\n", mcMeta, com.google.common.base.Charsets.UTF_8);
+ } catch (java.io.IOException ex) {
+ throw new RuntimeException("Could not initialize Bukkit datapack", ex);
+ }
+ AtomicReference<WorldLoader.DataLoadContext> worldLoader = new AtomicReference<>();
+ // CraftBukkit end
WorldStem worldstem;
@@ -189,6 +271,7 @@
worldstem = (WorldStem) Util.blockUntilDone((executor) -> {
return WorldLoader.load(worldloader_c, (worldloader_a) -> {
+ worldLoader.set(worldloader_a); // CraftBukkit
Registry<LevelStem> iregistry = worldloader_a.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
if (dynamic1 != null) {
@@ -201,7 +284,7 @@
WorldOptions worldoptions;
WorldDimensions worlddimensions;
- if (optionset.has(optionspec2)) {
+ if (optionset.has("demo")) { // CraftBukkit
worldsettings = MinecraftServer.DEMO_SETTINGS;
worldoptions = WorldOptions.DEMO_OPTIONS;
worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
@@ -209,7 +292,7 @@
DedicatedServerProperties dedicatedserverproperties = dedicatedserversettings.getProperties();
worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration());
- worldoptions = optionset.has(optionspec3) ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions;
+ worldoptions = optionset.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions; // CraftBukkit
worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen());
}
@@ -225,32 +308,47 @@
return;
}
- RegistryAccess.Frozen iregistrycustom_dimension = worldstem.registries().compositeAccess();
+ /*
+ IRegistryCustom.Dimension iregistrycustom_dimension = worldstem.registries().compositeAccess();
boolean flag1 = optionset.has(optionspec6);
if (optionset.has(optionspec4) || flag1) {
- Main.forceUpgrade(convertable_conversionsession, DataFixers.getDataFixer(), optionset.has(optionspec5), () -> {
+ forceUpgrade(convertable_conversionsession, DataConverterRegistry.getDataFixer(), optionset.has(optionspec5), () -> {
return true;
}, iregistrycustom_dimension, flag1);
}
- WorldData savedata = worldstem.worldData();
+ SaveData savedata = worldstem.worldData();
convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata);
+ */
final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> {
- DedicatedServer dedicatedserver1 = new DedicatedServer(thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius);
+ DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius);
+ /*
dedicatedserver1.setPort((Integer) optionset.valueOf(optionspec11));
- dedicatedserver1.setDemo(optionset.has(optionspec2));
+ */
+ dedicatedserver1.setDemo(optionset.has("demo")); // Paper
+ /*
dedicatedserver1.setId((String) optionset.valueOf(optionspec12));
- boolean flag2 = !optionset.has(optionspec) && !optionset.valuesOf(optionspec15).contains("nogui");
+ */
+ boolean flag2 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui");
+ if(!Boolean.parseBoolean(System.getenv().getOrDefault("PAPER_DISABLE_SERVER_GUI", String.valueOf(false)))) // Paper - Add environment variable to disable server gui
if (flag2 && !GraphicsEnvironment.isHeadless()) {
dedicatedserver1.showGui();
}
+ if (optionset.has("port")) {
+ int port = (Integer) optionset.valueOf("port");
+ if (port > 0) {
+ dedicatedserver1.setPort(port);
+ }
+ }
+
return dedicatedserver1;
});
+ /* CraftBukkit start
Thread thread = new Thread("Server Shutdown Thread") {
public void run() {
dedicatedserver.halt(true);
@@ -259,6 +357,7 @@
thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(Main.LOGGER));
Runtime.getRuntime().addShutdownHook(thread);
+ */ // CraftBukkit end
} catch (Exception exception1) {
Main.LOGGER.error(LogUtils.FATAL_MARKER, "Failed to start the minecraft server", exception1);
}
@@ -295,7 +394,7 @@
}
public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier continueCheck, RegistryAccess dynamicRegistryManager, boolean recreateRegionFiles) {
- Main.LOGGER.info("Forcing world upgrade!");
+ Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit
WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, dynamicRegistryManager, eraseCache, recreateRegionFiles);
try {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
--- a/net/minecraft/server/PlayerAdvancements.java
+++ b/net/minecraft/server/PlayerAdvancements.java
@@ -50,7 +50,7 @@
public class PlayerAdvancements {
private static final Logger LOGGER = LogUtils.getLogger();
- private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create();
+ private static final Gson GSON = (new GsonBuilder()).create(); // Paper - Remove pretty printing from advancements
private final PlayerList playerList;
private final Path playerSavePath;
private AdvancementTree tree;
@@ -63,6 +63,7 @@
private AdvancementHolder lastSelectedTab;
private boolean isFirstPacket = true;
private final Codec<PlayerAdvancements.Data> codec;
+ public final Map<net.minecraft.advancements.critereon.SimpleCriterionTrigger<?>, Set<CriterionTrigger.Listener<?>>> criterionData = new java.util.IdentityHashMap<>(); // Paper - fix advancement data player leakage
public PlayerAdvancements(DataFixer dataFixer, PlayerList playerManager, ServerAdvancementManager advancementLoader, Path filePath, ServerPlayer owner) {
this.playerList = playerManager;
@@ -162,6 +163,7 @@
}
public void save() {
+ if (org.spigotmc.SpigotConfig.disableAdvancementSaving) return; // Spigot
JsonElement jsonelement = (JsonElement) this.codec.encodeStart(JsonOps.INSTANCE, this.asData()).getOrThrow();
try {
@@ -196,6 +198,7 @@
AdvancementHolder advancementholder = loader.get(minecraftkey);
if (advancementholder == null) {
+ if (!minecraftkey.getNamespace().equals("minecraft")) return; // CraftBukkit
PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", minecraftkey, this.playerSavePath);
} else {
this.startProgress(advancementholder, advancementprogress);
@@ -223,14 +226,31 @@
boolean flag1 = advancementprogress.isDone();
if (advancementprogress.grantProgress(criterionName)) {
+ // Paper start - Add PlayerAdvancementCriterionGrantEvent
+ if (!new com.destroystokyo.paper.event.player.PlayerAdvancementCriterionGrantEvent(this.player.getBukkitEntity(), advancement.toBukkit(), criterionName).callEvent()) {
+ advancementprogress.revokeProgress(criterionName);
+ return false;
+ }
+ // Paper end - Add PlayerAdvancementCriterionGrantEvent
this.unregisterListeners(advancement);
this.progressChanged.add(advancement);
flag = true;
if (!flag1 && advancementprogress.isDone()) {
+ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent
+ final net.kyori.adventure.text.Component message = advancement.value().display().flatMap(info -> {
+ return java.util.Optional.ofNullable(
+ info.shouldAnnounceChat() ? io.papermc.paper.adventure.PaperAdventure.asAdventure(info.getType().createAnnouncement(advancement, this.player)) : null
+ );
+ }).orElse(null);
+ final org.bukkit.event.player.PlayerAdvancementDoneEvent event = new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.toBukkit(), message);
+ this.player.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit
+ // Paper end
advancement.value().rewards().grant(this.player);
advancement.value().display().ifPresent((advancementdisplay) -> {
- if (advancementdisplay.shouldAnnounceChat() && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
- this.playerList.broadcastSystemMessage(advancementdisplay.getType().createAnnouncement(advancement, this.player), false);
+ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent
+ if (event.message() != null && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
+ this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false);
+ // Paper end
}
});

View File

@@ -0,0 +1,32 @@
--- a/net/minecraft/server/ReloadableServerRegistries.java
+++ b/net/minecraft/server/ReloadableServerRegistries.java
@@ -50,8 +50,9 @@
);
HolderLookup.Provider provider = HolderLookup.Provider.create(list.stream());
RegistryOps<JsonElement> registryOps = provider.createSerializationContext(JsonOps.INSTANCE);
+ final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(registryOps.lookupProvider); // Paper
List<CompletableFuture<WritableRegistry<?>>> list2 = LootDataType.values()
- .map(type -> scheduleRegistryLoad((LootDataType<?>)type, registryOps, resourceManager, prepareExecutor))
+ .map(type -> scheduleRegistryLoad((LootDataType<?>)type, registryOps, resourceManager, prepareExecutor, conversions)) // Paper
.toList();
CompletableFuture<List<WritableRegistry<?>>> completableFuture = Util.sequence(list2);
return completableFuture.thenApplyAsync(
@@ -60,14 +61,15 @@
}
private static <T> CompletableFuture<WritableRegistry<?>> scheduleRegistryLoad(
- LootDataType<T> type, RegistryOps<JsonElement> ops, ResourceManager resourceManager, Executor prepareExecutor
+ LootDataType<T> type, RegistryOps<JsonElement> ops, ResourceManager resourceManager, Executor prepareExecutor, io.papermc.paper.registry.data.util.Conversions conversions // Paper
) {
return CompletableFuture.supplyAsync(() -> {
WritableRegistry<T> writableRegistry = new MappedRegistry<>(type.registryKey(), Lifecycle.experimental());
+ io.papermc.paper.registry.PaperRegistryAccess.instance().registerReloadableRegistry(type.registryKey(), writableRegistry); // Paper - register reloadable registry
Map<ResourceLocation, T> map = new HashMap<>();
SimpleJsonResourceReloadListener.scanDirectory(resourceManager, type.registryKey(), ops, type.codec(), map);
- map.forEach((id, value) -> writableRegistry.register(ResourceKey.create(type.registryKey(), id), (T)value, DEFAULT_REGISTRATION_INFO));
- TagLoader.loadTagsForRegistry(resourceManager, writableRegistry);
+ map.forEach((id, value) -> io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(writableRegistry, ResourceKey.create(type.registryKey(), id), value, DEFAULT_REGISTRATION_INFO, conversions)); // Paper - register with listeners
+ TagLoader.loadTagsForRegistry(resourceManager, writableRegistry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // Paper - tag life cycle - reload
return writableRegistry;
}, prepareExecutor);
}

View File

@@ -0,0 +1,25 @@
--- a/net/minecraft/server/ReloadableServerResources.java
+++ b/net/minecraft/server/ReloadableServerResources.java
@@ -39,6 +39,7 @@
this.postponedTags = pendingTagLoads;
this.recipes = new RecipeManager(registries);
this.commands = new Commands(environment, CommandBuildContext.simple(registries, enabledFeatures));
+ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setDispatcher(this.commands, CommandBuildContext.simple(registries, enabledFeatures)); // Paper - Brigadier Command API
this.advancements = new ServerAdvancementManager(registries);
this.functionLibrary = new ServerFunctionLibrary(functionPermissionLevel, this.commands.getDispatcher());
}
@@ -83,6 +84,14 @@
ReloadableServerResources reloadableServerResources = new ReloadableServerResources(
reloadResult.layers(), reloadResult.lookupWithUpdatedTags(), enabledFeatures, environment, pendingTagLoads, functionPermissionLevel
);
+ // Paper start - call commands event for bootstraps
+ //noinspection ConstantValue
+ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(
+ io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS,
+ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE,
+ io.papermc.paper.plugin.bootstrap.BootstrapContext.class,
+ MinecraftServer.getServer() == null ? io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL : io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD);
+ // Paper end - call commands event
return SimpleReloadInstance.create(
resourceManager,
reloadableServerResources.listeners(),

View File

@@ -0,0 +1,39 @@
--- a/net/minecraft/server/ServerAdvancementManager.java
+++ b/net/minecraft/server/ServerAdvancementManager.java
@@ -21,10 +21,14 @@
import net.minecraft.util.profiling.ProfilerFiller;
import org.slf4j.Logger;
+// CraftBukkit start
+import java.util.HashMap;
+// CraftBukkit end
+
public class ServerAdvancementManager extends SimpleJsonResourceReloadListener<Advancement> {
private static final Logger LOGGER = LogUtils.getLogger();
- public Map<ResourceLocation, AdvancementHolder> advancements = Map.of();
+ public Map<ResourceLocation, AdvancementHolder> advancements = new HashMap<>(); // CraftBukkit - SPIGOT-7734: mutable
private AdvancementTree tree = new AdvancementTree();
private final HolderLookup.Provider registries;
@@ -37,13 +41,19 @@
Builder<ResourceLocation, AdvancementHolder> builder = ImmutableMap.builder();
prepared.forEach((minecraftkey, advancement) -> {
+ // Spigot start
+ if (org.spigotmc.SpigotConfig.disabledAdvancements != null && (org.spigotmc.SpigotConfig.disabledAdvancements.contains("*") || org.spigotmc.SpigotConfig.disabledAdvancements.contains(minecraftkey.toString()) || org.spigotmc.SpigotConfig.disabledAdvancements.contains(minecraftkey.getNamespace()))) {
+ return;
+ }
+ // Spigot end
this.validate(minecraftkey, advancement);
builder.put(minecraftkey, new AdvancementHolder(minecraftkey, advancement));
});
- this.advancements = builder.buildOrThrow();
+ this.advancements = new HashMap<>(builder.buildOrThrow()); // CraftBukkit - SPIGOT-7734: mutable
AdvancementTree advancementtree = new AdvancementTree();
advancementtree.addAll(this.advancements.values());
+ LOGGER.info("Loaded {} advancements", advancementtree.nodes().size()); // Paper - Improve logging and errors; moved from AdvancementTree#addAll
Iterator iterator = advancementtree.roots().iterator();
while (iterator.hasNext()) {

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/ServerFunctionLibrary.java
+++ b/net/minecraft/server/ServerFunctionLibrary.java
@@ -113,7 +113,7 @@
return null;
}).join());
this.functions = builder.build();
- this.tags = this.tagsLoader.build((Map<ResourceLocation, List<TagLoader.EntryWithSource>>)intermediate.getFirst());
+ this.tags = this.tagsLoader.build((Map<ResourceLocation, List<TagLoader.EntryWithSource>>)intermediate.getFirst(), null); // Paper - command function tags are not implemented yet
},
applyExecutor
);

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/ServerFunctionManager.java
+++ b/net/minecraft/server/ServerFunctionManager.java
@@ -37,7 +37,7 @@
}
public CommandDispatcher<CommandSourceStack> getDispatcher() {
- return this.server.getCommands().getDispatcher();
+ return this.server.getCommands().getDispatcher(); // CraftBukkit // Paper - Don't override command dispatcher
}
public void tick() {

View File

@@ -0,0 +1,168 @@
--- a/net/minecraft/server/ServerScoreboard.java
+++ b/net/minecraft/server/ServerScoreboard.java
@@ -42,7 +42,7 @@
protected void onScoreChanged(ScoreHolder scoreHolder, Objective objective, Score score) {
super.onScoreChanged(scoreHolder, objective, score);
if (this.trackedObjectives.contains(objective)) {
- this.server.getPlayerList().broadcastAll(new ClientboundSetScorePacket(scoreHolder.getScoreboardName(), objective.getName(), score.value(), Optional.ofNullable(score.display()), Optional.ofNullable(score.numberFormat())));
+ this.broadcastAll(new ClientboundSetScorePacket(scoreHolder.getScoreboardName(), objective.getName(), score.value(), Optional.ofNullable(score.display()), Optional.ofNullable(score.numberFormat()))); // CraftBukkit
}
this.setDirty();
@@ -57,7 +57,7 @@
@Override
public void onPlayerRemoved(ScoreHolder scoreHolder) {
super.onPlayerRemoved(scoreHolder);
- this.server.getPlayerList().broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), (String) null));
+ this.broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), (String) null)); // CraftBukkit
this.setDirty();
}
@@ -65,7 +65,7 @@
public void onPlayerScoreRemoved(ScoreHolder scoreHolder, Objective objective) {
super.onPlayerScoreRemoved(scoreHolder, objective);
if (this.trackedObjectives.contains(objective)) {
- this.server.getPlayerList().broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), objective.getName()));
+ this.broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), objective.getName())); // CraftBukkit
}
this.setDirty();
@@ -78,7 +78,7 @@
super.setDisplayObjective(slot, objective);
if (scoreboardobjective1 != objective && scoreboardobjective1 != null) {
if (this.getObjectiveDisplaySlotCount(scoreboardobjective1) > 0) {
- this.server.getPlayerList().broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective));
+ this.broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective)); // CraftBukkit
} else {
this.stopTrackingObjective(scoreboardobjective1);
}
@@ -86,7 +86,7 @@
if (objective != null) {
if (this.trackedObjectives.contains(objective)) {
- this.server.getPlayerList().broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective));
+ this.broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective)); // CraftBukkit
} else {
this.startTrackingObjective(objective);
}
@@ -98,7 +98,7 @@
@Override
public boolean addPlayerToTeam(String scoreHolderName, PlayerTeam team) {
if (super.addPlayerToTeam(scoreHolderName, team)) {
- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.ADD));
+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.ADD)); // CraftBukkit
this.setDirty();
return true;
} else {
@@ -106,13 +106,43 @@
}
}
+ // Paper start - Multiple Entries with Scoreboards
+ public boolean addPlayersToTeam(java.util.Collection<String> players, PlayerTeam team) {
+ boolean anyAdded = false;
+ for (String playerName : players) {
+ if (super.addPlayerToTeam(playerName, team)) {
+ anyAdded = true;
+ }
+ }
+
+ if (anyAdded) {
+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.ADD));
+ this.setDirty();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ // Paper end - Multiple Entries with Scoreboards
+
@Override
public void removePlayerFromTeam(String scoreHolderName, PlayerTeam team) {
super.removePlayerFromTeam(scoreHolderName, team);
- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.REMOVE));
+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.REMOVE)); // CraftBukkit
this.setDirty();
}
+ // Paper start - Multiple Entries with Scoreboards
+ public void removePlayersFromTeam(java.util.Collection<String> players, PlayerTeam team) {
+ for (String playerName : players) {
+ super.removePlayerFromTeam(playerName, team);
+ }
+
+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.REMOVE));
+ this.setDirty();
+ }
+ // Paper end - Multiple Entries with Scoreboards
+
@Override
public void onObjectiveAdded(Objective objective) {
super.onObjectiveAdded(objective);
@@ -123,7 +153,7 @@
public void onObjectiveChanged(Objective objective) {
super.onObjectiveChanged(objective);
if (this.trackedObjectives.contains(objective)) {
- this.server.getPlayerList().broadcastAll(new ClientboundSetObjectivePacket(objective, 2));
+ this.broadcastAll(new ClientboundSetObjectivePacket(objective, 2)); // CraftBukkit
}
this.setDirty();
@@ -142,21 +172,21 @@
@Override
public void onTeamAdded(PlayerTeam team) {
super.onTeamAdded(team);
- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));
+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); // CraftBukkit
this.setDirty();
}
@Override
public void onTeamChanged(PlayerTeam team) {
super.onTeamChanged(team);
- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false));
+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false)); // CraftBukkit
this.setDirty();
}
@Override
public void onTeamRemoved(PlayerTeam team) {
super.onTeamRemoved(team);
- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(team));
+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(team)); // CraftBukkit
this.setDirty();
}
@@ -207,6 +237,7 @@
while (iterator.hasNext()) {
ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
Iterator iterator1 = list.iterator();
while (iterator1.hasNext()) {
@@ -243,6 +274,7 @@
while (iterator.hasNext()) {
ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
Iterator iterator1 = list.iterator();
while (iterator1.hasNext()) {
@@ -287,6 +319,16 @@
return this.createData().load(nbt, registries);
}
+ // CraftBukkit start - Send to players
+ private void broadcastAll(Packet packet) {
+ for (ServerPlayer entityplayer : (List<ServerPlayer>) this.server.getPlayerList().players) {
+ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() == this) {
+ entityplayer.connection.send(packet);
+ }
+ }
+ }
+ // CraftBukkit end
+
public static enum Method {
CHANGE, REMOVE;

View File

@@ -0,0 +1,53 @@
--- a/net/minecraft/server/ServerTickRateManager.java
+++ b/net/minecraft/server/ServerTickRateManager.java
@@ -59,8 +59,14 @@
}
public boolean stopSprinting() {
+ // CraftBukkit start, add sendLog parameter
+ return this.stopSprinting(true);
+ }
+
+ public boolean stopSprinting(boolean sendLog) {
+ // CraftBukkit end
if (this.remainingSprintTicks > 0L) {
- this.finishTickSprint();
+ this.finishTickSprint(sendLog); // CraftBukkit - add sendLog parameter
return true;
} else {
return false;
@@ -78,7 +84,7 @@
return flag;
}
- private void finishTickSprint() {
+ private void finishTickSprint(boolean sendLog) { // CraftBukkit - add sendLog parameter
long i = this.scheduledCurrentSprintTicks - this.remainingSprintTicks;
double d0 = Math.max(1.0D, (double) this.sprintTimeSpend) / (double) TimeUtil.NANOSECONDS_PER_MILLISECOND;
int j = (int) ((double) (TimeUtil.MILLISECONDS_PER_SECOND * i) / d0);
@@ -86,9 +92,13 @@
this.scheduledCurrentSprintTicks = 0L;
this.sprintTimeSpend = 0L;
- this.server.createCommandSourceStack().sendSuccess(() -> {
- return Component.translatable("commands.tick.sprint.report", j, s);
- }, true);
+ // CraftBukkit start - add sendLog parameter
+ if (sendLog) {
+ this.server.createCommandSourceStack().sendSuccess(() -> {
+ return Component.translatable("commands.tick.sprint.report", j, s);
+ }, true);
+ }
+ // CraftBukkit end
this.remainingSprintTicks = 0L;
this.setFrozen(this.previousIsFrozen);
this.server.onTickRateChanged();
@@ -102,7 +112,7 @@
--this.remainingSprintTicks;
return true;
} else {
- this.finishTickSprint();
+ this.finishTickSprint(true); // CraftBukkit - add sendLog parameter
return false;
}
}

View File

@@ -0,0 +1,40 @@
--- a/net/minecraft/server/Services.java
+++ b/net/minecraft/server/Services.java
@@ -10,16 +10,32 @@
import net.minecraft.server.players.GameProfileCache;
import net.minecraft.util.SignatureValidator;
+
public record Services(
- MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache
+ MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache, @javax.annotation.Nullable io.papermc.paper.configuration.PaperConfigurations paperConfigurations // Paper - add paper configuration files
) {
- private static final String USERID_CACHE_FILE = "usercache.json";
+ // Paper start - add paper configuration files
+ public Services(MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache) {
+ this(sessionService, servicesKeySet, profileRepository, profileCache, null);
+ }
- public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory) {
+ @Override
+ public io.papermc.paper.configuration.PaperConfigurations paperConfigurations() {
+ return java.util.Objects.requireNonNull(this.paperConfigurations);
+ }
+ // Paper end - add paper configuration files
+ public static final String USERID_CACHE_FILE = "usercache.json"; // Paper - private -> public
+
+ public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper - add optionset to load paper config files; add userCacheFile parameter
MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService();
GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository();
- GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(rootDirectory, "usercache.json"));
- return new Services(minecraftSessionService, authenticationService.getServicesKeySet(), gameProfileRepository, gameProfileCache);
+ GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, userCacheFile); // Paper - use specified user cache file
+ // Paper start - load paper config files from cli options
+ final java.nio.file.Path legacyConfigPath = ((File) optionSet.valueOf("paper-settings")).toPath();
+ final java.nio.file.Path configDirPath = ((File) optionSet.valueOf("paper-settings-directory")).toPath();
+ io.papermc.paper.configuration.PaperConfigurations paperConfigurations = io.papermc.paper.configuration.PaperConfigurations.setup(legacyConfigPath, configDirPath, rootDirectory.toPath(), (File) optionSet.valueOf("spigot-settings"));
+ return new Services(minecraftSessionService, authenticationService.getServicesKeySet(), gameProfileRepository, gameProfileCache, paperConfigurations);
+ // Paper end - load paper config files from cli options
}
@Nullable

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/WorldLoader.java
+++ b/net/minecraft/server/WorldLoader.java
@@ -37,7 +37,7 @@
CloseableResourceManager closeableResourceManager = pair.getSecond();
LayeredRegistryAccess<RegistryLayer> layeredRegistryAccess = RegistryLayer.createRegistryAccess();
List<Registry.PendingTags<?>> list = TagLoader.loadTagsForExistingRegistries(
- closeableResourceManager, layeredRegistryAccess.getLayer(RegistryLayer.STATIC)
+ closeableResourceManager, layeredRegistryAccess.getLayer(RegistryLayer.STATIC), io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL // Paper - tag lifecycle - add cause
);
RegistryAccess.Frozen frozen = layeredRegistryAccess.getAccessForLoading(RegistryLayer.WORLDGEN);
List<HolderLookup.RegistryLookup<?>> list2 = TagLoader.buildUpdatedLookups(frozen, list);

View File

@@ -0,0 +1,31 @@
--- a/net/minecraft/server/bossevents/CustomBossEvent.java
+++ b/net/minecraft/server/bossevents/CustomBossEvent.java
@@ -18,6 +18,10 @@
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.BossEvent;
+// CraftBukkit start
+import org.bukkit.boss.KeyedBossBar;
+import org.bukkit.craftbukkit.boss.CraftKeyedBossbar;
+// CraftBukkit end
public class CustomBossEvent extends ServerBossEvent {
@@ -25,7 +29,17 @@
private final Set<UUID> players = Sets.newHashSet();
private int value;
private int max = 100;
+ // CraftBukkit start
+ private KeyedBossBar bossBar;
+ public KeyedBossBar getBukkitEntity() {
+ if (this.bossBar == null) {
+ this.bossBar = new CraftKeyedBossbar(this);
+ }
+ return this.bossBar;
+ }
+ // CraftBukkit end
+
public CustomBossEvent(ResourceLocation id, Component displayName) {
super(displayName, BossEvent.BossBarColor.WHITE, BossEvent.BossBarOverlay.PROGRESS);
this.id = id;

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/commands/BanIpCommands.java
+++ b/net/minecraft/server/commands/BanIpCommands.java
@@ -66,7 +66,7 @@
}
for (ServerPlayer serverPlayer : list) {
- serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned"));
+ serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned"), org.bukkit.event.player.PlayerKickEvent.Cause.IP_BANNED); // Paper - kick event cause
}
return list.size();

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/commands/BanPlayerCommands.java
+++ b/net/minecraft/server/commands/BanPlayerCommands.java
@@ -55,7 +55,7 @@
);
ServerPlayer serverPlayer = source.getServer().getPlayerList().getPlayer(gameProfile.getId());
if (serverPlayer != null) {
- serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"));
+ serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"), org.bukkit.event.player.PlayerKickEvent.Cause.BANNED); // Paper - kick event cause
}
}
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/commands/DeOpCommands.java
+++ b/net/minecraft/server/commands/DeOpCommands.java
@@ -35,7 +35,7 @@
if (playerList.isOp(gameProfile)) {
playerList.deop(gameProfile);
i++;
- source.sendSuccess(() -> Component.translatable("commands.deop.success", targets.iterator().next().getName()), true);
+ source.sendSuccess(() -> Component.translatable("commands.deop.success", gameProfile.getName()), true); // Paper - fixes MC-253721
}
}

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/server/commands/DefaultGameModeCommands.java
+++ b/net/minecraft/server/commands/DefaultGameModeCommands.java
@@ -28,9 +28,13 @@
GameType gameType = minecraftServer.getForcedGameType();
if (gameType != null) {
for (ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) {
- if (serverPlayer.setGameMode(gameType)) {
- i++;
+ // Paper start - Expand PlayerGameModeChangeEvent
+ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty());
+ if (event != null && event.isCancelled()) {
+ source.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false);
}
+ // Paper end - Expand PlayerGameModeChangeEvent
+ i++;
}
}

View File

@@ -0,0 +1,17 @@
--- a/net/minecraft/server/commands/DifficultyCommand.java
+++ b/net/minecraft/server/commands/DifficultyCommand.java
@@ -44,11 +44,12 @@
public static int setDifficulty(CommandSourceStack source, Difficulty difficulty) throws CommandSyntaxException {
MinecraftServer minecraftserver = source.getServer();
+ net.minecraft.server.level.ServerLevel worldServer = source.getLevel(); // CraftBukkit
- if (minecraftserver.getWorldData().getDifficulty() == difficulty) {
+ if (worldServer.getDifficulty() == difficulty) { // CraftBukkit
throw DifficultyCommand.ERROR_ALREADY_DIFFICULT.create(difficulty.getKey());
} else {
- minecraftserver.setDifficulty(difficulty, true);
+ minecraftserver.setDifficulty(worldServer, difficulty, true); // Paper - per level difficulty; don't skip other difficulty-changing logic (fix upstream's fix)
source.sendSuccess(() -> {
return Component.translatable("commands.difficulty.success", difficulty.getDisplayName());
}, true);

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/server/commands/EffectCommands.java
+++ b/net/minecraft/server/commands/EffectCommands.java
@@ -84,7 +84,7 @@
if (entity instanceof LivingEntity) {
MobEffectInstance mobeffect = new MobEffectInstance(statusEffect, k, amplifier, false, showParticles);
- if (((LivingEntity) entity).addEffect(mobeffect, source.getEntity())) {
+ if (((LivingEntity) entity).addEffect(mobeffect, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
++j;
}
}
@@ -114,7 +114,7 @@
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
- if (entity instanceof LivingEntity && ((LivingEntity) entity).removeAllEffects()) {
+ if (entity instanceof LivingEntity && ((LivingEntity) entity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
++i;
}
}
@@ -144,7 +144,7 @@
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
- if (entity instanceof LivingEntity && ((LivingEntity) entity).removeEffect(statusEffect)) {
+ if (entity instanceof LivingEntity && ((LivingEntity) entity).removeEffect(statusEffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
++i;
}
}

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/server/commands/GameModeCommand.java
+++ b/net/minecraft/server/commands/GameModeCommand.java
@@ -60,9 +60,14 @@
int i = 0;
for (ServerPlayer serverPlayer : targets) {
- if (serverPlayer.setGameMode(gameMode)) {
+ // Paper start - Expand PlayerGameModeChangeEvent
+ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty());
+ if (event != null && !event.isCancelled()) {
logGamemodeChange(context.getSource(), serverPlayer, gameMode);
i++;
+ } else if (event != null && event.cancelMessage() != null) {
+ context.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true);
+ // Paper end - Expand PlayerGameModeChangeEvent
}
}

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/server/commands/GameRuleCommand.java
+++ b/net/minecraft/server/commands/GameRuleCommand.java
@@ -34,9 +34,9 @@
static <T extends GameRules.Value<T>> int setRule(CommandContext<CommandSourceStack> context, GameRules.Key<T> key) {
CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource();
- T t0 = commandlistenerwrapper.getServer().getGameRules().getRule(key);
+ T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit
- t0.setFromArgument(context, "value");
+ t0.setFromArgument(context, "value", key); // Paper - Add WorldGameRuleChangeEvent
commandlistenerwrapper.sendSuccess(() -> {
return Component.translatable("commands.gamerule.set", key.getId(), t0.toString());
}, true);
@@ -44,7 +44,7 @@
}
static <T extends GameRules.Value<T>> int queryRule(CommandSourceStack source, GameRules.Key<T> key) {
- T t0 = source.getServer().getGameRules().getRule(key);
+ T t0 = source.getLevel().getGameRules().getRule(key); // CraftBukkit
source.sendSuccess(() -> {
return Component.translatable("commands.gamerule.query", key.getId(), t0.toString());

View File

@@ -0,0 +1,33 @@
--- a/net/minecraft/server/commands/GiveCommand.java
+++ b/net/minecraft/server/commands/GiveCommand.java
@@ -38,6 +38,7 @@
private static int giveItem(CommandSourceStack source, ItemInput item, Collection<ServerPlayer> targets, int count) throws CommandSyntaxException {
ItemStack itemstack = item.createItemStack(1, false);
+ final Component displayName = itemstack.getDisplayName(); // Paper - get display name early
int j = itemstack.getMaxStackSize();
int k = j * 100;
@@ -60,7 +61,7 @@
ItemEntity entityitem;
if (flag && itemstack1.isEmpty()) {
- entityitem = entityplayer.drop(itemstack, false);
+ entityitem = entityplayer.drop(itemstack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event
if (entityitem != null) {
entityitem.makeFakeItem();
}
@@ -79,11 +80,11 @@
if (targets.size() == 1) {
source.sendSuccess(() -> {
- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), ((ServerPlayer) targets.iterator().next()).getDisplayName());
+ return Component.translatable("commands.give.success.single", count, displayName, ((ServerPlayer) targets.iterator().next()).getDisplayName()); // Paper - use cached display name
}, true);
} else {
source.sendSuccess(() -> {
- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), targets.size());
+ return Component.translatable("commands.give.success.single", count, displayName, targets.size()); // Paper - use cached display name
}, true);
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/commands/KickCommand.java
+++ b/net/minecraft/server/commands/KickCommand.java
@@ -48,7 +48,7 @@
for (ServerPlayer serverPlayer : targets) {
if (!source.getServer().isSingleplayerOwner(serverPlayer.getGameProfile())) {
- serverPlayer.connection.disconnect(reason);
+ serverPlayer.connection.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.KICK_COMMAND); // Paper - kick event cause
source.sendSuccess(() -> Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason), true);
i++;
}

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/server/commands/ListPlayersCommand.java
+++ b/net/minecraft/server/commands/ListPlayersCommand.java
@@ -35,7 +35,14 @@
private static int format(CommandSourceStack source, Function<ServerPlayer, Component> nameProvider) {
PlayerList playerlist = source.getServer().getPlayerList();
- List<ServerPlayer> list = playerlist.getPlayers();
+ // CraftBukkit start
+ List<ServerPlayer> players = playerlist.getPlayers();
+ if (source.getBukkitSender() instanceof org.bukkit.entity.Player) {
+ org.bukkit.entity.Player sender = (org.bukkit.entity.Player) source.getBukkitSender();
+ players = players.stream().filter((ep) -> sender.canSee(ep.getBukkitEntity())).collect(java.util.stream.Collectors.toList());
+ }
+ List<ServerPlayer> list = players;
+ // CraftBukkit end
Component ichatbasecomponent = ComponentUtils.formatList(list, nameProvider);
source.sendSuccess(() -> {

View File

@@ -0,0 +1,19 @@
--- a/net/minecraft/server/commands/LootCommand.java
+++ b/net/minecraft/server/commands/LootCommand.java
@@ -95,7 +95,7 @@
}
private static <T extends ArgumentBuilder<CommandSourceStack, T>> T addTargets(T rootArgument, LootCommand.TailProvider sourceConstructor) {
- return rootArgument.then(((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("replace").then(net.minecraft.commands.Commands.literal("entity").then(net.minecraft.commands.Commands.argument("entities", EntityArgument.entities()).then(sourceConstructor.construct(net.minecraft.commands.Commands.argument("slot", SlotArgument.slot()), (commandcontext, list, commandloot_a) -> {
+ return (T) rootArgument.then(((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("replace").then(net.minecraft.commands.Commands.literal("entity").then(net.minecraft.commands.Commands.argument("entities", EntityArgument.entities()).then(sourceConstructor.construct(net.minecraft.commands.Commands.argument("slot", SlotArgument.slot()), (commandcontext, list, commandloot_a) -> { // CraftBukkit - decompile error
return LootCommand.entityReplace(EntityArgument.getEntities(commandcontext, "entities"), SlotArgument.getSlot(commandcontext, "slot"), list.size(), list, commandloot_a);
}).then(sourceConstructor.construct(net.minecraft.commands.Commands.argument("count", IntegerArgumentType.integer(0)), (commandcontext, list, commandloot_a) -> {
return LootCommand.entityReplace(EntityArgument.getEntities(commandcontext, "entities"), SlotArgument.getSlot(commandcontext, "slot"), IntegerArgumentType.getInteger(commandcontext, "count"), list, commandloot_a);
@@ -250,6 +250,7 @@
private static int dropInWorld(CommandSourceStack source, Vec3 pos, List<ItemStack> stacks, LootCommand.Callback messageSender) throws CommandSyntaxException {
ServerLevel worldserver = source.getLevel();
+ stacks.removeIf(ItemStack::isEmpty); // CraftBukkit - SPIGOT-6959 Remove empty items for avoid throw an error in new EntityItem
stacks.forEach((itemstack) -> {
ItemEntity entityitem = new ItemEntity(worldserver, pos.x, pos.y, pos.z, itemstack.copy());

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/commands/OpCommand.java
+++ b/net/minecraft/server/commands/OpCommand.java
@@ -46,7 +46,7 @@
if (!playerList.isOp(gameProfile)) {
playerList.op(gameProfile);
i++;
- source.sendSuccess(() -> Component.translatable("commands.op.success", targets.iterator().next().getName()), true);
+ source.sendSuccess(() -> Component.translatable("commands.op.success", gameProfile.getName()), true); // Paper - fixes MC-253721
}
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/server/commands/PlaceCommand.java
+++ b/net/minecraft/server/commands/PlaceCommand.java
@@ -132,6 +132,7 @@
if (!structurestart.isValid()) {
throw PlaceCommand.ERROR_STRUCTURE_FAILED.create();
} else {
+ structurestart.generationEventCause = org.bukkit.event.world.AsyncStructureGenerateEvent.Cause.COMMAND; // CraftBukkit - set AsyncStructureGenerateEvent.Cause.COMMAND as generation cause
BoundingBox structureboundingbox = structurestart.getBoundingBox();
ChunkPos chunkcoordintpair = new ChunkPos(SectionPos.blockToSectionCoord(structureboundingbox.minX()), SectionPos.blockToSectionCoord(structureboundingbox.minZ()));
ChunkPos chunkcoordintpair1 = new ChunkPos(SectionPos.blockToSectionCoord(structureboundingbox.maxX()), SectionPos.blockToSectionCoord(structureboundingbox.maxZ()));

View File

@@ -0,0 +1,28 @@
--- a/net/minecraft/server/commands/ReloadCommand.java
+++ b/net/minecraft/server/commands/ReloadCommand.java
@@ -20,7 +20,7 @@
public ReloadCommand() {}
public static void reloadPacks(Collection<String> dataPacks, CommandSourceStack source) {
- source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> {
+ source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper - Add ServerResourcesReloadedEvent
ReloadCommand.LOGGER.warn("Failed to execute reload", throwable);
source.sendFailure(Component.translatable("commands.reload.failure"));
return null;
@@ -44,6 +44,16 @@
return collection1;
}
+ // CraftBukkit start
+ public static void reload(MinecraftServer minecraftserver) {
+ PackRepository resourcepackrepository = minecraftserver.getPackRepository();
+ WorldData savedata = minecraftserver.getWorldData();
+ Collection<String> collection = resourcepackrepository.getSelectedIds();
+ Collection<String> collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection);
+ minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent
+ }
+ // CraftBukkit end
+
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register((LiteralArgumentBuilder) ((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("reload").requires((commandlistenerwrapper) -> {
return commandlistenerwrapper.hasPermission(2);

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/server/commands/ScheduleCommand.java
+++ b/net/minecraft/server/commands/ScheduleCommand.java
@@ -33,7 +33,7 @@
});
private static final SimpleCommandExceptionType ERROR_MACRO = new SimpleCommandExceptionType(Component.translatableEscape("commands.schedule.macro"));
private static final SuggestionProvider<CommandSourceStack> SUGGEST_SCHEDULE = (commandcontext, suggestionsbuilder) -> {
- return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder);
+ return SharedSuggestionProvider.suggest((Iterable) ((net.minecraft.commands.CommandSourceStack) commandcontext.getSource()).getLevel().serverLevelData.getScheduledEvents().getEventsIds(), suggestionsbuilder); // Paper - Make schedule command per-world
};
public ScheduleCommand() {}
@@ -58,7 +58,7 @@
} else {
long j = source.getLevel().getGameTime() + (long) time;
ResourceLocation minecraftkey = (ResourceLocation) function.getFirst();
- TimerQueue<MinecraftServer> customfunctioncallbacktimerqueue = source.getServer().getWorldData().overworldData().getScheduledEvents();
+ TimerQueue<MinecraftServer> customfunctioncallbacktimerqueue = source.getLevel().serverLevelData.overworldData().getScheduledEvents(); // CraftBukkit - SPIGOT-6667: Use world specific function timer
Optional<net.minecraft.commands.functions.CommandFunction<CommandSourceStack>> optional = ((Either) function.getSecond()).left();
String s;
@@ -93,7 +93,7 @@
}
private static int remove(CommandSourceStack source, String eventName) throws CommandSyntaxException {
- int i = source.getServer().getWorldData().overworldData().getScheduledEvents().remove(eventName);
+ int i = source.getLevel().serverLevelData.getScheduledEvents().remove(eventName); // Paper - Make schedule command per-world
if (i == 0) {
throw ScheduleCommand.ERROR_CANT_REMOVE.create(eventName);

View File

@@ -0,0 +1,42 @@
--- a/net/minecraft/server/commands/SetSpawnCommand.java
+++ b/net/minecraft/server/commands/SetSpawnCommand.java
@@ -38,24 +38,34 @@
ResourceKey<Level> resourcekey = source.getLevel().dimension();
Iterator iterator = targets.iterator();
+ final Collection<ServerPlayer> actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent
while (iterator.hasNext()) {
ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false);
+ // Paper start - Add PlayerSetSpawnEvent
+ if (entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) {
+ actualTargets.add(entityplayer);
+ }
+ // Paper end - Add PlayerSetSpawnEvent
}
+ // Paper start - Add PlayerSetSpawnEvent
+ if (actualTargets.isEmpty()) {
+ return 0;
+ }
+ // Paper end - Add PlayerSetSpawnEvent
String s = resourcekey.location().toString();
- if (targets.size() == 1) {
+ if (actualTargets.size() == 1) { // Paper - Add PlayerSetSpawnEvent
source.sendSuccess(() -> {
- return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) targets.iterator().next()).getDisplayName());
+ return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) actualTargets.iterator().next()).getDisplayName()); // Paper - Add PlayerSetSpawnEvent
}, true);
} else {
source.sendSuccess(() -> {
- return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, targets.size());
+ return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, actualTargets.size()); // Paper - Add PlayerSetSpawnEvent
}, true);
}
- return targets.size();
+ return actualTargets.size(); // Paper - Add PlayerSetSpawnEvent
}
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/commands/SetWorldSpawnCommand.java
+++ b/net/minecraft/server/commands/SetWorldSpawnCommand.java
@@ -30,7 +30,7 @@
private static int setSpawn(CommandSourceStack source, BlockPos pos, float angle) {
ServerLevel worldserver = source.getLevel();
- if (worldserver.dimension() != Level.OVERWORLD) {
+ if (false && worldserver.dimension() != Level.OVERWORLD) { // CraftBukkit - SPIGOT-7649: allow in all worlds
source.sendFailure(Component.translatable("commands.setworldspawn.failure.not_overworld"));
return 0;
} else {

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/server/commands/SpreadPlayersCommand.java
+++ b/net/minecraft/server/commands/SpreadPlayersCommand.java
@@ -93,7 +93,7 @@
if (entity instanceof Player) {
set.add(entity.getTeam());
} else {
- set.add((Object) null);
+ set.add((Team) null); // CraftBukkit - decompile error
}
}
@@ -203,7 +203,7 @@
commandspreadplayers_a = piles[j++];
}
- entity.teleportTo(world, (double) Mth.floor(commandspreadplayers_a.x) + 0.5D, (double) commandspreadplayers_a.getSpawnY(world, maxY), (double) Mth.floor(commandspreadplayers_a.z) + 0.5D, Set.of(), entity.getYRot(), entity.getXRot(), true);
+ entity.teleportTo(world, (double) Mth.floor(commandspreadplayers_a.x) + 0.5D, (double) commandspreadplayers_a.getSpawnY(world, maxY), (double) Mth.floor(commandspreadplayers_a.z) + 0.5D, Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND); // CraftBukkit - handle teleport reason
d1 = Double.MAX_VALUE;
SpreadPlayersCommand.Position[] acommandspreadplayers_a1 = piles;
int k = piles.length;

View File

@@ -0,0 +1,19 @@
--- a/net/minecraft/server/commands/SummonCommand.java
+++ b/net/minecraft/server/commands/SummonCommand.java
@@ -57,6 +57,7 @@
ServerLevel worldserver = source.getLevel();
Entity entity = EntityType.loadEntityRecursive(nbttagcompound1, worldserver, EntitySpawnReason.COMMAND, (entity1) -> {
entity1.moveTo(pos.x, pos.y, pos.z, entity1.getYRot(), entity1.getXRot());
+ entity1.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND; // Paper - Entity#getEntitySpawnReason
return entity1;
});
@@ -67,7 +68,7 @@
((Mob) entity).finalizeSpawn(source.getLevel(), source.getLevel().getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.COMMAND, (SpawnGroupData) null);
}
- if (!worldserver.tryAddFreshEntityWithPassengers(entity)) {
+ if (!worldserver.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND)) { // CraftBukkit - pass a spawn reason of "COMMAND"
throw SummonCommand.ERROR_DUPLICATE_UUID.create();
} else {
return entity;

View File

@@ -0,0 +1,55 @@
--- a/net/minecraft/server/commands/TeleportCommand.java
+++ b/net/minecraft/server/commands/TeleportCommand.java
@@ -22,6 +22,7 @@
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
@@ -30,6 +31,11 @@
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.event.entity.EntityTeleportEvent;
+import org.bukkit.event.player.PlayerTeleportEvent;
+// CraftBukkit end
public class TeleportCommand {
@@ -167,7 +173,31 @@
float f4 = Mth.wrapDegrees(f2);
float f5 = Mth.wrapDegrees(f3);
- if (target.teleportTo(world, d3, d4, d5, movementFlags, f4, f5, true)) {
+ // CraftBukkit start - Teleport event
+ boolean result;
+ if (target instanceof ServerPlayer player) {
+ result = player.teleportTo(world, d3, d4, d5, movementFlags, f4, f5, true, PlayerTeleportEvent.TeleportCause.COMMAND);
+ } else {
+ Location to = new Location(world.getWorld(), d3, d4, d5, f4, f5);
+ EntityTeleportEvent event = new EntityTeleportEvent(target.getBukkitEntity(), target.getBukkitEntity().getLocation(), to);
+ world.getCraftServer().getPluginManager().callEvent(event);
+ if (event.isCancelled() || event.getTo() == null) { // Paper
+ return;
+ }
+ to = event.getTo(); // Paper - actually track new location
+
+ d3 = to.getX();
+ d4 = to.getY();
+ d5 = to.getZ();
+ f4 = to.getYaw();
+ f5 = to.getPitch();
+ world = ((CraftWorld) to.getWorld()).getHandle();
+
+ result = target.teleportTo(world, d3, d4, d5, movementFlags, f4, f5, true);
+ }
+
+ if (result) {
+ // CraftBukkit end
if (facingLocation != null) {
facingLocation.perform(source, target);
}

View File

@@ -0,0 +1,55 @@
--- a/net/minecraft/server/commands/TimeCommand.java
+++ b/net/minecraft/server/commands/TimeCommand.java
@@ -8,6 +8,10 @@
import net.minecraft.commands.arguments.TimeArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
+// CraftBukkit start
+import org.bukkit.Bukkit;
+import org.bukkit.event.world.TimeSkipEvent;
+// CraftBukkit end
public class TimeCommand {
@@ -49,12 +53,18 @@
}
public static int setTime(CommandSourceStack source, int time) {
- Iterator iterator = source.getServer().getAllLevels().iterator();
+ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
while (iterator.hasNext()) {
ServerLevel worldserver = (ServerLevel) iterator.next();
- worldserver.setDayTime((long) time);
+ // CraftBukkit start
+ TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time - worldserver.getDayTime());
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ worldserver.setDayTime((long) worldserver.getDayTime() + event.getSkipAmount());
+ }
+ // CraftBukkit end
}
source.getServer().forceTimeSynchronization();
@@ -65,12 +75,18 @@
}
public static int addTime(CommandSourceStack source, int time) {
- Iterator iterator = source.getServer().getAllLevels().iterator();
+ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
while (iterator.hasNext()) {
ServerLevel worldserver = (ServerLevel) iterator.next();
- worldserver.setDayTime(worldserver.getDayTime() + (long) time);
+ // CraftBukkit start
+ TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ worldserver.setDayTime(worldserver.getDayTime() + event.getSkipAmount());
+ }
+ // CraftBukkit end
}
source.getServer().forceTimeSynchronization();

View File

@@ -0,0 +1,34 @@
--- a/net/minecraft/server/commands/WeatherCommand.java
+++ b/net/minecraft/server/commands/WeatherCommand.java
@@ -34,11 +34,11 @@
}
private static int getDuration(CommandSourceStack source, int duration, IntProvider provider) {
- return duration == -1 ? provider.sample(source.getServer().overworld().getRandom()) : duration;
+ return duration == -1 ? provider.sample(source.getLevel().getRandom()) : duration; // CraftBukkit - SPIGOT-7680: per-world
}
private static int setClear(CommandSourceStack source, int duration) {
- source.getServer().overworld().setWeatherParameters(WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DELAY), 0, false, false);
+ source.getLevel().setWeatherParameters(WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DELAY), 0, false, false); // CraftBukkit - SPIGOT-7680: per-world
source.sendSuccess(() -> {
return Component.translatable("commands.weather.set.clear");
}, true);
@@ -46,7 +46,7 @@
}
private static int setRain(CommandSourceStack source, int duration) {
- source.getServer().overworld().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DURATION), true, false);
+ source.getLevel().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DURATION), true, false); // CraftBukkit - SPIGOT-7680: per-world
source.sendSuccess(() -> {
return Component.translatable("commands.weather.set.rain");
}, true);
@@ -54,7 +54,7 @@
}
private static int setThunder(CommandSourceStack source, int duration) {
- source.getServer().overworld().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.THUNDER_DURATION), true, true);
+ source.getLevel().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.THUNDER_DURATION), true, true); // CraftBukkit - SPIGOT-7680: per-world
source.sendSuccess(() -> {
return Component.translatable("commands.weather.set.thunder");
}, true);

View File

@@ -0,0 +1,65 @@
--- a/net/minecraft/server/commands/WorldBorderCommand.java
+++ b/net/minecraft/server/commands/WorldBorderCommand.java
@@ -57,7 +57,7 @@
}
private static int setDamageBuffer(CommandSourceStack source, float distance) throws CommandSyntaxException {
- WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
+ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldborder.getDamageSafeZone() == (double) distance) {
throw WorldBorderCommand.ERROR_SAME_DAMAGE_BUFFER.create();
@@ -71,7 +71,7 @@
}
private static int setDamageAmount(CommandSourceStack source, float damagePerBlock) throws CommandSyntaxException {
- WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
+ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldborder.getDamagePerBlock() == (double) damagePerBlock) {
throw WorldBorderCommand.ERROR_SAME_DAMAGE_AMOUNT.create();
@@ -85,7 +85,7 @@
}
private static int setWarningTime(CommandSourceStack source, int time) throws CommandSyntaxException {
- WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
+ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldborder.getWarningTime() == time) {
throw WorldBorderCommand.ERROR_SAME_WARNING_TIME.create();
@@ -99,7 +99,7 @@
}
private static int setWarningDistance(CommandSourceStack source, int distance) throws CommandSyntaxException {
- WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
+ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldborder.getWarningBlocks() == distance) {
throw WorldBorderCommand.ERROR_SAME_WARNING_DISTANCE.create();
@@ -113,7 +113,7 @@
}
private static int getSize(CommandSourceStack source) {
- double d0 = source.getServer().overworld().getWorldBorder().getSize();
+ double d0 = source.getLevel().getWorldBorder().getSize(); // CraftBukkit
source.sendSuccess(() -> {
return Component.translatable("commands.worldborder.get", String.format(Locale.ROOT, "%.0f", d0));
@@ -122,7 +122,7 @@
}
private static int setCenter(CommandSourceStack source, Vec2 pos) throws CommandSyntaxException {
- WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
+ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldborder.getCenterX() == (double) pos.x && worldborder.getCenterZ() == (double) pos.y) {
throw WorldBorderCommand.ERROR_SAME_CENTER.create();
@@ -138,7 +138,7 @@
}
private static int setSize(CommandSourceStack source, double distance, long time) throws CommandSyntaxException {
- WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
+ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
double d1 = worldborder.getSize();
if (d1 == distance) {

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/server/dedicated/DedicatedPlayerList.java
+++ b/net/minecraft/server/dedicated/DedicatedPlayerList.java
@@ -18,6 +18,11 @@
this.setViewDistance(dedicatedServerProperties.viewDistance);
this.setSimulationDistance(dedicatedServerProperties.simulationDistance);
super.setUsingWhiteList(dedicatedServerProperties.whiteList.get());
+ // Paper start - fix converting txt to json file; moved from constructor
+ }
+ @Override
+ public void loadAndSaveFiles() {
+ // Paper end - fix converting txt to json file
this.loadUserBanList();
this.saveUserBanList();
this.loadIpBanList();

View File

@@ -0,0 +1,475 @@
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -54,20 +54,31 @@
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.GameType;
-import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.SkullBlockEntity;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.slf4j.Logger;
+// CraftBukkit start
+import net.minecraft.server.WorldLoader;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.io.IoBuilder;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.util.TerminalCompletionHandler;
+import org.bukkit.craftbukkit.util.TerminalConsoleWriterThread;
+import org.bukkit.event.server.ServerCommandEvent;
+import org.bukkit.event.server.RemoteServerCommandEvent;
+// CraftBukkit end
+
public class DedicatedServer extends MinecraftServer implements ServerInterface {
static final Logger LOGGER = LogUtils.getLogger();
private static final int CONVERSION_RETRY_DELAY_MS = 5000;
private static final int CONVERSION_RETRIES = 2;
- private final List<ConsoleInput> consoleInput = Collections.synchronizedList(Lists.newArrayList());
+ private final java.util.Queue<ConsoleInput> serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - Perf: use a proper queue
@Nullable
private QueryThreadGs4 queryThreadGs4;
- private final RconConsoleSource rconConsoleSource;
+ // private final RemoteControlCommandListener rconConsoleSource; // CraftBukkit - remove field
@Nullable
private RconThread rconThread;
public DedicatedServerSettings settings;
@@ -81,41 +92,117 @@
private DebugSampleSubscriptionTracker debugSampleSubscriptionTracker;
public ServerLinks serverLinks;
- public DedicatedServer(Thread serverThread, LevelStorageSource.LevelStorageAccess session, PackRepository dataPackManager, WorldStem saveLoader, DedicatedServerSettings propertiesLoader, DataFixer dataFixer, Services apiServices, ChunkProgressListenerFactory worldGenerationProgressListenerFactory) {
- super(serverThread, session, dataPackManager, saveLoader, Proxy.NO_PROXY, dataFixer, apiServices, worldGenerationProgressListenerFactory);
- this.settings = propertiesLoader;
- this.rconConsoleSource = new RconConsoleSource(this);
- this.serverTextFilter = ServerTextFilter.createFromConfig(propertiesLoader.getProperties());
- this.serverLinks = DedicatedServer.createServerLinks(propertiesLoader);
+ // CraftBukkit start - Signature changed
+ public DedicatedServer(joptsimple.OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings dedicatedserversettings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
+ super(options, worldLoader, thread, convertable_conversionsession, resourcepackrepository, worldstem, Proxy.NO_PROXY, datafixer, services, worldloadlistenerfactory);
+ // CraftBukkit end
+ this.settings = dedicatedserversettings;
+ // this.rconConsoleSource = new RemoteControlCommandListener(this); // CraftBukkit - remove field
+ this.serverTextFilter = ServerTextFilter.createFromConfig(dedicatedserversettings.getProperties());
+ this.serverLinks = DedicatedServer.createServerLinks(dedicatedserversettings);
}
@Override
public boolean initServer() throws IOException {
Thread thread = new Thread("Server console handler") {
public void run() {
- BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
+ // CraftBukkit start
+ if (!org.bukkit.craftbukkit.Main.useConsole) {
+ return;
+ }
+ // Paper start - Use TerminalConsoleAppender
+ new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
+ /*
+ jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader;
+ // MC-33041, SPIGOT-5538: if System.in is not valid due to javaw, then return
+ try {
+ System.in.available();
+ } catch (IOException ex) {
+ return;
+ }
+ // CraftBukkit end
+
String s;
try {
- while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning() && (s = bufferedreader.readLine()) != null) {
- DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack());
+ // CraftBukkit start - JLine disabling compatibility
+ while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning()) {
+ if (org.bukkit.craftbukkit.Main.useJline) {
+ s = bufferedreader.readLine(">", null);
+ } else {
+ s = bufferedreader.readLine();
+ }
+
+ // SPIGOT-5220: Throttle if EOF (ctrl^d) or stdin is /dev/null
+ if (s == null) {
+ try {
+ Thread.sleep(50L);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ continue;
+ }
+ if (s.trim().length() > 0) { // Trim to filter lines which are just spaces
+ DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener());
+ }
+ // CraftBukkit end
}
} catch (IOException ioexception) {
DedicatedServer.LOGGER.error("Exception handling console input", ioexception);
}
+ */
+ // Paper end
}
};
+ // CraftBukkit start - TODO: handle command-line logging arguments
+ java.util.logging.Logger global = java.util.logging.Logger.getLogger("");
+ global.setUseParentHandlers(false);
+ for (java.util.logging.Handler handler : global.getHandlers()) {
+ global.removeHandler(handler);
+ }
+ global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler());
+
+ // Paper start - Not needed with TerminalConsoleAppender
+ final org.apache.logging.log4j.Logger logger = LogManager.getRootLogger();
+ /*
+ final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger());
+ for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) {
+ if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) {
+ logger.removeAppender(appender);
+ }
+ }
+
+ TerminalConsoleWriterThread writerThread = new TerminalConsoleWriterThread(System.out, this.reader);
+ this.reader.setCompletionHandler(new TerminalCompletionHandler(writerThread, this.reader.getCompletionHandler()));
+ writerThread.start();
+ */
+ // Paper end - Not needed with TerminalConsoleAppender
+
+ System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream());
+ System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream());
+ // CraftBukkit end
+
thread.setDaemon(true);
thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER));
- thread.start();
+ // thread.start(); // Paper - Enhance console tab completions for brigadier commands; moved down
DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName());
if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) {
DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
}
+ // Paper start - detect running as root
+ if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) {
+ DedicatedServer.LOGGER.warn("****************************");
+ DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED.");
+ DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS.");
+ DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/");
+ DedicatedServer.LOGGER.warn("****************************");
+ }
+ // Paper end - detect running as root
+
DedicatedServer.LOGGER.info("Loading properties");
DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties();
@@ -126,14 +213,51 @@
this.setPreventProxyConnections(dedicatedserverproperties.preventProxyConnections);
this.setLocalIp(dedicatedserverproperties.serverIp);
}
+ // Spigot start
+ this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
+ org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings"));
+ org.spigotmc.SpigotConfig.registerCommands();
+ // Spigot end
+ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc.
+ // Paper start - initialize global and world-defaults configuration
+ this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
+ this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
+ // Paper end - initialize global and world-defaults configuration
+ this.server.spark.enableEarlyIfRequested(); // Paper - spark
+ // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
+ if (this.convertOldUsers()) {
+ this.getProfileCache().save(false); // Paper
+ }
+ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames
+ // Paper end - fix converting txt to json file
+ org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread
+ thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized
+ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
+ this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
+ com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
this.setPvpAllowed(dedicatedserverproperties.pvp);
this.setFlightAllowed(dedicatedserverproperties.allowFlight);
this.setMotd(dedicatedserverproperties.motd);
super.setPlayerIdleTimeout((Integer) dedicatedserverproperties.playerIdleTimeout.get());
this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist);
- this.worldData.setGameType(dedicatedserverproperties.gamemode);
+ // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading
DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode);
+ // Paper start - Unix domain socket support
+ java.net.SocketAddress bindAddress;
+ if (this.getLocalIp().startsWith("unix:")) {
+ if (!io.netty.channel.epoll.Epoll.isAvailable()) {
+ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!");
+ DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS.");
+ return false;
+ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) {
+ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!");
+ DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy.");
+ return false;
+ }
+ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length()));
+ } else {
InetAddress inetaddress = null;
if (!this.getLocalIp().isEmpty()) {
@@ -143,34 +267,55 @@
if (this.getPort() < 0) {
this.setPort(dedicatedserverproperties.serverPort);
}
+ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort());
+ }
+ // Paper end - Unix domain socket support
this.initializeKeyPair();
DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort());
try {
- this.getConnection().startTcpServerListener(inetaddress, this.getPort());
+ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support
} catch (IOException ioexception) {
DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!");
DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString());
DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?");
+ if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error
return false;
}
+ // CraftBukkit start
+ // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up
+ this.server.loadPlugins();
+ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP);
+ // CraftBukkit end
+
+ // Paper start - Add Velocity IP Forwarding Support
+ boolean usingProxy = org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled;
+ String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord";
+ String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/";
+ // Paper end - Add Velocity IP Forwarding Support
if (!this.usesAuthentication()) {
DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
- DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
+ // Spigot start
+ // Paper start - Add Velocity IP Forwarding Support
+ if (usingProxy) {
+ DedicatedServer.LOGGER.warn("Whilst this makes it possible to use " + proxyFlavor + ", unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose.");
+ DedicatedServer.LOGGER.warn("Please see " + proxyLink + " for further information.");
+ // Paper end - Add Velocity IP Forwarding Support
+ } else {
+ DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
+ }
+ // Spigot end
DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
}
- if (this.convertOldUsers()) {
- this.getProfileCache().save();
- }
if (!OldUsersConverter.serverReadyAfterUserconversion(this)) {
return false;
} else {
- this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
+ // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // CraftBukkit - moved up
this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList());
this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME);
long i = Util.getNanos();
@@ -178,13 +323,13 @@
SkullBlockEntity.setup(this.services, this);
GameProfileCache.setUsesAuthentication(this.usesAuthentication());
DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
- this.loadLevel();
+ this.loadLevel(this.storageSource.getLevelId()); // CraftBukkit
long j = Util.getNanos() - i;
String s = String.format(Locale.ROOT, "%.3fs", (double) j / 1.0E9D);
DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s);
if (dedicatedserverproperties.announcePlayerAchievements != null) {
- ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this);
+ ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
}
if (dedicatedserverproperties.enableQuery) {
@@ -197,7 +342,7 @@
this.rconThread = RconThread.create(this);
}
- if (this.getMaxTickLength() > 0L) {
+ if (false && this.getMaxTickLength() > 0L) { // Spigot - disable
Thread thread1 = new Thread(new ServerWatchdog(this));
thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(DedicatedServer.LOGGER));
@@ -215,6 +360,12 @@
}
}
+ // Paper start
+ public java.io.File getPluginsFolder() {
+ return (java.io.File) this.options.valueOf("plugins");
+ }
+ // Paper end
+
@Override
public boolean isSpawningMonsters() {
return this.settings.getProperties().spawnMonsters && super.isSpawningMonsters();
@@ -227,7 +378,7 @@
@Override
public void forceDifficulty() {
- this.setDifficulty(this.getProperties().difficulty, true);
+ // this.setDifficulty(this.getProperties().difficulty, true); // Paper - per level difficulty; Don't overwrite level.dat's difficulty, keep current
}
@Override
@@ -286,13 +437,14 @@
}
if (this.rconThread != null) {
- this.rconThread.stop();
+ this.rconThread.stopNonBlocking(); // Paper - don't wait for remote connections
}
if (this.queryThreadGs4 != null) {
- this.queryThreadGs4.stop();
+ // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections
}
+ System.exit(0); // CraftBukkit
}
@Override
@@ -302,19 +454,29 @@
}
@Override
- public boolean isLevelEnabled(Level world) {
- return world.dimension() == Level.NETHER ? this.getProperties().allowNether : true;
+ public boolean isLevelEnabled(net.minecraft.world.level.Level world) {
+ return world.dimension() == net.minecraft.world.level.Level.NETHER ? this.getProperties().allowNether : true;
}
public void handleConsoleInput(String command, CommandSourceStack commandSource) {
- this.consoleInput.add(new ConsoleInput(command, commandSource));
+ this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue
}
public void handleConsoleInputs() {
- while (!this.consoleInput.isEmpty()) {
- ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0);
+ // Paper start - Perf: use proper queue
+ ConsoleInput servercommand;
+ while ((servercommand = this.serverCommandQueue.poll()) != null) {
+ // Paper end - Perf: use proper queue
- this.getCommands().performPrefixedCommand(servercommand.source, servercommand.msg);
+ // CraftBukkit start - ServerCommand for preprocessing
+ ServerCommandEvent event = new ServerCommandEvent(this.console, servercommand.msg);
+ this.server.getPluginManager().callEvent(event);
+ if (event.isCancelled()) continue;
+ servercommand = new ConsoleInput(event.getCommand(), servercommand.source);
+
+ // this.getCommands().performPrefixedCommand(servercommand.source, servercommand.msg); // Called in dispatchServerCommand
+ this.server.dispatchServerCommand(this.console, servercommand);
+ // CraftBukkit end
}
}
@@ -383,7 +545,7 @@
@Override
public boolean isUnderSpawnProtection(ServerLevel world, BlockPos pos, Player player) {
- if (world.dimension() != Level.OVERWORLD) {
+ if (world.dimension() != net.minecraft.world.level.Level.OVERWORLD) {
return false;
} else if (this.getPlayerList().getOps().isEmpty()) {
return false;
@@ -453,7 +615,11 @@
public boolean enforceSecureProfile() {
DedicatedServerProperties dedicatedserverproperties = this.getProperties();
- return dedicatedserverproperties.enforceSecureProfile && dedicatedserverproperties.onlineMode && this.services.canValidateProfileKeys();
+ // Paper start - Add setting for proxy online mode status
+ return dedicatedserverproperties.enforceSecureProfile
+ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()
+ && this.services.canValidateProfileKeys();
+ // Paper end - Add setting for proxy online mode status
}
@Override
@@ -541,16 +707,52 @@
@Override
public String getPluginNames() {
- return "";
+ // CraftBukkit start - Whole method
+ StringBuilder result = new StringBuilder();
+ org.bukkit.plugin.Plugin[] plugins = this.server.getPluginManager().getPlugins();
+
+ result.append(this.server.getName());
+ result.append(" on Bukkit ");
+ result.append(this.server.getBukkitVersion());
+
+ if (plugins.length > 0 && this.server.getQueryPlugins()) {
+ result.append(": ");
+
+ for (int i = 0; i < plugins.length; i++) {
+ if (i > 0) {
+ result.append("; ");
+ }
+
+ result.append(plugins[i].getDescription().getName());
+ result.append(" ");
+ result.append(plugins[i].getDescription().getVersion().replaceAll(";", ","));
+ }
+ }
+
+ return result.toString();
+ // CraftBukkit end
}
@Override
public String runCommand(String command) {
- this.rconConsoleSource.prepareForCommand();
+ // CraftBukkit start - fire RemoteServerCommandEvent
+ throw new UnsupportedOperationException("Not supported - remote source required.");
+ }
+
+ public String runCommand(RconConsoleSource rconConsoleSource, String s) {
+ rconConsoleSource.prepareForCommand();
this.executeBlocking(() -> {
- this.getCommands().performPrefixedCommand(this.rconConsoleSource.createCommandSourceStack(), command);
+ CommandSourceStack wrapper = rconConsoleSource.createCommandSourceStack();
+ RemoteServerCommandEvent event = new RemoteServerCommandEvent(rconConsoleSource.getBukkitSender(wrapper), s);
+ this.server.getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ return;
+ }
+ ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), wrapper);
+ this.server.dispatchServerCommand(event.getSender(), serverCommand);
});
- return this.rconConsoleSource.getCommandResponse();
+ return rconConsoleSource.getCommandResponse();
+ // CraftBukkit end
}
public void storeUsingWhiteList(boolean useWhitelist) {
@@ -660,4 +862,15 @@
}
}
}
+
+ // CraftBukkit start
+ public boolean isDebugging() {
+ return this.getProperties().debug;
+ }
+
+ @Override
+ public CommandSender getBukkitSender(CommandSourceStack wrapper) {
+ return this.console;
+ }
+ // CraftBukkit end
}

View File

@@ -0,0 +1,106 @@
--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java
+++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java
@@ -43,11 +43,16 @@
import net.minecraft.world.level.levelgen.presets.WorldPresets;
import org.slf4j.Logger;
+// CraftBukkit start
+import joptsimple.OptionSet;
+// CraftBukkit end
+
public class DedicatedServerProperties extends Settings<DedicatedServerProperties> {
static final Logger LOGGER = LogUtils.getLogger();
private static final Pattern SHA1 = Pattern.compile("^[a-fA-F0-9]{40}$");
private static final Splitter COMMA_SPLITTER = Splitter.on(',').trimResults();
+ public final boolean debug = this.get("debug", false); // CraftBukkit
public final boolean onlineMode = this.get("online-mode", true);
public final boolean preventProxyConnections = this.get("prevent-proxy-connections", false);
public final String serverIp = this.get("server-ip", "");
@@ -100,13 +105,17 @@
public final Settings<DedicatedServerProperties>.MutableValue<Boolean> whiteList;
public final boolean enforceSecureProfile;
public final boolean logIPs;
- public final int pauseWhenEmptySeconds;
+ public int pauseWhenEmptySeconds;
private final DedicatedServerProperties.WorldDimensionData worldDimensionData;
public final WorldOptions worldOptions;
public boolean acceptsTransfers;
- public DedicatedServerProperties(Properties properties) {
- super(properties);
+ public final String rconIp; // Paper - Configurable rcon ip
+
+ // CraftBukkit start
+ public DedicatedServerProperties(Properties properties, OptionSet optionset) {
+ super(properties, optionset);
+ // CraftBukkit end
this.difficulty = (Difficulty) this.get("difficulty", dispatchNumberOrString(Difficulty::byId, Difficulty::byName), Difficulty::getKey, Difficulty.EASY);
this.gamemode = (GameType) this.get("gamemode", dispatchNumberOrString(GameType::byId, GameType::byName), GameType::getName, GameType.SURVIVAL);
this.levelName = this.get("level-name", "world");
@@ -137,7 +146,7 @@
this.maxWorldSize = this.get("max-world-size", (integer) -> {
return Mth.clamp(integer, 1, 29999984);
}, 29999984);
- this.syncChunkWrites = this.get("sync-chunk-writes", true);
+ this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - Hide sync chunk writes behind flag
this.regionFileComression = this.get("region-file-compression", "deflate");
this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false);
this.enableStatus = this.get("enable-status", true);
@@ -151,7 +160,7 @@
this.whiteList = this.getMutable("white-list", false);
this.enforceSecureProfile = this.get("enforce-secure-profile", true);
this.logIPs = this.get("log-ips", true);
- this.pauseWhenEmptySeconds = this.get("pause-when-empty-seconds", 60);
+ this.pauseWhenEmptySeconds = this.get("pause-when-empty-seconds", -1); // Paper - disable tick sleeping by default
this.acceptsTransfers = this.get("accepts-transfers", false);
String s = this.get("level-seed", "");
boolean flag = this.get("generate-structures", true);
@@ -165,15 +174,21 @@
}, WorldPresets.NORMAL.location().toString()));
this.serverResourcePackInfo = DedicatedServerProperties.getServerPackInfo(this.get("resource-pack-id", ""), this.get("resource-pack", ""), this.get("resource-pack-sha1", ""), this.getLegacyString("resource-pack-hash"), this.get("require-resource-pack", false), this.get("resource-pack-prompt", ""));
this.initialDataPackConfiguration = DedicatedServerProperties.getDatapackConfig(this.get("initial-enabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getEnabled())), this.get("initial-disabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getDisabled())));
+ // Paper start - Configurable rcon ip
+ final String rconIp = this.getStringRaw("rcon.ip");
+ this.rconIp = rconIp == null ? this.serverIp : rconIp;
+ // Paper end - Configurable rcon ip
}
- public static DedicatedServerProperties fromFile(Path path) {
- return new DedicatedServerProperties(loadFromFile(path));
+ // CraftBukkit start
+ public static DedicatedServerProperties fromFile(Path path, OptionSet optionset) {
+ return new DedicatedServerProperties(loadFromFile(path), optionset);
}
@Override
- protected DedicatedServerProperties reload(RegistryAccess registryManager, Properties properties) {
- return new DedicatedServerProperties(properties);
+ public DedicatedServerProperties reload(RegistryAccess iregistrycustom, Properties properties, OptionSet optionset) {
+ return new DedicatedServerProperties(properties, optionset);
+ // CraftBukkit end
}
@Nullable
@@ -254,10 +269,10 @@
}).orElseThrow(() -> {
return new IllegalStateException("Invalid datapack contents: can't find default preset");
});
- Optional optional = Optional.ofNullable(ResourceLocation.tryParse(this.levelType)).map((minecraftkey) -> {
+ Optional<ResourceKey<WorldPreset>> optional = Optional.ofNullable(ResourceLocation.tryParse(this.levelType)).map((minecraftkey) -> { // CraftBukkit - decompile error
return ResourceKey.create(Registries.WORLD_PRESET, minecraftkey);
}).or(() -> {
- return Optional.ofNullable((ResourceKey) DedicatedServerProperties.WorldDimensionData.LEGACY_PRESET_NAMES.get(this.levelType));
+ return Optional.ofNullable(DedicatedServerProperties.WorldDimensionData.LEGACY_PRESET_NAMES.get(this.levelType)); // CraftBukkit - decompile error
});
Objects.requireNonNull(holderlookup);
@@ -269,7 +284,7 @@
if (holder.is(WorldPresets.FLAT)) {
RegistryOps<JsonElement> registryops = registries.createSerializationContext(JsonOps.INSTANCE);
- DataResult dataresult = FlatLevelGeneratorSettings.CODEC.parse(new Dynamic(registryops, this.generatorSettings()));
+ DataResult<FlatLevelGeneratorSettings> dataresult = FlatLevelGeneratorSettings.CODEC.parse(new Dynamic(registryops, this.generatorSettings())); // CraftBukkit - decompile error
Logger logger = DedicatedServerProperties.LOGGER;
Objects.requireNonNull(logger);

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/server/dedicated/DedicatedServerSettings.java
+++ b/net/minecraft/server/dedicated/DedicatedServerSettings.java
@@ -3,14 +3,21 @@
import java.nio.file.Path;
import java.util.function.UnaryOperator;
+// CraftBukkit start
+import java.io.File;
+import joptsimple.OptionSet;
+// CraftBukkit end
+
public class DedicatedServerSettings {
private final Path source;
private DedicatedServerProperties properties;
- public DedicatedServerSettings(Path path) {
- this.source = path;
- this.properties = DedicatedServerProperties.fromFile(path);
+ // CraftBukkit start
+ public DedicatedServerSettings(OptionSet optionset) {
+ this.source = ((File) optionset.valueOf("config")).toPath();
+ this.properties = DedicatedServerProperties.fromFile(this.source, optionset);
+ // CraftBukkit end
}
public DedicatedServerProperties getProperties() {

View File

@@ -0,0 +1,179 @@
--- a/net/minecraft/server/dedicated/Settings.java
+++ b/net/minecraft/server/dedicated/Settings.java
@@ -20,20 +20,41 @@
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import javax.annotation.Nullable;
-import net.minecraft.core.RegistryAccess;
import org.slf4j.Logger;
+import joptsimple.OptionSet; // CraftBukkit
+import net.minecraft.core.RegistryAccess;
+
public abstract class Settings<T extends Settings<T>> {
private static final Logger LOGGER = LogUtils.getLogger();
public final Properties properties;
+ private static final boolean skipComments = Boolean.getBoolean("Paper.skipServerPropertiesComments"); // Paper - allow skipping server.properties comments
+ // CraftBukkit start
+ private OptionSet options = null;
- public Settings(Properties properties) {
+ public Settings(Properties properties, final OptionSet options) {
this.properties = properties;
+
+ this.options = options;
}
+ private String getOverride(String name, String value) {
+ if ((this.options != null) && (this.options.has(name))) {
+ return String.valueOf(this.options.valueOf(name));
+ }
+
+ return value;
+ // CraftBukkit end
+ }
+
public static Properties loadFromFile(Path path) {
try {
+ // CraftBukkit start - SPIGOT-7465, MC-264979: Don't load if file doesn't exist
+ if (!path.toFile().exists()) {
+ return new Properties();
+ }
+ // CraftBukkit end
Properties properties;
Properties properties1;
@@ -97,8 +118,53 @@
public void store(Path path) {
try {
- BufferedWriter bufferedwriter = Files.newBufferedWriter(path, StandardCharsets.UTF_8);
+ // CraftBukkit start - Don't attempt writing to file if it's read only
+ if (path.toFile().exists() && !path.toFile().canWrite()) {
+ Settings.LOGGER.warn("Can not write to file {}, skipping.", path); // Paper - log message file is read-only
+ return;
+ }
+ // CraftBukkit end
+ // Paper start - allow skipping server.properties comments
+ java.io.OutputStream outputstream = Files.newOutputStream(path);
+ java.io.BufferedOutputStream bufferedOutputStream = !skipComments ? new java.io.BufferedOutputStream(outputstream) : new java.io.BufferedOutputStream(outputstream) {
+ private boolean isRightAfterNewline = true; // If last written char was newline
+ private boolean isComment = false; // Are we writing comment currently?
+
+ @Override
+ public void write(@org.jetbrains.annotations.NotNull byte[] b) throws IOException {
+ this.write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(@org.jetbrains.annotations.NotNull byte[] bbuf, int off, int len) throws IOException {
+ int latest_offset = off; // The latest offset, updated when comment ends
+ for (int index = off; index < off + len; ++index ) {
+ byte c = bbuf[index];
+ boolean isNewline = (c == '\n' || c == '\r');
+ if (isNewline && this.isComment) {
+ // Comment has ended
+ this.isComment = false;
+ latest_offset = index+1;
+ }
+ if (c == '#' && this.isRightAfterNewline) {
+ this.isComment = true;
+ if (index != latest_offset) {
+ // We got some non-comment data earlier
+ super.write(bbuf, latest_offset, index-latest_offset);
+ }
+ }
+ this.isRightAfterNewline = isNewline; // Store for next iteration
+ }
+ if (latest_offset < off+len && !this.isComment) {
+ // We have some unwritten data, that isn't part of a comment
+ super.write(bbuf, latest_offset, (off + len) - latest_offset);
+ }
+ }
+ };
+ BufferedWriter bufferedwriter = new BufferedWriter(new java.io.OutputStreamWriter(bufferedOutputStream, java.nio.charset.StandardCharsets.UTF_8.newEncoder()));
+ // Paper end - allow skipping server.properties comments
+
try {
this.properties.store(bufferedwriter, "Minecraft server properties");
} catch (Throwable throwable) {
@@ -125,7 +191,7 @@
private static <V extends Number> Function<String, V> wrapNumberDeserializer(Function<String, V> parser) {
return (s) -> {
try {
- return (Number) parser.apply(s);
+ return (V) parser.apply(s); // CraftBukkit - decompile error
} catch (NumberFormatException numberformatexception) {
return null;
}
@@ -144,7 +210,7 @@
@Nullable
public String getStringRaw(String key) {
- return (String) this.properties.get(key);
+ return (String) this.getOverride(key, this.properties.getProperty(key)); // CraftBukkit
}
@Nullable
@@ -160,10 +226,20 @@
}
protected <V> V get(String key, Function<String, V> parser, Function<V, String> stringifier, V fallback) {
- String s1 = this.getStringRaw(key);
- V v1 = MoreObjects.firstNonNull(s1 != null ? parser.apply(s1) : null, fallback);
+ // CraftBukkit start
+ try {
+ return this.get0(key, parser, stringifier, fallback);
+ } catch (Exception ex) {
+ throw new RuntimeException("Could not load invalidly configured property '" + key + "'", ex);
+ }
+ }
- this.properties.put(key, stringifier.apply(v1));
+ private <V> V get0(String s, Function<String, V> function, Function<V, String> function1, V v0) {
+ // CraftBukkit end
+ String s1 = this.getStringRaw(s);
+ V v1 = MoreObjects.firstNonNull(s1 != null ? function.apply(s1) : null, v0);
+
+ this.properties.put(s, function1.apply(v1));
return v1;
}
@@ -172,7 +248,7 @@
V v1 = MoreObjects.firstNonNull(s1 != null ? parser.apply(s1) : null, fallback);
this.properties.put(key, stringifier.apply(v1));
- return new Settings.MutableValue<>(key, v1, stringifier);
+ return new Settings.MutableValue(key, v1, stringifier); // CraftBukkit - decompile error
}
protected <V> V get(String key, Function<String, V> parser, UnaryOperator<V> parsedTransformer, Function<V, String> stringifier, V fallback) {
@@ -236,7 +312,7 @@
return properties;
}
- protected abstract T reload(RegistryAccess registryManager, Properties properties);
+ protected abstract T reload(RegistryAccess iregistrycustom, Properties properties, OptionSet optionset); // CraftBukkit
public class MutableValue<V> implements Supplier<V> {
@@ -244,7 +320,7 @@
private final V value;
private final Function<V, String> serializer;
- MutableValue(final String s, final Object object, final Function function) {
+ MutableValue(final String s, final V object, final Function function) { // CraftBukkit - decompile error
this.key = s;
this.value = object;
this.serializer = function;
@@ -258,7 +334,7 @@
Properties properties = Settings.this.cloneProperties();
properties.put(this.key, this.serializer.apply(value));
- return Settings.this.reload(registryManager, properties);
+ return Settings.this.reload(registryManager, properties, Settings.this.options); // CraftBukkit
}
}
}

View File

@@ -0,0 +1,103 @@
--- a/net/minecraft/server/gui/MinecraftServerGui.java
+++ b/net/minecraft/server/gui/MinecraftServerGui.java
@@ -59,6 +59,15 @@
jframe.pack();
jframe.setLocationRelativeTo((Component) null);
jframe.setVisible(true);
+ jframe.setName("Minecraft server"); // Paper - Improve ServerGUI
+
+ // Paper start - Improve ServerGUI
+ try {
+ jframe.setIconImage(javax.imageio.ImageIO.read(Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png"))));
+ } catch (java.io.IOException ignore) {
+ }
+ // Paper end - Improve ServerGUI
+
jframe.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent windowevent) {
if (!servergui.isClosing.getAndSet(true)) {
@@ -81,6 +90,7 @@
this.setLayout(new BorderLayout());
try {
+ this.add(this.buildOnboardingPanel(), "North"); // Paper - Add onboarding message for initial server start
this.add(this.buildChatPanel(), "Center");
this.add(this.buildInfoPanel(), "West");
} catch (Exception exception) {
@@ -95,8 +105,8 @@
private JComponent buildInfoPanel() {
JPanel jpanel = new JPanel(new BorderLayout());
- StatsComponent guistatscomponent = new StatsComponent(this.server);
- Collection collection = this.finalizers;
+ com.destroystokyo.paper.gui.GuiStatsComponent guistatscomponent = new com.destroystokyo.paper.gui.GuiStatsComponent(this.server); // Paper - Make GUI graph fancier
+ Collection<Runnable> collection = this.finalizers; // CraftBukkit - decompile error
Objects.requireNonNull(guistatscomponent);
collection.add(guistatscomponent::close);
@@ -106,6 +116,39 @@
return jpanel;
}
+ // Paper start - Add onboarding message for initial server start
+ private JComponent buildOnboardingPanel() {
+ String onboardingLink = "https://docs.papermc.io/paper/next-steps";
+ JPanel jPanel = new JPanel();
+
+ javax.swing.JLabel jLabel = new javax.swing.JLabel("If you need help setting up your server you can visit:");
+ jLabel.setFont(MinecraftServerGui.MONOSPACED);
+
+ javax.swing.JLabel link = new javax.swing.JLabel("<html><u> " + onboardingLink + "</u></html>");
+ link.setFont(MinecraftServerGui.MONOSPACED);
+ link.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
+ link.addMouseListener(new java.awt.event.MouseAdapter() {
+ @Override
+ public void mouseClicked(final java.awt.event.MouseEvent e) {
+ try {
+ java.awt.Desktop.getDesktop().browse(java.net.URI.create(onboardingLink));
+ } catch (java.io.IOException exception) {
+ LOGGER.error("Unable to find a default browser. Please manually visit the website: " + onboardingLink, exception);
+ } catch (UnsupportedOperationException exception) {
+ LOGGER.error("This platform does not support the BROWSE action. Please manually visit the website: " + onboardingLink, exception);
+ } catch (SecurityException exception) {
+ LOGGER.error("This action has been denied by the security manager. Please manually visit the website: " + onboardingLink, exception);
+ }
+ }
+ });
+
+ jPanel.add(jLabel);
+ jPanel.add(link);
+
+ return jPanel;
+ }
+ // Paper end - Add onboarding message for initial server start
+
private JComponent buildPlayerPanel() {
JList<?> jlist = new PlayerListComponent(this.server);
JScrollPane jscrollpane = new JScrollPane(jlist, 22, 30);
@@ -132,7 +175,7 @@
jtextfield.setText("");
});
- jtextarea.addFocusListener(new FocusAdapter(this) {
+ jtextarea.addFocusListener(new FocusAdapter() { // CraftBukkit - decompile error
public void focusGained(FocusEvent focusevent) {}
});
jpanel.add(jscrollpane, "Center");
@@ -166,6 +209,7 @@
this.finalizers.forEach(Runnable::run);
}
+ private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper
public void print(JTextArea textArea, JScrollPane scrollPane, String message) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> {
@@ -181,7 +225,7 @@
}
try {
- document.insertString(document.getLength(), message, (AttributeSet) null);
+ document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit
} catch (BadLocationException badlocationexception) {
;
}

View File

@@ -0,0 +1,33 @@
--- a/net/minecraft/server/gui/StatsComponent.java
+++ b/net/minecraft/server/gui/StatsComponent.java
@@ -34,10 +34,19 @@
private void tick() {
long l = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ // Paper start - Improve ServerGUI
+ double[] tps = org.bukkit.Bukkit.getTPS();
+ String[] tpsAvg = new String[tps.length];
+
+ for ( int g = 0; g < tps.length; g++) {
+ tpsAvg[g] = format( tps[g] );
+ }
this.msgs[0] = "Memory use: " + l / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)";
this.msgs[1] = "Avg tick: "
+ DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double)TimeUtil.NANOSECONDS_PER_MILLISECOND)
+ " ms";
+ this.msgs[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg);
+ // Paper end - Improve ServerGUI
this.values[this.vp++ & 0xFF] = (int)(l * 100L / Runtime.getRuntime().maxMemory());
this.repaint();
}
@@ -66,4 +75,10 @@
public void close() {
this.timer.stop();
}
+
+ // Paper start - Improve ServerGUI
+ private static String format(double tps) {
+ return (( tps > 21.0 ) ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0); // only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise
+ }
+ // Paper end - Improve ServerGUI
}

View File

@@ -0,0 +1,245 @@
--- a/net/minecraft/server/level/ChunkHolder.java
+++ b/net/minecraft/server/level/ChunkHolder.java
@@ -28,14 +28,18 @@
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.lighting.LevelLightEngine;
+// CraftBukkit start
+import net.minecraft.server.MinecraftServer;
+// CraftBukkit end
+
public class ChunkHolder extends GenerationChunkHolder {
public static final ChunkResult<LevelChunk> UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk");
private static final CompletableFuture<ChunkResult<LevelChunk>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK);
private final LevelHeightAccessor levelHeightAccessor;
- private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture;
- private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture;
- private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture;
+ private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
+ private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
+ private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
public int oldTicketLevel;
private int ticketLevel;
private int queueLevel;
@@ -58,9 +62,9 @@
this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
this.blockChangedLightSectionFilter = new BitSet();
this.skyChangedLightSectionFilter = new BitSet();
- this.pendingFullStateConfirmation = CompletableFuture.completedFuture((Object) null);
- this.sendSync = CompletableFuture.completedFuture((Object) null);
- this.saveSync = CompletableFuture.completedFuture((Object) null);
+ this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
+ this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
+ this.saveSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
this.levelHeightAccessor = world;
this.lightEngine = lightingProvider;
this.onLevelChange = levelUpdateListener;
@@ -72,6 +76,18 @@
this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
}
+ // CraftBukkit start
+ public LevelChunk getFullChunkNow() {
+ // Note: We use the oldTicketLevel for isLoaded checks.
+ if (!ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) return null;
+ return this.getFullChunkNowUnchecked();
+ }
+
+ public LevelChunk getFullChunkNowUnchecked() {
+ return (LevelChunk) this.getChunkIfPresentUnchecked(ChunkStatus.FULL);
+ }
+ // CraftBukkit end
+
public CompletableFuture<ChunkResult<LevelChunk>> getTickingChunkFuture() {
return this.tickingChunkFuture;
}
@@ -85,8 +101,8 @@
}
@Nullable
- public LevelChunk getTickingChunk() {
- return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse((Object) null);
+ public final LevelChunk getTickingChunk() { // Paper - final for inline
+ return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse(null); // CraftBukkit - decompile error
}
@Nullable
@@ -138,6 +154,7 @@
boolean flag = this.hasChangedSections;
int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
+ if (i < 0 || i >= this.changedBlocksPerSection.length) return false; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
if (this.changedBlocksPerSection[i] == null) {
this.hasChangedSections = true;
this.changedBlocksPerSection[i] = new ShortOpenHashSet();
@@ -224,8 +241,11 @@
ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection);
this.broadcast(list, packetplayoutmultiblockchange);
+ // CraftBukkit start
+ List finalList = list;
packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> {
- this.broadcastBlockEntityIfNeeded(list, world, blockposition1, iblockdata1);
+ this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1);
+ // CraftBukkit end
});
}
}
@@ -291,7 +311,7 @@
this.pendingFullStateConfirmation = completablefuture1;
chunkFuture.thenAccept((chunkresult) -> {
chunkresult.ifSuccess((chunk) -> {
- completablefuture1.complete((Object) null);
+ completablefuture1.complete(null); // CraftBukkit - decompile error
});
});
}
@@ -301,6 +321,38 @@
chunkLoadingManager.onFullChunkStatusChange(this.pos, target);
}
+ // CraftBukkit start
+ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
+ // SPIGOT-7780: Moved out of updateFutures to call all chunk unload events before calling updateHighestAllowedStatus for all chunks
+ protected void callEventIfUnloading(ChunkMap playerchunkmap) {
+ FullChunkStatus oldFullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel);
+ FullChunkStatus newFullChunkStatus = ChunkLevel.fullStatus(this.ticketLevel);
+ boolean oldIsFull = oldFullChunkStatus.isOrAfter(FullChunkStatus.FULL);
+ boolean newIsFull = newFullChunkStatus.isOrAfter(FullChunkStatus.FULL);
+ if (oldIsFull && !newIsFull) {
+ this.getFullChunkFuture().thenAccept((either) -> {
+ LevelChunk chunk = (LevelChunk) either.orElse(null);
+ if (chunk != null) {
+ playerchunkmap.callbackExecutor.execute(() -> {
+ // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick
+ // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag.
+ // These actions may however happen deferred, so we manually set the needsSaving flag already here.
+ chunk.markUnsaved();
+ chunk.unloadCallback();
+ });
+ }
+ }).exceptionally((throwable) -> {
+ // ensure exceptions are printed, by default this is not the case
+ MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable);
+ return null;
+ });
+
+ // Run callback right away if the future was already done
+ playerchunkmap.callbackExecutor.run();
+ }
+ }
+ // CraftBukkit end
+
protected void updateFutures(ChunkMap chunkLoadingManager, Executor executor) {
FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel);
FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel);
@@ -309,12 +361,28 @@
this.wasAccessibleSinceLastSave |= flag1;
if (!flag && flag1) {
+ int expectCreateCount = ++this.fullChunkCreateCount; // Paper
this.fullChunkFuture = chunkLoadingManager.prepareAccessibleChunk(this);
this.scheduleFullChunkPromotion(chunkLoadingManager, this.fullChunkFuture, executor, FullChunkStatus.FULL);
+ // Paper start - cache ticking ready status
+ this.fullChunkFuture.thenAccept(chunkResult -> {
+ chunkResult.ifSuccess(chunk -> {
+ if (ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
+ ChunkHolder.this.isFullChunkReady = true;
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkBorder(chunk, this);
+ }
+ });
+ });
+ // Paper end - cache ticking ready status
this.addSaveDependency(this.fullChunkFuture);
}
if (flag && !flag1) {
+ // Paper start
+ if (this.isFullChunkReady) {
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
+ }
+ // Paper end
this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
}
@@ -325,11 +393,25 @@
if (!flag2 && flag3) {
this.tickingChunkFuture = chunkLoadingManager.prepareTickingChunk(this);
this.scheduleFullChunkPromotion(chunkLoadingManager, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING);
+ // Paper start - cache ticking ready status
+ this.tickingChunkFuture.thenAccept(chunkResult -> {
+ chunkResult.ifSuccess(chunk -> {
+ // note: Here is a very good place to add callbacks to logic waiting on this.
+ ChunkHolder.this.isTickingReady = true;
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkTicking(chunk, this);
+ });
+ });
+ // Paper end
this.addSaveDependency(this.tickingChunkFuture);
}
if (flag2 && !flag3) {
- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+ // Paper start
+ if (this.isTickingReady) {
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
+ }
+ // Paper end
+ this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
}
@@ -343,11 +425,24 @@
this.entityTickingChunkFuture = chunkLoadingManager.prepareEntityTickingChunk(this);
this.scheduleFullChunkPromotion(chunkLoadingManager, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING);
+ // Paper start - cache ticking ready status
+ this.entityTickingChunkFuture.thenAccept(chunkResult -> {
+ chunkResult.ifSuccess(chunk -> {
+ ChunkHolder.this.isEntityTickingReady = true;
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkEntityTicking(chunk, this);
+ });
+ });
+ // Paper end
this.addSaveDependency(this.entityTickingChunkFuture);
}
if (flag4 && !flag5) {
- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+ // Paper start
+ if (this.isEntityTickingReady) {
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this);
+ }
+ // Paper end
+ this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
}
@@ -357,6 +452,26 @@
this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
this.oldTicketLevel = this.ticketLevel;
+ // CraftBukkit start
+ // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
+ if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) {
+ this.getFullChunkFuture().thenAccept((either) -> {
+ LevelChunk chunk = (LevelChunk) either.orElse(null);
+ if (chunk != null) {
+ chunkLoadingManager.callbackExecutor.execute(() -> {
+ chunk.loadCallback();
+ });
+ }
+ }).exceptionally((throwable) -> {
+ // ensure exceptions are printed, by default this is not the case
+ MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable);
+ return null;
+ });
+
+ // Run callback right away if the future was already done
+ chunkLoadingManager.callbackExecutor.run();
+ }
+ // CraftBukkit end
}
public boolean wasAccessibleSinceLastSave() {

View File

@@ -0,0 +1,433 @@
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -104,6 +104,10 @@
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
+// CraftBukkit start
+import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
+// CraftBukkit end
+
public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap {
private static final ChunkResult<List<ChunkAccess>> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range");
@@ -149,6 +153,33 @@
public int serverViewDistance;
private final WorldGenContext worldGenContext;
+ // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
+ public final CallbackExecutor callbackExecutor = new CallbackExecutor();
+ public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
+
+ private final java.util.Queue<Runnable> queue = new java.util.ArrayDeque<>();
+
+ @Override
+ public void execute(Runnable runnable) {
+ this.queue.add(runnable);
+ }
+
+ @Override
+ public void run() {
+ Runnable task;
+ while ((task = this.queue.poll()) != null) {
+ task.run();
+ }
+ }
+ };
+ // CraftBukkit end
+
+ // Paper start
+ public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
+ return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
+ }
+ // Paper end
+
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
this.visibleChunkMap = this.updatingChunkMap.clone();
@@ -170,13 +201,19 @@
RegistryAccess iregistrycustom = world.registryAccess();
long j = world.getSeed();
- if (chunkGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) {
+ // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random.
+ ChunkGenerator randomGenerator = chunkGenerator;
+ if (randomGenerator instanceof CustomChunkGenerator customChunkGenerator) {
+ randomGenerator = customChunkGenerator.getDelegate();
+ }
+ if (randomGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) {
+ // CraftBukkit end
this.randomState = RandomState.create((NoiseGeneratorSettings) chunkgeneratorabstract.generatorSettings().value(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
} else {
this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
}
- this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j);
+ this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot
this.mainThreadExecutor = mainThreadExecutor;
ConsecutiveExecutor consecutiveexecutor = new ConsecutiveExecutor(executor, "worldgen");
@@ -198,6 +235,12 @@
this.chunksToEagerlySave.add(pos.toLong());
}
+ // Paper start
+ public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
+ return -1;
+ }
+ // Paper end
+
protected ChunkGenerator generator() {
return this.worldGenContext.generator();
}
@@ -325,7 +368,7 @@
throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
}
- ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse((Object) null);
+ ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error
if (ichunkaccess == null) {
return ChunkMap.UNLOADED_CHUNK_LIST_RESULT;
@@ -354,9 +397,9 @@
};
stringbuilder.append("Updating:").append(System.lineSeparator());
- this.updatingChunkMap.values().forEach(consumer);
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
stringbuilder.append("Visible:").append(System.lineSeparator());
- this.visibleChunkMap.values().forEach(consumer);
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading");
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading");
@@ -398,6 +441,9 @@
holder.setTicketLevel(level);
} else {
holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this::onLevelChange, this);
+ // Paper start
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder);
+ // Paper end
}
this.updatingChunkMap.put(pos, holder);
@@ -427,7 +473,7 @@
protected void saveAllChunks(boolean flush) {
if (flush) {
- List<ChunkHolder> list = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList();
+ List<ChunkHolder> list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper
MutableBoolean mutableboolean = new MutableBoolean();
do {
@@ -453,7 +499,7 @@
} else {
this.nextChunkSaveTime.clear();
long i = Util.getMillis();
- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator();
+ Iterator<ChunkHolder> objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
while (objectiterator.hasNext()) {
ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
@@ -478,7 +524,7 @@
}
public boolean hasWork() {
- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets();
+ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets();
}
private void processUnloads(BooleanSupplier shouldKeepTicking) {
@@ -537,8 +583,11 @@
this.scheduleUnload(pos, chunk);
} else {
ChunkAccess ichunkaccess = chunk.getLatestChunk();
-
- if (this.pendingUnloads.remove(pos, chunk) && ichunkaccess != null) {
+ // Paper start
+ boolean removed;
+ if ((removed = this.pendingUnloads.remove(pos, chunk)) && ichunkaccess != null) {
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
+ // Paper end
LevelChunk chunk1;
if (ichunkaccess instanceof LevelChunk) {
@@ -556,7 +605,9 @@
this.lightEngine.tryScheduleUpdate();
this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null);
this.nextChunkSaveTime.remove(ichunkaccess.getPos().toLong());
- }
+ } else if (removed) { // Paper start
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
+ } // Paper end
}
};
@@ -905,7 +956,7 @@
}
}
- protected void setServerViewDistance(int watchDistance) {
+ public void setServerViewDistance(int watchDistance) { // Paper - public
int j = Mth.clamp(watchDistance, 2, 32);
if (j != this.serverViewDistance) {
@@ -922,7 +973,7 @@
}
- int getPlayerViewDistance(ServerPlayer player) {
+ public int getPlayerViewDistance(ServerPlayer player) { // Paper - public
return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
}
@@ -951,7 +1002,7 @@
}
public int size() {
- return this.visibleChunkMap.size();
+ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
}
public DistanceManager getDistanceManager() {
@@ -959,25 +1010,26 @@
}
protected Iterable<ChunkHolder> getChunks() {
- return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
+ return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
}
void dumpChunks(Writer writer) throws IOException {
CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
TickingTracker tickingtracker = this.distanceManager.tickingTracker();
- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator();
+ Iterator<ChunkHolder> objectbidirectionaliterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
while (objectbidirectionaliterator.hasNext()) {
- Entry<ChunkHolder> entry = (Entry) objectbidirectionaliterator.next();
- long i = entry.getLongKey();
+ ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper
+ long i = playerchunk.pos.toLong(); // Paper
ChunkPos chunkcoordintpair = new ChunkPos(i);
- ChunkHolder playerchunk = (ChunkHolder) entry.getValue();
+ // Paper - move up
Optional<ChunkAccess> optional = Optional.ofNullable(playerchunk.getLatestChunk());
Optional<LevelChunk> optional1 = optional.flatMap((ichunkaccess) -> {
return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty();
});
- csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse((Object) null), optional1.map(LevelChunk::getFullStatus).orElse((Object) null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
+ // CraftBukkit - decompile error
+ csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
return chunk.getBlockEntities().size();
}).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> {
return chunk.getBlockTicks().count();
@@ -990,7 +1042,7 @@
private static String printFuture(CompletableFuture<ChunkResult<LevelChunk>> future) {
try {
- ChunkResult<LevelChunk> chunkresult = (ChunkResult) future.getNow((Object) null);
+ ChunkResult<LevelChunk> chunkresult = (ChunkResult) future.getNow(null); // CraftBukkit - decompile error
return chunkresult != null ? (chunkresult.isSuccess() ? "done" : "unloaded") : "not completed";
} catch (CompletionException completionexception) {
@@ -1002,12 +1054,14 @@
private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkPos) {
return this.read(chunkPos).thenApplyAsync((optional) -> {
- return optional.map(this::upgradeChunkTag);
+ return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit
}, Util.backgroundExecutor().forName("upgradeChunk"));
}
- private CompoundTag upgradeChunkTag(CompoundTag nbt) {
- return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, nbt, this.generator().getTypeNameForDataFixer());
+ // CraftBukkit start
+ private CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) {
+ return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator().getTypeNameForDataFixer(), chunkcoordintpair, this.level);
+ // CraftBukkit end
}
void forEachSpawnCandidateChunk(Consumer<ChunkHolder> callback) {
@@ -1025,10 +1079,23 @@
}
public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) {
- return !this.distanceManager.hasPlayersNearby(pos.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(pos);
+ // Spigot start
+ return this.anyPlayerCloseEnoughForSpawning(pos, false);
}
+ boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
+ return !this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(chunkcoordintpair, reducedRange);
+ // Spigot end
+ }
+
private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos pos) {
+ // Spigot start
+ return this.anyPlayerCloseEnoughForSpawningInternal(pos, false);
+ }
+
+ private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkcoordintpair, boolean reducedRange) {
+ double blockRange; // Paper - use from event
+ // Spigot end
Iterator iterator = this.playerMap.getAllPlayers().iterator();
ServerPlayer entityplayer;
@@ -1039,7 +1106,16 @@
}
entityplayer = (ServerPlayer) iterator.next();
- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, pos));
+ // Paper start - PlayerNaturallySpawnCreaturesEvent
+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
+ blockRange = 16384.0D;
+ if (reducedRange) {
+ event = entityplayer.playerNaturallySpawnedEvent;
+ if (event == null || event.isCancelled()) continue;
+ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
+ }
+ // Paper end - PlayerNaturallySpawnCreaturesEvent
+ } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
return true;
}
@@ -1056,7 +1132,7 @@
while (iterator.hasNext()) {
ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- if (this.playerIsCloseEnoughForSpawning(entityplayer, pos)) {
+ if (this.playerIsCloseEnoughForSpawning(entityplayer, pos, 16384.0D)) { // Spigot
builder.add(entityplayer);
}
}
@@ -1065,13 +1141,13 @@
}
}
- private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) {
- if (player.isSpectator()) {
+ private boolean playerIsCloseEnoughForSpawning(ServerPlayer entityplayer, ChunkPos chunkcoordintpair, double range) { // Spigot
+ if (entityplayer.isSpectator()) {
return false;
} else {
- double d0 = ChunkMap.euclideanDistanceSquared(pos, player);
+ double d0 = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, entityplayer);
- return d0 < 16384.0D;
+ return d0 < range; // Spigot
}
}
@@ -1215,9 +1291,19 @@
}
public void addEntity(Entity entity) {
+ org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
+ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server
+ if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) {
+ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName()
+ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable());
+ return;
+ }
+ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server
+ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets
if (!(entity instanceof EnderDragonPart)) {
EntityType<?> entitytypes = entity.getType();
int i = entitytypes.clientTrackingRange() * 16;
+ i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot
if (i != 0) {
int j = entitytypes.updateInterval();
@@ -1250,6 +1336,7 @@
}
protected void removeEntity(Entity entity) {
+ org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot
if (entity instanceof ServerPlayer entityplayer) {
this.updatePlayerStatus(entityplayer, false);
ObjectIterator objectiterator = this.entityMap.values().iterator();
@@ -1391,7 +1478,7 @@
});
}
- private class ChunkDistanceManager extends DistanceManager {
+ public class ChunkDistanceManager extends DistanceManager { // Paper - public
protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) {
super(workerExecutor, mainThreadExecutor);
@@ -1421,10 +1508,10 @@
final Entity entity;
private final int range;
SectionPos lastSectionPos;
- public final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
+ public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast);
+ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
this.entity = entity;
this.range = i;
this.lastSectionPos = SectionPos.of((EntityAccess) entity);
@@ -1469,6 +1556,7 @@
}
public void removePlayer(ServerPlayer player) {
+ org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
}
@@ -1476,17 +1564,41 @@
}
public void updatePlayer(ServerPlayer player) {
+ org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
if (player != this.entity) {
- Vec3 vec3d = player.position().subtract(this.entity.position());
+ // Paper start - remove allocation of Vec3D here
+ // Vec3 vec3d = player.position().subtract(this.entity.position());
+ double vec3d_dx = player.getX() - this.entity.getX();
+ double vec3d_dz = player.getZ() - this.entity.getZ();
+ // Paper end - remove allocation of Vec3D here
int i = ChunkMap.this.getPlayerViewDistance(player);
double d0 = (double) Math.min(this.getEffectiveRange(), i * 16);
- double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z;
+ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
double d2 = d0 * d0;
- boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
+ // Paper start - Configurable entity tracking range by Y
+ boolean flag = d1 <= d2;
+ if (flag && level.paperConfig().entities.trackingRangeY.enabled) {
+ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
+ if (rangeY != -1) {
+ double vec3d_dy = player.getY() - this.entity.getY();
+ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY;
+ }
+ }
+ flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
+ // Paper end - Configurable entity tracking range by Y
+ // CraftBukkit start - respect vanish API
+ if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits
+ flag = false;
+ }
+ // CraftBukkit end
if (flag) {
if (this.seenBy.add(player.connection)) {
+ // Paper start - entity tracking events
+ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) {
this.serverEntity.addPairing(player);
+ }
+ // Paper end - entity tracking events
}
} else if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
@@ -1506,6 +1618,7 @@
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
int j = entity.getType().clientTrackingRange() * 16;
+ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper
if (j > i) {
i = j;

View File

@@ -0,0 +1,152 @@
--- a/net/minecraft/server/level/DistanceManager.java
+++ b/net/minecraft/server/level/DistanceManager.java
@@ -117,8 +117,17 @@
ChunkHolder playerchunk;
+ // CraftBukkit start - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus
while (iterator.hasNext()) {
playerchunk = (ChunkHolder) iterator.next();
+ playerchunk.callEventIfUnloading(chunkLoadingManager);
+ }
+
+ iterator = this.chunksToUpdateFutures.iterator();
+ // CraftBukkit end
+
+ while (iterator.hasNext()) {
+ playerchunk = (ChunkHolder) iterator.next();
playerchunk.updateHighestAllowedStatus(chunkLoadingManager);
}
@@ -165,30 +174,33 @@
}
}
- void addTicket(long position, Ticket<?> ticket) {
- SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(position);
+ boolean addTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
+ SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
int j = DistanceManager.getTicketLevelAt(arraysetsorted);
Ticket<?> ticket1 = (Ticket) arraysetsorted.addOrGet(ticket);
ticket1.setCreatedTick(this.ticketTickCounter);
if (ticket.getTicketLevel() < j) {
- this.ticketTracker.update(position, ticket.getTicketLevel(), true);
+ this.ticketTracker.update(i, ticket.getTicketLevel(), true);
}
+ return ticket == ticket1; // CraftBukkit
}
- void removeTicket(long pos, Ticket<?> ticket) {
- SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(pos);
+ boolean removeTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
+ SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
+ boolean removed = false; // CraftBukkit
if (arraysetsorted.remove(ticket)) {
- ;
+ removed = true; // CraftBukkit
}
if (arraysetsorted.isEmpty()) {
- this.tickets.remove(pos);
+ this.tickets.remove(i);
}
- this.ticketTracker.update(pos, DistanceManager.getTicketLevelAt(arraysetsorted), false);
+ this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false);
+ return removed; // CraftBukkit
}
public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T argument) {
@@ -202,19 +214,33 @@
}
public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int radius, T argument) {
- Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument);
- long j = pos.toLong();
+ // CraftBukkit start
+ this.addRegionTicketAtDistance(type, pos, radius, argument);
+ }
- this.addTicket(j, ticket);
+ public <T> boolean addRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
+ // CraftBukkit end
+ Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
+ long j = chunkcoordintpair.toLong();
+
+ boolean added = this.addTicket(j, ticket); // CraftBukkit
this.tickingTicketsTracker.addTicket(j, ticket);
+ return added; // CraftBukkit
}
public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int radius, T argument) {
- Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument);
- long j = pos.toLong();
+ // CraftBukkit start
+ this.removeRegionTicketAtDistance(type, pos, radius, argument);
+ }
- this.removeTicket(j, ticket);
+ public <T> boolean removeRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
+ // CraftBukkit end
+ Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
+ long j = chunkcoordintpair.toLong();
+
+ boolean removed = this.removeTicket(j, ticket); // CraftBukkit
this.tickingTicketsTracker.removeTicket(j, ticket);
+ return removed; // CraftBukkit
}
private SortedArraySet<Ticket<?>> getTickets(long position) {
@@ -253,9 +279,10 @@
ChunkPos chunkcoordintpair = pos.chunk();
long i = chunkcoordintpair.toLong();
ObjectSet<ServerPlayer> objectset = (ObjectSet) this.playersPerChunk.get(i);
+ if (objectset == null) return; // CraftBukkit - SPIGOT-6208
- objectset.remove(player);
- if (objectset.isEmpty()) {
+ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully
+ if (objectset == null || objectset.isEmpty()) { // Paper
this.playersPerChunk.remove(i);
this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
@@ -358,7 +385,7 @@
}
public void removeTicketsOnClosing() {
- ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN);
+ ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
while (objectiterator.hasNext()) {
@@ -389,7 +416,27 @@
public boolean hasTickets() {
return !this.tickets.isEmpty();
+ }
+
+ // CraftBukkit start
+ public <T> void removeAllTicketsFor(TicketType<T> ticketType, int ticketLevel, T ticketIdentifier) {
+ Ticket<T> target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier);
+
+ for (java.util.Iterator<Entry<SortedArraySet<Ticket<?>>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
+ Entry<SortedArraySet<Ticket<?>>> entry = iterator.next();
+ SortedArraySet<Ticket<?>> tickets = entry.getValue();
+ if (tickets.remove(target)) {
+ // copied from removeTicket
+ this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false);
+
+ // can't use entry after it's removed
+ if (tickets.isEmpty()) {
+ iterator.remove();
+ }
+ }
+ }
}
+ // CraftBukkit end
private class ChunkTicketTracker extends ChunkTracker {

View File

@@ -0,0 +1,255 @@
--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -74,6 +74,13 @@
@Nullable
@VisibleForDebug
private NaturalSpawner.SpawnState lastSpawnState;
+ // Paper start
+ private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
+ public int getFullChunksCount() {
+ return this.fullChunks.size();
+ }
+ long chunkFutureAwaitCounter;
+ // Paper end
public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
this.level = world;
@@ -95,6 +102,64 @@
this.clearCache();
}
+ // CraftBukkit start - properly implement isChunkLoaded
+ public boolean isChunkLoaded(int chunkX, int chunkZ) {
+ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(ChunkPos.asLong(chunkX, chunkZ));
+ if (chunk == null) {
+ return false;
+ }
+ return chunk.getFullChunkNow() != null;
+ }
+ // CraftBukkit end
+ // Paper start
+ public void addLoadedChunk(LevelChunk chunk) {
+ this.fullChunks.put(chunk.coordinateKey, chunk);
+ }
+
+ public void removeLoadedChunk(LevelChunk chunk) {
+ this.fullChunks.remove(chunk.coordinateKey);
+ }
+
+ @Nullable
+ public ChunkAccess getChunkAtImmediately(int x, int z) {
+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
+ if (holder == null) {
+ return null;
+ }
+
+ return holder.getLatestChunk();
+ }
+
+ public <T> void addTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
+ this.distanceManager.addTicket(ticketType, chunkPos, ticketLevel, identifier);
+ }
+
+ public <T> void removeTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
+ this.distanceManager.removeTicket(ticketType, chunkPos, ticketLevel, identifier);
+ }
+
+ // "real" get chunk if loaded
+ // Note: Partially copied from the getChunkAt method below
+ @Nullable
+ public LevelChunk getChunkAtIfCachedImmediately(int x, int z) {
+ long k = ChunkPos.asLong(x, z);
+
+ // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe
+
+ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k);
+ if (playerChunk == null) {
+ return null;
+ }
+
+ return playerChunk.getFullChunkNowUnchecked();
+ }
+
+ @Nullable
+ public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) {
+ return this.fullChunks.get(ChunkPos.asLong(x, z));
+ }
+ // Paper end
+
@Override
public ThreadedLevelLightEngine getLightEngine() {
return this.lightEngine;
@@ -138,7 +203,7 @@
if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) {
ChunkAccess ichunkaccess = this.lastChunk[l];
- if (ichunkaccess != null || !create) {
+ if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
return ichunkaccess;
}
}
@@ -150,8 +215,9 @@
Objects.requireNonNull(completablefuture);
chunkproviderserver_b.managedBlock(completablefuture::isDone);
+ // com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads
ChunkResult<ChunkAccess> chunkresult = (ChunkResult) completablefuture.join();
- ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse((Object) null);
+ ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error
if (ichunkaccess1 == null && create) {
throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkresult.getError()));
@@ -231,7 +297,15 @@
int l = ChunkLevel.byStatus(leastStatus);
ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
- if (create) {
+ // CraftBukkit start - don't add new ticket for currently unloading chunk
+ boolean currentlyUnloading = false;
+ if (playerchunk != null) {
+ FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel);
+ FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel());
+ currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL));
+ }
+ if (create && !currentlyUnloading) {
+ // CraftBukkit end
this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
if (this.chunkAbsent(playerchunk, l)) {
ProfilerFiller gameprofilerfiller = Profiler.get();
@@ -250,7 +324,7 @@
}
private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) {
- return holder == null || holder.getTicketLevel() > maxLevel;
+ return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks
}
@Override
@@ -279,7 +353,7 @@
return this.mainThreadProcessor.pollTask();
}
- boolean runDistanceManagerUpdates() {
+ public boolean runDistanceManagerUpdates() { // Paper - public
boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
boolean flag1 = this.chunkMap.promoteChunkMap();
@@ -309,18 +383,40 @@
@Override
public void close() throws IOException {
- this.save(true);
+ // CraftBukkit start
+ this.close(true);
+ }
+
+ public void close(boolean save) throws IOException {
+ if (save) {
+ this.save(true);
+ }
+ // CraftBukkit end
this.dataStorage.close();
this.lightEngine.close();
this.chunkMap.close();
}
+ // CraftBukkit start - modelled on below
+ public void purgeUnload() {
+ ProfilerFiller gameprofilerfiller = Profiler.get();
+
+ gameprofilerfiller.push("purge");
+ this.distanceManager.purgeStaleTickets();
+ this.runDistanceManagerUpdates();
+ gameprofilerfiller.popPush("unload");
+ this.chunkMap.tick(() -> true);
+ gameprofilerfiller.pop();
+ this.clearCache();
+ }
+ // CraftBukkit end
+
@Override
public void tick(BooleanSupplier shouldKeepTicking, boolean tickChunks) {
ProfilerFiller gameprofilerfiller = Profiler.get();
gameprofilerfiller.push("purge");
- if (this.level.tickRateManager().runsNormally() || !tickChunks) {
+ if (this.level.tickRateManager().runsNormally() || !tickChunks || this.level.spigotConfig.unloadFrozenChunks) { // Spigot
this.distanceManager.purgeStaleTickets();
}
@@ -401,14 +497,22 @@
this.lastSpawnState = spawnercreature_d;
profiler.popPush("spawnAndTick");
- boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
+ boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
List list1;
if (flag && (this.spawnEnemies || this.spawnFriendlies)) {
- boolean flag1 = this.level.getLevelData().getGameTime() % 400L == 0L;
+ // Paper start - PlayerNaturallySpawnCreaturesEvent
+ for (ServerPlayer entityPlayer : this.level.players()) {
+ int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance());
+ chunkRange = Math.min(chunkRange, 8);
+ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
+ entityPlayer.playerNaturallySpawnedEvent.callEvent();
+ }
+ // Paper end - PlayerNaturallySpawnCreaturesEvent
+ boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
- list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
+ list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit
} else {
list1 = List.of();
}
@@ -420,7 +524,7 @@
ChunkPos chunkcoordintpair = chunk.getPos();
chunk.incrementInhabitedTime(timeDelta);
- if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) {
+ if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot
NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1);
}
@@ -541,10 +645,16 @@
@Override
public void setSpawnSettings(boolean spawnMonsters) {
- this.spawnEnemies = spawnMonsters;
- this.spawnFriendlies = this.spawnFriendlies;
+ // CraftBukkit start
+ this.setSpawnSettings(spawnMonsters, this.spawnFriendlies);
}
+ public void setSpawnSettings(boolean flag, boolean spawnFriendlies) {
+ this.spawnEnemies = flag;
+ this.spawnFriendlies = spawnFriendlies;
+ // CraftBukkit end
+ }
+
public String getChunkDebugData(ChunkPos pos) {
return this.chunkMap.getChunkDebugData(pos);
}
@@ -618,14 +728,20 @@
}
@Override
- protected boolean pollTask() {
+ // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
+ public boolean pollTask() {
+ try {
if (ServerChunkCache.this.runDistanceManagerUpdates()) {
return true;
} else {
ServerChunkCache.this.lightEngine.tryScheduleUpdate();
return super.pollTask();
}
+ } finally {
+ ServerChunkCache.this.chunkMap.callbackExecutor.run();
}
+ // CraftBukkit end
+ }
}
private static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) {

View File

@@ -0,0 +1,179 @@
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
@@ -31,7 +31,6 @@
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
import net.minecraft.network.protocol.game.VecDeltaCodec;
import net.minecraft.network.syncher.SynchedEntityData;
-import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.Leashable;
@@ -50,6 +49,13 @@
import net.minecraft.world.phys.Vec3;
import org.slf4j.Logger;
+// CraftBukkit start
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.util.Mth;
+import org.bukkit.entity.Player;
+import org.bukkit.event.player.PlayerVelocityEvent;
+// CraftBukkit end
+
public class ServerEntity {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -69,18 +75,22 @@
private Vec3 lastSentMovement;
private int tickCount;
private int teleportDelay;
- private List<Entity> lastPassengers = Collections.emptyList();
+ private List<Entity> lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks
private boolean wasRiding;
private boolean wasOnGround;
@Nullable
private List<SynchedEntityData.DataValue<?>> trackedDataValues;
+ // CraftBukkit start
+ private final Set<ServerPlayerConnection> trackedPlayers;
- public ServerEntity(ServerLevel world, Entity entity, int tickInterval, boolean alwaysUpdateVelocity, Consumer<Packet<?>> receiver) {
- this.level = world;
- this.broadcast = receiver;
+ public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer<Packet<?>> consumer, Set<ServerPlayerConnection> trackedPlayers) {
+ this.trackedPlayers = trackedPlayers;
+ // CraftBukkit end
+ this.level = worldserver;
+ this.broadcast = consumer;
this.entity = entity;
- this.updateInterval = tickInterval;
- this.trackDelta = alwaysUpdateVelocity;
+ this.updateInterval = i;
+ this.trackDelta = flag;
this.positionCodec.setBase(entity.trackingPosition());
this.lastSentMovement = entity.getDeltaMovement();
this.lastSentYRot = Mth.packDegrees(entity.getYRot());
@@ -94,7 +104,7 @@
List<Entity> list = this.entity.getPassengers();
if (!list.equals(this.lastPassengers)) {
- this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity));
+ this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
ServerEntity.removedPassengers(list, this.lastPassengers).forEach((entity) -> {
if (entity instanceof ServerPlayer entityplayer) {
entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot());
@@ -106,19 +116,19 @@
Entity entity = this.entity;
- if (entity instanceof ItemFrame entityitemframe) {
- if (this.tickCount % 10 == 0) {
+ if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame entityitemframe) { // Paper - Perf: Only tick item frames if players can see it
+ if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block
ItemStack itemstack = entityitemframe.getItem();
- if (itemstack.getItem() instanceof MapItem) {
- MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID);
+ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable
+ MapId mapid = entityitemframe.cachedMapId; // Paper - Perf: Cache map ids on item frames
MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level);
if (worldmap != null) {
- Iterator iterator = this.level.players().iterator();
+ Iterator<ServerPlayerConnection> iterator = this.trackedPlayers.iterator(); // CraftBukkit
while (iterator.hasNext()) {
- ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+ ServerPlayer entityplayer = iterator.next().getPlayer(); // CraftBukkit
worldmap.tickCarriedBy(entityplayer, itemstack);
Packet<?> packet = worldmap.getUpdatePacket(mapid, entityplayer);
@@ -168,7 +178,13 @@
++this.teleportDelay;
Vec3 vec3d = this.entity.trackingPosition();
- boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D;
+ // Paper start - reduce allocation of Vec3D here
+ Vec3 base = this.positionCodec.base;
+ double vec3d_dx = vec3d.x - base.x;
+ double vec3d_dy = vec3d.y - base.y;
+ double vec3d_dz = vec3d.z - base.z;
+ boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D;
+ // Paper end - reduce allocation of Vec3D here
Packet<?> packet1 = null;
boolean flag2 = flag1 || this.tickCount % 60 == 0;
boolean flag3 = false;
@@ -248,6 +264,27 @@
++this.tickCount;
if (this.entity.hurtMarked) {
+ // CraftBukkit start - Create PlayerVelocity event
+ boolean cancelled = false;
+
+ if (this.entity instanceof ServerPlayer) {
+ Player player = (Player) this.entity.getBukkitEntity();
+ org.bukkit.util.Vector velocity = player.getVelocity();
+
+ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
+ this.entity.level().getCraftServer().getPluginManager().callEvent(event);
+
+ if (event.isCancelled()) {
+ cancelled = true;
+ } else if (!velocity.equals(event.getVelocity())) {
+ player.setVelocity(event.getVelocity());
+ }
+ }
+
+ if (cancelled) {
+ return;
+ }
+ // CraftBukkit end
this.entity.hurtMarked = false;
this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
}
@@ -298,7 +335,10 @@
public void sendPairingData(ServerPlayer player, Consumer<Packet<ClientGamePacketListener>> sender) {
if (this.entity.isRemoved()) {
- ServerEntity.LOGGER.warn("Fetching packet for removed entity {}", this.entity);
+ // CraftBukkit start - Remove useless error spam, just return
+ // EntityTrackerEntry.LOGGER.warn("Fetching packet for removed entity {}", this.entity);
+ return;
+ // CraftBukkit end
}
Packet<ClientGamePacketListener> packet = this.entity.getAddEntityPacket(this);
@@ -313,6 +353,12 @@
if (this.entity instanceof LivingEntity) {
Collection<AttributeInstance> collection = ((LivingEntity) this.entity).getAttributes().getSyncableAttributes();
+ // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
+ if (this.entity.getId() == player.getId()) {
+ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(collection, false);
+ }
+ // CraftBukkit end
+
if (!collection.isEmpty()) {
sender.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection));
}
@@ -342,8 +388,9 @@
}
if (!list.isEmpty()) {
- sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list));
+ sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization
}
+ ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending
}
if (!this.entity.getPassengers().isEmpty()) {
@@ -396,6 +443,11 @@
Set<AttributeInstance> set = ((LivingEntity) this.entity).getAttributes().getAttributesToSync();
if (!set.isEmpty()) {
+ // CraftBukkit start - Send scaled max health
+ if (this.entity instanceof ServerPlayer) {
+ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false);
+ }
+ // CraftBukkit end
this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set));
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,463 @@
--- a/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -13,6 +13,7 @@
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.item.DoubleHighBlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
@@ -20,12 +21,30 @@
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.GameMasterBlock;
+import net.minecraft.world.level.block.TrapDoorBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import org.slf4j.Logger;
+// CraftBukkit start
+import java.util.ArrayList;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.CakeBlock;
+import net.minecraft.world.level.block.DoorBlock;
+import org.bukkit.GameMode;
+import org.bukkit.craftbukkit.block.CraftBlock;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.event.Event;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.player.PlayerGameModeChangeEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+// CraftBukkit end
+
public class ServerPlayerGameMode {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -42,6 +61,8 @@
private BlockPos delayedDestroyPos;
private int delayedTickStart;
private int lastSentState;
+ public boolean captureSentBlockEntities = false; // Paper - Send block entities after destroy prediction
+ public boolean capturedBlockEntity = false; // Paper - Send block entities after destroy prediction
public ServerPlayerGameMode(ServerPlayer player) {
this.gameModeForPlayer = GameType.DEFAULT_MODE;
@@ -53,18 +74,32 @@
}
public boolean changeGameModeForPlayer(GameType gameMode) {
+ // Paper start - Expand PlayerGameModeChangeEvent
+ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null);
+ return event != null && event.isCancelled();
+ }
+ @Nullable
+ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) {
+ // Paper end - Expand PlayerGameModeChangeEvent
if (gameMode == this.gameModeForPlayer) {
- return false;
+ return null; // Paper - Expand PlayerGameModeChangeEvent
} else {
- this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer);
+ // CraftBukkit start
+ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper
+ this.level.getCraftServer().getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ return event; // Paper - Expand PlayerGameModeChangeEvent
+ }
+ // CraftBukkit end
+ this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571
this.player.onUpdateAbilities();
- this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player));
+ this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit
this.level.updateSleepingPlayerList();
if (gameMode == GameType.CREATIVE) {
this.player.resetCurrentImpulseContext();
}
- return true;
+ return event; // Paper - Expand PlayerGameModeChangeEvent
}
}
@@ -92,12 +127,12 @@
}
public void tick() {
- ++this.gameTicks;
+ this.gameTicks = MinecraftServer.currentTick; // CraftBukkit;
BlockState iblockdata;
if (this.hasDelayedDestroy) {
- iblockdata = this.level.getBlockState(this.delayedDestroyPos);
- if (iblockdata.isAir()) {
+ iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks
+ if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks
this.hasDelayedDestroy = false;
} else {
float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart);
@@ -108,7 +143,13 @@
}
}
} else if (this.isDestroyingBlock) {
- iblockdata = this.level.getBlockState(this.destroyPos);
+ // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead
+ iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos);
+ if (iblockdata == null) {
+ this.isDestroyingBlock = false;
+ return;
+ }
+ // Paper end - Don't allow digging into unloaded chunks
if (iblockdata.isAir()) {
this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
this.lastSentState = -1;
@@ -137,6 +178,7 @@
public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) {
if (!this.player.canInteractWithBlock(pos, 1.0D)) {
+ if (true) return; // Paper - Don't allow digging into unloaded chunks; Don't notify if unreasonably far away
this.debugLogging(pos, false, sequence, "too far");
} else if (pos.getY() > worldHeight) {
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
@@ -146,16 +188,40 @@
if (action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
if (!this.level.mayInteract(this.player, pos)) {
+ // CraftBukkit start - fire PlayerInteractEvent
+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
this.debugLogging(pos, false, sequence, "may not interact");
+ // Update any tile entity data for this block
+ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
+ // CraftBukkit end
return;
}
+ // CraftBukkit start
+ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
+ if (event.isCancelled()) {
+ // Let the client know the block still exists
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks
+ // Update any tile entity data for this block
+ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
+ return;
+ }
+ // CraftBukkit end
+
if (this.isCreative()) {
this.destroyAndAck(pos, sequence, "creative destroy");
return;
}
+ // Spigot start - handle debug stick left click for non-creative
+ if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK)
+ && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) {
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block
+ return;
+ }
+ // Spigot end
+
if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
this.debugLogging(pos, false, sequence, "block action restricted");
@@ -166,7 +232,21 @@
float f = 1.0F;
iblockdata = this.level.getBlockState(pos);
- if (!iblockdata.isAir()) {
+ // CraftBukkit start - Swings at air do *NOT* exist.
+ if (event.useInteractedBlock() == Event.Result.DENY) {
+ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
+ // Paper start - Don't resync blocks
+ //BlockState data = this.level.getBlockState(pos);
+ //if (data.getBlock() instanceof DoorBlock) {
+ // // For some reason *BOTH* the bottom/top part have to be marked updated.
+ // boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER;
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below()));
+ //} else if (data.getBlock() instanceof TrapDoorBlock) {
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
+ //}
+ // Paper end - Don't resync blocks
+ } else if (!iblockdata.isAir()) {
EnchantmentHelper.onHitBlock(this.level, this.player.getMainHandItem(), this.player, this.player, EquipmentSlot.MAINHAND, Vec3.atCenterOf(pos), iblockdata, (item) -> {
this.player.onEquippedItemBroken(item, EquipmentSlot.MAINHAND);
});
@@ -174,6 +254,26 @@
f = iblockdata.getDestroyProgress(this.player, this.player.level(), pos);
}
+ if (event.useItemInHand() == Event.Result.DENY) {
+ // If we 'insta destroyed' then the client needs to be informed.
+ if (f > 1.0f) {
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks
+ }
+ return;
+ }
+ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, direction, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent
+
+ if (blockEvent.isCancelled()) {
+ // Let the client know the block still exists
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block
+ return;
+ }
+
+ if (blockEvent.getInstaBreak()) {
+ f = 2.0f;
+ }
+ // CraftBukkit end
+
if (!iblockdata.isAir() && f >= 1.0F) {
this.destroyAndAck(pos, sequence, "insta mine");
} else {
@@ -217,14 +317,18 @@
this.debugLogging(pos, true, sequence, "stopped destroying");
} else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
this.isDestroyingBlock = false;
- if (!Objects.equals(this.destroyPos, pos)) {
- ServerPlayerGameMode.LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, pos);
- this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
- this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
+ if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper
+ ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled
+ BlockState type = this.level.getBlockStateIfLoaded(this.destroyPos); // Paper - don't load unloaded chunks for stale records here
+ if (type != null) this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
+ if (type != null) this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
+ this.destroyPos = BlockPos.ZERO; // Paper
}
this.level.destroyBlockProgress(this.player.getId(), pos, -1);
this.debugLogging(pos, true, sequence, "aborted destroying");
+
+ CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
}
}
@@ -242,19 +346,82 @@
public boolean destroyBlock(BlockPos pos) {
BlockState iblockdata = this.level.getBlockState(pos);
+ // CraftBukkit start - fire BlockBreakEvent
+ org.bukkit.block.Block bblock = CraftBlock.at(this.level, pos);
+ BlockBreakEvent event = null;
- if (!this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) {
+ if (this.player instanceof ServerPlayer) {
+ // Sword + Creative mode pre-cancel
+ boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player);
+
+ // Tell client the block is gone immediately then process events
+ // Don't tell the client if its a creative sword break because its not broken!
+ if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block
+ ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState());
+ this.player.connection.send(packet);
+ }
+
+ event = new BlockBreakEvent(bblock, this.player.getBukkitEntity());
+
+ // Sword + Creative mode pre-cancel
+ event.setCancelled(isSwordNoBreak);
+
+ // Calculate default block experience
+ BlockState nmsData = this.level.getBlockState(pos);
+ Block nmsBlock = nmsData.getBlock();
+
+ ItemStack itemstack = this.player.getItemBySlot(EquipmentSlot.MAINHAND);
+
+ if (nmsBlock != null && !event.isCancelled() && !this.isCreative() && this.player.hasCorrectToolForDrops(nmsBlock.defaultBlockState())) {
+ event.setExpToDrop(nmsBlock.getExpDrop(nmsData, this.level, pos, itemstack, true));
+ }
+
+ this.level.getCraftServer().getPluginManager().callEvent(event);
+
+ if (event.isCancelled()) {
+ if (isSwordNoBreak) {
+ return false;
+ }
+ // Paper start - Don't resync blocks
+ // Let the client know the block still exists
+ //this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
+
+ // Brute force all possible updates
+ //for (Direction dir : Direction.values()) {
+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir)));
+ //}
+ // Paper end - Don't resync blocks
+
+ // Update any tile entity data for this block
+ if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction
+ BlockEntity tileentity = this.level.getBlockEntity(pos);
+ if (tileentity != null) {
+ this.player.connection.send(tileentity.getUpdatePacket());
+ }
+ } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction
+ return false;
+ }
+ }
+ // CraftBukkit end
+
+ if (false && !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) { // CraftBukkit - false
return false;
} else {
+ iblockdata = this.level.getBlockState(pos); // CraftBukkit - update state from plugins
+ if (iblockdata.isAir()) return false; // CraftBukkit - A plugin set block to air without cancelling
BlockEntity tileentity = this.level.getBlockEntity(pos);
Block block = iblockdata.getBlock();
- if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) {
+ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission
this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3);
return false;
} else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
return false;
} else {
+ // CraftBukkit start
+ org.bukkit.block.BlockState state = bblock.getState();
+ this.level.captureDrops = new ArrayList<>();
+ // CraftBukkit end
BlockState iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player);
boolean flag = this.level.removeBlock(pos, false);
@@ -262,20 +429,46 @@
block.destroy(this.level, pos, iblockdata1);
}
+ ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place
+ boolean isCorrectTool = false; // Paper - Trigger bee_nest_destroyed trigger in the correct place
if (this.isCreative()) {
- return true;
+ // return true; // CraftBukkit
} else {
ItemStack itemstack = this.player.getMainHandItem();
ItemStack itemstack1 = itemstack.copy();
boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1);
+ mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place
+ isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place
itemstack.mineBlock(this.level, iblockdata1, pos, this.player);
- if (flag && flag1) {
- block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1);
+ if (flag && flag1/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion
+ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion
}
- return true;
+ // return true; // CraftBukkit
}
+ // CraftBukkit start
+ java.util.List<net.minecraft.world.entity.item.ItemEntity> itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world
+ this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff
+ if (event.isDropItems()) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world
+ }
+ //this.level.captureDrops = null; // Paper - capture all item additions to the world; move up
+
+ // Drop event experience
+ if (flag && event != null) {
+ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper
+ }
+ // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy)
+ if (mainHandStack != null) {
+ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above
+ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount());
+ }
+ }
+ // Paper end - Trigger bee_nest_destroyed trigger in the correct place
+
+ return true;
+ // CraftBukkit end
}
}
}
@@ -321,17 +514,63 @@
}
}
+ // CraftBukkit start - whole method
+ public boolean interactResult = false;
+ public boolean firedInteract = false;
+ public BlockPos interactPosition;
+ public InteractionHand interactHand;
+ public ItemStack interactItemStack;
public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
BlockPos blockposition = hitResult.getBlockPos();
BlockState iblockdata = world.getBlockState(blockposition);
+ boolean cancelledBlock = false;
+ boolean cancelledItem = false; // Paper - correctly handle items on cooldown
if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) {
return InteractionResult.FAIL;
} else if (this.gameModeForPlayer == GameType.SPECTATOR) {
MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition);
+ cancelledBlock = !(itileinventory instanceof MenuProvider);
+ }
- if (itileinventory != null) {
- player.openMenu(itileinventory);
+ if (player.getCooldowns().isOnCooldown(stack)) {
+ cancelledItem = true; // Paper - correctly handle items on cooldown
+ }
+
+ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown
+ this.firedInteract = true;
+ this.interactResult = event.useItemInHand() == Event.Result.DENY;
+ this.interactPosition = blockposition.immutable();
+ this.interactHand = hand;
+ this.interactItemStack = stack.copy();
+
+ if (event.useInteractedBlock() == Event.Result.DENY) {
+ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
+ if (iblockdata.getBlock() instanceof DoorBlock) {
+ // Paper start - Don't resync blocks
+ // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER;
+ // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below()));
+ // Paper end - Don't resync blocks
+ } else if (iblockdata.getBlock() instanceof CakeBlock) {
+ player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake
+ } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) {
+ // send a correcting update to the client, as it already placed the upper half of the bisected item
+ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); // Paper - Don't resync blocks
+
+ // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc)
+ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks
+ // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method
+ } else if (iblockdata.is(Blocks.JIGSAW) || iblockdata.is(Blocks.STRUCTURE_BLOCK) || iblockdata.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) {
+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId));
+ }
+ // Paper end - extend Player Interact cancellation
+ player.getBukkitEntity().updateInventory(); // SPIGOT-2867
+ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items
+ return (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS;
+ } else if (this.gameModeForPlayer == GameType.SPECTATOR) {
+ MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition);
+
+ if (itileinventory != null && player.openMenu(itileinventory).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
return InteractionResult.CONSUME;
} else {
return InteractionResult.PASS;
@@ -359,7 +598,7 @@
}
}
- if (!stack.isEmpty() && !player.getCooldowns().isOnCooldown(stack)) {
+ if (!stack.isEmpty() && !this.interactResult) { // add !interactResult SPIGOT-764
UseOnContext itemactioncontext = new UseOnContext(player, hand, hitResult);
if (this.isCreative()) {
@@ -377,6 +616,11 @@
return enuminteractionresult;
} else {
+ // Paper start - Properly cancel usable items; Cancel only if cancelled + if the interact result is different from default response
+ if (this.interactResult && this.interactResult != cancelledItem) {
+ this.player.resyncUsingItem(this.player);
+ }
+ // Paper end - Properly cancel usable items
return InteractionResult.PASS;
}
}

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/server/level/TicketType.java
+++ b/net/minecraft/server/level/TicketType.java
@@ -7,6 +7,7 @@
import net.minecraft.world.level.ChunkPos;
public class TicketType<T> {
+ public static final TicketType<Long> FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper
private final String name;
private final Comparator<T> comparator;
@@ -22,6 +23,9 @@
public static final TicketType<BlockPos> PORTAL = TicketType.create("portal", Vec3i::compareTo, 300);
public static final TicketType<ChunkPos> ENDER_PEARL = TicketType.create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40);
public static final TicketType<ChunkPos> UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1);
+ public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
+ public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
+ public static final TicketType<Integer> POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type
public static <T> TicketType<T> create(String name, Comparator<T> argumentComparator) {
return new TicketType<>(name, argumentComparator, 0L);

View File

@@ -0,0 +1,105 @@
--- a/net/minecraft/server/level/WorldGenRegion.java
+++ b/net/minecraft/server/level/WorldGenRegion.java
@@ -167,7 +167,27 @@
int k = this.center.getPos().getChessboardDistance(chunkX, chunkZ);
return k < this.generatingStep.directDependencies().size();
+ }
+
+ // Paper start - if loaded util
+ @Nullable
+ @Override
+ public ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
+ return this.getChunk(x, z, ChunkStatus.FULL, false);
+ }
+
+ @Override
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+ return chunk == null ? null : chunk.getBlockState(blockposition);
+ }
+
+ @Override
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+ return chunk == null ? null : chunk.getFluidState(blockposition);
}
+ // Paper end
@Override
public BlockState getBlockState(BlockPos pos) {
@@ -217,7 +237,8 @@
if (iblockdata.isAir()) {
return false;
} else {
- if (drop) {
+ if (drop) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens
+ if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation
BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null;
Block.dropResources(iblockdata, this.level, pos, tileentity, breakingEntity, ItemStack.EMPTY);
@@ -264,6 +285,7 @@
}
}
+ private boolean hasSetFarWarned = false; // Paper - Buffer OOB setBlock calls
@Override
public boolean ensureCanWrite(BlockPos pos) {
int i = SectionPos.blockToSectionCoord(pos.getX());
@@ -283,7 +305,15 @@
return true;
} else {
+ // Paper start - Buffer OOB setBlock calls
+ if (!hasSetFarWarned) {
Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get()));
+ hasSetFarWarned = true;
+ if (this.getServer() != null && this.getServer().isDebugging()) {
+ io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call");
+ }
+ }
+ // Paper end - Buffer OOB setBlock calls
return false;
}
}
@@ -294,7 +324,7 @@
return false;
} else {
ChunkAccess ichunkaccess = this.getChunk(pos);
- BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false);
+ BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); final BlockState previousBlockState = iblockdata1; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper
if (iblockdata1 != null) {
this.level.onBlockStateChange(pos, iblockdata1, state);
@@ -310,6 +340,17 @@
ichunkaccess.removeBlockEntity(pos);
}
} else {
+ // Paper start - Clear block entity before setting up a DUMMY block entity
+ // The concept of removing a block entity when the block itself changes is generally lifted
+ // from LevelChunk#setBlockState.
+ // It is however to note that this may only run if the block actually changes.
+ // Otherwise a chest block entity generated by a structure template that is later "updated" to
+ // be waterlogged would remove its existing block entity (see PaperMC/Paper#10750)
+ // This logic is *also* found in LevelChunk#setBlockState.
+ if (previousBlockState != null && !java.util.Objects.equals(previousBlockState.getBlock(), state.getBlock())) {
+ ichunkaccess.removeBlockEntity(pos);
+ }
+ // Paper end - Clear block entity before setting up a DUMMY block entity
CompoundTag nbttagcompound = new CompoundTag();
nbttagcompound.putInt("x", pos.getX());
@@ -336,6 +377,13 @@
@Override
public boolean addFreshEntity(Entity entity) {
+ // CraftBukkit start
+ return this.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
+ @Override
+ public boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
+ // CraftBukkit end
int i = SectionPos.blockToSectionCoord(entity.getBlockX());
int j = SectionPos.blockToSectionCoord(entity.getBlockZ());

View File

@@ -0,0 +1,211 @@
--- a/net/minecraft/server/network/LegacyQueryHandler.java
+++ b/net/minecraft/server/network/LegacyQueryHandler.java
@@ -15,6 +15,7 @@
private static final Logger LOGGER = LogUtils.getLogger();
private final ServerInfo server;
+ private ByteBuf buf; // Paper
public LegacyQueryHandler(ServerInfo server) {
this.server = server;
@@ -23,6 +24,16 @@
public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) {
ByteBuf bytebuf = (ByteBuf) object;
+ // Paper start - Make legacy ping handler more reliable
+ if (this.buf != null) {
+ try {
+ readLegacy1_6(channelhandlercontext, bytebuf);
+ } finally {
+ bytebuf.release();
+ }
+ return;
+ }
+ // Paper end
bytebuf.markReaderIndex();
boolean flag = true;
@@ -34,11 +45,23 @@
SocketAddress socketaddress = channelhandlercontext.channel().remoteAddress();
int i = bytebuf.readableBytes();
- String s;
+ String s = null; // Paper
+ // org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketaddress, this.server.getMotd(), this.server.getPlayerCount(), this.server.getMaxPlayers()); // CraftBukkit // Paper
+ com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper
if (i == 0) {
- LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", socketaddress);
- s = LegacyQueryHandler.createVersion0Response(this.server);
+ LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketaddress: "<ip address withheld>"); // Paper - Respect logIPs option
+
+ // Paper start - Call PaperServerListPingEvent and use results
+ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketaddress, 39, null);
+ if (event == null) {
+ channelhandlercontext.close();
+ bytebuf.release();
+ flag = false;
+ return;
+ }
+ s = String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", com.destroystokyo.paper.network.PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers());
+ // Paper end
LegacyQueryHandler.sendFlushAndClose(channelhandlercontext, LegacyQueryHandler.createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
} else {
if (bytebuf.readUnsignedByte() != 1) {
@@ -46,16 +69,35 @@
}
if (bytebuf.isReadable()) {
- if (!LegacyQueryHandler.readCustomPayloadPacket(bytebuf)) {
- return;
+ // Paper start - Replace with improved version below
+ if (bytebuf.readUnsignedByte() != 250) {
+ s = this.readLegacy1_6(channelhandlercontext, bytebuf);
+ if (s == null) {
+ return;
+ }
}
-
- LegacyQueryHandler.LOGGER.debug("Ping: (1.6) from {}", socketaddress);
+ // if (!LegacyQueryHandler.readCustomPayloadPacket(bytebuf)) {
+ // return;
+ // }
+ //
+ // LegacyQueryHandler.LOGGER.debug("Ping: (1.6) from {}", socketaddress);
+ // Paper end
} else {
- LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketaddress);
+ LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketaddress: "<ip address withheld>"); // Paper - Respect logIPs option
}
- s = LegacyQueryHandler.createVersion1Response(this.server);
+ if (s == null) {
+ // Paper start - Call PaperServerListPingEvent and use results
+ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketaddress, 127, null); // Paper
+ if (event == null) {
+ channelhandlercontext.close();
+ bytebuf.release();
+ flag = false;
+ return;
+ }
+ s = String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", new Object[] { event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()}); // CraftBukkit
+ // Paper end
+ }
LegacyQueryHandler.sendFlushAndClose(channelhandlercontext, LegacyQueryHandler.createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
}
@@ -106,14 +148,110 @@
}
}
- private static String createVersion0Response(ServerInfo server) {
- return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", server.getMotd(), server.getPlayerCount(), server.getMaxPlayers());
+ // Paper start
+ private static String readLegacyString(ByteBuf buf) {
+ int size = buf.readShort() * Character.BYTES;
+ if (!buf.isReadable(size)) {
+ return null;
+ }
+
+ String result = buf.toString(buf.readerIndex(), size, java.nio.charset.StandardCharsets.UTF_16BE);
+ buf.skipBytes(size); // toString doesn't increase readerIndex automatically
+ return result;
}
- private static String createVersion1Response(ServerInfo server) {
- return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, server.getServerVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers());
+ private String readLegacy1_6(ChannelHandlerContext ctx, ByteBuf part) {
+ ByteBuf buf = this.buf;
+
+ if (buf == null) {
+ this.buf = buf = ctx.alloc().buffer();
+ buf.markReaderIndex();
+ } else {
+ buf.resetReaderIndex();
+ }
+
+ buf.writeBytes(part);
+
+ if (!buf.isReadable(Short.BYTES + Short.BYTES + Byte.BYTES + Short.BYTES + Integer.BYTES)) {
+ return null;
+ }
+
+ String s = readLegacyString(buf);
+ if (s == null) {
+ return null;
+ }
+
+ if (!s.equals("MC|PingHost")) {
+ removeHandler(ctx);
+ return null;
+ }
+
+ if (!buf.isReadable(Short.BYTES) || !buf.isReadable(buf.readShort())) {
+ return null;
+ }
+
+ net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer();
+ int protocolVersion = buf.readByte();
+ String host = readLegacyString(buf);
+ if (host == null) {
+ removeHandler(ctx);
+ return null;
+ }
+ int port = buf.readInt();
+
+ if (buf.isReadable()) {
+ removeHandler(ctx);
+ return null;
+ }
+
+ buf.release();
+ this.buf = null;
+
+ LOGGER.debug("Ping: (1.6) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? ctx.channel().remoteAddress(): "<ip address withheld>"); // Paper - Respect logIPs option
+
+ java.net.InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port);
+ com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(
+ server, (java.net.InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost);
+ if (event == null) {
+ ctx.close();
+ return null;
+ }
+
+ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(),
+ com.destroystokyo.paper.network.PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers());
+ return response;
}
+ private void removeHandler(ChannelHandlerContext ctx) {
+ ByteBuf buf = this.buf;
+ this.buf = null;
+
+ buf.resetReaderIndex();
+ ctx.pipeline().remove(this);
+ ctx.fireChannelRead(buf);
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) {
+ if (this.buf != null) {
+ this.buf.release();
+ this.buf = null;
+ }
+ }
+ // Paper end
+
+ // CraftBukkit start
+ private static String createVersion0Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) {
+ return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", event.getMotd(), event.getNumPlayers(), event.getMaxPlayers());
+ // CraftBukkit end
+ }
+
+ // CraftBukkit start
+ private static String createVersion1Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) {
+ return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, serverinfo.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers());
+ // CraftBukkit end
+ }
+
private static void sendFlushAndClose(ChannelHandlerContext context, ByteBuf buf) {
context.pipeline().firstContext().writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE);
}

View File

@@ -0,0 +1,26 @@
--- a/net/minecraft/server/network/PlayerChunkSender.java
+++ b/net/minecraft/server/network/PlayerChunkSender.java
@@ -44,6 +44,11 @@
public void dropChunk(ServerPlayer player, ChunkPos pos) {
if (!this.pendingChunks.remove(pos.toLong()) && player.isAlive()) {
player.connection.send(new ClientboundForgetLevelChunkPacket(pos));
+ // Paper start - PlayerChunkUnloadEvent
+ if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) {
+ new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(pos.longKey), player.getBukkitEntity()).callEvent();
+ }
+ // Paper end - PlayerChunkUnloadEvent
}
}
@@ -75,6 +80,11 @@
private static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) {
handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), null, null));
+ // Paper start - PlayerChunkLoadEvent
+ if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) {
+ new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), handler.getPlayer().getBukkitEntity()).callEvent();
+ }
+ // Paper end - PlayerChunkLoadEvent
ChunkPos chunkPos = chunk.getPos();
DebugPackets.sendPoiPacketsForChunk(world, chunkPos);
}

View File

@@ -0,0 +1,418 @@
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -4,11 +4,13 @@
import com.mojang.logging.LogUtils;
import java.util.Objects;
import javax.annotation.Nullable;
+import net.minecraft.ChatFormatting;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.Util;
import net.minecraft.network.Connection;
+import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.DisconnectionDetails;
import net.minecraft.network.PacketSendListener;
import net.minecraft.network.chat.Component;
@@ -22,39 +24,88 @@
import net.minecraft.network.protocol.common.ServerboundPongPacket;
import net.minecraft.network.protocol.common.ServerboundResourcePackPacket;
import net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket;
+import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
+import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ClientInformation;
+import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.VisibleForDebug;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.thread.BlockableEventLoop;
import org.slf4j.Logger;
-public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener {
+// CraftBukkit start
+import io.netty.buffer.ByteBuf;
+import java.util.concurrent.ExecutionException;
+import net.minecraft.network.protocol.common.custom.DiscardedPayload;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.craftbukkit.util.CraftChatMessage;
+import org.bukkit.craftbukkit.util.CraftLocation;
+import org.bukkit.craftbukkit.util.Waitable;
+import org.bukkit.event.player.PlayerKickEvent;
+import org.bukkit.event.player.PlayerResourcePackStatusEvent;
+public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener, CraftPlayer.TransferCookieConnection {
+
+ @Override
+ public boolean isTransferred() {
+ return this.transferred;
+ }
+
+ @Override
+ public ConnectionProtocol getProtocol() {
+ return this.protocol();
+ }
+
+ @Override
+ public void sendPacket(Packet<?> packet) {
+ this.send(packet);
+ }
+
+ @Override
+ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes
+ this.disconnect(reason, cause); // Paper - kick event causes
+ }
+ // CraftBukkit end
private static final Logger LOGGER = LogUtils.getLogger();
public static final int LATENCY_CHECK_INTERVAL = 15000;
private static final int CLOSED_LISTENER_TIMEOUT = 15000;
private static final Component TIMEOUT_DISCONNECTION_MESSAGE = Component.translatable("disconnect.timeout");
static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response");
protected final MinecraftServer server;
- protected final Connection connection;
+ public final Connection connection; // Paper
private final boolean transferred;
- private long keepAliveTime;
+ private long keepAliveTime = Util.getMillis(); // Paper
private boolean keepAlivePending;
private long keepAliveChallenge;
private long closedListenerTime;
private boolean closed = false;
private int latency;
private volatile boolean suspendFlushingOnServerThread = false;
+ public final java.util.Map<java.util.UUID, net.kyori.adventure.resource.ResourcePackCallback> packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks
+ private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit
+ protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
- public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie clientData) {
- this.server = server;
- this.connection = connection;
+ public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit
+ this.server = minecraftserver;
+ this.connection = networkmanager;
this.keepAliveTime = Util.getMillis();
- this.latency = clientData.latency();
- this.transferred = clientData.transferred();
+ this.latency = commonlistenercookie.latency();
+ this.transferred = commonlistenercookie.transferred();
+ // CraftBukkit start - add fields and methods
+ this.player = player;
+ this.player.transferCookieConnection = this;
+ this.cserver = minecraftserver.server;
}
+ protected final ServerPlayer player;
+ protected final org.bukkit.craftbukkit.CraftServer cserver;
+ public boolean processedDisconnect;
+ public CraftPlayer getCraftPlayer() {
+ return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity();
+ // CraftBukkit end
+ }
+
private void close() {
if (!this.closed) {
this.closedListenerTime = Util.getMillis();
@@ -65,6 +116,11 @@
@Override
public void onDisconnect(DisconnectionDetails info) {
+ // Paper start - Fix kick event leave message not being sent
+ this.onDisconnect(info, null);
+ }
+ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) {
+ // Paper end - Fix kick event leave message not being sent
if (this.isSingleplayerOwner()) {
ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out");
this.server.halt(false);
@@ -80,13 +136,14 @@
@Override
public void handleKeepAlive(ServerboundKeepAlivePacket packet) {
+ //PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // CraftBukkit // Paper - handle ServerboundKeepAlivePacket async
if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) {
int i = (int) (Util.getMillis() - this.keepAliveTime);
this.latency = (this.latency * 3 + i) / 4;
this.keepAlivePending = false;
} else if (!this.isSingleplayerOwner()) {
- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
+ this.disconnectAsync(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect
}
}
@@ -94,38 +151,127 @@
@Override
public void handlePong(ServerboundPongPacket packet) {}
+ // CraftBukkit start
+ private static final ResourceLocation CUSTOM_REGISTER = ResourceLocation.withDefaultNamespace("register");
+ private static final ResourceLocation CUSTOM_UNREGISTER = ResourceLocation.withDefaultNamespace("unregister");
+
@Override
- public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {}
+ public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
+ // Paper start - Brand support
+ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) {
+ this.player.clientBrandName = brandPayload.brand();
+ }
+ // Paper end - Brand support
+ if (!(packet.payload() instanceof DiscardedPayload)) {
+ return;
+ }
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+ ResourceLocation identifier = packet.payload().type().id();
+ ByteBuf payload = ((DiscardedPayload)packet.payload()).data();
+ if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_REGISTER)) {
+ try {
+ String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
+ for (String channel : channels.split("\0")) {
+ this.getCraftPlayer().addChannel(channel);
+ }
+ } catch (Exception ex) {
+ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex);
+ this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
+ }
+ } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) {
+ try {
+ String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
+ for (String channel : channels.split("\0")) {
+ this.getCraftPlayer().removeChannel(channel);
+ }
+ } catch (Exception ex) {
+ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex);
+ this.disconnect(Component.literal("Invalid payload UNREGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
+ }
+ } else {
+ try {
+ byte[] data = new byte[payload.readableBytes()];
+ payload.readBytes(data);
+ // Paper start - Brand support; Retain this incase upstream decides to 'break' the new mechanism in favour of backwards compat...
+ if (identifier.equals(MINECRAFT_BRAND)) {
+ try {
+ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256);
+ } catch (StringIndexOutOfBoundsException ex) {
+ this.player.clientBrandName = "illegal";
+ }
+ }
+ // Paper end - Brand support
+ this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data);
+ } catch (Exception ex) {
+ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex);
+ this.disconnect(Component.literal("Invalid custom payload!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
+ }
+ }
+
+ }
+
+ public final boolean isDisconnected() {
+ return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs
+ }
+ // CraftBukkit end
+
@Override
public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server);
if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) {
ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id());
- this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"));
+ this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause
}
+ // Paper start - adventure pack callbacks
+ // call the callbacks before the previously-existing event so the event has final say
+ final net.kyori.adventure.resource.ResourcePackCallback callback;
+ if (packet.action().isTerminal()) {
+ callback = this.packCallbacks.remove(packet.id());
+ } else {
+ callback = this.packCallbacks.get(packet.id());
+ }
+ if (callback != null) {
+ callback.packEventReceived(packet.id(), net.kyori.adventure.resource.ResourcePackStatus.valueOf(packet.action().name()), this.getCraftPlayer());
+ }
+ // Paper end
+ // Paper start - store last pack status
+ PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()];
+ player.getBukkitEntity().resourcePackStatus = packStatus;
+ this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit
+ // Paper end - store last pack status
}
@Override
public void handleCookieResponse(ServerboundCookieResponsePacket packet) {
- this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
+ // CraftBukkit start
+ PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server);
+ if (this.player.getBukkitEntity().handleCookieResponse(packet)) {
+ return;
+ }
+ // CraftBukkit end
+ this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause
}
protected void keepConnectionAlive() {
Profiler.get().push("keepAlive");
- long i = Util.getMillis();
+ // Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings
+ // This should effectively place the keepalive handling back to "as it was" before 1.12.2
+ long currentTime = Util.getMillis();
+ long elapsedTime = currentTime - this.keepAliveTime;
- if (!this.isSingleplayerOwner() && i - this.keepAliveTime >= 15000L) {
- if (this.keepAlivePending) {
- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
- } else if (this.checkIfClosed(i)) {
+ if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets
+ if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected
+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
+ } else if (this.checkIfClosed(currentTime)) { // Paper
this.keepAlivePending = true;
- this.keepAliveTime = i;
- this.keepAliveChallenge = i;
+ this.keepAliveTime = currentTime;
+ this.keepAliveChallenge = currentTime;
this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge));
}
}
+ // Paper end - give clients a longer time to respond to pings as per pre 1.12.2 timings
Profiler.get().pop();
}
@@ -133,7 +279,7 @@
private boolean checkIfClosed(long time) {
if (this.closed) {
if (time - this.closedListenerTime >= 15000L) {
- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
}
return false;
@@ -156,6 +302,14 @@
}
public void send(Packet<?> packet, @Nullable PacketSendListener callbacks) {
+ // CraftBukkit start
+ if (packet == null || this.processedDisconnect) { // Spigot
+ return;
+ } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket) {
+ ClientboundSetDefaultSpawnPositionPacket packet6 = (ClientboundSetDefaultSpawnPositionPacket) packet;
+ this.player.compassTarget = CraftLocation.toBukkit(packet6.pos, this.getCraftPlayer().getWorld());
+ }
+ // CraftBukkit end
if (packet.isTerminal()) {
this.close();
}
@@ -175,22 +329,109 @@
}
}
+ // Paper start - adventure
+ public void disconnect(final net.kyori.adventure.text.Component reason) {
+ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
+ }
+ public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
+ // Paper end - kick event causes
+ }
+ // Paper end - adventure
+
+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes
public void disconnect(Component reason) {
- this.disconnect(new DisconnectionDetails(reason));
+ // Paper start - kick event causes
+ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
}
+ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) {
+ this.disconnect(new DisconnectionDetails(reason), cause);
+ // Paper end - kick event causes
+ }
- public void disconnect(DisconnectionDetails disconnectionInfo) {
- this.connection.send(new ClientboundDisconnectPacket(disconnectionInfo.reason()), PacketSendListener.thenRun(() -> {
- this.connection.disconnect(disconnectionInfo);
+ public void disconnect(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) { // Paper - kick event cause
+ // CraftBukkit start - fire PlayerKickEvent
+ if (this.processedDisconnect) {
+ return;
+ }
+ if (!this.cserver.isPrimaryThread()) {
+ Waitable waitable = new Waitable() {
+ @Override
+ protected Object evaluate() {
+ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); // Paper - kick event causes
+ return null;
+ }
+ };
+
+ this.server.processQueue.add(waitable);
+
+ try {
+ waitable.get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ return;
+ }
+
+ net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure
+
+ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage, cause); // Paper - adventure & kick event causes
+
+ if (this.cserver.getServer().isRunning()) {
+ this.cserver.getPluginManager().callEvent(event);
+ }
+
+ if (event.isCancelled()) {
+ // Do not kick the player
+ return;
+ }
+ // Send the possibly modified leave message
+ this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message
+ }
+
+ private void disconnect0(DisconnectionDetails disconnectiondetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message
+ // CraftBukkit end
+ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason
+ this.connection.send(new ClientboundDisconnectPacket(disconnectiondetails.reason()), PacketSendListener.thenRun(() -> {
+ this.connection.disconnect(disconnectiondetails);
}));
+ this.onDisconnect(disconnectiondetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message
this.connection.setReadOnly();
MinecraftServer minecraftserver = this.server;
Connection networkmanager = this.connection;
Objects.requireNonNull(this.connection);
- minecraftserver.executeBlocking(networkmanager::handleDisconnection);
+ // CraftBukkit - Don't wait
+ minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper
}
+ // Paper start - add proper async disconnect
+ public void disconnectAsync(net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
+ this.disconnectAsync(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
+ }
+
+ public void disconnectAsync(Component reason, PlayerKickEvent.Cause cause) {
+ this.disconnectAsync(new DisconnectionDetails(reason), cause);
+ }
+
+ public void disconnectAsync(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) {
+ if (this.cserver.isPrimaryThread()) {
+ this.disconnect(disconnectionInfo, cause);
+ return;
+ }
+ this.connection.setReadOnly();
+ this.server.scheduleOnMain(() -> {
+ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause);
+ if (ServerCommonPacketListenerImpl.this.player.quitReason == null) {
+ // cancelled
+ ServerCommonPacketListenerImpl.this.connection.enableAutoRead();
+ }
+ });
+ }
+ // Paper end - add proper async disconnect
+
protected boolean isSingleplayerOwner() {
return this.server.isSingleplayerOwner(this.playerProfile());
}

View File

@@ -0,0 +1,89 @@
--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
@@ -38,6 +38,11 @@
import net.minecraft.world.flag.FeatureFlags;
import org.slf4j.Logger;
+// CraftBukkit start
+import org.bukkit.craftbukkit.CraftServerLinks;
+import org.bukkit.event.player.PlayerLinksSendEvent;
+// CraftBukkit end
+
public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerConfigurationPacketListener, TickablePacketListener {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -50,10 +55,12 @@
@Nullable
private SynchronizeRegistriesTask synchronizeRegistriesTask;
- public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie clientData) {
- super(server, connection, clientData);
- this.gameProfile = clientData.gameProfile();
- this.clientInformation = clientData.clientInformation();
+ // CraftBukkit start
+ public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) {
+ super(minecraftserver, networkmanager, commonlistenercookie, player);
+ // CraftBukkit end
+ this.gameProfile = commonlistenercookie.gameProfile();
+ this.clientInformation = commonlistenercookie.clientInformation();
}
@Override
@@ -63,6 +70,10 @@
@Override
public void onDisconnect(DisconnectionDetails info) {
+ // Paper start - Debugging
+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) {
+ ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}, while in configuration phase {}", this.gameProfile, info.reason().getString(), currentTask != null ? currentTask.type().id() : "null");
+ } else // Paper end
ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}", this.gameProfile, info.reason().getString());
super.onDisconnect(info);
}
@@ -75,6 +86,12 @@
public void startConfiguration() {
this.send(new ClientboundCustomPayloadPacket(new BrandPayload(this.server.getServerModName())));
ServerLinks serverlinks = this.server.serverLinks();
+ // CraftBukkit start
+ CraftServerLinks wrapper = new CraftServerLinks(serverlinks);
+ PlayerLinksSendEvent event = new PlayerLinksSendEvent(this.player.getBukkitEntity(), wrapper);
+ this.player.getBukkitEntity().getServer().getPluginManager().callEvent(event);
+ serverlinks = wrapper.getServerLinks();
+ // CraftBukkit end
if (!serverlinks.isEmpty()) {
this.send(new ClientboundServerLinksPacket(serverlinks.untrust()));
@@ -107,6 +124,7 @@
@Override
public void handleClientInformation(ServerboundClientInformationPacket packet) {
this.clientInformation = packet.information();
+ this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper
}
@Override
@@ -143,18 +161,23 @@
return;
}
- Component ichatbasecomponent = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile);
+ Component ichatbasecomponent = null; // CraftBukkit - login checks already completed
if (ichatbasecomponent != null) {
this.disconnect(ichatbasecomponent);
return;
}
- ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation);
+ ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit
playerlist.placeNewPlayer(this.connection, entityplayer, this.createCookie(this.clientInformation));
} catch (Exception exception) {
ServerConfigurationPacketListenerImpl.LOGGER.error("Couldn't place player in world", exception);
+ // Paper start - Debugging
+ if (MinecraftServer.getServer().isDebugging()) {
+ exception.printStackTrace();
+ }
+ // Paper end - Debugging
this.connection.send(new ClientboundDisconnectPacket(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA));
this.connection.disconnect(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA);
}

View File

@@ -0,0 +1,156 @@
--- a/net/minecraft/server/network/ServerConnectionListener.java
+++ b/net/minecraft/server/network/ServerConnectionListener.java
@@ -52,22 +52,36 @@
private static final Logger LOGGER = LogUtils.getLogger();
public static final Supplier<NioEventLoopGroup> SERVER_EVENT_GROUP = Suppliers.memoize(() -> {
- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build());
+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
public static final Supplier<EpollEventLoopGroup> SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> {
- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build());
+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
final MinecraftServer server;
public volatile boolean running;
private final List<ChannelFuture> channels = Collections.synchronizedList(Lists.newArrayList());
final List<Connection> connections = Collections.synchronizedList(Lists.newArrayList());
+ // Paper start - prevent blocking on adding a new connection while the server is ticking
+ private final java.util.Queue<Connection> pending = new java.util.concurrent.ConcurrentLinkedQueue<>();
+ private final void addPending() {
+ Connection connection;
+ while ((connection = pending.poll()) != null) {
+ connections.add(connection);
+ }
+ }
+ // Paper end - prevent blocking on adding a new connection while the server is ticking
public ServerConnectionListener(MinecraftServer server) {
this.server = server;
this.running = true;
}
+ // Paper start - Unix domain socket support
public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException {
+ bind(new java.net.InetSocketAddress(address, port));
+ }
+ public void bind(java.net.SocketAddress address) throws IOException {
+ // Paper end - Unix domain socket support
List list = this.channels;
synchronized (this.channels) {
@@ -75,7 +89,13 @@
EventLoopGroup eventloopgroup;
if (Epoll.isAvailable() && this.server.isEpollEnabled()) {
+ // Paper start - Unix domain socket support
+ if (address instanceof io.netty.channel.unix.DomainSocketAddress) {
+ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class;
+ } else {
oclass = EpollServerSocketChannel.class;
+ }
+ // Paper end - Unix domain socket support
eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get();
ServerConnectionListener.LOGGER.info("Using epoll channel type");
} else {
@@ -84,6 +104,12 @@
ServerConnectionListener.LOGGER.info("Using default channel type");
}
+ // Paper start - Warn people with console access that HAProxy is in use.
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
+ ServerConnectionListener.LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled.");
+ }
+ // Paper end - Warn people with console access that HAProxy is in use.
+
this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
protected void initChannel(Channel channel) {
try {
@@ -100,16 +126,58 @@
Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, false, (BandwidthDebugMonitor) null);
int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
- Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND);
+ Connection object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); // CraftBukkit - decompile error
- ServerConnectionListener.this.connections.add(object);
+ //ServerConnectionListener.this.connections.add(object); // Paper
+ // Paper start - Add support for Proxy Protocol
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
+ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder());
+ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() {
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) {
+ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) {
+ String realaddress = message.sourceAddress();
+ int realport = message.sourcePort();
+
+ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport);
+
+ Connection connection = (Connection) channel.pipeline().get("packet_handler");
+ connection.address = socketaddr;
+
+ // Paper start - Add API to get player's proxy address
+ final String proxyAddress = message.destinationAddress();
+ final int proxyPort = message.destinationPort();
+
+ connection.haProxyAddress = new java.net.InetSocketAddress(proxyAddress, proxyPort);
+ // Paper end - Add API to get player's proxy address
+ }
+ } else {
+ super.channelRead(ctx, msg);
+ }
+ }
+ });
+ }
+ // Paper end - Add support for proxy protocol
+ pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking
((Connection) object).configurePacketHandler(channelpipeline);
((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object));
+ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners
}
- }).group(eventloopgroup).localAddress(address, port)).bind().syncUninterruptibly());
+ }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support
}
}
+ // CraftBukkit start
+ public void acceptConnections() {
+ synchronized (this.channels) {
+ for (ChannelFuture future : this.channels) {
+ future.channel().config().setAutoRead(true);
+ }
+ }
+ }
+ // CraftBukkit end
+
public SocketAddress startMemoryChannel() {
List list = this.channels;
ChannelFuture channelfuture;
@@ -153,6 +221,14 @@
List list = this.connections;
synchronized (this.connections) {
+ // Spigot Start
+ this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking
+ // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order
+ if ( org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0 )
+ {
+ Collections.shuffle( this.connections );
+ }
+ // Spigot End
Iterator<Connection> iterator = this.connections.iterator();
while (iterator.hasNext()) {
@@ -176,6 +252,10 @@
networkmanager.setReadOnly();
}
} else {
+ // Spigot Start
+ // Fix a race condition where a NetworkManager could be unregistered just before connection.
+ if (networkmanager.preparing) continue;
+ // Spigot End
iterator.remove();
networkmanager.handleDisconnection();
}

View File

@@ -0,0 +1,169 @@
--- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
@@ -13,11 +13,26 @@
import net.minecraft.network.protocol.status.StatusProtocols;
import net.minecraft.server.MinecraftServer;
+// CraftBukkit start
+import java.net.InetAddress;
+import java.util.HashMap;
+// CraftBukkit end
+
public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketListener {
+ // Spigot start
+ private static final com.google.gson.Gson gson = new com.google.gson.Gson();
+ static final java.util.regex.Pattern HOST_PATTERN = java.util.regex.Pattern.compile("[0-9a-f\\.:]{0,45}");
+ static final java.util.regex.Pattern PROP_PATTERN = java.util.regex.Pattern.compile("\\w{0,16}");
+ // Spigot end
+ // CraftBukkit start - add fields
+ private static final HashMap<InetAddress, Long> throttleTracker = new HashMap<InetAddress, Long>();
+ private static int throttleCounter = 0;
+ // CraftBukkit end
private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request");
private final MinecraftServer server;
private final Connection connection;
+ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper
public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) {
this.server = server;
@@ -26,6 +41,7 @@
@Override
public void handleIntention(ClientIntentionPacket packet) {
+ this.connection.hostname = packet.hostName() + ":" + packet.port(); // CraftBukkit - set hostname
switch (packet.intention()) {
case LOGIN:
this.beginLogin(packet, false);
@@ -55,23 +71,127 @@
throw new UnsupportedOperationException("Invalid intention " + String.valueOf(packet.intention()));
}
+ // Paper start - NetworkClient implementation
+ this.connection.protocolVersion = packet.protocolVersion();
+ this.connection.virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(packet.hostName(), packet.port());
+ // Paper end
}
private void beginLogin(ClientIntentionPacket packet, boolean transfer) {
this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND);
+ // CraftBukkit start - Connection throttle
+ try {
+ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket
+ long currentTime = System.currentTimeMillis();
+ long connectionThrottle = this.server.server.getConnectionThrottle();
+ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
+
+ synchronized (ServerHandshakePacketListenerImpl.throttleTracker) {
+ if (ServerHandshakePacketListenerImpl.throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - ServerHandshakePacketListenerImpl.throttleTracker.get(address) < connectionThrottle) {
+ ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime);
+ Component chatmessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.connectionThrottle); // Paper - Configurable connection throttle kick message
+ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
+ this.connection.disconnect(chatmessage);
+ return;
+ }
+
+ ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime);
+ ServerHandshakePacketListenerImpl.throttleCounter++;
+ if (ServerHandshakePacketListenerImpl.throttleCounter > 200) {
+ ServerHandshakePacketListenerImpl.throttleCounter = 0;
+
+ // Cleanup stale entries
+ java.util.Iterator iter = ServerHandshakePacketListenerImpl.throttleTracker.entrySet().iterator();
+ while (iter.hasNext()) {
+ java.util.Map.Entry<InetAddress, Long> entry = (java.util.Map.Entry) iter.next();
+ if (entry.getValue() > connectionThrottle) {
+ iter.remove();
+ }
+ }
+ }
+ }
+ } // Paper - Unix domain socket support
+ } catch (Throwable t) {
+ org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t);
+ }
+ // CraftBukkit end
if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) {
- MutableComponent ichatmutablecomponent;
+ net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages
- if (packet.protocolVersion() < 754) {
- ichatmutablecomponent = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName());
+ if (packet.protocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Spigot - SPIGOT-7546: Handle version check correctly for outdated client message
+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages
} else {
- ichatmutablecomponent = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName());
+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages
}
+ Component ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages
+
this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent));
this.connection.disconnect((Component) ichatmutablecomponent);
} else {
this.connection.setupInboundProtocol(LoginProtocols.SERVERBOUND, new ServerLoginPacketListenerImpl(this.server, this.connection, transfer));
+ // Paper start - PlayerHandshakeEvent
+ boolean proxyLogicEnabled = org.spigotmc.SpigotConfig.bungee;
+ boolean handledByEvent = false;
+ // Try and handle the handshake through the event
+ if (com.destroystokyo.paper.event.player.PlayerHandshakeEvent.getHandlerList().getRegisteredListeners().length != 0) { // Hello? Can you hear me?
+ java.net.SocketAddress socketAddress = this.connection.address;
+ String hostnameOfRemote = socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getHostString() : InetAddress.getLoopbackAddress().getHostAddress();
+ com.destroystokyo.paper.event.player.PlayerHandshakeEvent event = new com.destroystokyo.paper.event.player.PlayerHandshakeEvent(packet.hostName(), hostnameOfRemote, !proxyLogicEnabled);
+ if (event.callEvent()) {
+ // If we've failed somehow, let the client know so and go no further.
+ if (event.isFailed()) {
+ Component component = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.failMessage());
+ this.connection.send(new ClientboundLoginDisconnectPacket(component));
+ this.connection.disconnect(component);
+ return;
+ }
+
+ if (event.getServerHostname() != null) {
+ // change hostname
+ packet = new ClientIntentionPacket(
+ packet.protocolVersion(),
+ event.getServerHostname(),
+ packet.port(),
+ packet.intention()
+ );
+ }
+ if (event.getSocketAddressHostname() != null) this.connection.address = new java.net.InetSocketAddress(event.getSocketAddressHostname(), socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0);
+ this.connection.spoofedUUID = event.getUniqueId();
+ this.connection.spoofedProfile = gson.fromJson(event.getPropertiesJson(), com.mojang.authlib.properties.Property[].class);
+ handledByEvent = true; // Hooray, we did it!
+ }
+ }
+ // Paper end
+ // Spigot Start
+ String[] split = packet.hostName().split("\00");
+ if (!handledByEvent && proxyLogicEnabled) { // Paper
+ // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above!
+ if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check
+ // Paper start - Unix domain socket support
+ java.net.SocketAddress socketAddress = this.connection.getRemoteAddress();
+ this.connection.hostname = split[0];
+ this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0);
+ // Paper end - Unix domain socket support
+ this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] );
+ } else
+ {
+ Component chatmessage = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!");
+ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
+ this.connection.disconnect(chatmessage);
+ return;
+ }
+ if ( split.length == 4 )
+ {
+ this.connection.spoofedProfile = ServerHandshakePacketListenerImpl.gson.fromJson(split[3], com.mojang.authlib.properties.Property[].class);
+ }
+ } else if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) {
+ Component chatmessage = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?");
+ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
+ this.connection.disconnect(chatmessage);
+ return;
+ }
+ // Spigot End
}
}

View File

@@ -0,0 +1,424 @@
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -20,6 +20,7 @@
import net.minecraft.DefaultUncaughtExceptionHandler;
import net.minecraft.core.UUIDUtil;
import net.minecraft.network.Connection;
+import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.DisconnectionDetails;
import net.minecraft.network.PacketSendListener;
import net.minecraft.network.TickablePacketListener;
@@ -36,6 +37,7 @@
import net.minecraft.network.protocol.login.ServerboundKeyPacket;
import net.minecraft.network.protocol.login.ServerboundLoginAcknowledgedPacket;
import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.PlayerList;
import net.minecraft.util.Crypt;
import net.minecraft.util.CryptException;
@@ -43,11 +45,38 @@
import net.minecraft.util.StringUtil;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.PacketUtils;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.craftbukkit.util.Waitable;
+import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
+import org.bukkit.event.player.PlayerPreLoginEvent;
-public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener {
+public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener, CraftPlayer.TransferCookieConnection {
+ @Override
+ public boolean isTransferred() {
+ return this.transferred;
+ }
+
+ @Override
+ public ConnectionProtocol getProtocol() {
+ return ConnectionProtocol.LOGIN;
+ }
+
+ @Override
+ public void sendPacket(Packet<?> packet) {
+ this.connection.send(packet);
+ }
+
+ @Override
+ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes - during login, no event can be called.
+ this.disconnect(reason);
+ }
+ // CraftBukkit end
private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
static final Logger LOGGER = LogUtils.getLogger();
+ private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads
private static final int MAX_TICKS_BEFORE_LOGIN = 600;
private final byte[] challenge;
final MinecraftServer server;
@@ -57,9 +86,12 @@
@Nullable
String requestedUsername;
@Nullable
- private GameProfile authenticatedProfile;
+ public GameProfile authenticatedProfile; // Paper - public
private final String serverId;
private final boolean transferred;
+ private ServerPlayer player; // CraftBukkit
+ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding
+ private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support
public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) {
this.state = ServerLoginPacketListenerImpl.State.HELLO;
@@ -72,10 +104,24 @@
@Override
public void tick() {
+ // Paper start - Do not allow logins while the server is shutting down
+ if (!MinecraftServer.getServer().isRunning()) {
+ this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(org.spigotmc.SpigotConfig.restartMessage)[0]);
+ return;
+ }
+ // Paper end - Do not allow logins while the server is shutting down
if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) {
+ if (this.connection.isConnected()) { // Paper - prevent logins to be processed even though disconnect was called
this.verifyLoginAndFinishConnectionSetup((GameProfile) Objects.requireNonNull(this.authenticatedProfile));
+ } // Paper - prevent logins to be processed even though disconnect was called
}
+ // CraftBukkit start
+ if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES && !this.player.getBukkitEntity().isAwaitingCookies()) {
+ this.postCookies(this.authenticatedProfile);
+ }
+ // CraftBukkit end
+
if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT && !this.isPlayerAlreadyInWorld((GameProfile) Objects.requireNonNull(this.authenticatedProfile))) {
this.finishLoginAndWaitForClient(this.authenticatedProfile);
}
@@ -86,6 +132,13 @@
}
+ // CraftBukkit start
+ @Deprecated
+ public void disconnect(String s) {
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s))); // Paper - Fix hex colors not working in some kick messages
+ }
+ // CraftBukkit end
+
@Override
public boolean isAcceptingMessages() {
return this.connection.isConnected();
@@ -120,7 +173,13 @@
@Override
public void handleHello(ServerboundHelloPacket packet) {
Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]);
- Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username", new Object[0]);
+ // Paper start - Validate usernames
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()
+ && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation
+ && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) {
+ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", new Object[0]);
+ }
+ // Paper end - Validate usernames
this.requestedUsername = packet.name();
GameProfile gameprofile = this.server.getSingleplayerProfile();
@@ -131,7 +190,36 @@
this.state = ServerLoginPacketListenerImpl.State.KEY;
this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge, true));
} else {
- this.startClientVerification(UUIDUtil.createOfflineProfile(this.requestedUsername));
+ // Paper start - Add Velocity IP Forwarding Support
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) {
+ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt();
+ net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer());
+ buf.writeByte(com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION);
+ net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 = new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(this.velocityLoginMessageId, new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket.PlayerInfoChannelPayload(com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, buf));
+ this.connection.send(packet1);
+ return;
+ }
+ // Paper end - Add Velocity IP Forwarding Support
+ // CraftBukkit start
+ // Paper start - Cache authenticator threads
+ authenticatorPool.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot
+
+ gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent
+ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
+ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile);
+ } catch (Exception ex) {
+ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!");
+ ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + ServerLoginPacketListenerImpl.this.requestedUsername, ex);
+ }
+ }
+ });
+ // Paper end - Cache authenticator threads
+ // CraftBukkit end
}
}
@@ -144,10 +232,24 @@
private void verifyLoginAndFinishConnectionSetup(GameProfile profile) {
PlayerList playerlist = this.server.getPlayerList();
- Component ichatbasecomponent = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), profile);
+ // CraftBukkit start - fire PlayerLoginEvent
+ this.player = playerlist.canPlayerLogin(this, profile); // CraftBukkit
- if (ichatbasecomponent != null) {
- this.disconnect(ichatbasecomponent);
+ if (this.player != null) {
+ if (this.player.getBukkitEntity().isAwaitingCookies()) {
+ this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES;
+ } else {
+ this.postCookies(profile);
+ }
+ }
+ }
+
+ private void postCookies(GameProfile gameprofile) {
+ PlayerList playerlist = this.server.getPlayerList();
+
+ if (this.player == null) {
+ // this.disconnect(ichatbasecomponent);
+ // CraftBukkit end
} else {
if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) {
this.connection.send(new ClientboundLoginCompressionPacket(this.server.getCompressionThreshold()), PacketSendListener.thenRun(() -> {
@@ -155,12 +257,12 @@
}));
}
- boolean flag = playerlist.disconnectAllPlayersWithProfile(profile);
+ boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile, this.player); // CraftBukkit - add player reference
if (flag) {
this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT;
} else {
- this.finishLoginAndWaitForClient(profile);
+ this.finishLoginAndWaitForClient(gameprofile);
}
}
@@ -195,7 +297,8 @@
throw new IllegalStateException("Protocol error", cryptographyexception);
}
- Thread thread = new Thread("User Authenticator #" + ServerLoginPacketListenerImpl.UNIQUE_THREAD_ID.incrementAndGet()) {
+ // Paper start - Cache authenticator threads
+ authenticatorPool.execute(new Runnable() {
public void run() {
String s1 = (String) Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized");
@@ -205,11 +308,17 @@
if (profileresult != null) {
GameProfile gameprofile = profileresult.profile();
+ // CraftBukkit start - fire PlayerPreLoginEvent
+ if (!ServerLoginPacketListenerImpl.this.connection.isConnected()) {
+ return;
+ }
+ gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent
+ // CraftBukkit end
ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile);
} else if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) {
ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!");
- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(s1));
+ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot
} else {
ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", s1);
@@ -217,11 +326,16 @@
} catch (AuthenticationUnavailableException authenticationunavailableexception) {
if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) {
ServerLoginPacketListenerImpl.LOGGER.warn("Authentication servers are down but will let them in anyway!");
- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(s1));
+ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot
} else {
- ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
+ ServerLoginPacketListenerImpl.this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.authenticationServersDown)); // Paper - Configurable kick message
ServerLoginPacketListenerImpl.LOGGER.error("Couldn't verify username because servers are unavailable");
}
+ // CraftBukkit start - catch all exceptions
+ } catch (Exception exception) {
+ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!");
+ ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + s1, exception);
+ // CraftBukkit end
}
}
@@ -232,23 +346,118 @@
return ServerLoginPacketListenerImpl.this.server.getPreventProxyConnections() && socketaddress instanceof InetSocketAddress ? ((InetSocketAddress) socketaddress).getAddress() : null;
}
- };
+ });
+ // Paper end - Cache authenticator threads
+ }
- thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(ServerLoginPacketListenerImpl.LOGGER));
- thread.start();
+ // CraftBukkit start
+ private GameProfile callPlayerPreLoginEvents(GameProfile gameprofile) throws Exception { // Paper - Add more fields to AsyncPlayerPreLoginEvent
+ // Paper start - Add Velocity IP Forwarding Support
+ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) {
+ disconnect("This server requires you to connect with Velocity.");
+ return gameprofile;
+ }
+ // Paper end - Add Velocity IP Forwarding Support
+ String playerName = gameprofile.getName();
+ java.net.InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
+ java.util.UUID uniqueId = gameprofile.getId();
+ final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server;
+
+ // Paper start - Add more fields to AsyncPlayerPreLoginEvent
+ final InetAddress rawAddress = ((InetSocketAddress) this.connection.channel.remoteAddress()).getAddress();
+ com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile); // Paper - setPlayerProfileAPI
+ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, this.transferred, profile, this.connection.hostname);
+ server.getPluginManager().callEvent(asyncEvent);
+ profile = asyncEvent.getPlayerProfile();
+ profile.complete(true); // Paper - setPlayerProfileAPI
+ gameprofile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile);
+ playerName = gameprofile.getName();
+ uniqueId = gameprofile.getId();
+ // Paper end - Add more fields to AsyncPlayerPreLoginEvent
+
+ if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) {
+ final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId);
+ if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) {
+ event.disallow(asyncEvent.getResult(), asyncEvent.kickMessage()); // Paper - Adventure
+ }
+ Waitable<PlayerPreLoginEvent.Result> waitable = new Waitable<PlayerPreLoginEvent.Result>() {
+ @Override
+ protected PlayerPreLoginEvent.Result evaluate() {
+ server.getPluginManager().callEvent(event);
+ return event.getResult();
+ }
+ };
+
+ ServerLoginPacketListenerImpl.this.server.processQueue.add(waitable);
+ if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) {
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure
+ }
+ } else {
+ if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(asyncEvent.kickMessage())); // Paper - Adventure
+ }
+ }
+ return gameprofile; // Paper - Add more fields to AsyncPlayerPreLoginEvent
}
+ // CraftBukkit end
@Override
public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) {
+ // Paper start - Add Velocity IP Forwarding Support
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) {
+ ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload();
+ if (payload == null) {
+ this.disconnect("This server requires you to connect with Velocity.");
+ return;
+ }
+
+ net.minecraft.network.FriendlyByteBuf buf = payload.buffer;
+
+ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) {
+ this.disconnect("Unable to verify player details");
+ return;
+ }
+
+ int version = buf.readVarInt();
+ if (version > com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION) {
+ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION);
+ }
+
+ java.net.SocketAddress listening = this.connection.getRemoteAddress();
+ int port = 0;
+ if (listening instanceof java.net.InetSocketAddress) {
+ port = ((java.net.InetSocketAddress) listening).getPort();
+ }
+ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port);
+
+ this.authenticatedProfile = com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf);
+
+ //TODO Update handling for lazy sessions, might not even have to do anything?
+
+ // Proceed with login
+ authenticatorPool.execute(() -> {
+ try {
+ final GameProfile gameprofile = this.callPlayerPreLoginEvents(this.authenticatedProfile);
+ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
+ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile);
+ } catch (Exception ex) {
+ disconnect("Failed to verify username!");
+ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + this.authenticatedProfile.getName(), ex);
+ }
+ });
+ return;
+ }
+ // Paper end - Add Velocity IP Forwarding Support
this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
}
@Override
public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.server); // CraftBukkit
Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet", new Object[0]);
this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
CommonListenerCookie commonlistenercookie = CommonListenerCookie.createInitial((GameProfile) Objects.requireNonNull(this.authenticatedProfile), this.transferred);
- ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie);
+ ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie, this.player); // CraftBukkit
this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverconfigurationpacketlistenerimpl);
serverconfigurationpacketlistenerimpl.startConfiguration();
@@ -264,12 +473,44 @@
@Override
public void handleCookieResponse(ServerboundCookieResponsePacket packet) {
+ // CraftBukkit start
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.server);
+ if (this.player != null && this.player.getBukkitEntity().handleCookieResponse(packet)) {
+ return;
+ }
+ // CraftBukkit end
this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
}
+ // Spigot start
+ protected GameProfile createOfflineProfile(String s) {
+ java.util.UUID uuid;
+ if ( this.connection.spoofedUUID != null )
+ {
+ uuid = this.connection.spoofedUUID;
+ } else
+ {
+ uuid = UUIDUtil.createOfflinePlayerUUID( s );
+ }
+
+ GameProfile gameProfile = new GameProfile( uuid, s );
+
+ if (this.connection.spoofedProfile != null)
+ {
+ for ( com.mojang.authlib.properties.Property property : this.connection.spoofedProfile )
+ {
+ if ( !ServerHandshakePacketListenerImpl.PROP_PATTERN.matcher( property.name()).matches() ) continue;
+ gameProfile.getProperties().put( property.name(), property );
+ }
+ }
+
+ return gameProfile;
+ }
+ // Spigot end
+
public static enum State {
- HELLO, KEY, AUTHENTICATING, NEGOTIATING, VERIFYING, WAITING_FOR_DUPE_DISCONNECT, PROTOCOL_SWITCHING, ACCEPTED;
+ HELLO, KEY, AUTHENTICATING, NEGOTIATING, VERIFYING, WAITING_FOR_COOKIES, WAITING_FOR_DUPE_DISCONNECT, PROTOCOL_SWITCHING, ACCEPTED; // CraftBukkit
private State() {}
}

View File

@@ -0,0 +1,137 @@
--- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
@@ -9,6 +9,19 @@
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.network.protocol.status.ServerStatusPacketListener;
import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket;
+// CraftBukkit start
+import com.mojang.authlib.GameProfile;
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Optional;
+import net.minecraft.SharedConstants;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerPlayer;
+import org.bukkit.craftbukkit.util.CraftChatMessage;
+import org.bukkit.craftbukkit.util.CraftIconCache;
+import org.bukkit.entity.Player;
+// CraftBukkit end
public class ServerStatusPacketListenerImpl implements ServerStatusPacketListener {
@@ -36,7 +49,113 @@
this.connection.disconnect(ServerStatusPacketListenerImpl.DISCONNECT_REASON);
} else {
this.hasRequestedStatus = true;
- this.connection.send(new ClientboundStatusResponsePacket(this.status));
+ // Paper start - Replace everything
+ /*
+ // CraftBukkit start
+ // this.connection.send(new PacketStatusOutServerInfo(this.status));
+ MinecraftServer server = MinecraftServer.getServer();
+ final Object[] players = server.getPlayerList().players.toArray();
+ class ServerListPingEvent extends org.bukkit.event.server.ServerListPingEvent {
+
+ CraftIconCache icon = server.server.getServerIcon();
+
+ ServerListPingEvent() {
+ super(ServerStatusPacketListenerImpl.this.connection.hostname, ((InetSocketAddress) ServerStatusPacketListenerImpl.this.connection.getRemoteAddress()).getAddress(), server.server.motd(), server.getPlayerList().getMaxPlayers()); // Paper - Adventure
+ }
+
+ @Override
+ public void setServerIcon(org.bukkit.util.CachedServerIcon icon) {
+ if (!(icon instanceof CraftIconCache)) {
+ throw new IllegalArgumentException(icon + " was not created by " + org.bukkit.craftbukkit.CraftServer.class);
+ }
+ this.icon = (CraftIconCache) icon;
+ }
+
+ @Override
+ public Iterator<Player> iterator() throws UnsupportedOperationException {
+ return new Iterator<Player>() {
+ int i;
+ int ret = Integer.MIN_VALUE;
+ ServerPlayer player;
+
+ @Override
+ public boolean hasNext() {
+ if (this.player != null) {
+ return true;
+ }
+ final Object[] currentPlayers = players;
+ for (int length = currentPlayers.length, i = this.i; i < length; i++) {
+ final ServerPlayer player = (ServerPlayer) currentPlayers[i];
+ if (player != null) {
+ this.i = i + 1;
+ this.player = player;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Player next() {
+ if (!this.hasNext()) {
+ throw new java.util.NoSuchElementException();
+ }
+ final ServerPlayer player = this.player;
+ this.player = null;
+ this.ret = this.i - 1;
+ return player.getBukkitEntity();
+ }
+
+ @Override
+ public void remove() {
+ final Object[] currentPlayers = players;
+ final int i = this.ret;
+ if (i < 0 || currentPlayers[i] == null) {
+ throw new IllegalStateException();
+ }
+ currentPlayers[i] = null;
+ }
+ };
+ }
+ }
+
+ ServerListPingEvent event = new ServerListPingEvent();
+ server.server.getPluginManager().callEvent(event);
+
+ java.util.List<GameProfile> profiles = new java.util.ArrayList<GameProfile>(players.length);
+ for (Object player : players) {
+ if (player != null) {
+ ServerPlayer entityPlayer = ((ServerPlayer) player);
+ if (entityPlayer.allowsListing()) {
+ profiles.add(entityPlayer.getGameProfile());
+ } else {
+ profiles.add(MinecraftServer.ANONYMOUS_PLAYER_PROFILE);
+ }
+ }
+ }
+
+ // Spigot Start
+ if ( !server.hidesOnlinePlayers() && !profiles.isEmpty() )
+ {
+ java.util.Collections.shuffle( profiles ); // This sucks, its inefficient but we have no simple way of doing it differently
+ profiles = profiles.subList( 0, Math.min( profiles.size(), org.spigotmc.SpigotConfig.playerSample ) ); // Cap the sample to n (or less) displayed players, ie: Vanilla behaviour
+ }
+ // Spigot End
+ ServerStatus.Players playerSample = new ServerStatus.Players(event.getMaxPlayers(), event.getNumPlayers(), (server.hidesOnlinePlayers()) ? Collections.emptyList() : profiles);
+
+ ServerStatus ping = new ServerStatus(
+ CraftChatMessage.fromString(event.getMotd(), true)[0],
+ Optional.of(playerSample),
+ Optional.of(new ServerStatus.Version(server.getServerModName() + " " + server.getServerVersion(), SharedConstants.getCurrentVersion().getProtocolVersion())),
+ (event.icon.value != null) ? Optional.of(new ServerStatus.Favicon(event.icon.value)) : Optional.empty(),
+ server.enforceSecureProfile()
+ );
+
+ this.connection.send(new ClientboundStatusResponsePacket(ping));
+ // CraftBukkit end
+ */
+ com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(MinecraftServer.getServer(), this.connection);
+ // Paper end
}
}

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/server/packs/PathPackResources.java
+++ b/net/minecraft/server/packs/PathPackResources.java
@@ -103,6 +103,12 @@
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path)) {
for (Path path2 : directoryStream) {
String string = path2.getFileName().toString();
+ // Paper start - Improve logging and errors
+ if (!Files.isDirectory(path2)) {
+ LOGGER.error("Invalid directory entry: {} in {}.", string, this.root, new java.nio.file.NotDirectoryException(string));
+ continue;
+ }
+ // Paper end - Improve logging and errors
if (ResourceLocation.isValidNamespace(string)) {
set.add(string);
} else {

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/server/packs/VanillaPackResourcesBuilder.java
+++ b/net/minecraft/server/packs/VanillaPackResourcesBuilder.java
@@ -138,6 +138,15 @@
public VanillaPackResourcesBuilder applyDevelopmentConfig() {
developmentConfig.accept(this);
+ if (Boolean.getBoolean("Paper.pushPaperAssetsRoot")) {
+ try {
+ this.pushAssetPath(net.minecraft.server.packs.PackType.SERVER_DATA, net.minecraft.server.packs.VanillaPackResourcesBuilder.safeGetPath(java.util.Objects.requireNonNull(
+ // Important that this is a patched class
+ VanillaPackResourcesBuilder.class.getResource("/data/.paperassetsroot"), "Missing required .paperassetsroot file").toURI()).getParent());
+ } catch (java.net.URISyntaxException | IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
return this;
}

View File

@@ -0,0 +1,31 @@
--- a/net/minecraft/server/packs/repository/ServerPacksSource.java
+++ b/net/minecraft/server/packs/repository/ServerPacksSource.java
@@ -48,7 +48,7 @@
public static VanillaPackResources createVanillaPackSource() {
return new VanillaPackResourcesBuilder()
.setMetadata(BUILT_IN_METADATA)
- .exposeNamespace("minecraft")
+ .exposeNamespace("minecraft", ResourceLocation.PAPER_NAMESPACE) // Paper
.applyDevelopmentConfig()
.pushJarResources()
.build(VANILLA_PACK_INFO);
@@ -68,7 +68,18 @@
@Nullable
@Override
protected Pack createBuiltinPack(String fileName, Pack.ResourcesSupplier packFactory, Component displayName) {
- return Pack.readMetaAndCreate(createBuiltInPackLocation(fileName, displayName), packFactory, PackType.SERVER_DATA, FEATURE_SELECTION_CONFIG);
+ // Paper start - custom built-in pack
+ final PackLocationInfo info;
+ final PackSelectionConfig packConfig;
+ if ("paper".equals(fileName)) {
+ info = new PackLocationInfo(fileName, displayName, PackSource.BUILT_IN, Optional.empty());
+ packConfig = new PackSelectionConfig(true, Pack.Position.TOP, true);
+ } else {
+ info = createBuiltInPackLocation(fileName, displayName);
+ packConfig = FEATURE_SELECTION_CONFIG;
+ }
+ return Pack.readMetaAndCreate(info, packFactory, PackType.SERVER_DATA, packConfig);
+ // Paper end - custom built-in pack
}
public static PackRepository createPackRepository(Path dataPacksPath, DirectoryValidator symlinkFinder) {

View File

@@ -0,0 +1,34 @@
--- a/net/minecraft/server/players/BanListEntry.java
+++ b/net/minecraft/server/players/BanListEntry.java
@@ -27,7 +27,7 @@
}
protected BanListEntry(@Nullable T key, JsonObject json) {
- super(key);
+ super(BanListEntry.checkExpiry(key, json)); // CraftBukkit
Date date;
@@ -83,4 +83,22 @@
json.addProperty("expires", this.expires == null ? "forever" : BanListEntry.DATE_FORMAT.format(this.expires));
json.addProperty("reason", this.reason);
}
+
+ // CraftBukkit start
+ private static <T> T checkExpiry(T object, JsonObject jsonobject) {
+ Date expires = null;
+
+ try {
+ expires = jsonobject.has("expires") ? BanListEntry.DATE_FORMAT.parse(jsonobject.get("expires").getAsString()) : null;
+ } catch (ParseException ex) {
+ // Guess we don't have a date
+ }
+
+ if (expires == null || expires.after(new Date())) {
+ return object;
+ } else {
+ return null;
+ }
+ }
+ // CraftBukkit end
}

View File

@@ -0,0 +1,217 @@
--- a/net/minecraft/server/players/GameProfileCache.java
+++ b/net/minecraft/server/players/GameProfileCache.java
@@ -60,6 +60,11 @@
@Nullable
private Executor executor;
+ // Paper start - Fix GameProfileCache concurrency
+ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock();
+ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock();
+ // Paper end - Fix GameProfileCache concurrency
+
public GameProfileCache(GameProfileRepository profileRepository, File cacheFile) {
this.profileRepository = profileRepository;
this.file = cacheFile;
@@ -67,11 +72,13 @@
}
private void safeAdd(GameProfileCache.GameProfileInfo entry) {
+ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
GameProfile gameprofile = entry.getProfile();
entry.setLastAccess(this.getNextOperation());
this.profilesByName.put(gameprofile.getName().toLowerCase(Locale.ROOT), entry);
this.profilesByUUID.put(gameprofile.getId(), entry);
+ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
}
private static Optional<GameProfile> lookupGameProfile(GameProfileRepository repository, String name) {
@@ -85,10 +92,12 @@
}
public void onProfileLookupFailed(String s1, Exception exception) {
- atomicreference.set((Object) null);
+ atomicreference.set(null); // CraftBukkit - decompile error
}
};
+ if (!org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name
+ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) // Paper - Add setting for proxy online mode status
repository.findProfilesByNames(new String[]{name}, profilelookupcallback);
GameProfile gameprofile = (GameProfile) atomicreference.get();
@@ -105,7 +114,7 @@
}
private static boolean usesAuthentication() {
- return GameProfileCache.usesAuthentication;
+ return io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode(); // Paper - Add setting for proxy online mode status
}
public void add(GameProfile profile) {
@@ -117,15 +126,29 @@
GameProfileCache.GameProfileInfo usercache_usercacheentry = new GameProfileCache.GameProfileInfo(profile, date);
this.safeAdd(usercache_usercacheentry);
- this.save();
+ if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.save(true); // Spigot - skip saving if disabled // Paper - Perf: Async GameProfileCache saving
}
private long getNextOperation() {
return this.operationCount.incrementAndGet();
}
+ // Paper start
+ public @Nullable GameProfile getProfileIfCached(String name) {
+ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
+ GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT));
+ if (entry == null) {
+ return null;
+ }
+ entry.setLastAccess(this.getNextOperation());
+ return entry.getProfile();
+ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
+ }
+ // Paper end
+
public Optional<GameProfile> get(String name) {
String s1 = name.toLowerCase(Locale.ROOT);
+ boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1);
boolean flag = false;
@@ -141,19 +164,24 @@
if (usercache_usercacheentry != null) {
usercache_usercacheentry.setLastAccess(this.getNextOperation());
optional = Optional.of(usercache_usercacheentry.getProfile());
+ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency
} else {
- optional = GameProfileCache.lookupGameProfile(this.profileRepository, s1);
+ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency
+ try { this.lookupLock.lock(); // Paper - Fix GameProfileCache concurrency
+ optional = GameProfileCache.lookupGameProfile(this.profileRepository, name); // CraftBukkit - use correct case for offline players
+ } finally { this.lookupLock.unlock(); } // Paper - Fix GameProfileCache concurrency
if (optional.isPresent()) {
this.add((GameProfile) optional.get());
flag = false;
}
}
- if (flag) {
- this.save();
+ if (flag && !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { // Spigot - skip saving if disabled
+ this.save(true); // Paper - Perf: Async GameProfileCache saving
}
return optional;
+ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Paper - Fix GameProfileCache concurrency
}
public CompletableFuture<Optional<GameProfile>> getAsync(String username) {
@@ -167,7 +195,7 @@
} else {
CompletableFuture<Optional<GameProfile>> completablefuture1 = CompletableFuture.supplyAsync(() -> {
return this.get(username);
- }, Util.backgroundExecutor().forName("getProfile")).whenCompleteAsync((optional, throwable) -> {
+ }, Util.PROFILE_EXECUTOR).whenCompleteAsync((optional, throwable) -> { // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread
this.requests.remove(username);
}, this.executor);
@@ -178,6 +206,7 @@
}
public Optional<GameProfile> get(UUID uuid) {
+ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByUUID.get(uuid);
if (usercache_usercacheentry == null) {
@@ -186,6 +215,7 @@
usercache_usercacheentry.setLastAccess(this.getNextOperation());
return Optional.of(usercache_usercacheentry.getProfile());
}
+ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
}
public void setExecutor(Executor executor) {
@@ -208,7 +238,7 @@
label54:
{
- ArrayList arraylist;
+ List<GameProfileCache.GameProfileInfo> arraylist; // CraftBukkit - decompile error
try {
JsonArray jsonarray = (JsonArray) this.gson.fromJson(bufferedreader, JsonArray.class);
@@ -217,7 +247,7 @@
DateFormat dateformat = GameProfileCache.createDateFormat();
jsonarray.forEach((jsonelement) -> {
- Optional optional = GameProfileCache.readGameProfile(jsonelement, dateformat);
+ Optional<GameProfileCache.GameProfileInfo> optional = GameProfileCache.readGameProfile(jsonelement, dateformat); // CraftBukkit - decompile error
Objects.requireNonNull(list);
optional.ifPresent(list::add);
@@ -250,6 +280,11 @@
}
} catch (FileNotFoundException filenotfoundexception) {
;
+ // Spigot Start
+ } catch (com.google.gson.JsonSyntaxException | NullPointerException ex) {
+ GameProfileCache.LOGGER.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." );
+ this.file.delete();
+ // Spigot End
} catch (JsonParseException | IOException ioexception) {
GameProfileCache.LOGGER.warn("Failed to load profile cache {}", this.file, ioexception);
}
@@ -257,14 +292,15 @@
return list;
}
- public void save() {
+ public void save(boolean asyncSave) { // Paper - Perf: Async GameProfileCache saving
JsonArray jsonarray = new JsonArray();
DateFormat dateformat = GameProfileCache.createDateFormat();
- this.getTopMRUProfiles(1000).forEach((usercache_usercacheentry) -> {
+ this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot // Paper - Fix GameProfileCache concurrency
jsonarray.add(GameProfileCache.writeGameProfile(usercache_usercacheentry, dateformat));
});
String s = this.gson.toJson(jsonarray);
+ Runnable save = () -> { // Paper - Perf: Async GameProfileCache saving
try {
BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8);
@@ -289,13 +325,32 @@
} catch (IOException ioexception) {
;
}
+ // Paper start - Perf: Async GameProfileCache saving
+ };
+ if (asyncSave) {
+ io.papermc.paper.util.MCUtil.scheduleAsyncTask(save);
+ } else {
+ save.run();
+ }
+ // Paper end - Perf: Async GameProfileCache saving
}
private Stream<GameProfileCache.GameProfileInfo> getTopMRUProfiles(int limit) {
- return ImmutableList.copyOf(this.profilesByUUID.values()).stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit((long) limit);
+ // Paper start - Fix GameProfileCache concurrency
+ return this.listTopMRUProfiles(limit).stream();
}
+ private List<GameProfileCache.GameProfileInfo> listTopMRUProfiles(int limit) {
+ try {
+ this.stateLock.lock();
+ return this.profilesByUUID.values().stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit(limit).toList();
+ } finally {
+ this.stateLock.unlock();
+ }
+ }
+ // Paper end - Fix GameProfileCache concurrency
+
private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo entry, DateFormat dateFormat) {
JsonObject jsonobject = new JsonObject();

View File

@@ -0,0 +1,98 @@
--- a/net/minecraft/server/players/OldUsersConverter.java
+++ b/net/minecraft/server/players/OldUsersConverter.java
@@ -21,6 +21,9 @@
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.core.UUIDUtil;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.NbtAccounter;
+import net.minecraft.nbt.NbtIo;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.util.StringUtil;
@@ -62,7 +65,8 @@
return new String[i];
});
- if (server.usesAuthentication()) {
+ if (server.usesAuthentication() ||
+ (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode())) { // Spigot: bungee = online mode, for now. // Paper - Add setting for proxy online mode status
server.getProfileRepository().findProfilesByNames(astring, callback);
} else {
String[] astring1 = astring;
@@ -85,7 +89,7 @@
try {
gameprofilebanlist.load();
} catch (IOException ioexception) {
- OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName(), ioexception);
+ OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName()); // CraftBukkit - don't print stacktrace
}
}
@@ -143,7 +147,7 @@
try {
ipbanlist.load();
} catch (IOException ioexception) {
- OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName(), ioexception);
+ OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName()); // CraftBukkit - don't print stacktrace
}
}
@@ -184,7 +188,7 @@
try {
oplist.load();
} catch (IOException ioexception) {
- OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName(), ioexception);
+ OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName()); // CraftBukkit - don't print stacktrace
}
}
@@ -228,7 +232,7 @@
try {
whitelist.load();
} catch (IOException ioexception) {
- OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName(), ioexception);
+ OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName()); // CraftBukkit - don't print stacktrace
}
}
@@ -346,7 +350,39 @@
private void movePlayerFile(File playerDataFolder, String fileName, String uuid) {
File file5 = new File(file, fileName + ".dat");
File file6 = new File(playerDataFolder, uuid + ".dat");
+
+ // CraftBukkit start - Use old file name to seed lastKnownName
+ CompoundTag root = null;
+
+ try {
+ root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap());
+ } catch (Exception exception) {
+ // Paper start
+ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception);
+ exception.printStackTrace();
+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception);
+ // Paper end
+ }
+ if (root != null) {
+ if (!root.contains("bukkit")) {
+ root.put("bukkit", new CompoundTag());
+ }
+ CompoundTag data = root.getCompound("bukkit");
+ data.putString("lastKnownName", fileName);
+
+ try {
+ NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2));
+ } catch (Exception exception) {
+ // Paper start
+ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception);
+ exception.printStackTrace();
+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception);
+ // Paper end
+ }
+ }
+ // CraftBukkit end
+
OldUsersConverter.ensureDirectoryExists(playerDataFolder);
if (!file5.renameTo(file6)) {
throw new OldUsersConverter.ConversionError("Could not convert file for " + fileName);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
--- a/net/minecraft/server/players/SleepStatus.java
+++ b/net/minecraft/server/players/SleepStatus.java
@@ -18,9 +18,12 @@
}
public boolean areEnoughDeepSleeping(int percentage, List<ServerPlayer> players) {
- int j = (int) players.stream().filter(Player::isSleepingLongEnough).count();
+ // CraftBukkit start
+ int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count();
+ boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough);
- return j >= this.sleepersNeeded(percentage);
+ return anyDeepSleep && j >= this.sleepersNeeded(percentage);
+ // CraftBukkit end
}
public int sleepersNeeded(int percentage) {
@@ -42,18 +45,24 @@
this.activePlayers = 0;
this.sleepingPlayers = 0;
Iterator iterator = players.iterator();
+ boolean anySleep = false; // CraftBukkit
while (iterator.hasNext()) {
ServerPlayer entityplayer = (ServerPlayer) iterator.next();
if (!entityplayer.isSpectator()) {
++this.activePlayers;
- if (entityplayer.isSleeping()) {
+ if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit
++this.sleepingPlayers;
}
+ // CraftBukkit start
+ if (entityplayer.isSleeping()) {
+ anySleep = true;
+ }
+ // CraftBukkit end
}
}
- return (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers);
+ return anySleep && (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers); // CraftBukkit
}
}

View File

@@ -0,0 +1,96 @@
--- a/net/minecraft/server/players/StoredUserList.java
+++ b/net/minecraft/server/players/StoredUserList.java
@@ -30,7 +30,7 @@
private static final Logger LOGGER = LogUtils.getLogger();
private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create();
private final File file;
- private final Map<String, V> map = Maps.newHashMap();
+ private final Map<String, V> map = Maps.newConcurrentMap(); // Paper - Use ConcurrentHashMap in JsonList
public StoredUserList(File file) {
this.file = file;
@@ -53,8 +53,11 @@
@Nullable
public V get(K key) {
- this.removeExpired();
- return (StoredUserEntry) this.map.get(this.getKeyForUser(key));
+ // Paper start - Use ConcurrentHashMap in JsonList
+ return (V) this.map.computeIfPresent(this.getKeyForUser(key), (k, v) -> {
+ return v.hasExpired() ? null : v;
+ });
+ // Paper end - Use ConcurrentHashMap in JsonList
}
public void remove(K key) {
@@ -77,7 +80,7 @@
}
public boolean isEmpty() {
- return this.map.size() < 1;
+ return this.map.isEmpty(); // Paper - Use ConcurrentHashMap in JsonList
}
protected String getKeyForUser(K profile) {
@@ -85,29 +88,12 @@
}
protected boolean contains(K k0) {
+ this.removeExpired(); // CraftBukkit - SPIGOT-7589: Consistently remove expired entries to mirror .get(...)
return this.map.containsKey(this.getKeyForUser(k0));
}
private void removeExpired() {
- List<K> list = Lists.newArrayList();
- Iterator iterator = this.map.values().iterator();
-
- while (iterator.hasNext()) {
- V v0 = (StoredUserEntry) iterator.next();
-
- if (v0.hasExpired()) {
- list.add(v0.getUser());
- }
- }
-
- iterator = list.iterator();
-
- while (iterator.hasNext()) {
- K k0 = iterator.next();
-
- this.map.remove(this.getKeyForUser(k0));
- }
-
+ this.map.values().removeIf(StoredUserEntry::hasExpired); // Paper - Use ConcurrentHashMap in JsonList
}
protected abstract StoredUserEntry<K> createEntry(JsonObject json);
@@ -117,8 +103,9 @@
}
public void save() throws IOException {
+ this.removeExpired(); // Paper - remove expired values before saving
JsonArray jsonarray = new JsonArray();
- Stream stream = this.map.values().stream().map((jsonlistentry) -> {
+ Stream<JsonObject> stream = this.map.values().stream().map((jsonlistentry) -> { // CraftBukkit - decompile error
JsonObject jsonobject = new JsonObject();
Objects.requireNonNull(jsonlistentry);
@@ -171,9 +158,17 @@
StoredUserEntry<K> jsonlistentry = this.createEntry(jsonobject);
if (jsonlistentry.getUser() != null) {
- this.map.put(this.getKeyForUser(jsonlistentry.getUser()), jsonlistentry);
+ this.map.put(this.getKeyForUser(jsonlistentry.getUser()), (V) jsonlistentry); // CraftBukkit - decompile error
}
}
+ // Spigot Start
+ } catch ( com.google.gson.JsonParseException | NullPointerException ex )
+ {
+ org.bukkit.Bukkit.getLogger().log( java.util.logging.Level.WARNING, "Unable to read file " + this.file + ", backing it up to {0}.backup and creating new copy.", ex );
+ File backup = new File( this.file + ".backup" );
+ this.file.renameTo( backup );
+ this.file.delete();
+ // Spigot End
} catch (Throwable throwable) {
if (bufferedreader != null) {
try {

View File

@@ -0,0 +1,42 @@
--- a/net/minecraft/server/players/UserBanListEntry.java
+++ b/net/minecraft/server/players/UserBanListEntry.java
@@ -1,3 +1,4 @@
+// mc-dev import
package net.minecraft.server.players;
import com.google.gson.JsonObject;
@@ -39,20 +40,29 @@
@Nullable
private static GameProfile createGameProfile(JsonObject json) {
- if (json.has("uuid") && json.has("name")) {
+ // Spigot start
+ // this whole method has to be reworked to account for the fact Bukkit only accepts UUID bans and gives no way for usernames to be stored!
+ UUID uuid = null;
+ String name = null;
+ if (json.has("uuid")) {
String s = json.get("uuid").getAsString();
- UUID uuid;
-
try {
uuid = UUID.fromString(s);
} catch (Throwable throwable) {
- return null;
}
- return new GameProfile(uuid, json.get("name").getAsString());
+ }
+ if ( json.has("name"))
+ {
+ name = json.get("name").getAsString();
+ }
+ if ( uuid != null || name != null )
+ {
+ return new GameProfile( uuid, name );
} else {
return null;
}
+ // Spigot End
}
}

View File

@@ -0,0 +1,26 @@
--- a/net/minecraft/server/players/UserWhiteList.java
+++ b/net/minecraft/server/players/UserWhiteList.java
@@ -28,4 +28,23 @@
protected String getKeyForUser(GameProfile gameProfile) {
return gameProfile.getId().toString();
}
+ // Paper start - Add whitelist events
+ @Override
+ public void add(UserWhiteListEntry entry) {
+ if (!new io.papermc.paper.event.server.WhitelistStateUpdateEvent(com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitCopy(entry.getUser()), io.papermc.paper.event.server.WhitelistStateUpdateEvent.WhitelistStatus.ADDED).callEvent()) {
+ return;
+ }
+
+ super.add(entry);
+ }
+
+ @Override
+ public void remove(GameProfile profile) {
+ if (!new io.papermc.paper.event.server.WhitelistStateUpdateEvent(com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitCopy(profile), io.papermc.paper.event.server.WhitelistStateUpdateEvent.WhitelistStatus.REMOVED).callEvent()) {
+ return;
+ }
+
+ super.remove(profile);
+ }
+ // Paper end - Add whitelist events
}

View File

@@ -0,0 +1,49 @@
--- a/net/minecraft/server/rcon/RconConsoleSource.java
+++ b/net/minecraft/server/rcon/RconConsoleSource.java
@@ -8,16 +8,24 @@
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
-
+// CraftBukkit start
+import java.net.SocketAddress;
+import org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender;
+// CraftBukkit end
public class RconConsoleSource implements CommandSource {
private static final String RCON = "Rcon";
private static final Component RCON_COMPONENT = Component.literal("Rcon");
private final StringBuffer buffer = new StringBuffer();
private final MinecraftServer server;
+ // CraftBukkit start
+ public final SocketAddress socketAddress;
+ private final CraftRemoteConsoleCommandSender remoteConsole = new CraftRemoteConsoleCommandSender(this);
- public RconConsoleSource(MinecraftServer server) {
- this.server = server;
+ public RconConsoleSource(MinecraftServer minecraftserver, SocketAddress socketAddress) {
+ this.socketAddress = socketAddress;
+ // CraftBukkit end
+ this.server = minecraftserver;
}
public void prepareForCommand() {
@@ -34,7 +42,18 @@
return new CommandSourceStack(this, Vec3.atLowerCornerOf(worldserver.getSharedSpawnPos()), Vec2.ZERO, worldserver, 4, "Rcon", RconConsoleSource.RCON_COMPONENT, this.server, (Entity) null);
}
+ // CraftBukkit start - Send a String
+ public void sendMessage(String message) {
+ this.buffer.append(message);
+ }
+
@Override
+ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
+ return this.remoteConsole;
+ }
+ // CraftBukkit end
+
+ @Override
public void sendSystemMessage(Component message) {
this.buffer.append(message.getString());
}

View File

@@ -0,0 +1,126 @@
--- a/net/minecraft/server/rcon/thread/QueryThreadGs4.java
+++ b/net/minecraft/server/rcon/thread/QueryThreadGs4.java
@@ -106,13 +106,32 @@
NetworkDataOutputStream networkDataOutputStream = new NetworkDataOutputStream(1460);
networkDataOutputStream.write(0);
networkDataOutputStream.writeBytes(this.getIdentBytes(packet.getSocketAddress()));
- networkDataOutputStream.writeString(this.serverName);
+
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.BASIC;
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder()
+ .motd(this.serverName)
+ .map(this.worldName)
+ .currentPlayers(this.serverInterface.getPlayerCount())
+ .maxPlayers(this.maxPlayers)
+ .port(this.serverPort)
+ .hostname(this.hostIp)
+ .gameVersion(this.serverInterface.getServerVersion())
+ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion())
+ .build();
+ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
+ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
+ queryEvent.callEvent();
+ queryResponse = queryEvent.getResponse();
+
+ networkDataOutputStream.writeString(queryResponse.getMotd());
networkDataOutputStream.writeString("SMP");
- networkDataOutputStream.writeString(this.worldName);
- networkDataOutputStream.writeString(Integer.toString(this.serverInterface.getPlayerCount()));
- networkDataOutputStream.writeString(Integer.toString(this.maxPlayers));
- networkDataOutputStream.writeShort((short)this.serverPort);
- networkDataOutputStream.writeString(this.hostIp);
+ networkDataOutputStream.writeString(queryResponse.getMap());
+ networkDataOutputStream.writeString(Integer.toString(queryResponse.getCurrentPlayers()));
+ networkDataOutputStream.writeString(Integer.toString(queryResponse.getMaxPlayers()));
+ networkDataOutputStream.writeShort((short) queryResponse.getPort());
+ networkDataOutputStream.writeString(queryResponse.getHostname());
+ // Paper end
this.sendTo(networkDataOutputStream.toByteArray(), packet);
LOGGER.debug("Status [{}]", socketAddress);
}
@@ -147,31 +166,75 @@
this.rulesResponse.writeString("splitnum");
this.rulesResponse.write(128);
this.rulesResponse.write(0);
+ // Paper start
+ // Pack plugins
+ java.util.List<com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation> plugins = java.util.Collections.emptyList();
+ org.bukkit.plugin.Plugin[] bukkitPlugins;
+ if (((net.minecraft.server.dedicated.DedicatedServer) this.serverInterface).server.getQueryPlugins() && (bukkitPlugins = org.bukkit.Bukkit.getPluginManager().getPlugins()).length > 0) {
+ plugins = java.util.stream.Stream.of(bukkitPlugins)
+ .map(plugin -> com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation.of(plugin.getName(), plugin.getDescription().getVersion()))
+ .collect(java.util.stream.Collectors.toList());
+ }
+
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder()
+ .motd(this.serverName)
+ .map(this.worldName)
+ .currentPlayers(this.serverInterface.getPlayerCount())
+ .maxPlayers(this.maxPlayers)
+ .port(this.serverPort)
+ .hostname(this.hostIp)
+ .plugins(plugins)
+ .players(this.serverInterface.getPlayerNames())
+ .gameVersion(this.serverInterface.getServerVersion())
+ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion())
+ .build();
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.FULL;
+ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
+ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
+ queryEvent.callEvent();
+ queryResponse = queryEvent.getResponse();
this.rulesResponse.writeString("hostname");
- this.rulesResponse.writeString(this.serverName);
+ this.rulesResponse.writeString(queryResponse.getMotd());
this.rulesResponse.writeString("gametype");
this.rulesResponse.writeString("SMP");
this.rulesResponse.writeString("game_id");
this.rulesResponse.writeString("MINECRAFT");
this.rulesResponse.writeString("version");
- this.rulesResponse.writeString(this.serverInterface.getServerVersion());
+ this.rulesResponse.writeString(queryResponse.getGameVersion());
this.rulesResponse.writeString("plugins");
- this.rulesResponse.writeString(this.serverInterface.getPluginNames());
+ java.lang.StringBuilder pluginsString = new java.lang.StringBuilder();
+ pluginsString.append(queryResponse.getServerVersion());
+ if (!queryResponse.getPlugins().isEmpty()) {
+ pluginsString.append(": ");
+ java.util.Iterator<com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation> iter = queryResponse.getPlugins().iterator();
+ while (iter.hasNext()) {
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation info = iter.next();
+ pluginsString.append(info.getName());
+ if (info.getVersion() != null) {
+ pluginsString.append(' ').append(info.getVersion().replace(";", ","));
+ }
+ if (iter.hasNext()) {
+ pluginsString.append(';').append(' ');
+ }
+ }
+ }
+ this.rulesResponse.writeString(pluginsString.toString());
this.rulesResponse.writeString("map");
- this.rulesResponse.writeString(this.worldName);
+ this.rulesResponse.writeString(queryResponse.getMap());
this.rulesResponse.writeString("numplayers");
- this.rulesResponse.writeString(this.serverInterface.getPlayerCount() + "");
+ this.rulesResponse.writeString(Integer.toString(queryResponse.getCurrentPlayers()));
this.rulesResponse.writeString("maxplayers");
- this.rulesResponse.writeString(this.maxPlayers + "");
+ this.rulesResponse.writeString(Integer.toString(queryResponse.getMaxPlayers()));
this.rulesResponse.writeString("hostport");
- this.rulesResponse.writeString(this.serverPort + "");
+ this.rulesResponse.writeString(Integer.toString(queryResponse.getPort()));
this.rulesResponse.writeString("hostip");
- this.rulesResponse.writeString(this.hostIp);
+ this.rulesResponse.writeString(queryResponse.getHostname());
this.rulesResponse.write(0);
this.rulesResponse.write(1);
this.rulesResponse.writeString("player_");
this.rulesResponse.write(0);
- String[] strings = this.serverInterface.getPlayerNames();
+ String[] strings = queryResponse.getPlayers().toArray(String[]::new);
for (String string : strings) {
this.rulesResponse.writeString(string);

View File

@@ -0,0 +1,80 @@
--- a/net/minecraft/server/rcon/thread/RconClient.java
+++ b/net/minecraft/server/rcon/thread/RconClient.java
@@ -8,9 +8,12 @@
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
+import org.slf4j.Logger;
import net.minecraft.server.ServerInterface;
+// CraftBukkit start
+import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.rcon.PktUtils;
-import org.slf4j.Logger;
+import net.minecraft.server.rcon.RconConsoleSource;
public class RconClient extends GenericThread {
@@ -24,11 +27,14 @@
private final Socket client;
private final byte[] buf = new byte[1460];
private final String rconPassword;
- private final ServerInterface serverInterface;
+ // CraftBukkit start
+ private final DedicatedServer serverInterface;
+ private final RconConsoleSource rconConsoleSource;
+ // CraftBukkit end
RconClient(ServerInterface server, String password, Socket socket) {
super("RCON Client " + String.valueOf(socket.getInetAddress()));
- this.serverInterface = server;
+ this.serverInterface = (DedicatedServer) server; // CraftBukkit
this.client = socket;
try {
@@ -38,11 +44,14 @@
}
this.rconPassword = password;
+ this.rconConsoleSource = new net.minecraft.server.rcon.RconConsoleSource(this.serverInterface, socket.getRemoteSocketAddress()); // CraftBukkit
}
public void run() {
- while (true) {
- try {
+ // CraftBukkit start - decompile error: switch try / while statement
+ try {
+ while (true) {
+ // CraftBukkit end
if (!this.running) {
return;
}
@@ -71,7 +80,7 @@
String s = PktUtils.stringFromByteArray(this.buf, j, i);
try {
- this.sendCmdResponse(l, this.serverInterface.runCommand(s));
+ this.sendCmdResponse(l, this.serverInterface.runCommand(this.rconConsoleSource, s)); // CraftBukkit
} catch (Exception exception) {
this.sendCmdResponse(l, "Error executing: " + s + " (" + exception.getMessage() + ")");
}
@@ -98,6 +107,7 @@
continue;
}
}
+ } // CraftBukkit - decompile error: switch try / while statement
} catch (IOException ioexception) {
return;
} catch (Exception exception1) {
@@ -109,8 +119,10 @@
this.running = false;
}
- return;
- }
+ // CraftBukkit start - decompile error: switch try / while statement
+ // return;
+ // }
+ // CraftBukkit end
}
private void send(int sessionToken, int responseType, String message) throws IOException {

View File

@@ -0,0 +1,26 @@
--- a/net/minecraft/server/rcon/thread/RconThread.java
+++ b/net/minecraft/server/rcon/thread/RconThread.java
@@ -57,7 +57,7 @@
@Nullable
public static RconThread create(ServerInterface server) {
DedicatedServerProperties dedicatedServerProperties = server.getProperties();
- String string = server.getServerIp();
+ String string = dedicatedServerProperties.rconIp; // Paper - Configurable rcon ip
if (string.isEmpty()) {
string = "0.0.0.0";
}
@@ -104,6 +104,14 @@
this.clients.clear();
}
+ // Paper start - don't wait for remote connections
+ public void stopNonBlocking() {
+ this.running = false;
+ for (RconClient client : this.clients) {
+ client.running = false;
+ }
+ }
+ // Paper end - don't wait for remote connections
private void closeSocket(ServerSocket socket) {
LOGGER.debug("closeSocket: {}", socket);