More more more more more more more more more more more work

This commit is contained in:
Nassim Jahnke 2021-11-24 12:38:00 +01:00
parent 3b7830d806
commit 65d44a991e
47 changed files with 51 additions and 1160 deletions

View file

@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: nossr50 <nossr50@gmail.com>
Date: Thu, 26 Mar 2020 19:44:50 -0700
Subject: [PATCH] Add PlayerAttackEntityCooldownResetEvent
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity {
EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption);
if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) {
- ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired
+ // Paper start - PlayerAttackEntityCooldownResetEvent
+ if (damagesource.getEntity() instanceof ServerPlayer) {
+ ServerPlayer player = (ServerPlayer) damagesource.getEntity();
+ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) {
+ player.resetAttackStrengthTicker();
+ }
+ } else {
+ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker();
+ }
+ // Paper end
}
if (event.isCancelled()) {
return false;

View file

@ -0,0 +1,61 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mariell Hoversholm <proximyst@proximyst.com>
Date: Sat, 16 May 2020 10:12:15 +0200
Subject: [PATCH] Add option for console having all permissions
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig {
config.set("settings.unsupported-settings.allow-permanent-block-break-exploits-readme", "This setting controls if players should be able to break bedrock, end portals and other intended to be permanent blocks.");
allowBlockPermanentBreakingExploits = getBoolean("settings.unsupported-settings.allow-permanent-block-break-exploits", allowBlockPermanentBreakingExploits);
}
+
+ public static boolean consoleHasAllPermissions = false;
+ private static void consoleHasAllPermissions() {
+ consoleHasAllPermissions = getBoolean("settings.console-has-all-permissions", consoleHasAllPermissions);
+ }
}
diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
@@ -0,0 +0,0 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co
public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) {
this.sendRawMessage(io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.serialize(message));
}
+
+ @Override
+ public boolean hasPermission(String name) {
+ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(name);
+ }
+
+ @Override
+ public boolean hasPermission(org.bukkit.permissions.Permission perm) {
+ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(perm);
+ }
// Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java
@@ -0,0 +0,0 @@ public class CraftRemoteConsoleCommandSender extends ServerCommandSender impleme
public void setOp(boolean value) {
throw new UnsupportedOperationException("Cannot change operator status of remote controller.");
}
+
+ // Paper start
+ @Override
+ public boolean hasPermission(String name) {
+ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(name);
+ }
+
+ @Override
+ public boolean hasPermission(org.bukkit.permissions.Permission perm) {
+ return com.destroystokyo.paper.PaperConfig.consoleHasAllPermissions || super.hasPermission(perm);
+ }
+ // Paper end
}

View file

