diff --git a/sources/src/main/java/net/minecraft/server/Chunk.java b/sources/src/main/java/net/minecraft/server/Chunk.java index 7ac8a24e0..cc6416146 100644 --- a/sources/src/main/java/net/minecraft/server/Chunk.java +++ b/sources/src/main/java/net/minecraft/server/Chunk.java @@ -1,15 +1,18 @@ package net.minecraft.server; +import com.destroystokyo.paper.PaperWorldConfig.DuplicateUUIDMode; import com.destroystokyo.paper.exception.ServerInternalException; import com.google.common.base.Predicate; import com.google.common.collect.Queues; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nullable; import org.apache.logging.log4j.LogManager; @@ -36,6 +39,7 @@ public class Chunk { public final World world; public final int[] heightMap; public Long scheduledForUnload; // Paper - delay chunk unloads + private static final Logger logger = LogManager.getLogger(); // Paper public final int locX; public final int locZ; private boolean m; @@ -679,8 +683,34 @@ public class Chunk { entity.ab = this.locX; entity.ac = k; entity.ad = this.locZ; - this.entitySlices[k].add(entity); + // Paper start + List entitySlice = this.entitySlices[k]; + boolean inThis = entitySlice.contains(entity); + if (entity.entitySlice != null || inThis) { + if (entity.entitySlice == entitySlice || inThis) { + LogManager.getLogger().warn(entity + " was already in this chunk section! Report this to https://github.com/PaperMC/Paper/issues/1223"); + new Throwable().printStackTrace(); + return; + } else { + LogManager.getLogger().warn(entity + " is still in another ChunkSection! Report this to https://github.com/PaperMC/Paper/issues/1223"); + + Chunk chunk = entity.getCurrentChunk(); + if (chunk != null) { + if (chunk != this) { + LogManager.getLogger().warn(entity + " was in another chunk at that! " + chunk.locX + "," + chunk.locZ); + } + chunk.removeEntity(entity); + } else { + removeEntity(entity); + } + new Throwable().printStackTrace(); + } + } + entity.entitySlice = entitySlice; + entitySlice.add(entity); + + this.markDirty(); entity.setCurrentChunk(this); entityCounts.increment(entity.entityKeyString); if (entity instanceof EntityItem) { @@ -723,6 +753,13 @@ public class Chunk { // Paper start if (!this.entitySlices[i].remove(entity)) { return; } + if (entitySlices[i] == entity.entitySlice) { + entity.entitySlice = null; + } else { + LogManager.getLogger().warn(entity + " was removed from a entitySlice we did not expect. Report this to https://github.com/PaperMC/Paper/issues/1223"); + new Throwable().printStackTrace(); + } + this.markDirty(); entity.setCurrentChunk(null); entityCounts.decrement(entity.entityKeyString); if (entity instanceof EntityItem) { @@ -856,6 +893,36 @@ public class Chunk { for (int j = 0; j < i; ++j) { List entityslice = aentityslice[j]; // Spigot + // Paper start + DuplicateUUIDMode mode = world.paperConfig.duplicateUUIDMode; + if (mode == DuplicateUUIDMode.DELETE || mode == DuplicateUUIDMode.REGEN) { + Map thisChunk = new HashMap<>(); + for (Iterator iterator = ((List) entityslice).iterator(); iterator.hasNext(); ) { + Entity entity = iterator.next(); + if (entity.dead) continue; + Entity other = ((WorldServer) world).entitiesByUUID.get(entity.uniqueID); + if (other == null) { + other = thisChunk.get(entity.uniqueID); + } + if (other != null && !other.dead) { + switch (mode) { + case REGEN: { + entity.setUUID(UUID.randomUUID()); + logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", regenerated UUID for " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); + break; + } + case DELETE: { + logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about."); + entity.die(); + iterator.remove(); + break; + } + } + } + thisChunk.put(entity.uniqueID, entity); + } + } + // Paper end this.world.a((Collection) entityslice); } diff --git a/sources/src/main/java/net/minecraft/server/Entity.java b/sources/src/main/java/net/minecraft/server/Entity.java index 2e3e1e6ff..f1cae87cf 100644 --- a/sources/src/main/java/net/minecraft/server/Entity.java +++ b/sources/src/main/java/net/minecraft/server/Entity.java @@ -50,7 +50,21 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper // CraftBukkit start private static final int CURRENT_LEVEL = 2; - public static Random SHARED_RANDOM = new io.akarin.api.internal.utils.random.LightRandom(); // Paper // Akarin - LightRNG + // Paper start + public static Random SHARED_RANDOM = new io.akarin.api.internal.utils.random.LightRandom() { // Akarin - LightRNG + private boolean locked = false; + @Override + public synchronized void setSeed(long seed) { + if (locked) { + LogManager.getLogger().error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable()); + } else { + super.setSeed(seed); + locked = true; + } + } + }; + Object entitySlice = null; + // Paper end static boolean isLevelAtLeast(NBTTagCompound tag, int level) { return tag.hasKey("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; } @@ -64,6 +78,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper } return bukkitEntity; } + Throwable addedToWorldStack; // Paper - entity debug // CraftBukikt end private static final Logger a = LogManager.getLogger(); @@ -2352,7 +2367,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper } public String toString() { - return String.format("%s[\'%s\'/%d, uuid=\'%s\', l=\'%s\', x=%.2f, y=%.2f, z=%.2f]", new Object[] { this.getClass().getSimpleName(), this.getName(), Integer.valueOf(this.id), this.uniqueID.toString(), this.world == null ? "~NULL~" : this.world.getWorldData().getName(), Double.valueOf(this.locX), Double.valueOf(this.locY), Double.valueOf(this.locZ)}); // Paper - add UUID + return String.format("%s[\'%s\'/%d, uuid=\'%s\', l=\'%s\', x=%.2f, y=%.2f, z=%.2f, cx=%d, cd=%d, tl=%d, v=%b, d=%b]", new Object[] { this.getClass().getSimpleName(), this.getName(), Integer.valueOf(this.id), this.uniqueID.toString(), this.world == null ? "~NULL~" : this.world.getWorldData().getName(), Double.valueOf(this.locX), Double.valueOf(this.locY), Double.valueOf(this.locZ), getChunkX(), getChunkZ(), this.ticksLived, this.valid, this.dead}); // Paper - add more information } public boolean isInvulnerable(DamageSource damagesource) { @@ -2604,6 +2619,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper }); } + public void setUUID(UUID uuid) { a(uuid); } // Paper - OBFHELPER public void a(UUID uuid) { this.uniqueID = uuid; this.ar = this.uniqueID.toString(); diff --git a/sources/src/main/java/net/minecraft/server/WorldServer.java b/sources/src/main/java/net/minecraft/server/WorldServer.java index 5d264eef8..262dcce6d 100644 --- a/sources/src/main/java/net/minecraft/server/WorldServer.java +++ b/sources/src/main/java/net/minecraft/server/WorldServer.java @@ -6,6 +6,7 @@ import com.google.common.util.concurrent.ListenableFuture; import java.io.File; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -40,7 +41,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { public final PlayerChunkMap manager; // Akarin - private -> public // private final Set nextTickListHash = Sets.newHashSet(); private final HashTreeSet nextTickList = new HashTreeSet(); // CraftBukkit - HashTreeSet - private final Map entitiesByUUID = Maps.newHashMap(); + public final Map entitiesByUUID = Maps.newHashMap(); // Paper public boolean savingDisabled; private boolean Q; private int emptyTime; @@ -52,6 +53,10 @@ public class WorldServer extends World implements IAsyncTaskHandler { private final List W = Lists.newArrayList(); // CraftBukkit start + private static final boolean DEBUG_ENTITIES = Boolean.getBoolean("debug.entities"); // Paper + private static Throwable getAddToWorldStackTrace(Entity entity) { + return new Throwable(entity + " Added to world at " + new Date()); + } public final int dimension; // Add env and gen to constructor @@ -1181,6 +1186,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { private boolean j(Entity entity) { if (entity.dead) { WorldServer.a.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.a(entity)); // CraftBukkit // Paper + if (DEBUG_ENTITIES) getAddToWorldStackTrace(entity).printStackTrace(); return false; } else { UUID uuid = entity.getUniqueID(); @@ -1188,12 +1194,21 @@ public class WorldServer extends World implements IAsyncTaskHandler { if (this.entitiesByUUID.containsKey(uuid)) { Entity entity1 = this.entitiesByUUID.get(uuid); - if (this.f.contains(entity1)) { + if (this.f.contains(entity1) || entity1.dead) { // Paper - overwrite the current dead one this.f.remove(entity1); } else { if (!(entity instanceof EntityHuman)) { - WorldServer.a.error("Keeping entity {} that already exists with UUID {} - " + entity1, EntityTypes.a(entity1), uuid.toString()); // CraftBukkit // Paper - WorldServer.a.error("Deleting duplicate entity {}", entity); // Paper + if (entity.world.paperConfig.duplicateUUIDMode != com.destroystokyo.paper.PaperWorldConfig.DuplicateUUIDMode.NOTHING) { + WorldServer.a.error("Keeping entity {} that already exists with UUID {}", entity1, uuid.toString()); // CraftBukkit // Paper + WorldServer.a.error("Duplicate entity {} will not be added to the world. See paper.yml duplicate-uuid-resolver and set this to either regen, delete or nothing to get rid of this message", entity); // Paper + if (DEBUG_ENTITIES) { + if (entity1.addedToWorldStack != null) { + entity1.addedToWorldStack.printStackTrace(); + } + getAddToWorldStackTrace(entity).printStackTrace(); + } + } + return false; } @@ -1211,7 +1226,24 @@ public class WorldServer extends World implements IAsyncTaskHandler { protected void b(Entity entity) { super.b(entity); this.entitiesById.a(entity.getId(), entity); - this.entitiesByUUID.put(entity.getUniqueID(), entity); + // Paper start + if (DEBUG_ENTITIES) { + entity.addedToWorldStack = getAddToWorldStackTrace(entity); + } + Entity old = this.entitiesByUUID.put(entity.getUniqueID(), entity); + if (old != null && old.getId() != entity.getId() && old.valid && entity.world.paperConfig.duplicateUUIDMode != com.destroystokyo.paper.PaperWorldConfig.DuplicateUUIDMode.NOTHING) { + Logger logger = LogManager.getLogger(); + logger.error("Overwrote an existing entity " + old + " with " + entity); + if (DEBUG_ENTITIES) { + if (old.addedToWorldStack != null) { + old.addedToWorldStack.printStackTrace(); + } else { + logger.error("Oddly, the old entity was not added to the world in the normal way. Plugins?"); + } + entity.addedToWorldStack.printStackTrace(); + } + } + // Paper end Entity[] aentity = entity.bb(); if (aentity != null) { diff --git a/sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 66c757e17..78812800f 100644 --- a/sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1179,11 +1179,32 @@ public class CraftPlayer extends CraftHumanEntity implements Player { for (EntityPlayer player : players) { player.getBukkitEntity().reregisterPlayer(self); } + refreshPlayer(); } @Override public PlayerProfile getPlayerProfile() { return new CraftPlayerProfile(this).clone(); } + + private void refreshPlayer() { + EntityPlayer handle = getHandle(); + + Location loc = getLocation(); + + PlayerConnection connection = handle.playerConnection; + reregisterPlayer(handle); + + //Respawn the player then update their position and selected slot + connection.sendPacket(new PacketPlayOutRespawn(handle.dimension, handle.world.getDifficulty(), handle.world.getWorldData().getType(), handle.playerInteractManager.getGameMode())); + handle.updateAbilities(); + connection.sendPacket(new PacketPlayOutPosition(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch(), new HashSet<>(), 0)); + MinecraftServer.getServer().getPlayerList().updateClient(handle); + + if (this.isOp()) { + this.setOp(false); + this.setOp(true); + } + } // Paper end public void removeDisconnectingPlayer(Player player) { diff --git a/work/Paper b/work/Paper index ca2209ff2..8b96ee7ea 160000 --- a/work/Paper +++ b/work/Paper @@ -1 +1 @@ -Subproject commit ca2209ff2017c16099b3c5005fabc4b0356fd3ef +Subproject commit 8b96ee7ea8d947a979896eb7a1353c7e8f1479f6