@ -0,0 +1,59 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Sat, 25 Apr 2020 15:13:41 -0500
Subject: [PATCH] Add phantom creative and insomniac controls
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
}
perPlayerMobSpawns = getBoolean("per-player-mob-spawns", true);
}
+
+ public boolean phantomIgnoreCreative = true;
+ public boolean phantomOnlyAttackInsomniacs = true;
+ private void phantomSettings() {
+ phantomIgnoreCreative = getBoolean("phantoms-do-not-spawn-on-creative-players", phantomIgnoreCreative);
+ phantomOnlyAttackInsomniacs = getBoolean("phantoms-only-attack-insomniacs", phantomOnlyAttackInsomniacs);
+ }
}
diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java
+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java
@@ -0,0 +0,0 @@ public final class EntitySelector {
return !entity.isSpectator();
};
public static final Predicate<Entity> CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith);
+ public static Predicate<Player> isInsomniac = (player) -> net.minecraft.util.Mth.clamp(((net.minecraft.server.level.ServerPlayer) player).getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper
private EntitySelector() {}
// Paper start
diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
@@ -0,0 +0,0 @@ public class Phantom extends FlyingMob implements Enemy {
Player entityhuman = (Player) iterator.next();
if (Phantom.this.canAttack(entityhuman, TargetingConditions.DEFAULT)) {
+ if (!level.paperConfig.phantomOnlyAttackInsomniacs || EntitySelector.isInsomniac.test(entityhuman)) // Paper
Phantom.this.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason
return true;
}
diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java
@@ -0,0 +0,0 @@ public class PhantomSpawner implements CustomSpawner {
while (iterator.hasNext()) {
Player entityhuman = (Player) iterator.next();
- if (!entityhuman.isSpectator()) {
+ if (!entityhuman.isSpectator() && (!world.paperConfig.phantomIgnoreCreative || !entityhuman.isCreative())) { // Paper
BlockPos blockposition = entityhuman.blockPosition();
if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) {

View file

@ -0,0 +1,127 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mariell Hoversholm <proximyst@proximyst.com>
Date: Wed, 22 Apr 2020 23:29:20 +0200
Subject: [PATCH] Add villager reputation API
diff --git a/src/main/java/com/destroystokyo/paper/entity/villager/ReputationConstructor.java b/src/main/java/com/destroystokyo/paper/entity/villager/ReputationConstructor.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/entity/villager/ReputationConstructor.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.entity.villager;
+// Must have own package due to package-level constructor.
+
+public final class ReputationConstructor {
+ // Abuse the package-level constructor.
+ public static Reputation construct(int[] values) {
+ return new Reputation(values);
+ }
+}
diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java
+++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java
@@ -0,0 +0,0 @@ import net.minecraft.util.VisibleForDebug;
public class GossipContainer {
public static final int DISCARD_THRESHOLD = 2;
- private final Map<UUID, GossipContainer.EntityGossips> gossips = Maps.newHashMap();
+ private final Map<UUID, GossipContainer.EntityGossips> gossips = Maps.newHashMap(); public Map<UUID, GossipContainer.EntityGossips> getReputations() { return this.gossips; } // Paper - add getter for reputations
@VisibleForDebug
public Map<UUID, Object2IntMap<GossipType>> getGossipEntries() {
@@ -0,0 +0,0 @@ public class GossipContainer {
public void remove(GossipType gossipType) {
this.entries.removeInt(gossipType);
}
+
+ // Paper start - Add villager reputation API
+ private static final com.destroystokyo.paper.entity.villager.ReputationType[] REPUTATION_TYPES = com.destroystokyo.paper.entity.villager.ReputationType.values();
+ public com.destroystokyo.paper.entity.villager.Reputation getPaperReputation() {
+ int[] reputation = new int[REPUTATION_TYPES.length];
+ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_NEGATIVE.ordinal()] = entries.getOrDefault(GossipType.MAJOR_NEGATIVE, 0);
+ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE.ordinal()] = entries.getOrDefault(GossipType.MAJOR_POSITIVE, 0);
+ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE.ordinal()] = entries.getOrDefault(GossipType.MINOR_NEGATIVE, 0);
+ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE.ordinal()] = entries.getOrDefault(GossipType.MINOR_POSITIVE, 0);
+ reputation[com.destroystokyo.paper.entity.villager.ReputationType.TRADING.ordinal()] = entries.getOrDefault(GossipType.TRADING, 0);
+ return com.destroystokyo.paper.entity.villager.ReputationConstructor.construct(reputation);
+ }
+
+ public void assignFromPaperReputation(com.destroystokyo.paper.entity.villager.Reputation rep) {
+ int val;
+ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_NEGATIVE)) != 0) this.entries.put(GossipType.MAJOR_NEGATIVE, val);
+ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE)) != 0) this.entries.put(GossipType.MAJOR_POSITIVE, val);
+ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE)) != 0) this.entries.put(GossipType.MINOR_NEGATIVE, val);
+ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE)) != 0) this.entries.put(GossipType.MINOR_POSITIVE, val);
+ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.TRADING)) != 0) this.entries.put(GossipType.TRADING, val);
+ }
+ // Paper end
}
static class GossipEntry {
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
@@ -0,0 +0,0 @@ import org.bukkit.entity.Villager;
import org.bukkit.entity.Villager.Profession;
import org.bukkit.entity.Villager.Type;
+// Paper start
+import com.destroystokyo.paper.entity.villager.Reputation;
+import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.UUID;
+// Paper end
+
public class CraftVillager extends CraftAbstractVillager implements Villager {
public CraftVillager(CraftServer server, net.minecraft.world.entity.npc.Villager entity) {
@@ -0,0 +0,0 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
public static VillagerProfession bukkitToNmsProfession(Profession bukkit) {
return Registry.VILLAGER_PROFESSION.get(CraftNamespacedKey.toMinecraft(bukkit.getKey()));
}
+
+ // Paper start - Add villager reputation API
+ @Override
+ public Reputation getReputation(UUID uniqueId) {
+ net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips rep = getHandle().getGossips().getReputations().get(uniqueId);
+ if (rep == null) {
+ return new Reputation(Maps.newHashMap());
+ }
+
+ return rep.getPaperReputation();
+ }
+
+ @Override
+ public Map<UUID, Reputation> getReputations() {
+ return getHandle().getGossips().getReputations().entrySet()
+ .stream()
+ .collect(java.util.stream.Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getPaperReputation()));
+ }
+
+ @Override
+ public void setReputation(UUID uniqueId, Reputation reputation) {
+ net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips nmsReputation =
+ getHandle().getGossips().getReputations().computeIfAbsent(
+ uniqueId,
+ key -> new net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips()
+ );
+ nmsReputation.assignFromPaperReputation(reputation);
+ }
+
+ @Override
+ public void setReputations(Map<UUID, Reputation> reputations) {
+ for (Map.Entry<UUID, Reputation> entry : reputations.entrySet()) {
+ setReputation(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public void clearReputations() {
+ getHandle().getGossips().getReputations().clear();
+ }
+ // Paper end
}

View file

@ -0,0 +1,578 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sun, 20 Jun 2021 18:19:09 -0700
Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and
etc.
diff --git a/build.gradle.kts b/build.gradle.kts
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -0,0 +0,0 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
+import io.papermc.paperweight.tasks.BaseTask
import io.papermc.paperweight.util.*
import shadow.org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor.PLUGIN_CACHE_FILE
+import java.nio.file.Files
import java.util.Locale
plugins {
@@ -0,0 +0,0 @@ plugins {
repositories {
maven("https://libraries.minecraft.net/")
+ // Paper start
+ maven("https://maven.fabricmc.net/") {
+ mavenContent { includeModule("net.fabricmc", "mapping-io") }
+ }
+ // Paper end
}
dependencies {
@@ -0,0 +0,0 @@ dependencies {
runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.0")
runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.0")
+ implementation("net.fabricmc:mapping-io:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation
+
testImplementation("junit:junit:4.13.1")
testImplementation("org.hamcrest:hamcrest-library:1.3")
}
@@ -0,0 +0,0 @@ relocation {
relocate("org.bukkit.craftbukkit" to "org.bukkit.craftbukkit.v$packageVersion") {
exclude("org.bukkit.craftbukkit.Main*")
}
+ relocate("net.fabricmc.mapping-io" to "io.papermc.dependency.mappingio") // Paper
}
val generatePom = tasks.named<GenerateMavenPom>("generatePomFileForMavenPublication")
@@ -0,0 +0,0 @@ tasks.shadowJar {
transform(ModifiedLog4j2PluginsCacheFileTransformer::class.java)
}
+// Paper start - include reobf mappings in jar for stacktrace deobfuscation
+abstract class IncludeMappings : BaseTask() {
+ @get:InputFile
+ abstract val inputJar: RegularFileProperty
+
+ @get:InputFile
+ abstract val mappings: RegularFileProperty
+
+ @get:OutputFile
+ abstract val outputJar: RegularFileProperty
+
+ override fun init() {
+ super.init()
+ outputJar.convention(defaultOutput())
+ }
+
+ @TaskAction
+ private fun addMappings() {
+ outputJar.get().asFile.parentFile.mkdirs()
+ inputJar.get().asFile.copyTo(outputJar.get().asFile, overwrite = true)
+ outputJar.get().path.openZip().use { fs ->
+ val dir = fs.getPath("META-INF/mappings/")
+ Files.createDirectories(dir)
+ val target = dir.resolve("reobf.tiny")
+ Files.copy(mappings.path, target)
+ }
+ }
+}
+
+val includeMappings = tasks.register<IncludeMappings>("includeMappings") {
+ inputJar.set(tasks.fixJarForReobf.flatMap { it.outputJar })
+ mappings.set(tasks.reobfJar.flatMap { it.mappingsFile })
+}
+
+tasks.reobfJar {
+ inputJar.set(includeMappings.flatMap { it.outputJar })
+}
+// Paper end - include reobf mappings in jar for stacktrace deobfuscation
+
tasks.test {
exclude("org/bukkit/craftbukkit/inventory/ItemStack*Test.class")
}
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig {
log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag.");
}
}
+
+ public static boolean deobfuscateStacktraces = true;
+ private static void loggerSettings() {
+ deobfuscateStacktraces = getBoolean("settings.loggers.deobfuscate-stacktraces", deobfuscateStacktraces);
+ }
}
diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
@@ -0,0 +0,0 @@ public class SyncLoadFinder {
final JsonArray traces = new JsonArray();
- for (StackTraceElement element : pair.getFirst().stacktrace) {
+ for (StackTraceElement element : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(pair.getFirst().stacktrace)) {
traces.add(String.valueOf(element));
}
diff --git a/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java b/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.logging;
+
+import io.papermc.paper.util.StacktraceDeobfuscator;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+@Plugin(
+ name = "StacktraceDeobfuscatingRewritePolicy",
+ category = Core.CATEGORY_NAME,
+ elementType = "rewritePolicy",
+ printObject = true
+)
+public final class StacktraceDeobfuscatingRewritePolicy implements RewritePolicy {
+ private StacktraceDeobfuscatingRewritePolicy() {
+ }
+
+ @Override
+ public @NonNull LogEvent rewrite(final @NonNull LogEvent rewrite) {
+ final Throwable thrown = rewrite.getThrown();
+ if (thrown != null) {
+ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thrown);
+ return new Log4jLogEvent.Builder(rewrite)
+ .setThrownProxy(null)
+ .build();
+ }
+ return rewrite;
+ }
+
+ @PluginFactory
+ public static @NonNull StacktraceDeobfuscatingRewritePolicy createPolicy() {
+ return new StacktraceDeobfuscatingRewritePolicy();
+ }
+}
diff --git a/src/main/java/io/papermc/paper/util/ObfHelper.java b/src/main/java/io/papermc/paper/util/ObfHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/ObfHelper.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import net.fabricmc.mappingio.MappingReader;
+import net.fabricmc.mappingio.format.MappingFormat;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public enum ObfHelper {
+ INSTANCE;
+
+ public static final String MOJANG_PLUS_YARN_NAMESPACE = "mojang+yarn";
+ public static final String SPIGOT_NAMESPACE = "spigot";
+
+ private final @Nullable Map<String, ClassMapping> mappingsByObfName;
+ private final @Nullable Map<String, ClassMapping> mappingsByMojangName;
+
+ ObfHelper() {
+ final @Nullable Set<ClassMapping> maps = loadMappingsIfPresent();
+ if (maps != null) {
+ this.mappingsByObfName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::obfName, map -> map));
+ this.mappingsByMojangName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::mojangName, map -> map));
+ } else {
+ this.mappingsByObfName = null;
+ this.mappingsByMojangName = null;
+ }
+ }
+
+ public @Nullable Map<String, ClassMapping> mappingsByObfName() {
+ return this.mappingsByObfName;
+ }
+
+ public @Nullable Map<String, ClassMapping> mappingsByMojangName() {
+ return this.mappingsByMojangName;
+ }
+
+ /**
+ * Attempts to get the obf name for a given class by its Mojang name. Will
+ * return the input string if mappings are not present.
+ *
+ * @param fullyQualifiedMojangName fully qualified class name (dotted)
+ * @return mapped or original fully qualified (dotted) class name
+ */
+ public String reobfClassName(final String fullyQualifiedMojangName) {
+ if (this.mappingsByMojangName == null) {
+ return fullyQualifiedMojangName;
+ }
+
+ final ClassMapping map = this.mappingsByMojangName.get(fullyQualifiedMojangName);
+ if (map == null) {
+ return fullyQualifiedMojangName;
+ }
+
+ return map.obfName();
+ }
+
+ /**
+ * Attempts to get the Mojang name for a given class by its obf name. Will
+ * return the input string if mappings are not present.
+ *
+ * @param fullyQualifiedObfName fully qualified class name (dotted)
+ * @return mapped or original fully qualified (dotted) class name
+ */
+ public String deobfClassName(final String fullyQualifiedObfName) {
+ if (this.mappingsByObfName == null) {
+ return fullyQualifiedObfName;
+ }
+
+ final ClassMapping map = this.mappingsByObfName.get(fullyQualifiedObfName);
+ if (map == null) {
+ return fullyQualifiedObfName;
+ }
+
+ return map.mojangName();
+ }
+
+ private static @Nullable Set<ClassMapping> loadMappingsIfPresent() {
+ try (final @Nullable InputStream mappingsInputStream = ObfHelper.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) {
+ if (mappingsInputStream == null) {
+ return null;
+ }
+ final MemoryMappingTree tree = new MemoryMappingTree();
+ MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), MappingFormat.TINY_2, tree);
+ final Set<ClassMapping> classes = new HashSet<>();
+
+ final StringPool pool = new StringPool();
+ for (final MappingTree.ClassMapping cls : tree.getClasses()) {
+ final Map<String, String> methods = new HashMap<>();
+
+ for (final MappingTree.MethodMapping methodMapping : cls.getMethods()) {
+ methods.put(
+ pool.string(methodKey(
+ methodMapping.getName(SPIGOT_NAMESPACE),
+ methodMapping.getDesc(SPIGOT_NAMESPACE)
+ )),
+ pool.string(methodMapping.getName(MOJANG_PLUS_YARN_NAMESPACE))
+ );
+ }
+
+ final ClassMapping map = new ClassMapping(
+ cls.getName(SPIGOT_NAMESPACE).replace('/', '.'),
+ cls.getName(MOJANG_PLUS_YARN_NAMESPACE).replace('/', '.'),
+ Map.copyOf(methods)
+ );
+ classes.add(map);
+ }
+
+ return Set.copyOf(classes);
+ } catch (final IOException ex) {
+ System.err.println("Failed to load mappings for stacktrace deobfuscation.");
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ public static String methodKey(final String obfName, final String obfDescriptor) {
+ return obfName + obfDescriptor;
+ }
+
+ private static final class StringPool {
+ private final Map<String, String> pool = new HashMap<>();
+
+ public String string(final String string) {
+ return this.pool.computeIfAbsent(string, Function.identity());
+ }
+ }
+
+ public record ClassMapping(
+ String obfName,
+ String mojangName,
+ Map<String, String> methodsByObf
+ ) {}
+}
diff --git a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+import com.destroystokyo.paper.PaperConfig;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@DefaultQualifier(NonNull.class)
+public enum StacktraceDeobfuscator {
+ INSTANCE;
+
+ private final Map<Class<?>, Map<String, IntList>> lineMapCache = Collections.synchronizedMap(new LinkedHashMap<>(128, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(final Map.Entry<Class<?>, Map<String, IntList>> eldest) {
+ return this.size() > 127;
+ }
+ });
+
+ public void deobfuscateThrowable(final Throwable throwable) {
+ if (!PaperConfig.deobfuscateStacktraces) {
+ return;
+ }
+
+ throwable.setStackTrace(this.deobfuscateStacktrace(throwable.getStackTrace()));
+ final Throwable cause = throwable.getCause();
+ if (cause != null) {
+ this.deobfuscateThrowable(cause);
+ }
+ for (final Throwable suppressed : throwable.getSuppressed()) {
+ this.deobfuscateThrowable(suppressed);
+ }
+ }
+
+ public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) {
+ if (!PaperConfig.deobfuscateStacktraces) {
+ return traceElements;
+ }
+
+ final @Nullable Map<String, ObfHelper.ClassMapping> mappings = ObfHelper.INSTANCE.mappingsByObfName();
+ if (mappings == null || traceElements.length == 0) {
+ return traceElements;
+ }
+ final StackTraceElement[] result = new StackTraceElement[traceElements.length];
+ for (int i = 0; i < traceElements.length; i++) {
+ final StackTraceElement element = traceElements[i];
+
+ final String className = element.getClassName();
+ final String methodName = element.getMethodName();
+
+ final ObfHelper.ClassMapping classMapping = mappings.get(className);
+ if (classMapping == null) {
+ result[i] = element;
+ continue;
+ }
+
+ final Class<?> clazz;
+ try {
+ clazz = Class.forName(className);
+ } catch (final ClassNotFoundException ex) {
+ throw new RuntimeException(ex);
+ }
+ final @Nullable String methodKey = this.determineMethodForLine(clazz, element.getLineNumber());
+ final @Nullable String mappedMethodName = methodKey == null ? null : classMapping.methodsByObf().get(methodKey);
+
+ result[i] = new StackTraceElement(
+ element.getClassLoaderName(),
+ element.getModuleName(),
+ element.getModuleVersion(),
+ classMapping.mojangName(),
+ mappedMethodName != null ? mappedMethodName : methodName,
+ sourceFileName(classMapping.mojangName()),
+ element.getLineNumber()
+ );
+ }
+ return result;
+ }
+
+ private @Nullable String determineMethodForLine(final Class<?> clazz, final int lineNumber) {
+ final Map<String, IntList> lineMap = this.lineMapCache.computeIfAbsent(clazz, StacktraceDeobfuscator::buildLineMap);
+ for (final var entry : lineMap.entrySet()) {
+ final String methodKey = entry.getKey();
+ final IntList lines = entry.getValue();
+ for (int i = 0, linesSize = lines.size(); i < linesSize; i++) {
+ final int num = lines.getInt(i);
+ if (num == lineNumber) {
+ return methodKey;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static String sourceFileName(final String fullClassName) {
+ final int dot = fullClassName.lastIndexOf('.');
+ final String className = dot == -1
+ ? fullClassName
+ : fullClassName.substring(dot + 1);
+ final String rootClassName = className.split("\\$")[0];
+ return rootClassName + ".java";
+ }
+
+ private static Map<String, IntList> buildLineMap(final Class<?> key) {
+ final Map<String, IntList> lineMap = new HashMap<>();
+ final class LineCollectingMethodVisitor extends MethodVisitor {
+ private final IntList lines = new IntArrayList();
+ private final String name;
+ private final String descriptor;
+
+ LineCollectingMethodVisitor(String name, String descriptor) {
+ super(Opcodes.ASM9);
+ this.name = name;
+ this.descriptor = descriptor;
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ super.visitLineNumber(line, start);
+ this.lines.add(line);
+ }
+
+ @Override
+ public void visitEnd() {
+ super.visitEnd();
+ lineMap.put(ObfHelper.methodKey(this.name, this.descriptor), this.lines);
+ }
+ }
+ final ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) {
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ return new LineCollectingMethodVisitor(name, descriptor);
+ }
+ };
+ try {
+ final ClassReader reader = new ClassReader(key.getName());
+ reader.accept(classVisitor, 0);
+ } catch (final IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ return lineMap;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/io/papermc/paper/util/TraceUtil.java
+++ b/src/main/java/io/papermc/paper/util/TraceUtil.java
@@ -0,0 +0,0 @@ public final class TraceUtil {
public static void dumpTraceForThread(Thread thread, String reason) {
Bukkit.getLogger().warning(thread.getName() + ": " + reason);
- StackTraceElement[] trace = thread.getStackTrace();
+ StackTraceElement[] trace = StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace());
for (StackTraceElement traceElement : trace) {
Bukkit.getLogger().warning("\tat " + traceElement);
}
}
public static void dumpTraceForThread(String reason) {
- new Throwable(reason).printStackTrace();
+ final Throwable throwable = new Throwable(reason);
+ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(throwable);
+ throwable.printStackTrace();
}
}
diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/CrashReport.java
+++ b/src/main/java/net/minecraft/CrashReport.java
@@ -0,0 +0,0 @@ public class CrashReport {
private final SystemReport systemReport = new SystemReport();
public CrashReport(String message, Throwable cause) {
+ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper
this.title = message;
this.exception = cause;
this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit
diff --git a/src/main/java/net/minecraft/CrashReportCategory.java b/src/main/java/net/minecraft/CrashReportCategory.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/CrashReportCategory.java
+++ b/src/main/java/net/minecraft/CrashReportCategory.java
@@ -0,0 +0,0 @@ public class CrashReportCategory {
} else {
this.stackTrace = new StackTraceElement[stackTraceElements.length - 3 - ignoredCallCount];
System.arraycopy(stackTraceElements, 3 + ignoredCallCount, this.stackTrace, 0, this.stackTrace.length);
+ this.stackTrace = io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(this.stackTrace); // Paper
return this.stackTrace.length;
}
}
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
}
com.destroystokyo.paper.PaperConfig.registerCommands();
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now
+ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // load mappings for stacktrace deobf and etc.
// Paper end
this.setPvpAllowed(dedicatedserverproperties.pvp);
diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
log.log( Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity" );
log.log( Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated" );
log.log( Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage());
- for ( StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace() )
+ for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) ) // Paper
{
log.log( Level.SEVERE, "\t\t" + stack );
}
@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
}
log.log( Level.SEVERE, "\tStack:" );
//
- for ( StackTraceElement stack : thread.getStackTrace() )
+ for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()) ) // Paper
{
log.log( Level.SEVERE, "\t\t" + stack );
}
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +0,0 @@
<DefaultRolloverStrategy max="1000"/>
</RollingRandomAccessFile>
<Async name="Async">
+ <AppenderRef ref="rewrite"/>
+ </Async>
+ <Rewrite name="rewrite">
+ <StacktraceDeobfuscatingRewritePolicy />
<AppenderRef ref="File"/>
<AppenderRef ref="TerminalConsole" level="info"/>
<AppenderRef ref="ServerGuiConsole" level="info"/>
- </Async>
+ </Rewrite>
</Appenders>
<Loggers>
<Root level="info">

View file

@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Thu, 23 Apr 2020 01:36:39 -0400
Subject: [PATCH] Don't fire BlockFade on worldgen threads
Caused a deadlock
diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java
@@ -0,0 +0,0 @@ public class FireBlock extends BaseFireBlock {
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
// CraftBukkit start
+ if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation
if (!this.canSurvive(state, world, pos)) {
// Suppress during worldgen
if (!(world instanceof Level)) {
@@ -0,0 +0,0 @@ public class FireBlock extends BaseFireBlock {
return blockState.getHandle();
}
}
- return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE));
+ return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - diff on change, see "don't fire events in world generation"
// CraftBukkit end
}

View file

@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Sat, 9 May 2020 02:01:48 -0400
Subject: [PATCH] Ensure EntityRaider respects game and entity rules for
picking up items
diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/raid/Raider.java
+++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java
@@ -0,0 +0,0 @@ public abstract class Raider extends PatrollingMonster {
@Override
public boolean canUse() {
+ if (!this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items
Raid raid = this.mob.getCurrentRaid();
if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.getLeaderBannerInstance())) {

View file

@ -0,0 +1,26 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Fri, 15 May 2020 01:10:03 -0400
Subject: [PATCH] Ensure safe gateway teleport
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
@@ -0,0 +0,0 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity {
List<Entity> list = world.getEntitiesOfClass(Entity.class, new AABB(pos), TheEndGatewayBlockEntity::canEntityTeleport);
if (!list.isEmpty()) {
- TheEndGatewayBlockEntity.teleportEntity(world, pos, state, (Entity) list.get(world.random.nextInt(list.size())), blockEntity);
+ // Paper start
+ for (Entity entity : list) {
+ if (entity.canChangeDimensions()) {
+ TheEndGatewayBlockEntity.teleportEntity(world, pos, state, entity, blockEntity);
+ break;
+ }
+ }
+ // Paper end
}
if (blockEntity.age % 2400L == 0L) {

View file

@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Tue, 19 Dec 2017 22:57:26 -0500
Subject: [PATCH] ExperienceOrbMergeEvent
Has to be reimplemented at one point maybe
Fired when the server is about to merge 2 experience orbs
Plugins can cancel this if they want to ensure experience orbs do not lose important
metadata such as spawn reason, or conditionally move data from source to target.
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -0,0 +0,0 @@ public class CraftEventFactory {
if (e instanceof net.minecraft.world.entity.ExperienceOrb) {
net.minecraft.world.entity.ExperienceOrb loopItem = (net.minecraft.world.entity.ExperienceOrb) e;
// Paper start
- if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) {
+ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue) && new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) entity.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) loopItem.getBukkitEntity()).callEvent()) { // Paper - ExperienceOrbMergeEvent
long newTotal = (long)xp.value + (long)loopItem.value;
if ((int) newTotal < 0) continue; // Overflow
if (maxValue > 0 && newTotal > (long)maxValue) {

View file

@ -0,0 +1,24 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mark Vainomaa <mikroskeem@mikroskeem.eu>
Date: Fri, 1 May 2020 17:39:26 +0300
Subject: [PATCH] Expose game version
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
return this.bukkitVersion;
}
+ // Paper start - expose game version
+ @Override
+ public String getMinecraftVersion() {
+ return console.getServerVersion();
+ }
+ // Paper end
+
@Override
public List<CraftPlayer> getOnlinePlayers() {
return this.playerView;

View file

@ -0,0 +1,59 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sat, 18 Apr 2020 04:36:11 -0400
Subject: [PATCH] Fix Chunk Post Processing deadlock risk
See: https://gist.github.com/aikar/dd22bbd2a3d78a2fd3d92e95e9f28dc6
as part of post processing a chunk, we can call ChunkConverter.
ChunkConverter then kicks off major physics updates, and when blocks
that have connections across chunk boundries occur, a recursive risk
can occur where A updates a block that triggers a physics request.
That physics request may trigger a chunk request, that then enqueues
a task into the Mailbox ChunkTaskQueueSorter.
If anything requests that same chunk that is in the middle of conversion,
it's mailbox queue is going to be held up, so the subsequent chunk request
will be unable to proceed.
We delay post processing of Chunk.A() 1 "pass" by re stuffing it back into
the executor so that the mailbox ChunkQueue is now considered empty.
This successfully fixed a reoccurring and highly reproduceable crash
for heightmaps.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
};
// CraftBukkit end
+ final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper
// Paper start - distance maps
private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return Either.left(chunk);
});
}, (runnable) -> {
- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
+ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, () -> ChunkMap.this.chunkLoadConversionCallbackExecutor.execute(runnable))); // Paper - delay running Chunk post processing until outside of the sorter to prevent a deadlock scenario when post processing causes another chunk request.
});
completablefuture1.thenAcceptAsync((either) -> {
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
return super.pollTask() || execChunkTask; // Paper
}
} finally {
+ chunkMap.chunkLoadConversionCallbackExecutor.run(); // Paper - Add chunk load conversion callback executor to prevent deadlock due to recursion in the chunk task queue sorter
chunkMap.callbackExecutor.run();
}
// CraftBukkit end

View file

@ -0,0 +1,110 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 19 Apr 2020 00:05:46 -0400
Subject: [PATCH] Fix Longstanding Broken behavior of PlayerJoinEvent
For years, plugin developers have had to delay many things they do
inside of the PlayerJoinEvent by 1 tick to make it actually work.
This all boiled down to 1 reason why: The event fired before the
player was fully ready and joined to the world!
Additionally, if that player logged out on a vehicle, the event
fired before the vehicle was even loaded, so that plugins had no
access to the vehicle during this event either.
This change finally fixes this issue, fully preparing the player
into the world as a fully ready entity, vehicle included.
There should be no plugins that break because of this change, but might
improve consistency with other plugins instead.
For example, if 2 plugins listens to this event, and the first one
teleported the player in the event, then the 2nd plugin actually
would be getting a valid player!
This was very non deterministic. This change will ensure every plugin
receives a deterministic result, and should no longer require 1 tick
delays anymore.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
.printStackTrace();
return;
}
+ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Delay adding to tracker until after list packets
// Paper end
if (!(entity instanceof EnderDragonPart)) {
EntityType<?> entitytypes = entity.getType();
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player {
public double maxHealthCache;
public boolean joining = true;
public boolean sentListPacket = false;
+ public boolean supressTrackerForLogin = false; // Paper
public Integer clientViewDistance;
// CraftBukkit end
public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -0,0 +0,0 @@ public abstract class PlayerList {
this.playersByUUID.put(player.getUUID(), player);
// this.broadcastAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[]{entityplayer})); // CraftBukkit - replaced with loop below
+ // Paper start - correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks
+ player.supressTrackerForLogin = true;
+ worldserver1.addNewPlayer(player);
+ this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer);
+ mountSavedVehicle(player, worldserver1, nbttagcompound);
+ // Paper end
// CraftBukkit start
CraftPlayer bukkitPlayer = player.getBukkitEntity();
@@ -0,0 +0,0 @@ public abstract class PlayerList {
player.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, new ServerPlayer[]{entityplayer1}));
}
player.sentListPacket = true;
+ player.supressTrackerForLogin = false; // Paper
+ ((ServerLevel)player.level).getChunkSource().chunkMap.addEntity(player); // Paper - track entity now
// CraftBukkit end
player.connection.send(new ClientboundSetEntityDataPacket(player.getId(), player.getEntityData(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn
@@ -0,0 +0,0 @@ public abstract class PlayerList {
playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect));
}
+ // Paper start - move vehicle into method so it can be called above - short circuit around that code
+ onPlayerJoinFinish(player, worldserver1, s1);
+ }
+ private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, CompoundTag nbttagcompound) {
+ // Paper end
if (nbttagcompound != null && nbttagcompound.contains("RootVehicle", 10)) {
CompoundTag nbttagcompound1 = nbttagcompound.getCompound("RootVehicle");
// CraftBukkit start
@@ -0,0 +0,0 @@ public abstract class PlayerList {
}
}
+ // Paper start
+ }
+ public void onPlayerJoinFinish(ServerPlayer player, ServerLevel worldserver1, String s1) {
+ // Paper end
player.initInventoryMenu();
// CraftBukkit - Moved from above, added world
// Paper start - Add to collideRule team if needed
@@ -0,0 +0,0 @@ public abstract class PlayerList {
scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam);
}
// Paper end
+ // CraftBukkit - Moved from above, added world
PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ());
}

View file

@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Fri, 8 May 2020 00:49:18 -0400
Subject: [PATCH] Fix PotionEffect ignores icon flag
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
@@ -0,0 +0,0 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
@Override
public boolean addPotionEffect(PotionEffect effect, boolean force) {
- this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN);
+ this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon
return true;
}

View file

@ -0,0 +1,117 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sat, 25 Apr 2020 06:46:35 -0400
Subject: [PATCH] Fix numerous item duplication issues and teleport issues
This notably fixes the newest "Donkey Dupe", but also fixes a lot
of dupe bugs in general around nether portals and entity world transfer
We also fix item duplication generically by anytime we clone an item
to drop it on the ground, destroy the source item.
This avoid an itemstack ever existing twice in the world state pre
clean up stage.
So even if something NEW comes up, it would be impossible to drop the
same item twice because the source was destroyed.
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
} else {
// CraftBukkit start - Capture drops for death event
if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) {
- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack));
+ ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later
return null;
}
// CraftBukkit end
- ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack);
+ ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - clone so we can destroy original
+ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe
entityitem.setDefaultPickUpDelay();
// CraftBukkit start
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
@Nullable
public Entity teleportTo(ServerLevel worldserver, BlockPos location) {
// CraftBukkit end
+ // Paper start - fix bad state entities causing dupes
+ if (!isAlive() || !valid) {
+ LOGGER.warn("Illegal Entity Teleport " + this + " to " + worldserver + ":" + location, new Throwable());
+ return null;
+ }
+ // Paper end
if (this.level instanceof ServerLevel && !this.isRemoved()) {
this.level.getProfiler().push("changeDimension");
// CraftBukkit start
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
// CraftBukkit end
this.level.getProfiler().popPush("reloading");
+ // Paper start - Change lead drop timing to prevent dupe
+ if (this instanceof Mob) {
+ ((Mob) this).dropLeash(true, true); // Paper drop lead
+ }
+ // Paper end
Entity entity = this.getType().create(worldserver);
if (entity != null) {
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
// CraftBukkit start - Forward the CraftEntity to the new entity
this.getBukkitEntity().setHandle(entity);
entity.bukkitEntity = this.getBukkitEntity();
-
- if (this instanceof Mob) {
- ((Mob) this).dropLeash(true, false); // Unleash to prevent duping of leads.
- }
// CraftBukkit end
}
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
}
public boolean canChangeDimensions() {
- return true;
+ return isAlive() && valid; // Paper
}
public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) {
diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
@@ -0,0 +0,0 @@ public class ArmorStand extends LivingEntity {
for (i = 0; i < this.handItems.size(); ++i) {
itemstack = (ItemStack) this.handItems.get(i);
if (!itemstack.isEmpty()) {
- drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops
+ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe
this.handItems.set(i, ItemStack.EMPTY);
}
}
@@ -0,0 +0,0 @@ public class ArmorStand extends LivingEntity {
for (i = 0; i < this.armorItems.size(); ++i) {
itemstack = (ItemStack) this.armorItems.get(i);
if (!itemstack.isEmpty()) {
- drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops
+ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe
this.armorItems.set(i, ItemStack.EMPTY);
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -0,0 +0,0 @@ public class CraftEventFactory {
for (org.bukkit.inventory.ItemStack stack : event.getDrops()) {
if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue;
- world.dropItem(entity.getLocation(), stack);
+ world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS
+ if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items
}
return event;

View file

@ -0,0 +1,20 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: chickeneer <emcchickeneer@gmail.com>
Date: Fri, 5 Jun 2020 20:02:04 -0500
Subject: [PATCH] Fix villager trading demand - MC-163962
Prevent demand from going negative and tending to negative infinity
diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java
+++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java
@@ -0,0 +0,0 @@ public class MerchantOffer {
}
public void updateDemand() {
- this.demand = this.demand + this.uses - (this.maxUses - this.uses);
+ this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper
}
public ItemStack assemble() {

View file

@ -0,0 +1,926 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MiniDigger | Martin <admin@minidigger.dev>
Date: Fri, 3 Jan 2020 16:26:19 +0100
Subject: [PATCH] Implement Mob Goal API
diff --git a/build.gradle.kts b/build.gradle.kts
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -0,0 +0,0 @@ dependencies {
implementation("net.fabricmc:mapping-io:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation
+ testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test
testImplementation("junit:junit:4.13.1")
testImplementation("org.hamcrest:hamcrest-library:1.3")
}
diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.entity.ai;
+
+import com.destroystokyo.paper.entity.RangedEntity;
+import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import io.papermc.paper.util.ObfHelper;
+import java.lang.reflect.Constructor;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import net.minecraft.world.entity.FlyingMob;
+import net.minecraft.world.entity.PathfinderMob;
+import net.minecraft.world.entity.TamableAnimal;
+import net.minecraft.world.entity.ai.goal.Goal;
+import net.minecraft.world.entity.ambient.AmbientCreature;
+import net.minecraft.world.entity.animal.AbstractFish;
+import net.minecraft.world.entity.animal.AbstractGolem;
+import net.minecraft.world.entity.animal.AbstractSchoolingFish;
+import net.minecraft.world.entity.animal.Animal;
+import net.minecraft.world.entity.animal.Pufferfish;
+import net.minecraft.world.entity.animal.ShoulderRidingEntity;
+import net.minecraft.world.entity.animal.SnowGolem;
+import net.minecraft.world.entity.animal.WaterAnimal;
+import net.minecraft.world.entity.animal.horse.AbstractChestedHorse;
+import net.minecraft.world.entity.boss.wither.WitherBoss;
+import net.minecraft.world.entity.monster.AbstractIllager;
+import net.minecraft.world.entity.monster.EnderMan;
+import net.minecraft.world.entity.monster.PatrollingMonster;
+import net.minecraft.world.entity.monster.RangedAttackMob;
+import net.minecraft.world.entity.monster.SpellcasterIllager;
+import net.minecraft.world.entity.monster.ZombifiedPiglin;
+import net.minecraft.world.entity.monster.piglin.AbstractPiglin;
+import org.bukkit.NamespacedKey;
+import org.bukkit.entity.AbstractHorse;
+import org.bukkit.entity.AbstractSkeleton;
+import org.bukkit.entity.AbstractVillager;
+import org.bukkit.entity.Ageable;
+import org.bukkit.entity.Ambient;
+import org.bukkit.entity.Animals;
+import org.bukkit.entity.Bat;
+import org.bukkit.entity.Bee;
+import org.bukkit.entity.Blaze;
+import org.bukkit.entity.Cat;
+import org.bukkit.entity.CaveSpider;
+import org.bukkit.entity.ChestedHorse;
+import org.bukkit.entity.Chicken;
+import org.bukkit.entity.Cod;
+import org.bukkit.entity.Cow;
+import org.bukkit.entity.Creature;
+import org.bukkit.entity.Creeper;
+import org.bukkit.entity.Dolphin;
+import org.bukkit.entity.Donkey;
+import org.bukkit.entity.Drowned;
+import org.bukkit.entity.ElderGuardian;
+import org.bukkit.entity.EnderDragon;
+import org.bukkit.entity.Enderman;
+import org.bukkit.entity.Endermite;
+import org.bukkit.entity.Evoker;
+import org.bukkit.entity.Fish;
+import org.bukkit.entity.Flying;
+import org.bukkit.entity.Fox;
+import org.bukkit.entity.Ghast;
+import org.bukkit.entity.Giant;
+import org.bukkit.entity.Golem;
+import org.bukkit.entity.Guardian;
+import org.bukkit.entity.Hoglin;
+import org.bukkit.entity.Horse;
+import org.bukkit.entity.Husk;
+import org.bukkit.entity.Illager;
+import org.bukkit.entity.Illusioner;
+import org.bukkit.entity.IronGolem;
+import org.bukkit.entity.Llama;
+import org.bukkit.entity.MagmaCube;
+import org.bukkit.entity.Mob;
+import org.bukkit.entity.Monster;
+import org.bukkit.entity.Mule;
+import org.bukkit.entity.MushroomCow;
+import org.bukkit.entity.Ocelot;
+import org.bukkit.entity.Panda;
+import org.bukkit.entity.Parrot;
+import org.bukkit.entity.Phantom;
+import org.bukkit.entity.Pig;
+import org.bukkit.entity.PigZombie;
+import org.bukkit.entity.Piglin;
+import org.bukkit.entity.PiglinAbstract;
+import org.bukkit.entity.PiglinBrute;
+import org.bukkit.entity.Pillager;
+import org.bukkit.entity.PolarBear;
+import org.bukkit.entity.PufferFish;
+import org.bukkit.entity.Rabbit;
+import org.bukkit.entity.Raider;
+import org.bukkit.entity.Ravager;
+import org.bukkit.entity.Salmon;
+import org.bukkit.entity.Sheep;
+import org.bukkit.entity.Shulker;
+import org.bukkit.entity.Silverfish;
+import org.bukkit.entity.Skeleton;
+import org.bukkit.entity.SkeletonHorse;
+import org.bukkit.entity.Slime;
+import org.bukkit.entity.Snowman;
+import org.bukkit.entity.Spellcaster;
+import org.bukkit.entity.Spider;
+import org.bukkit.entity.Squid;
+import org.bukkit.entity.Stray;
+import org.bukkit.entity.Strider;
+import org.bukkit.entity.Tameable;
+import org.bukkit.entity.TraderLlama;
+import org.bukkit.entity.TropicalFish;
+import org.bukkit.entity.Turtle;
+import org.bukkit.entity.Vex;
+import org.bukkit.entity.Villager;
+import org.bukkit.entity.Vindicator;
+import org.bukkit.entity.WanderingTrader;
+import org.bukkit.entity.WaterMob;
+import org.bukkit.entity.Witch;
+import org.bukkit.entity.Wither;
+import org.bukkit.entity.WitherSkeleton;
+import org.bukkit.entity.Wolf;
+import org.bukkit.entity.Zoglin;
+import org.bukkit.entity.Zombie;
+import org.bukkit.entity.ZombieHorse;
+import org.bukkit.entity.ZombieVillager;
+
+public class MobGoalHelper {
+
+ private static final BiMap<String, String> deobfuscationMap = HashBiMap.create();
+ private static final Map<Class<? extends Goal>, Class<? extends Mob>> entityClassCache = new HashMap<>();
+ private static final Map<Class<? extends net.minecraft.world.entity.Mob>, Class<? extends Mob>> bukkitMap = new HashMap<>();
+
+ static final Set<String> ignored = new HashSet<>();
+
+ static {
+ // TODO these kinda should be checked on each release, in case obfuscation changes
+ deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee");
+
+ ignored.add("goal_selector_1");
+ ignored.add("goal_selector_2");
+ ignored.add("selector_1");
+ ignored.add("selector_2");
+ ignored.add("wrapped");
+
+ bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class);
+ bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class);
+ bukkitMap.put(AmbientCreature.class, Ambient.class);
+ bukkitMap.put(Animal.class, Animals.class);
+ bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class);
+ bukkitMap.put(PathfinderMob.class, Creature.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class);
+ bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class);
+ bukkitMap.put(EnderMan.class, Enderman.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class);
+ bukkitMap.put(AbstractFish.class, Fish.class);
+ bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough
+ bukkitMap.put(FlyingMob.class, Flying.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class);
+ bukkitMap.put(AbstractGolem.class, Golem.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class);
+ bukkitMap.put(AbstractChestedHorse.class, ChestedHorse.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class);
+ bukkitMap.put(AbstractIllager.class, Illager.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class);
+ bukkitMap.put(SpellcasterIllager.class, Spellcaster.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class);
+ bukkitMap.put(PatrollingMonster.class, Raider.class); // close enough
+ bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class);
+ bukkitMap.put(ShoulderRidingEntity.class, Parrot.class); // close enough
+ bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class);
+ bukkitMap.put(ZombifiedPiglin.class, PigZombie.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class);
+ bukkitMap.put(Pufferfish.class, PufferFish.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class);
+ bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class);
+ bukkitMap.put(SnowGolem.class, Snowman.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class);
+ bukkitMap.put(TamableAnimal.class, Tameable.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class);
+ bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class);
+ bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class);
+ bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class);
+ bukkitMap.put(WaterAnimal.class, WaterMob.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class);
+ bukkitMap.put(WitherBoss.class, Wither.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class);
+ bukkitMap.put(AbstractPiglin.class, PiglinAbstract.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class);
+ bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, org.bukkit.entity.GlowSquid.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, org.bukkit.entity.Axolotl.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, org.bukkit.entity.Goat.class);
+ }
+
+ public static String getUsableName(Class<?> clazz) {
+ String name = ObfHelper.INSTANCE.deobfClassName(clazz.getName());
+ name = name.substring(name.lastIndexOf(".") + 1);
+ boolean flag = false;
+ // inner classes
+ if (name.contains("$")) {
+ String cut = name.substring(name.indexOf("$") + 1);
+ if (cut.length() <= 2) {
+ name = name.replace("Entity", "");
+ name = name.replace("$", "_");
+ flag = true;
+ } else {
+ // mapped, wooo
+ name = cut;
+ }
+ }
+ name = name.replace("PathfinderGoal", "");
+ name = name.replace("TargetGoal", "");
+ name = name.replace("Goal", "");
+ StringBuilder sb = new StringBuilder();
+ for (char c : name.toCharArray()) {
+ if (c >= 'A' && c <= 'Z') {
+ sb.append("_");
+ sb.append(Character.toLowerCase(c));
+ } else {
+ sb.append(c);
+ }
+ }
+ name = sb.toString();
+ name = name.replaceFirst("_", "");
+
+ if (flag && !deobfuscationMap.containsKey(name.toLowerCase()) && !ignored.contains(name)) {
+ System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase() + ")");
+ }
+
+ // did we rename this key?
+ return deobfuscationMap.getOrDefault(name, name);
+ }
+
+ public static EnumSet<GoalType> vanillaToPaper(OptimizedSmallEnumSet<Goal.Flag> types) {
+ EnumSet<GoalType> goals = EnumSet.noneOf(GoalType.class);
+ for (GoalType type : GoalType.values()) {
+ if (types.hasElement(paperToVanilla(type))) {
+ goals.add(type);
+ }
+ }
+ return goals;
+ }
+
+ public static GoalType vanillaToPaper(Goal.Flag type) {
+ switch (type) {
+ case MOVE:
+ return GoalType.MOVE;
+ case LOOK:
+ return GoalType.LOOK;
+ case JUMP:
+ return GoalType.JUMP;
+ case UNKNOWN_BEHAVIOR:
+ return GoalType.UNKNOWN_BEHAVIOR;
+ case TARGET:
+ return GoalType.TARGET;
+ default:
+ throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name());
+ }
+ }
+
+ public static EnumSet<Goal.Flag> paperToVanilla(EnumSet<GoalType> types) {
+ EnumSet<Goal.Flag> goals = EnumSet.noneOf(Goal.Flag.class);
+ for (GoalType type : types) {
+ goals.add(paperToVanilla(type));
+ }
+ return goals;
+ }
+
+ public static Goal.Flag paperToVanilla(GoalType type) {
+ switch (type) {
+ case MOVE:
+ return Goal.Flag.MOVE;
+ case LOOK:
+ return Goal.Flag.LOOK;
+ case JUMP:
+ return Goal.Flag.JUMP;
+ case UNKNOWN_BEHAVIOR:
+ return Goal.Flag.UNKNOWN_BEHAVIOR;
+ case TARGET:
+ return Goal.Flag.TARGET;
+ default:
+ throw new IllegalArgumentException("Unknown paper mob goal type " + type.name());
+ }
+ }
+
+ public static <T extends Mob> GoalKey<T> getKey(Class<? extends Goal> goalClass) {
+ String name = getUsableName(goalClass);
+ if (ignored.contains(name)) {
+ //noinspection unchecked
+ return (GoalKey<T>) GoalKey.of(Mob.class, NamespacedKey.minecraft(name));
+ }
+ return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name));
+ }
+
+ public static <T extends Mob> Class<T> getEntity(Class<? extends Goal> goalClass) {
+ //noinspection unchecked
+ return (Class<T>) entityClassCache.computeIfAbsent(goalClass, key -> {
+ for (Constructor<?> ctor : key.getDeclaredConstructors()) {
+ for (int i = 0; i < ctor.getParameterCount(); i++) {
+ Class<?> param = ctor.getParameterTypes()[i];
+ if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) {
+ //noinspection unchecked
+ return toBukkitClass((Class<? extends net.minecraft.world.entity.Mob>) param);
+ } else if (RangedAttackMob.class.isAssignableFrom(param)) {
+ return RangedEntity.class;
+ }
+ }
+ }
+ throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient?
+ });
+ }
+
+ public static Class<? extends Mob> toBukkitClass(Class<? extends net.minecraft.world.entity.Mob> nmsClass) {
+ Class<? extends Mob> bukkitClass = bukkitMap.get(nmsClass);
+ if (bukkitClass == null) {
+ throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob?
+ }
+ return bukkitClass;
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.entity.ai;
+
+import org.bukkit.entity.Mob;
+
+/**
+ * Wraps api in vanilla
+ */
+public class PaperCustomGoal<T extends Mob> extends net.minecraft.world.entity.ai.goal.Goal {
+
+ private final Goal<T> handle;
+
+ public PaperCustomGoal(Goal<T> handle) {
+ this.handle = handle;
+
+ this.setFlags(MobGoalHelper.paperToVanilla(handle.getTypes()));
+ if (this.getFlags().size() == 0) {
+ this.getFlags().addUnchecked(Flag.UNKNOWN_BEHAVIOR);
+ }
+ }
+
+ @Override
+ public boolean canUse() {
+ return handle.shouldActivate();
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ return handle.shouldStayActive();
+ }
+
+ @Override
+ public void start() {
+ handle.start();
+ }
+
+ @Override
+ public void stop() {
+ handle.stop();
+ }
+
+ @Override
+ public void tick() {
+ handle.tick();
+ }
+
+ public Goal<T> getHandle() {
+ return handle;
+ }
+
+ public GoalKey<T> getKey() {
+ return handle.getKey();
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.entity.ai;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import net.minecraft.world.entity.ai.goal.GoalSelector;
+import net.minecraft.world.entity.ai.goal.WrappedGoal;
+import org.bukkit.craftbukkit.entity.CraftMob;
+import org.bukkit.entity.Mob;
+
+public class PaperMobGoals implements MobGoals {
+
+ @Override
+ public <T extends Mob> void addGoal(T mob, int priority, Goal<T> goal) {
+ CraftMob craftMob = (CraftMob) mob;
+ getHandle(craftMob, goal.getTypes()).addGoal(priority, new PaperCustomGoal<>(goal));
+ }
+
+ @Override
+ public <T extends Mob> void removeGoal(T mob, Goal<T> goal) {
+ CraftMob craftMob = (CraftMob) mob;
+ if (goal instanceof PaperCustomGoal) {
+ getHandle(craftMob, goal.getTypes()).removeGoal((net.minecraft.world.entity.ai.goal.Goal) goal);
+ } else if (goal instanceof PaperVanillaGoal) {
+ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal<?>) goal).getHandle());
+ } else {
+ List<net.minecraft.world.entity.ai.goal.Goal> toRemove = new LinkedList<>();
+ for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).availableGoals) {
+ if (item.getGoal() instanceof PaperCustomGoal) {
+ //noinspection unchecked
+ if (((PaperCustomGoal<T>) item.getGoal()).getHandle() == goal) {
+ toRemove.add(item.getGoal());
+ }
+ }
+ }
+
+ for (net.minecraft.world.entity.ai.goal.Goal g : toRemove) {
+ getHandle(craftMob, goal.getTypes()).removeGoal(g);
+ }
+ }
+ }
+
+ @Override
+ public <T extends Mob> void removeAllGoals(T mob) {
+ for (GoalType type : GoalType.values()) {
+ removeAllGoals(mob, type);
+ }
+ }
+
+ @Override
+ public <T extends Mob> void removeAllGoals(T mob, GoalType type) {
+ for (Goal<T> goal : getAllGoals(mob, type)) {
+ removeGoal(mob, goal);
+ }
+ }
+
+ @Override
+ public <T extends Mob> void removeGoal(T mob, GoalKey<T> key) {
+ for (Goal<T> goal : getGoals(mob, key)) {
+ removeGoal(mob, goal);
+ }
+ }
+
+ @Override
+ public <T extends Mob> boolean hasGoal(T mob, GoalKey<T> key) {
+ for (Goal<T> g : getAllGoals(mob)) {
+ if (g.getKey().equals(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public <T extends Mob> Goal<T> getGoal(T mob, GoalKey<T> key) {
+ for (Goal<T> g : getAllGoals(mob)) {
+ if (g.getKey().equals(key)) {
+ return g;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public <T extends Mob> Collection<Goal<T>> getGoals(T mob, GoalKey<T> key) {
+ Set<Goal<T>> goals = new HashSet<>();
+ for (Goal<T> g : getAllGoals(mob)) {
+ if (g.getKey().equals(key)) {
+ goals.add(g);
+ }
+ }
+ return goals;
+ }
+
+ @Override
+ public <T extends Mob> Collection<Goal<T>> getAllGoals(T mob) {
+ Set<Goal<T>> goals = new HashSet<>();
+ for (GoalType type : GoalType.values()) {
+ goals.addAll(getAllGoals(mob, type));
+ }
+ return goals;
+ }
+
+ @Override
+ public <T extends Mob> Collection<Goal<T>> getAllGoals(T mob, GoalType type) {
+ CraftMob craftMob = (CraftMob) mob;
+ Set<Goal<T>> goals = new HashSet<>();
+ for (WrappedGoal item : getHandle(craftMob, type).availableGoals) {
+ if (!item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) {
+ continue;
+ }
+
+ if (item.getGoal() instanceof PaperCustomGoal) {
+ //noinspection unchecked
+ goals.add(((PaperCustomGoal<T>) item.getGoal()).getHandle());
+ } else {
+ goals.add(item.getGoal().asPaperVanillaGoal());
+ }
+ }
+ return goals;
+ }
+
+ @Override
+ public <T extends Mob> Collection<Goal<T>> getAllGoalsWithout(T mob, GoalType type) {
+ CraftMob craftMob = (CraftMob) mob;
+ Set<Goal<T>> goals = new HashSet<>();
+ for (GoalType internalType : GoalType.values()) {
+ if (internalType == type) {
+ continue;
+ }
+ for (WrappedGoal item : getHandle(craftMob, internalType).availableGoals) {
+ if (item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) {
+ continue;
+ }
+
+ if (item.getGoal() instanceof PaperCustomGoal) {
+ //noinspection unchecked
+ goals.add(((PaperCustomGoal<T>) item.getGoal()).getHandle());
+ } else {
+ goals.add(item.getGoal().asPaperVanillaGoal());
+ }
+ }
+ }
+ return goals;
+ }
+
+ @Override
+ public <T extends Mob> Collection<Goal<T>> getRunningGoals(T mob) {
+ Set<Goal<T>> goals = new HashSet<>();
+ for (GoalType type : GoalType.values()) {
+ goals.addAll(getRunningGoals(mob, type));
+ }
+ return goals;
+ }
+
+ @Override
+ public <T extends Mob> Collection<Goal<T>> getRunningGoals(T mob, GoalType type) {
+ CraftMob craftMob = (CraftMob) mob;
+ Set<Goal<T>> goals = new HashSet<>();
+ getHandle(craftMob, type).getRunningGoals()
+ .filter(item -> item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type)))
+ .forEach(item -> {
+ if (item.getGoal() instanceof PaperCustomGoal) {
+ //noinspection unchecked
+ goals.add(((PaperCustomGoal<T>) item.getGoal()).getHandle());
+ } else {
+ goals.add(item.getGoal().asPaperVanillaGoal());
+ }
+ });
+ return goals;
+ }
+
+ @Override
+ public <T extends Mob> Collection<Goal<T>> getRunningGoalsWithout(T mob, GoalType type) {
+ CraftMob craftMob = (CraftMob) mob;
+ Set<Goal<T>> goals = new HashSet<>();
+ for (GoalType internalType : GoalType.values()) {
+ if (internalType == type) {
+ continue;
+ }
+ getHandle(craftMob, internalType).getRunningGoals()
+ .filter(item -> !item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type)))
+ .forEach(item -> {
+ if (item.getGoal() instanceof PaperCustomGoal) {
+ //noinspection unchecked
+ goals.add(((PaperCustomGoal<T>) item.getGoal()).getHandle());
+ } else {
+ goals.add(item.getGoal().asPaperVanillaGoal());
+ }
+ });
+ }
+ return goals;
+ }
+
+ private GoalSelector getHandle(CraftMob mob, EnumSet<GoalType> types) {
+ if (types.contains(GoalType.TARGET)) {
+ return mob.getHandle().targetSelector;
+ } else {
+ return mob.getHandle().goalSelector;
+ }
+ }
+
+ private GoalSelector getHandle(CraftMob mob, GoalType type) {
+ if (type == GoalType.TARGET) {
+ return mob.getHandle().targetSelector;
+ } else {
+ return mob.getHandle().goalSelector;
+ }
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.entity.ai;
+
+import java.util.EnumSet;
+import net.minecraft.world.entity.ai.goal.Goal;
+import org.bukkit.entity.Mob;
+
+/**
+ * Wraps vanilla in api
+ */
+public class PaperVanillaGoal<T extends Mob> implements VanillaGoal<T> {
+
+ private final Goal handle;
+ private final GoalKey<T> key;
+
+ private final EnumSet<GoalType> types;
+
+ public PaperVanillaGoal(Goal handle) {
+ this.handle = handle;
+ this.key = MobGoalHelper.getKey(handle.getClass());
+ this.types = MobGoalHelper.vanillaToPaper(handle.getFlags());
+ }
+
+ public Goal getHandle() {
+ return handle;
+ }
+
+ @Override
+ public boolean shouldActivate() {
+ return handle.canUse();
+ }
+
+ @Override
+ public boolean shouldStayActive() {
+ return handle.canContinueToUse();
+ }
+
+ @Override
+ public void start() {
+ handle.start();
+ }
+
+ @Override
+ public void stop() {
+ handle.stop();
+ }
+
+ @Override
+ public void tick() {
+ handle.tick();
+ }
+
+ @Override
+ public GoalKey<T> getKey() {
+ return key;
+ }
+
+ @Override
+ public EnumSet<GoalType> getTypes() {
+ return types;
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java
+++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java
@@ -0,0 +0,0 @@ public final class OptimizedSmallEnumSet<E extends Enum<E>> {
return (other.backingSet & this.backingSet) != 0;
}
+ public boolean hasElement(final E element) {
+ return (this.backingSet & (1L << element.ordinal())) != 0;
+ }
+
public void forEach(final E[] values, final Consumer<E> action) {
long iterator = this.getBackingSet();
int wrappedGoalSize = this.size();
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java
@@ -0,0 +0,0 @@ public abstract class Goal {
private final EnumSet<Goal.Flag> flags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be.
private final com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
+ // Paper start make sure goaltypes is never empty
+ public Goal() {
+ if (this.goalTypes.size() == 0) {
+ this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR);
+ }
+ }
+ // Paper end
+
public abstract boolean canUse();
public boolean canContinueToUse() {
@@ -0,0 +0,0 @@ public abstract class Goal {
// Paper start - remove streams from pathfindergoalselector
this.goalTypes.clear();
this.goalTypes.addAllUnchecked(controls);
+ // make sure its never empty
+ if (this.goalTypes.size() == 0) {
+ this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR);
+ }
// Paper end - remove streams from pathfindergoalselector
}
@@ -0,0 +0,0 @@ public abstract class Goal {
return Mth.positiveCeilDiv(serverTicks, 2);
}
+ // Paper start - mob goal api
+ private com.destroystokyo.paper.entity.ai.PaperVanillaGoal<?> vanillaGoal = null;
+ public <T extends org.bukkit.entity.Mob> com.destroystokyo.paper.entity.ai.Goal<T> asPaperVanillaGoal() {
+ if(this.vanillaGoal == null) {
+ this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this);
+ }
+ //noinspection unchecked
+ return (com.destroystokyo.paper.entity.ai.Goal<T>) this.vanillaGoal;
+ }
+ // Paper end - mob goal api
+
public static enum Flag {
+ UNKNOWN_BEHAVIOR, // Paper - add UNKNOWN_BEHAVIOR
MOVE,
LOOK,
JUMP,
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
public boolean isStopping() {
return net.minecraft.server.MinecraftServer.getServer().hasStopped();
}
+
+ private com.destroystokyo.paper.entity.ai.MobGoals mobGoals = new com.destroystokyo.paper.entity.ai.PaperMobGoals();
+ @Override
+ public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() {
+ return mobGoals;
+ }
// Paper end
}
diff --git a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.entity.ai;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.bukkit.entity.Mob;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ScanResult;
+
+public class VanillaMobGoalTest {
+
+ @Test
+ public void testKeys() {
+ List<GoalKey<?>> deprecated = new ArrayList<>();
+ List<GoalKey<?>> keys = new ArrayList<>();
+ for (Field field : VanillaGoal.class.getFields()) {
+ if (field.getType().equals(GoalKey.class)) {
+ try {
+ GoalKey<?> goalKey = (GoalKey<?>) field.get(null);
+ if (field.getAnnotation(Deprecated.class) != null) {
+ deprecated.add(goalKey);
+ } else {
+ keys.add(goalKey);
+ }
+ } catch (IllegalAccessException e) {
+ System.out.println("Skipping " + field.getName() + ": " + e.getMessage());
+ }
+ }
+ }
+
+ List<Class<?>> classes;
+ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) {
+ classes = scanResult.getSubclasses(net.minecraft.world.entity.ai.goal.Goal.class.getName()).loadClasses();
+ }
+
+ List<GoalKey<?>> vanillaNames = classes.stream()
+ .filter(VanillaMobGoalTest::hasNoEnclosingClass)
+ .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
+ .filter(clazz -> !net.minecraft.world.entity.ai.goal.WrappedGoal.class.equals(clazz)) // TODO - properly fix
+ .map(goalClass -> MobGoalHelper.getKey((Class<? extends net.minecraft.world.entity.ai.goal.Goal>) goalClass))
+ .collect(Collectors.toList());
+
+ List<GoalKey<?>> missingFromAPI = new ArrayList<>(vanillaNames);
+ missingFromAPI.removeAll(keys);
+ missingFromAPI.removeIf(k -> MobGoalHelper.ignored.contains(k.getNamespacedKey().getKey()));
+ List<GoalKey<?>> missingFromVanilla = new ArrayList<>(keys);
+ missingFromVanilla.removeAll(vanillaNames);
+
+ boolean shouldFail = false;
+ if (missingFromAPI.size() != 0) {
+ System.out.println("Missing from API: ");
+ for (GoalKey<?> key : missingFromAPI) {
+ System.out.println("GoalKey<" + key.getEntityClass().getSimpleName() + "> " + key.getNamespacedKey().getKey().toUpperCase() +
+ " = GoalKey.of(" + key.getEntityClass().getSimpleName() + ".class, NamespacedKey.minecraft(\"" + key.getNamespacedKey().getKey() + "\"));");
+ }
+ shouldFail = true;
+ }
+ if (missingFromVanilla.size() != 0) {
+ System.out.println("Missing from vanilla: ");
+ missingFromVanilla.forEach(System.out::println);
+ shouldFail = true;
+ }
+
+ if (deprecated.size() != 0) {
+ System.out.println("Deprecated (might want to remove them at some point): ");
+ deprecated.forEach(System.out::println);
+ }
+
+ if (shouldFail) Assert.fail("See above");
+ }
+
+ private static boolean hasNoEnclosingClass(Class<?> clazz) {
+ return clazz.getEnclosingClass() == null || hasNoEnclosingClass(clazz.getSuperclass());
+ }
+
+ @Test
+ public void testBukkitMap() {
+ List<Class<?>> classes;
+ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft.world.entity").scan()) {
+ classes = scanResult.getSubclasses("net.minecraft.world.entity.Mob").loadClasses();
+ }
+ Assert.assertNotEquals("There are supposed to be more than 0 entity types!", Collections.emptyList(), classes);
+
+ boolean shouldFail = false;
+ for (Class<?> nmsClass : classes) {
+ Class<? extends Mob> bukkitClass = MobGoalHelper.toBukkitClass((Class<? extends net.minecraft.world.entity.Mob>) nmsClass);
+ if (bukkitClass == null) {
+ shouldFail = true;
+ System.out.println("Missing bukkitMap.put(" + nmsClass.getSimpleName() + ".class, " + nmsClass.getSimpleName().replace("Entity", "") + ".class);");
+ }
+ }
+
+ if (shouldFail) Assert.fail("See above");
+ }
+}

View file

@ -0,0 +1,245 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 19 Apr 2020 04:28:29 -0400
Subject: [PATCH] Load Chunks for Login Asynchronously
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player {
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
public ServerGamePacketListenerImpl connection;
+ public net.minecraft.network.Connection networkManager; // Paper
public final MinecraftServer server;
public final ServerPlayerGameMode gameMode;
private final PlayerAdvancements advancements;
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player {
public boolean joining = true;
public boolean sentListPacket = false;
public boolean supressTrackerForLogin = false; // Paper
+ public boolean didPlayerJoinEvent = false; // Paper
public Integer clientViewDistance;
// CraftBukkit end
public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/TicketType.java
+++ b/src/main/java/net/minecraft/server/level/TicketType.java
@@ -0,0 +0,0 @@ public class TicketType<T> {
public static final TicketType<ChunkPos> FORCED = TicketType.create("forced", Comparator.comparingLong(ChunkPos::toLong));
public static final TicketType<ChunkPos> LIGHT = TicketType.create("light", Comparator.comparingLong(ChunkPos::toLong));
public static final TicketType<BlockPos> PORTAL = TicketType.create("portal", Vec3i::compareTo, 300);
+ public static final TicketType<Long> LOGIN = create("login", Long::compareTo, 100); // Paper
public static final TicketType<Integer> POST_TELEPORT = TicketType.create("post_teleport", Integer::compareTo, 5);
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
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
private static final int LATENCY_CHECK_INTERVAL = 15000;
public final Connection connection;
private final MinecraftServer server;
+ public Runnable playerJoinReady; // Paper
public ServerPlayer player;
private int tickCount;
private long keepAliveTime = Util.getMillis();
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
// CraftBukkit end
public void tick() {
+ // Paper start - login async
+ Runnable playerJoinReady = this.playerJoinReady;
+ if (playerJoinReady != null) {
+ this.playerJoinReady = null;
+ playerJoinReady.run();
+ }
+ // Don't tick if not valid (dead), otherwise we load chunks below
+ if (this.player.valid) {
+ // Paper end
this.resetPosition();
this.player.xo = this.player.getX();
this.player.yo = this.player.getY();
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
this.lastVehicle = null;
this.clientVehicleIsFloating = false;
this.aboveGroundVehicleTickCount = 0;
- }
+ }} // Paper - end if (valid)
this.server.getProfiler().push("keepAlive");
// Paper Start - give clients a longer time to respond to pings as per pre 1.12.2 timings
diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -0,0 +0,0 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener
}
// Paper end
} else if (this.state == ServerLoginPacketListenerImpl.State.DELAY_ACCEPT) {
- ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId());
+ ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper
if (entityplayer == null) {
this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT;
@@ -0,0 +0,0 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener
}
this.connection.send(new ClientboundGameProfilePacket(this.gameProfile));
- ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId());
+ ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper
try {
ServerPlayer entityplayer1 = this.server.getPlayerList().getPlayerForLogin(this.gameProfile, s); // CraftBukkit - add player reference
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -0,0 +0,0 @@ import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket;
import net.minecraft.network.protocol.game.ClientboundChatPacket;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
+import net.minecraft.network.protocol.game.ClientboundDisconnectPacket;
import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import net.minecraft.network.protocol.game.ClientboundInitializeBorderPacket;
@@ -0,0 +0,0 @@ public abstract class PlayerList {
private final IpBanList ipBans;
private final ServerOpList ops;
private final UserWhiteList whitelist;
+ private final Map<UUID, ServerPlayer> pendingPlayers = Maps.newHashMap(); // Paper
// CraftBukkit start
// private final Map<UUID, ServerStatisticManager> stats;
// private final Map<UUID, AdvancementDataPlayer> advancements;
@@ -0,0 +0,0 @@ public abstract class PlayerList {
}
public void placeNewPlayer(Connection connection, ServerPlayer player) {
+ ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper
+ if (prev != null) {
+ disconnectPendingPlayer(prev);
+ }
+ player.networkManager = connection; // Paper
player.loginTime = System.currentTimeMillis(); // Paper
GameProfile gameprofile = player.getGameProfile();
GameProfileCache usercache = this.server.getProfileCache();
@@ -0,0 +0,0 @@ public abstract class PlayerList {
if (nbttagcompound != null && nbttagcompound.contains("bukkit")) {
CompoundTag bukkit = nbttagcompound.getCompound("bukkit");
s = bukkit.contains("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s;
- }
+ }String lastKnownName = s; // Paper
// CraftBukkit end
if (nbttagcompound != null) {
@@ -0,0 +0,0 @@ public abstract class PlayerList {
player.getRecipeBook().sendInitialRecipeBook(player);
this.updateEntireScoreboard(worldserver1.getScoreboard(), player);
this.server.invalidateStatus();
+ // Paper start - async load spawn in chunk
+ ServerLevel finalWorldserver = worldserver1;
+ int chunkX = loc.getBlockX() >> 4;
+ int chunkZ = loc.getBlockZ() >> 4;
+ final net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
+ net.minecraft.server.level.ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap;
+ net.minecraft.server.level.DistanceManager distanceManager = playerChunkMap.distanceManager;
+ distanceManager.addTicketAtLevel(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong());
+ worldserver1.getChunkSource().runDistanceManagerUpdates();
+ worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> {
+ net.minecraft.server.level.ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong());
+ if (updatingChunk != null) {
+ return updatingChunk.getEntityTickingChunkFuture();
+ } else {
+ return java.util.concurrent.CompletableFuture.completedFuture(chunk);
+ }
+ }).thenAccept(chunk -> {
+ playerconnection.playerJoinReady = () -> {
+ postChunkLoadJoin(
+ player, finalWorldserver, connection, playerconnection,
+ nbttagcompound, connection.getRemoteAddress().toString(), lastKnownName
+ );
+ };
+ });
+ }
+
+ public ServerPlayer getActivePlayer(UUID uuid) {
+ ServerPlayer player = this.playersByUUID.get(uuid);
+ return player != null ? player : pendingPlayers.get(uuid);
+ }
+
+ void disconnectPendingPlayer(ServerPlayer entityplayer) {
+ TranslatableComponent msg = new TranslatableComponent("multiplayer.disconnect.duplicate_login", new Object[0]);
+ entityplayer.networkManager.send(new ClientboundDisconnectPacket(msg), (future) -> {
+ entityplayer.networkManager.disconnect(msg);
+ entityplayer.networkManager = null;
+ });
+ }
+
+ private void postChunkLoadJoin(ServerPlayer player, ServerLevel worldserver1, Connection networkmanager, ServerGamePacketListenerImpl playerconnection, CompoundTag nbttagcompound, String s1, String s) {
+ pendingPlayers.remove(player.getUUID(), player);
+ if (!networkmanager.isConnected()) {
+ return;
+ }
+ player.didPlayerJoinEvent = true;
+ // Paper end
TranslatableComponent chatmessage;
if (player.getGameProfile().getName().equalsIgnoreCase(s)) {
@@ -0,0 +0,0 @@ public abstract class PlayerList {
protected void save(ServerPlayer player) {
if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
+ if (!player.didPlayerJoinEvent) return; // Paper - If we never fired PJE, we disconnected during login. Data has not changed, and additionally, our saved vehicle is not loaded! If we save now, we will lose our vehicle (CraftBukkit bug)
this.playerIo.save(player);
ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit
@@ -0,0 +0,0 @@ public abstract class PlayerList {
}
PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName())));
- this.cserver.getPluginManager().callEvent(playerQuitEvent);
+ if (entityplayer.didPlayerJoinEvent) this.cserver.getPluginManager().callEvent(playerQuitEvent); // Paper - if we disconnected before join ever fired, don't fire quit
entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
if (server.isSameThread()) entityplayer.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog)
@@ -0,0 +0,0 @@ public abstract class PlayerList {
// this.advancements.remove(uuid);
// CraftBukkit end
}
+ // Paper start
+ entityplayer1 = pendingPlayers.get(uuid);
+ if (entityplayer1 == entityplayer) {
+ pendingPlayers.remove(uuid);
+ }
+ entityplayer.networkManager = null;
+ // Paper end
// CraftBukkit start
// this.broadcastAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, new EntityPlayer[]{entityplayer}));
@@ -0,0 +0,0 @@ public abstract class PlayerList {
this.cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity());
// CraftBukkit end
- return playerQuitEvent.quitMessage(); // Paper - Adventure
+ return entityplayer.didPlayerJoinEvent ? playerQuitEvent.quitMessage() : null; // CraftBukkit // Paper - Adventure // Paper - don't print quit if we never printed join
}
// CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer
@@ -0,0 +0,0 @@ public abstract class PlayerList {
list.add(entityplayer);
}
}
+ // Paper start - check pending players too
+ entityplayer = pendingPlayers.get(uuid);
+ if (entityplayer != null) {
+ this.pendingPlayers.remove(uuid);
+ disconnectPendingPlayer(entityplayer);
+ }
+ // Paper end
Iterator iterator = list.iterator();

View file

@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Phoenix616 <mail@moep.tv>
Date: Sun, 7 Jun 2020 21:43:42 +0100
Subject: [PATCH] Maps shouldn't load chunks
Previously maps would load all chunks in a certain radius depending on
their scale when trying to update their content. This would result in
main thread chunk loads when they weren't really necessary, especially
on low view distances or "slow" async chunk loads after teleports or
other prioritisation.
This changes it to only try to render already loaded chunks based on
the assumption that the chunks around the player will get loaded
eventually anyways and that maps will get checked for update every
five ticks that movement occur in anyways.
diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/item/MapItem.java
+++ b/src/main/java/net/minecraft/world/item/MapItem.java
@@ -0,0 +0,0 @@ public class MapItem extends ComplexItem {
int k2 = (j / i + k1 - 64) * i;
int l2 = (k / i + l1 - 64) * i;
Multiset<MaterialColor> multiset = LinkedHashMultiset.create();
- LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2));
+ LevelChunk chunk = world.getChunkIfLoaded(new BlockPos(k2, 0, l2)); // Paper - Maps shouldn't load chunks
- if (!chunk.isEmpty()) {
+ if (chunk != null && !chunk.isEmpty()) { // Paper - Maps shouldn't load chunks
ChunkPos chunkcoordintpair = chunk.getPos();
int i3 = k2 & 15;
int j3 = l2 & 15;

View file

@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: 2277 <38501234+2277@users.noreply.github.com>
Date: Tue, 31 Mar 2020 10:33:55 +0100
Subject: [PATCH] Move player to spawn point if spawn in unloaded world
The code following this has better support for null worlds to move
them back to the world spawn.
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
bworld = server.getWorld(worldName);
}
- if (bworld == null) {
- bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getLevel(Level.OVERWORLD).getWorld();
- }
+ // Paper start - Move player to spawn point if spawn in unloaded world
+// if (bworld == null) {
+// bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getWorldServer(World.OVERWORLD).getWorld();
+// }
+ // Paper end - Move player to spawn point if spawn in unloaded world
((ServerPlayer) this).setLevel(bworld == null ? null : ((CraftWorld) bworld).getHandle());
}

View file

@ -0,0 +1,121 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 3 May 2020 22:35:09 -0400
Subject: [PATCH] Optimize Voxel Shape Merging
This method shows up as super hot in profiler, and also a high "self" time.
Upon analyzing, it appears most usages of this method fall down to the final
else statement of the nasty ternary.
Upon even further analyzation, it appears then the majority of those have a
consistent list 1.... One with Infinity head and Tails.
First optimization is to detect these infinite states and immediately return that
VoxelShapeMergerList so we can avoid testing the rest for most cases.
Break the method into 2 to help the JVM promote inlining of this fast path.
Then it was also noticed that VoxelShapeMergerList constructor is also a hotspot
with a high self time...
Well, knowing that in most cases our list 1 is actualy the same value, it allows
us to know that with an infinite list1, the result on the merger is essentially
list2 as the final values.
This let us analyze the 2 potential states (Infinite with 2 sources or 4 sources)
and compute a deterministic result for the MergerList values.
Additionally, this lets us avoid even allocating new objects for this too, further
reducing memory usage.
diff --git a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java
@@ -0,0 +0,0 @@ public class IndirectMerger implements IndexMerger {
private final int[] firstIndices;
private final int[] secondIndices;
private final int resultLength;
+ // Paper start
+ private static final int[] INFINITE_B_1 = new int[]{1, 1};
+ private static final int[] INFINITE_B_0 = new int[]{0, 0};
+ private static final int[] INFINITE_C = new int[]{0, 1};
+ // Paper end
public IndirectMerger(DoubleList first, DoubleList second, boolean includeFirstOnly, boolean includeSecondOnly) {
double d = Double.NaN;
int i = first.size();
int j = second.size();
int k = i + j;
+ // Paper start - optimize common path of infinity doublelist
+ int size = first.size();
+ double tail = first.getDouble(size - 1);
+ double head = first.getDouble(0);
+ if (head == Double.NEGATIVE_INFINITY && tail == Double.POSITIVE_INFINITY && !includeFirstOnly && !includeSecondOnly && (size == 2 || size == 4)) {
+ this.result = second.toDoubleArray();
+ this.resultLength = second.size();
+ if (size == 2) {
+ this.firstIndices = INFINITE_B_0;
+ } else {
+ this.firstIndices = INFINITE_B_1;
+ }
+ this.secondIndices = INFINITE_C;
+ return;
+ }
+ // Paper end
this.result = new double[k];
this.firstIndices = new int[k];
this.secondIndices = new int[k];
diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
@@ -0,0 +0,0 @@ public final class Shapes {
}
@VisibleForTesting
- protected static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) {
+ private static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { // Paper - private
+ // Paper start - fast track the most common scenario
+ // doublelist is usually a DoubleArrayList with Infinite head/tails that falls to the final else clause
+ // This is actually the most common path, so jump to it straight away
+ if (first.getDouble(0) == Double.NEGATIVE_INFINITY && first.getDouble(first.size() - 1) == Double.POSITIVE_INFINITY) {
+ return new IndirectMerger(first, second, includeFirst, includeSecond);
+ }
+ // Split out rest to hopefully inline the above
+ return lessCommonMerge(size, first, second, includeFirst, includeSecond);
+ }
+
+ private static IndexMerger lessCommonMerge(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) {
int i = first.size() - 1;
int j = second.size() - 1;
+ // Paper note - Rewrite below as optimized order if instead of nasty ternary
if (first instanceof CubePointRange && second instanceof CubePointRange) {
long l = lcm(i, j);
if ((long)size * l <= 256L) {
@@ -0,0 +0,0 @@ public final class Shapes {
}
}
- if (first.getDouble(i) < second.getDouble(0) - 1.0E-7D) {
+ // Paper start - Identical happens more often than Disjoint
+ if (i == j && Objects.equals(first, second)) {
+ if (first instanceof IdenticalMerger) {
+ return (IndexMerger) first;
+ } else if (second instanceof IdenticalMerger) {
+ return (IndexMerger) second;
+ }
+ return new IdenticalMerger(first);
+ } else if (first.getDouble(i) < second.getDouble(0) - 1.0E-7D) {
return new NonOverlappingMerger(first, second, false);
} else if (second.getDouble(j) < first.getDouble(0) - 1.0E-7D) {
return new NonOverlappingMerger(second, first, true);
} else {
- return (IndexMerger)(i == j && Objects.equals(first, second) ? new IdenticalMerger(first) : new IndirectMerger(first, second, includeFirst, includeSecond));
+ return new IndirectMerger(first, second, includeFirst, includeSecond);
}
+ // Paper end
}
public interface DoubleLineConsumer {

View file

@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: virustotalop <virustotalop@gmail.com>
Date: Thu, 16 Apr 2020 20:51:32 -0700
Subject: [PATCH] Optimize brigadier child sorting performance
diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java
+++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java
@@ -0,0 +0,0 @@ import java.util.stream.Collectors;
import net.minecraft.commands.CommandSourceStack;
public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
- private Map<String, CommandNode<S>> children = Maps.newLinkedHashMap();
+ private Map<String, CommandNode<S>> children = Maps.newTreeMap(); // Paper - Switch to tree map for automatic sorting
private Map<String, LiteralCommandNode<S>> literals = Maps.newLinkedHashMap();
private Map<String, ArgumentCommandNode<S, ?>> arguments = Maps.newLinkedHashMap();
public Predicate<S> requirement;
@@ -0,0 +0,0 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
}
}
- this.children = this.children.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
+ // Paper - Remove manual sorting, it is no longer needed
}
public void findAmbiguities(final AmbiguityConsumer<S> consumer) {

View file

@ -0,0 +1,57 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: BillyGalbreath <Blake.Galbreath@GMail.com>
Date: Fri, 10 Nov 2017 23:03:12 -0500
Subject: [PATCH] Option for maximum exp value when merging orbs
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
phantomIgnoreCreative = getBoolean("phantoms-do-not-spawn-on-creative-players", phantomIgnoreCreative);
phantomOnlyAttackInsomniacs = getBoolean("phantoms-only-attack-insomniacs", phantomOnlyAttackInsomniacs);
}
+
+ public int expMergeMaxValue;
+ private void expMergeMaxValue() {
+ expMergeMaxValue = getInt("experience-merge-max-value", -1);
+ log("Experience Merge Max Value: " + expMergeMaxValue);
+ }
}
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -0,0 +0,0 @@ public class CraftEventFactory {
net.minecraft.world.entity.ExperienceOrb xp = (net.minecraft.world.entity.ExperienceOrb) entity;
double radius = world.spigotConfig.expMerge;
if (radius > 0) {
+ // Paper start - Maximum exp value when merging - Whole section has been tweaked, see comments for specifics
+ final int maxValue = world.paperConfig.expMergeMaxValue;
+ final boolean mergeUnconditionally = world.paperConfig.expMergeMaxValue <= 0;
+ if (mergeUnconditionally || xp.value < maxValue) { // Paper - Skip iteration if unnecessary
+
List<Entity> entities = world.getEntities(entity, entity.getBoundingBox().inflate(radius, radius, radius));
for (Entity e : entities) {
if (e instanceof net.minecraft.world.entity.ExperienceOrb) {
net.minecraft.world.entity.ExperienceOrb loopItem = (net.minecraft.world.entity.ExperienceOrb) e;
- if (!loopItem.isRemoved()) {
+ // Paper start
+ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) {
+ long newTotal = (long)xp.value + (long)loopItem.value;
+ if ((int) newTotal < 0) continue; // Overflow
+ if (maxValue > 0 && newTotal > (long)maxValue) {
+ loopItem.value = (int) (newTotal - maxValue);
+ xp.value = maxValue;
+ } else {
xp.value += loopItem.value;
loopItem.discard();
+ } // Paper end
}
}
}
+ } // Paper end - End iteration skip check - All tweaking ends here
}
// Spigot end
} else if (!(entity instanceof ServerPlayer)) {

View file

@ -0,0 +1,44 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Josh Roy <10731363+JRoy@users.noreply.github.com>
Date: Sun, 10 May 2020 23:06:30 -0400
Subject: [PATCH] Potential bed API
Adds a new method to fetch the location of a player's bed without generating any sync loads.
getPotentialBedLocation - Gets the last known location of a player's bed. This does not preform any check if the bed is still valid and does not load any chunks.
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
@@ -0,0 +0,0 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.network.protocol.game.ServerboundContainerClosePacket;
+import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.Entity;
@@ -0,0 +0,0 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity {
return this.getHandle().sleepCounter;
}
+ // Paper start - Potential bed api
+ @Override
+ public Location getPotentialBedLocation() {
+ ServerPlayer handle = (ServerPlayer) getHandle();
+ BlockPos bed = handle.getRespawnPosition();
+ if (bed == null) {
+ return null;
+ }
+
+ ServerLevel worldServer = handle.server.getLevel(handle.getRespawnDimension());
+ if (worldServer == null) {
+ return null;
+ }
+ return new Location(worldServer.getWorld(), bed.getX(), bed.getY(), bed.getZ());
+ }
+ // Paper end
@Override
public boolean sleep(Location location, boolean force) {
Preconditions.checkArgument(location != null, "Location cannot be null");

View file

@ -0,0 +1,189 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 13 May 2020 23:01:26 -0400
Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed
This fixes exploits that let players destroy bedrock by Pistons, explosions
and Mushrooom/Tree generation.
These blocks are designed to not be broken except by creative players/commands.
So protect them from a multitude of methods of destroying them.
A config is provided if you rather let players use these exploits, and let
them destroy the worlds End Portals and get on top of the nether easy.
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig {
private static void loggerSettings() {
deobfuscateStacktraces = getBoolean("settings.loggers.deobfuscate-stacktraces", deobfuscateStacktraces);
}
+
+ public static boolean allowBlockPermanentBreakingExploits = false;
+ private static void allowBlockPermanentBreakingExploits() {
+ if (config.contains("allow-perm-block-break-exploits")) {
+ allowBlockPermanentBreakingExploits = config.getBoolean("allow-perm-block-break-exploits", false);
+ config.set("allow-perm-block-break-exploits", null);
+ }
+
+ config.set("settings.unsupported-settings.allow-permanent-block-break-exploits-readme", "This setting controls if players should be able to break bedrock, end portals and other intended to be permanent blocks.");
+ allowBlockPermanentBreakingExploits = getBoolean("settings.unsupported-settings.allow-permanent-block-break-exploits", allowBlockPermanentBreakingExploits);
+ }
}
diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/Explosion.java
+++ b/src/main/java/net/minecraft/world/level/Explosion.java
@@ -0,0 +0,0 @@ public class Explosion {
for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
BlockPos blockposition = new BlockPos(d4, d5, d6);
BlockState iblockdata = this.level.getBlockState(blockposition);
+ if (!iblockdata.isDestroyable()) continue; // Paper
FluidState fluid = iblockdata.getFluidState(); // Paper
if (!this.level.isInWorldBounds(blockposition)) {
@@ -0,0 +0,0 @@ public class Explosion {
BlockState iblockdata = this.level.getBlockState(blockposition);
Block block = iblockdata.getBlock();
- if (!iblockdata.isAir()) {
+ if (!iblockdata.isAir() && iblockdata.isDestroyable()) { // Paper
BlockPos blockposition1 = blockposition.immutable();
this.level.getProfiler().push("explosion_blocks");
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) {
// CraftBukkit start - tree generation
if (this.captureTreeGeneration) {
+ // Paper start
+ BlockState type = getBlockState(pos);
+ if (!type.isDestroyable()) return false;
+ // Paper end
CraftBlockState blockstate = this.capturedBlockStates.get(pos);
if (blockstate == null) {
blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags);
diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/Block.java
+++ b/src/main/java/net/minecraft/world/level/block/Block.java
@@ -0,0 +0,0 @@ public class Block extends BlockBehaviour implements ItemLike {
protected final StateDefinition<Block, BlockState> stateDefinition;
private BlockState defaultBlockState;
// Paper start
+ public final boolean isDestroyable() {
+ return com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits ||
+ this != Blocks.BEDROCK &&
+ this != Blocks.END_PORTAL_FRAME &&
+ this != Blocks.END_PORTAL &&
+ this != Blocks.END_GATEWAY &&
+ this != Blocks.COMMAND_BLOCK &&
+ this != Blocks.REPEATING_COMMAND_BLOCK &&
+ this != Blocks.CHAIN_COMMAND_BLOCK &&
+ this != Blocks.BARRIER &&
+ this != Blocks.STRUCTURE_BLOCK &&
+ this != Blocks.JIGSAW;
+ }
public co.aikar.timings.Timing timing;
public co.aikar.timings.Timing getTiming() {
if (timing == null) {
diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
@@ -0,0 +0,0 @@ public class PistonBaseBlock extends DirectionalBlock {
@Override
public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) {
Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING);
+ // Paper start - prevent retracting when we're facing the wrong way (we were replaced before retraction could occur)
+ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below
+ if (!com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits && enumdirection != directionQueuedAs) {
+ return false;
+ }
+ // Paper end - prevent retracting when we're facing the wrong way
if (!world.isClientSide) {
boolean flag = this.getNeighborSignal(world, pos, enumdirection);
@@ -0,0 +0,0 @@ public class PistonBaseBlock extends DirectionalBlock {
BlockState iblockdata1 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT);
world.setBlock(pos, iblockdata1, 20);
- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata1, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true));
+ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata1, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - diff on change
world.blockUpdated(pos, iblockdata1.getBlock());
iblockdata1.updateNeighbourShapes(world, pos, 2);
if (this.isSticky) {
@@ -0,0 +0,0 @@ public class PistonBaseBlock extends DirectionalBlock {
}
}
} else {
- world.removeBlock(pos.relative(enumdirection), false);
+ // Paper start - fix headless pistons breaking blocks
+ BlockPos headPos = pos.relative(enumdirection);
+ if (com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston.
+ world.removeBlock(headPos, false);
+ } else {
+ ((ServerLevel)world).getChunkSource().blockChanged(headPos); // ... fix client desync
+ }
+ // Paper end - fix headless pistons breaking blocks
}
world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F);
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour {
@Deprecated
public boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
- return this.material.isReplaceable() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem()));
+ return this.material.isReplaceable() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper
}
@Deprecated
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour {
public Block getBlock() {
return (Block) this.owner;
}
-
+ // Paper start
+ public final boolean isDestroyable() {
+ return getBlock().isDestroyable();
+ }
+ // Paper end
public Material getMaterial() {
return this.material;
}
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour {
}
public PushReaction getPistonPushReaction() {
- return this.getBlock().getPistonPushReaction(this.asState());
+ return !isDestroyable() ? PushReaction.BLOCK : this.getBlock().getPistonPushReaction(this.asState()); // Paper
}
public boolean isSolidRender(BlockGetter world, BlockPos pos) {
diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
@@ -0,0 +0,0 @@ public class PortalForcer {
for (int j = -1; j < 3; ++j) {
for (int k = -1; k < 4; ++k) {
temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal);
+ // Paper start - prevent destroying unbreakable blocks
+ if (!com.destroystokyo.paper.PaperConfig.allowBlockPermanentBreakingExploits) {
+ if (!this.level.getBlockState(temp).isDestroyable()) {
+ return false;
+ }
+ }
+ // Paper end - prevent destroying unbreakable blocks
if (k < 0 && !this.level.getBlockState(temp).getMaterial().isSolid()) {
return false;
}

View file

@ -0,0 +1,50 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Mon, 27 Apr 2020 02:48:06 -0700
Subject: [PATCH] Reduce MutableInt allocations from light engine
We can abuse the fact light is single threaded and share an instance
per light engine instance
diff --git a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java
+++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java
@@ -0,0 +0,0 @@ import org.apache.commons.lang3.mutable.MutableInt;
public final class BlockLightEngine extends LayerLightEngine<BlockLightSectionStorage.BlockDataLayerStorageMap, BlockLightSectionStorage> {
private static final Direction[] DIRECTIONS = Direction.values();
private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
+ private final MutableInt mutableInt = new MutableInt(); // Paper
public BlockLightEngine(LightChunkGetter chunkProvider) {
super(chunkProvider, LightLayer.BLOCK, new BlockLightSectionStorage(chunkProvider));
@@ -0,0 +0,0 @@ public final class BlockLightEngine extends LayerLightEngine<BlockLightSectionSt
if (direction == null) {
return 15;
} else {
- MutableInt mutableInt = new MutableInt();
+ //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded
BlockState blockState = this.getStateAndOpacity(targetId, mutableInt);
if (mutableInt.getValue() >= 15) {
return 15;
diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java
+++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java
@@ -0,0 +0,0 @@ import org.apache.commons.lang3.mutable.MutableInt;
public final class SkyLightEngine extends LayerLightEngine<SkyLightSectionStorage.SkyDataLayerStorageMap, SkyLightSectionStorage> {
private static final Direction[] DIRECTIONS = Direction.values();
private static final Direction[] HORIZONTALS = new Direction[]{Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST};
+ private final MutableInt mutableInt = new MutableInt(); // Paper
public SkyLightEngine(LightChunkGetter chunkProvider) {
super(chunkProvider, LightLayer.SKY, new SkyLightSectionStorage(chunkProvider));
@@ -0,0 +0,0 @@ public final class SkyLightEngine extends LayerLightEngine<SkyLightSectionStorag
if (level >= 15) {
return level;
} else {
- MutableInt mutableInt = new MutableInt();
+ //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded
BlockState blockState = this.getStateAndOpacity(targetId, mutableInt);
if (mutableInt.getValue() >= 15) {
return 15;

View file

@ -0,0 +1,60 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Mon, 27 Apr 2020 00:04:16 -0700
Subject: [PATCH] Reduce allocation of Vec3D by entity tracker
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
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()); // MC-155077, SPIGOT-5113
+ // Paper start - remove allocation of Vec3D here
+ //Vec3 vec3d = player.position().subtract(this.entity.position()); // MC-155077, SPIGOT-5113
+ double vec3d_dx = player.getX() - this.entity.getX();
+ double vec3d_dz = player.getZ() - this.entity.getZ();
+ // Paper end - remove allocation of Vec3D here
double d0 = (double) Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 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);
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -0,0 +0,0 @@ public class ServerEntity {
++this.teleportDelay;
i = Mth.floor(this.entity.getYRot() * 256.0F / 360.0F);
j = Mth.floor(this.entity.getXRot() * 256.0F / 360.0F);
- Vec3 vec3d = this.entity.position().subtract(ClientboundMoveEntityPacket.packetToEntity(this.xp, this.yp, this.zp));
- boolean flag1 = vec3d.lengthSqr() >= 7.62939453125E-6D;
+ // Paper start - reduce allocation of Vec3D here
+ double vec3d_dx = this.entity.getX() - 2.44140625E-4D*(this.xp);
+ double vec3d_dy = this.entity.getY() - 2.44140625E-4D*(this.yp);
+ double vec3d_dz = this.entity.getZ() - 2.44140625E-4D*(this.zp);
+ 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 = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1;
@@ -0,0 +0,0 @@ public class ServerEntity {
// CraftBukkit end
if (this.tickCount > 0 || this.entity instanceof AbstractArrow) {
- long k = ClientboundMoveEntityPacket.entityToPacket(vec3d.x);
- long l = ClientboundMoveEntityPacket.entityToPacket(vec3d.y);
- long i1 = ClientboundMoveEntityPacket.entityToPacket(vec3d.z);
+ // Paper start - remove allocation of Vec3D here
+ long k = ClientboundMoveEntityPacket.entityToPacket(vec3d_dx);
+ long l = ClientboundMoveEntityPacket.entityToPacket(vec3d_dy);
+ long i1 = ClientboundMoveEntityPacket.entityToPacket(vec3d_dz);
+ // Paper end - remove allocation of Vec3D here
boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L;
if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround()) {

View file

@ -0,0 +1,30 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Mon, 4 May 2020 01:08:56 -0400
Subject: [PATCH] Set cap on JDK per-thread native byte buffer cache
See: https://www.evanjones.ca/java-bytebuffer-leak.html
This is potentially a source of lots of native memory usage.
We are clearly seeing native usage upwards to 1-4GB which doesn't make sense.
Region File usage fixed in previous patch should of tecnically only been somewhat
temporary until GC finally gets it some time later, but between all the various
plugins doing IO on various threads, this hidden detail of the JDK could be
keeping long lived large direct buffers in cache.
Set system properly at server startup if not set already to help protect from this.
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -0,0 +0,0 @@ public class Main {
}
// Paper end
// Todo: Installation script
+ if (System.getProperty("jdk.nio.maxCachedBufferSize") == null) System.setProperty("jdk.nio.maxCachedBufferSize", "262144"); // Paper - cap per-thread NIO cache size
OptionParser parser = new OptionParser() {
{
acceptsAll(Main.asList("?", "help"), "Show the help");

View file

@ -0,0 +1,20 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 7 Jun 2020 19:25:13 -0400
Subject: [PATCH] Use seed based lookup for Treasure Maps - Fixes lag from
carto/sunken maps
diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/item/MapItem.java
+++ b/src/main/java/net/minecraft/world/item/MapItem.java
@@ -0,0 +0,0 @@ public class MapItem extends ComplexItem {
for (l = 0; l < 128 * i; ++l) {
for (i1 = 0; i1 < 128 * i; ++i1) {
- Biome.BiomeCategory biomebase_geography = world.getBiome(new BlockPos((j / i - 64) * i + i1, 0, (k / i - 64) * i + l)).getBiomeCategory();
+ Biome.BiomeCategory biomebase_geography = world.getUncachedNoiseBiome((j / i - 64) * i + i1, 0, (k / i - 64) * i + l).getBiomeCategory(); // Paper
aboolean[l * 128 * i + i1] = biomebase_geography == Biome.BiomeCategory.OCEAN || biomebase_geography == Biome.BiomeCategory.RIVER || biomebase_geography == Biome.BiomeCategory.SWAMP;
}

View file

@ -0,0 +1,26 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sat, 2 May 2020 03:09:46 -0400
Subject: [PATCH] Validate PickItem Packet and kick for invalid
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
@Override
public void handlePickItem(ServerboundPickItemPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel());
- this.player.getInventory().pickSlot(packet.getSlot());
+ // Paper start - validate pick item position
+ if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) {
+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString());
+ this.disconnect("Invalid hotbar selection (Hacking?)");
+ return;
+ }
+ this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed
+ // Paper end
this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected)));
this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot())));
this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected));

View file

@ -0,0 +1,29 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: zbk <zbk@projectsolaris.net>
Date: Sun, 26 Apr 2020 23:49:01 -0400
Subject: [PATCH] Villager Restocks API
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
@@ -0,0 +0,0 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
this.getHandle().setVillagerXp(experience);
}
+ // Paper start
+ @Override
+ public int getRestocksToday() {
+ return getHandle().numberOfRestocksToday;
+ }
+
+ @Override
+ public void setRestocksToday(int restocksToday) {
+ getHandle().numberOfRestocksToday = restocksToday;
+ }
+ // Paper end
+
@Override
public boolean sleep(Location location) {
Preconditions.checkArgument(location != null, "Location cannot be null");

View file

@ -0,0 +1,63 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 10 May 2020 22:16:17 -0400
Subject: [PATCH] Wait for Async Tasks during shutdown
Server.reload() had this logic to give time for tasks to shutdown,
however shutdown did not...
Adds a 5 second grace period for any async tasks to finish and warns
if any are still running after that delay just as reload does.
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// CraftBukkit start
if (this.server != null) {
this.server.disablePlugins();
+ this.server.waitForAsyncTasksShutdown(); // Paper
}
// CraftBukkit end
if (this.getConnection() != null) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
org.spigotmc.WatchdogThread.hasStarted = true; // Paper - Disable watchdog early timeout on reload
}
+ // Paper start
+ public void waitForAsyncTasksShutdown() {
+ int pollCount = 0;
+
+ // Wait for at most 5 seconds for plugins to close their threads
+ while (pollCount < 10*5 && getScheduler().getActiveWorkers().size() > 0) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {}
+ pollCount++;
+ }
+
+ List<BukkitWorker> overdueWorkers = getScheduler().getActiveWorkers();
+ for (BukkitWorker worker : overdueWorkers) {
+ Plugin plugin = worker.getOwner();
+ String author = "<NoAuthorGiven>";
+ if (plugin.getDescription().getAuthors().size() > 0) {
+ author = plugin.getDescription().getAuthors().get(0);
+ }
+ getLogger().log(Level.SEVERE, String.format(
+ "Nag author: '%s' of '%s' about the following: %s",
+ author,
+ plugin.getDescription().getName(),
+ "This plugin is not properly shutting down its async tasks when it is being shut down. This task may throw errors during the final shutdown logs and might not complete before process dies."
+ ));
+ }
+ }
+ // Paper end
+
@Override
public void reloadData() {
ReloadCommand.reload(console);

View file

@ -0,0 +1,87 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Thu, 18 Feb 2021 20:23:28 +0000
Subject: [PATCH] misc debugging dumps
diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/TraceUtil.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+import org.bukkit.Bukkit;
+
+public final class TraceUtil {
+
+ public static void dumpTraceForThread(Thread thread, String reason) {
+ Bukkit.getLogger().warning(thread.getName() + ": " + reason);
+ StackTraceElement[] trace = thread.getStackTrace();
+ for (StackTraceElement traceElement : trace) {
+ Bukkit.getLogger().warning("\tat " + traceElement);
+ }
+ }
+
+ public static void dumpTraceForThread(String reason) {
+ new Throwable(reason).printStackTrace();
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// CraftBukkit start
private boolean hasStopped = false;
public volatile boolean hasFullyShutdown = false; // Paper
+ private boolean hasLoggedStop = false; // Paper
private final Object stopLock = new Object();
public final boolean hasStopped() {
synchronized (this.stopLock) {
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
if (this.hasStopped) return;
this.hasStopped = true;
}
+ if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper
// Paper start - kill main thread, and kill it hard
shutdownThread = Thread.currentThread();
org.spigotmc.WatchdogThread.doStop(); // Paper
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public void safeShutdown(boolean flag, boolean isRestarting) {
this.isRestarting = isRestarting;
+ this.hasLoggedStop = true; // Paper
+ if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper
// Paper end
this.running = false;
if (flag) {
diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -0,0 +0,0 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener
} catch (Exception exception) {
ServerLoginPacketListenerImpl.LOGGER.error("Couldn't place player in world", exception);
TranslatableComponent chatmessage = new TranslatableComponent("multiplayer.disconnect.invalid_player_data");
+ // Paper start
+ if (MinecraftServer.getServer().isDebugging()) {
+ exception.printStackTrace();
+ }
+ // Paper end
this.connection.send(new ClientboundDisconnectPacket(chatmessage));
this.connection.disconnect(chatmessage);
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
plugin.getDescription().getFullName(),
"This plugin is not properly shutting down its async tasks when it is being reloaded. This may cause conflicts with the newly loaded version of the plugin"
));
+ if (console.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread(worker.getThread(), "still running"); // Paper
}
this.loadPlugins();
this.enablePlugins(PluginLoadOrder.STARTUP);