More more more more more more more more more more work

This commit is contained in:
Nassim Jahnke 2021-11-24 10:01:27 +01:00 committed by MiniDigger | Martin
parent 6ac2614a5f
commit f43f1e217e
22 changed files with 138 additions and 156 deletions

View file

@ -1,129 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Sun, 9 Feb 2020 00:19:08 -0600
Subject: [PATCH] Add ThrownEggHatchEvent
Adds a new event similar to PlayerEggThrowEvent, but without the Player requirement
(dispensers can throw eggs to hatch them, too).
diff --git a/src/main/java/com/destroystokyo/paper/event/entity/ThrownEggHatchEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/ThrownEggHatchEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..085d77dde83d6ed13eb83f23cf3e51d380187c9c
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/event/entity/ThrownEggHatchEvent.java
@@ -0,0 +1,115 @@
+package com.destroystokyo.paper.event.entity;
+
+import org.bukkit.entity.Egg;
+import org.bukkit.entity.EntityType;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Called when a thrown egg might hatch.
+ * <p>
+ * This event fires for all thrown eggs that may hatch, players, dispensers, etc.
+ */
+public class ThrownEggHatchEvent extends Event {
+ private static final HandlerList handlers = new HandlerList();
+ private final Egg egg;
+ private boolean hatching;
+ private EntityType hatchType;
+ private byte numHatches;
+
+ public ThrownEggHatchEvent(@NotNull final Egg egg, final boolean hatching, final byte numHatches, @NotNull final EntityType hatchingType) {
+ this.egg = egg;
+ this.hatching = hatching;
+ this.numHatches = numHatches;
+ this.hatchType = hatchingType;
+ }
+
+ /**
+ * Gets the egg involved in this event.
+ *
+ * @return the egg involved in this event
+ */
+ @NotNull
+ public Egg getEgg() {
+ return egg;
+ }
+
+ /**
+ * Gets whether the egg is hatching or not. Will be what the server
+ * would've done without interaction.
+ *
+ * @return boolean Whether the egg is going to hatch or not
+ */
+ public boolean isHatching() {
+ return hatching;
+ }
+
+ /**
+ * Sets whether the egg will hatch or not.
+ *
+ * @param hatching true if you want the egg to hatch, false if you want it
+ * not to
+ */
+ public void setHatching(boolean hatching) {
+ this.hatching = hatching;
+ }
+
+ /**
+ * Get the type of the mob being hatched (EntityType.CHICKEN by default)
+ *
+ * @return The type of the mob being hatched by the egg
+ */
+ @NotNull
+ public EntityType getHatchingType() {
+ return hatchType;
+ }
+
+ /**
+ * Change the type of mob being hatched by the egg
+ *
+ * @param hatchType The type of the mob being hatched by the egg
+ */
+ public void setHatchingType(@NotNull EntityType hatchType) {
+ if (!hatchType.isSpawnable()) throw new IllegalArgumentException("Can't spawn that entity type from an egg!");
+ this.hatchType = hatchType;
+ }
+
+ /**
+ * Get the number of mob hatches from the egg. By default the number will
+ * be the number the server would've done
+ * <ul>
+ * <li>7/8 chance of being 0
+ * <li>31/256 ~= 1/8 chance to be 1
+ * <li>1/256 chance to be 4
+ * </ul>
+ *
+ * @return The number of mobs going to be hatched by the egg
+ */
+ public byte getNumHatches() {
+ return numHatches;
+ }
+
+ /**
+ * Change the number of mobs coming out of the hatched egg
+ * <p>
+ * The boolean hatching will override this number. Ie. If hatching =
+ * false, this number will not matter
+ *
+ * @param numHatches The number of mobs coming out of the egg
+ */
+ public void setNumHatches(byte numHatches) {
+ this.numHatches = numHatches;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}

View file

@ -1,88 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: BillyGalbreath <Blake.Galbreath@GMail.com>
Date: Sat, 8 Feb 2020 23:26:18 -0600
Subject: [PATCH] Entity Jump API
diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EntityJumpEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EntityJumpEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..f0067c2e953d18e1a33536980071ba3f0152ecb4
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/event/entity/EntityJumpEvent.java
@@ -0,0 +1,46 @@
+package com.destroystokyo.paper.event.entity;
+
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.entity.EntityEvent;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Called when an entity jumps
+ * <p>
+ * Cancelling the event will stop the entity from jumping
+ */
+public class EntityJumpEvent extends EntityEvent implements Cancellable {
+ private static final HandlerList handlers = new HandlerList();
+ private boolean canceled;
+
+ public EntityJumpEvent(@NotNull LivingEntity entity) {
+ super(entity);
+ }
+
+ @NotNull
+ @Override
+ public LivingEntity getEntity() {
+ return (LivingEntity) entity;
+ }
+
+ public boolean isCancelled() {
+ return canceled;
+ }
+
+ public void setCancelled(boolean cancel) {
+ canceled = cancel;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java
index 93d20f67bf856d80226470ae2442d199d3e2f45b..5ab8db52160049e36464df4e20e374b8849ef29c 100644
--- a/src/main/java/org/bukkit/entity/LivingEntity.java
+++ b/src/main/java/org/bukkit/entity/LivingEntity.java
@@ -802,5 +802,25 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource
*/
@NotNull
org.bukkit.inventory.EquipmentSlot getHandRaised();
+
+ /**
+ * Get entity jump state.
+ * <p>
+ * Jump state will be true when the entity has been marked to jump.
+ *
+ * @return entity jump state.
+ */
+ boolean isJumping();
+
+ /**
+ * Set entity jump state
+ * <p>
+ * Setting to true will mark the entity to jump.
+ * <p>
+ * Setting to false will unmark the entity to jump but will not stop a jump already in-progress.
+ *
+ * @param jumping entity jump state
+ */
+ void setJumping(boolean jumping);
// Paper end
}

View file

@ -1,29 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Trigary <trigary0@gmail.com>
Date: Sun, 1 Mar 2020 22:43:34 +0100
Subject: [PATCH] add hand to BlockMultiPlaceEvent
diff --git a/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java b/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java
index fe2ec79dd6084509cb8cbb8155a356f4162466fd..7ca2b1b321447289c50c210a608a88db9c4b4f99 100644
--- a/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java
+++ b/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java
@@ -18,9 +18,17 @@ import org.jetbrains.annotations.NotNull;
public class BlockMultiPlaceEvent extends BlockPlaceEvent {
private final List<BlockState> states;
+ @Deprecated // Paper
public BlockMultiPlaceEvent(@NotNull List<BlockState> states, @NotNull Block clicked, @NotNull ItemStack itemInHand, @NotNull Player thePlayer, boolean canBuild) {
- super(states.get(0).getBlock(), states.get(0), clicked, itemInHand, thePlayer, canBuild);
+ // Paper start - add hand to BlockMultiPlaceEvent
+ this(states, clicked, itemInHand, thePlayer, canBuild, org.bukkit.inventory.EquipmentSlot.HAND);
+ }
+
+
+ public BlockMultiPlaceEvent(@NotNull List<BlockState> states, @NotNull Block clicked, @NotNull ItemStack itemInHand, @NotNull Player thePlayer, boolean canBuild, @NotNull org.bukkit.inventory.EquipmentSlot hand) {
+ super(states.get(0).getBlock(), states.get(0), clicked, itemInHand, thePlayer, canBuild, hand);
this.states = ImmutableList.copyOf(states);
+ // Paper end
}
/**

View file

@ -1,105 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Mon, 3 Jun 2019 02:02:39 -0400
Subject: [PATCH] Implement alternative item-despawn-rate
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 604a0b423ce7863ad872e111257ac2fe8d635d5a..0ee9ce9b1214b91942541152443951c901aeafe6 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -536,6 +536,54 @@ public class PaperWorldConfig {
this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1);
}
+ public boolean altItemDespawnRateEnabled;
+ public java.util.Map<org.bukkit.Material, Integer> altItemDespawnRateMap;
+ private void altItemDespawnRate() {
+ String path = "alt-item-despawn-rate";
+
+ altItemDespawnRateEnabled = getBoolean(path + ".enabled", false);
+
+ java.util.Map<org.bukkit.Material, Integer> altItemDespawnRateMapDefault = new java.util.EnumMap<>(org.bukkit.Material.class);
+ altItemDespawnRateMapDefault.put(org.bukkit.Material.COBBLESTONE, 300);
+ for (org.bukkit.Material key : altItemDespawnRateMapDefault.keySet()) {
+ config.addDefault("world-settings.default." + path + ".items." + key, altItemDespawnRateMapDefault.get(key));
+ }
+
+ java.util.Map<String, Integer> rawMap = new java.util.HashMap<>();
+ try {
+ org.bukkit.configuration.ConfigurationSection mapSection = config.getConfigurationSection("world-settings." + worldName + "." + path + ".items");
+ if (mapSection == null) {
+ mapSection = config.getConfigurationSection("world-settings.default." + path + ".items");
+ }
+ for (String key : mapSection.getKeys(false)) {
+ int val = mapSection.getInt(key);
+ rawMap.put(key, val);
+ }
+ }
+ catch (Exception e) {
+ logError("alt-item-despawn-rate was malformatted");
+ altItemDespawnRateEnabled = false;
+ }
+
+ altItemDespawnRateMap = new java.util.EnumMap<>(org.bukkit.Material.class);
+ if (!altItemDespawnRateEnabled) {
+ return;
+ }
+
+ for(String key : rawMap.keySet()) {
+ try {
+ altItemDespawnRateMap.put(org.bukkit.Material.valueOf(key), rawMap.get(key));
+ } catch (Exception e) {
+ logError("Could not add item " + key + " to altItemDespawnRateMap: " + e.getMessage());
+ }
+ }
+ if(altItemDespawnRateEnabled) {
+ for(org.bukkit.Material key : altItemDespawnRateMap.keySet()) {
+ log("Alternative item despawn rate of " + key + ": " + altItemDespawnRateMap.get(key));
+ }
+ }
+ }
+
public boolean antiXray;
public EngineMode engineMode;
public int maxBlockHeight;
diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
index 9ee1dc89dd4c6b9453e1f6f92208d454877d23c9..e0c13a112c95eed9867d4608e18dc797b0c9c9cf 100644
--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
@@ -175,7 +175,7 @@ public class ItemEntity extends Entity {
}
}
- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot
+ if (!this.level.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper
// CraftBukkit start - fire ItemDespawnEvent
if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
this.age = 0;
@@ -199,7 +199,7 @@ public class ItemEntity extends Entity {
this.lastTick = MinecraftServer.currentTick;
// CraftBukkit end
- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot
+ if (!this.level.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper
// CraftBukkit start - fire ItemDespawnEvent
if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
this.age = 0;
@@ -559,9 +559,16 @@ public class ItemEntity extends Entity {
public void makeFakeItem() {
this.setNeverPickUp();
- this.age = level.spigotConfig.itemDespawnRate - 1; // Spigot
+ this.age = this.getDespawnRate() - 1; // Spigot
}
+ // Paper start
+ public int getDespawnRate(){
+ org.bukkit.Material material = this.getItem().getBukkitStack().getType();
+ return level.paperConfig.altItemDespawnRateMap.getOrDefault(material, level.spigotConfig.itemDespawnRate);
+ }
+ // Paper end
+
public float getSpin(float tickDelta) {
return ((float) this.getAge() + tickDelta) / 20.0F + this.bobOffs;
}

View file

@ -1,75 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Sat, 21 Dec 2019 15:22:09 -0500
Subject: [PATCH] Tracking Range Improvements
Sets tracking range of watermobs to animals instead of misc and simplifies code
Also ignores Enderdragon, defaulting it to Mojang's setting
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 72471db48a9c67ced8b1ef1ebb3381be4f773e06..e6ae3405995c4e472a731311695de7ef9b2d704b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -1885,6 +1885,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
int j = entity.getType().clientTrackingRange() * 16;
+ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper
if (j > i) {
i = j;
diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java
index 2d5cb8991e368372f1ab227735aac0c060deb199..24b1dfcf91d36947c87e9e5c2524317f8775ba95 100644
--- a/src/main/java/org/spigotmc/TrackingRange.java
+++ b/src/main/java/org/spigotmc/TrackingRange.java
@@ -6,7 +6,6 @@ import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.entity.decoration.Painting;
import net.minecraft.world.entity.item.ItemEntity;
-import net.minecraft.world.entity.monster.Ghast;
public class TrackingRange
{
@@ -29,26 +28,26 @@ public class TrackingRange
if ( entity instanceof ServerPlayer )
{
return config.playerTrackingRange;
- } else if ( entity.activationType == ActivationRange.ActivationType.MONSTER || entity.activationType == ActivationRange.ActivationType.RAIDER )
- {
- return config.monsterTrackingRange;
- } else if ( entity instanceof Ghast )
- {
- if ( config.monsterTrackingRange > config.monsterActivationRange )
- {
+ // Paper start - Simplify and set water mobs to animal tracking range
+ }
+ switch (entity.activationType) {
+ case RAIDER:
+ case MONSTER:
+ case FLYING_MONSTER:
return config.monsterTrackingRange;
- } else
- {
- return config.monsterActivationRange;
- }
- } else if ( entity.activationType == ActivationRange.ActivationType.ANIMAL )
- {
- return config.animalTrackingRange;
- } else if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb )
+ case WATER:
+ case VILLAGER:
+ case ANIMAL:
+ return config.animalTrackingRange;
+ case MISC:
+ }
+ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb )
+ // Paper end
{
return config.miscTrackingRange;
} else
{
+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.getLoadViewDistance(); // Paper - enderdragon is exempt
return config.otherTrackingRange;
}
}

View file

@ -1,28 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: AJMFactsheets <AJMFactsheets@gmail.com>
Date: Wed, 22 Jan 2020 19:52:28 -0600
Subject: [PATCH] Fix items vanishing through end portal
If the Paper configuration option "keep-spawn-loaded" is set to false,
items entering the overworld from the end will spawn at Y = 0.
This is due to logic in the getHighestBlockYAt method in World.java
only searching the heightmap if the chunk is loaded.
Quickly loading the exact world spawn chunk before searching the
heightmap resolves the issue without having to load all spawn chunks.
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 2aa4f6f30e12162f7cfae5180e40f7a32fc6f24d..06597d10c640daca400da9d5a4c186cc95bca1bf 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -3015,6 +3015,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
BlockPos blockposition1;
if (flag1) {
+ // Paper start - Ensure spawn chunk is always loaded before calculating Y coordinate
+ this.level.getChunkAt(((ServerLevel) this.level).getSharedSpawnPos());
+ // Paper end
blockposition1 = ServerLevel.END_SPAWN_POINT;
} else {
blockposition1 = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destination.getSharedSpawnPos());

View file

@ -1,799 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kickash32 <kickash32@gmail.com>
Date: Mon, 19 Aug 2019 01:27:58 +0500
Subject: [PATCH] implement optional per player mob spawns
diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
index fe79c0add4f7cb18d487c5bb9415c40c5b551ea2..8d9ddad1879e7616d980ca70de8aecacaa86db35 100644
--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
@@ -57,6 +57,7 @@ public class WorldTimingsHandler {
public final Timing miscMobSpawning;
+ public final Timing playerMobDistanceMapUpdate;
public final Timing poiUnload;
public final Timing chunkUnload;
@@ -121,6 +122,7 @@ public class WorldTimingsHandler {
miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc");
+ playerMobDistanceMapUpdate = Timings.ofSafe(name + "Per Player Mob Spawning - Distance Map Update");
poiUnload = Timings.ofSafe(name + "Chunk unload - POI");
chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk");
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 0ee9ce9b1214b91942541152443951c901aeafe6..c95659ffe4722b50ea341f60c99262b60b8d0b81 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -618,5 +618,13 @@ public class PaperWorldConfig {
Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues");
}
}
+
+ public boolean perPlayerMobSpawns = false;
+ private void perPlayerMobSpawns() {
+ if (PaperConfig.version < 22) {
+ set("per-player-mob-spawns", Boolean.TRUE);
+ }
+ perPlayerMobSpawns = getBoolean("per-player-mob-spawns", true);
+ }
}
diff --git a/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..72063ba7fb0d04594043cb07034590d597c3d77e
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java
@@ -0,0 +1,252 @@
+package com.destroystokyo.paper.util;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
+import java.util.List;
+import java.util.Map;
+import net.minecraft.core.SectionPos;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.ChunkPos;
+import org.spigotmc.AsyncCatcher;
+import java.util.HashMap;
+
+/** @author Spottedleaf */
+public final class PlayerMobDistanceMap {
+
+ private static final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> EMPTY_SET = new PooledHashSets.PooledObjectLinkedOpenHashSet<>();
+
+ private final Map<ServerPlayer, SectionPos> players = new HashMap<>();
+ // we use linked for better iteration.
+ private final Long2ObjectOpenHashMap<PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer>> playerMap = new Long2ObjectOpenHashMap<>(32, 0.5f);
+ private int viewDistance;
+
+ private final PooledHashSets<ServerPlayer> pooledHashSets = new PooledHashSets<>();
+
+ public PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInRange(final ChunkPos chunkPos) {
+ return this.getPlayersInRange(chunkPos.x, chunkPos.z);
+ }
+
+ public PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInRange(final int chunkX, final int chunkZ) {
+ return this.playerMap.getOrDefault(ChunkPos.asLong(chunkX, chunkZ), EMPTY_SET);
+ }
+
+ public void update(final List<ServerPlayer> currentPlayers, final int newViewDistance) {
+ AsyncCatcher.catchOp("Distance map update");
+ final ObjectLinkedOpenHashSet<ServerPlayer> gone = new ObjectLinkedOpenHashSet<>(this.players.keySet());
+
+ final int oldViewDistance = this.viewDistance;
+ this.viewDistance = newViewDistance;
+
+ for (final ServerPlayer player : currentPlayers) {
+ if (player.isSpectator() || !player.affectsSpawning) {
+ continue; // will be left in 'gone' (or not added at all)
+ }
+
+ gone.remove(player);
+
+ final SectionPos newPosition = player.getLastSectionPos();
+ final SectionPos oldPosition = this.players.put(player, newPosition);
+
+ if (oldPosition == null) {
+ this.addNewPlayer(player, newPosition, newViewDistance);
+ } else {
+ this.updatePlayer(player, oldPosition, newPosition, oldViewDistance, newViewDistance);
+ }
+ //this.validatePlayer(player, newViewDistance); // debug only
+ }
+
+ for (final ServerPlayer player : gone) {
+ final SectionPos oldPosition = this.players.remove(player);
+ if (oldPosition != null) {
+ this.removePlayer(player, oldPosition, oldViewDistance);
+ }
+ }
+ }
+
+ // expensive op, only for debug
+ private void validatePlayer(final ServerPlayer player, final int viewDistance) {
+ int entiesGot = 0;
+ int expectedEntries = (2 * viewDistance + 1);
+ expectedEntries *= expectedEntries;
+
+ final SectionPos currPosition = player.getLastSectionPos();
+
+ final int centerX = currPosition.getX();
+ final int centerZ = currPosition.getZ();
+
+ for (final Long2ObjectLinkedOpenHashMap.Entry<PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer>> entry : this.playerMap.long2ObjectEntrySet()) {
+ final long key = entry.getLongKey();
+ final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> map = entry.getValue();
+
+ if (map.referenceCount == 0) {
+ throw new IllegalStateException("Invalid map");
+ }
+
+ if (map.set.contains(player)) {
+ ++entiesGot;
+
+ final int chunkX = ChunkPos.getX(key);
+ final int chunkZ = ChunkPos.getZ(key);
+
+ final int dist = Math.max(Math.abs(chunkX - centerX), Math.abs(chunkZ - centerZ));
+
+ if (dist > viewDistance) {
+ throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist);
+ }
+ }
+ }
+
+ if (entiesGot != expectedEntries) {
+ throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot);
+ }
+ }
+
+ private void addPlayerTo(final ServerPlayer player, final int chunkX, final int chunkZ) {
+ this.playerMap.compute(ChunkPos.asLong(chunkX, chunkZ), (final Long key, final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players) -> {
+ if (players == null) {
+ return player.cachedSingleMobDistanceMap;
+ } else {
+ return PlayerMobDistanceMap.this.pooledHashSets.findMapWith(players, player);
+ }
+ });
+ }
+
+ private void removePlayerFrom(final ServerPlayer player, final int chunkX, final int chunkZ) {
+ this.playerMap.compute(ChunkPos.asLong(chunkX, chunkZ), (final Long keyInMap, final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players) -> {
+ return PlayerMobDistanceMap.this.pooledHashSets.findMapWithout(players, player); // rets null instead of an empty map
+ });
+ }
+
+ private void updatePlayer(final ServerPlayer player, final SectionPos oldPosition, final SectionPos newPosition, final int oldViewDistance, final int newViewDistance) {
+ final int toX = newPosition.getX();
+ final int toZ = newPosition.getZ();
+ final int fromX = oldPosition.getX();
+ final int fromZ = oldPosition.getZ();
+
+ final int dx = toX - fromX;
+ final int dz = toZ - fromZ;
+
+ final int totalX = Math.abs(fromX - toX);
+ final int totalZ = Math.abs(fromZ - toZ);
+
+ if (Math.max(totalX, totalZ) > (2 * oldViewDistance)) {
+ // teleported?
+ this.removePlayer(player, oldPosition, oldViewDistance);
+ this.addNewPlayer(player, newPosition, newViewDistance);
+ return;
+ }
+
+ // x axis is width
+ // z axis is height
+ // right refers to the x axis of where we moved
+ // top refers to the z axis of where we moved
+
+ if (oldViewDistance == newViewDistance) {
+ // same view distance
+
+ // used for relative positioning
+ final int up = 1 | (dz >> (Integer.SIZE - 1)); // 1 if dz >= 0, -1 otherwise
+ final int right = 1 | (dx >> (Integer.SIZE - 1)); // 1 if dx >= 0, -1 otherwise
+
+ // The area excluded by overlapping the two view distance squares creates four rectangles:
+ // Two on the left, and two on the right. The ones on the left we consider the "removed" section
+ // and on the right the "added" section.
+ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually
+ // exclusive to the regions they surround.
+
+ // 4 points of the rectangle
+ int maxX; // exclusive
+ int minX; // inclusive
+ int maxZ; // exclusive
+ int minZ; // inclusive
+
+ if (dx != 0) {
+ // handle right addition
+
+ maxX = toX + (oldViewDistance * right) + right; // exclusive
+ minX = fromX + (oldViewDistance * right) + right; // inclusive
+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
+ minZ = toZ - (oldViewDistance * up); // inclusive
+
+ for (int currX = minX; currX != maxX; currX += right) {
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
+ this.addPlayerTo(player, currX, currZ);
+ }
+ }
+ }
+
+ if (dz != 0) {
+ // handle up addition
+
+ maxX = toX + (oldViewDistance * right) + right; // exclusive
+ minX = toX - (oldViewDistance * right); // inclusive
+ maxZ = toZ + (oldViewDistance * up) + up; // exclusive
+ minZ = fromZ + (oldViewDistance * up) + up; // inclusive
+
+ for (int currX = minX; currX != maxX; currX += right) {
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
+ this.addPlayerTo(player, currX, currZ);
+ }
+ }
+ }
+
+ if (dx != 0) {
+ // handle left removal
+
+ maxX = toX - (oldViewDistance * right); // exclusive
+ minX = fromX - (oldViewDistance * right); // inclusive
+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
+ minZ = toZ - (oldViewDistance * up); // inclusive
+
+ for (int currX = minX; currX != maxX; currX += right) {
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
+ this.removePlayerFrom(player, currX, currZ);
+ }
+ }
+ }
+
+ if (dz != 0) {
+ // handle down removal
+
+ maxX = fromX + (oldViewDistance * right) + right; // exclusive
+ minX = fromX - (oldViewDistance * right); // inclusive
+ maxZ = toZ - (oldViewDistance * up); // exclusive
+ minZ = fromZ - (oldViewDistance * up); // inclusive
+
+ for (int currX = minX; currX != maxX; currX += right) {
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
+ this.removePlayerFrom(player, currX, currZ);
+ }
+ }
+ }
+ } else {
+ // different view distance
+ // for now :)
+ this.removePlayer(player, oldPosition, oldViewDistance);
+ this.addNewPlayer(player, newPosition, newViewDistance);
+ }
+ }
+
+ private void removePlayer(final ServerPlayer player, final SectionPos position, final int viewDistance) {
+ final int x = position.getX();
+ final int z = position.getZ();
+
+ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) {
+ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) {
+ this.removePlayerFrom(player, x + xoff, z + zoff);
+ }
+ }
+ }
+
+ private void addNewPlayer(final ServerPlayer player, final SectionPos position, final int viewDistance) {
+ final int x = position.getX();
+ final int z = position.getZ();
+
+ for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) {
+ for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) {
+ this.addPlayerTo(player, x + xoff, z + zoff);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java
new file mode 100644
index 0000000000000000000000000000000000000000..11de56afaf059b00fa5bec293516bcdce7c4b2b9
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java
@@ -0,0 +1,241 @@
+package com.destroystokyo.paper.util;
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+
+/** @author Spottedleaf */
+public class PooledHashSets<E> {
+
+ // we really want to avoid that equals() check as much as possible...
+ protected final Object2ObjectOpenHashMap<PooledObjectLinkedOpenHashSet<E>, PooledObjectLinkedOpenHashSet<E>> mapPool = new Object2ObjectOpenHashMap<>(64, 0.25f);
+
+ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet<E> current) {
+ if (current.referenceCount == 0) {
+ throw new IllegalStateException("Cannot decrement reference count for " + current);
+ }
+ if (current.referenceCount == -1 || --current.referenceCount > 0) {
+ return;
+ }
+
+ this.mapPool.remove(current);
+ return;
+ }
+
+ public PooledObjectLinkedOpenHashSet<E> findMapWith(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ final PooledObjectLinkedOpenHashSet<E> cached = current.getAddCache(object);
+
+ if (cached != null) {
+ if (cached.referenceCount != -1) {
+ ++cached.referenceCount;
+ }
+
+ decrementReferenceCount(current);
+
+ return cached;
+ }
+
+ if (!current.add(object)) {
+ return current;
+ }
+
+ // we use get/put since we use a different key on put
+ PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
+
+ if (ret == null) {
+ ret = new PooledObjectLinkedOpenHashSet<>(current);
+ current.remove(object);
+ this.mapPool.put(ret, ret);
+ ret.referenceCount = 1;
+ } else {
+ if (ret.referenceCount != -1) {
+ ++ret.referenceCount;
+ }
+ current.remove(object);
+ }
+
+ current.updateAddCache(object, ret);
+
+ decrementReferenceCount(current);
+ return ret;
+ }
+
+ // rets null if current.size() == 1
+ public PooledObjectLinkedOpenHashSet<E> findMapWithout(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ if (current.set.size() == 1) {
+ decrementReferenceCount(current);
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> cached = current.getRemoveCache(object);
+
+ if (cached != null) {
+ if (cached.referenceCount != -1) {
+ ++cached.referenceCount;
+ }
+
+ decrementReferenceCount(current);
+
+ return cached;
+ }
+
+ if (!current.remove(object)) {
+ return current;
+ }
+
+ // we use get/put since we use a different key on put
+ PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
+
+ if (ret == null) {
+ ret = new PooledObjectLinkedOpenHashSet<>(current);
+ current.add(object);
+ this.mapPool.put(ret, ret);
+ ret.referenceCount = 1;
+ } else {
+ if (ret.referenceCount != -1) {
+ ++ret.referenceCount;
+ }
+ current.add(object);
+ }
+
+ current.updateRemoveCache(object, ret);
+
+ decrementReferenceCount(current);
+ return ret;
+ }
+
+ public static final class PooledObjectLinkedOpenHashSet<E> implements Iterable<E> {
+
+ private static final WeakReference NULL_REFERENCE = new WeakReference(null);
+
+ final ObjectLinkedOpenHashSet<E> set;
+ int referenceCount; // -1 if special
+ int hash; // optimize hashcode
+
+ // add cache
+ WeakReference<E> lastAddObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> lastAddMap = NULL_REFERENCE;
+
+ // remove cache
+ WeakReference<E> lastRemoveObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> lastRemoveMap = NULL_REFERENCE;
+
+ public PooledObjectLinkedOpenHashSet() {
+ this.set = new ObjectLinkedOpenHashSet<>(2, 0.6f);
+ }
+
+ public PooledObjectLinkedOpenHashSet(final E single) {
+ this();
+ this.referenceCount = -1;
+ this.add(single);
+ }
+
+ public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet<E> other) {
+ this.set = other.set.clone();
+ this.hash = other.hash;
+ }
+
+ // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
+ // generated by https://github.com/skeeto/hash-prospector
+ static int hash0(int x) {
+ x *= 0x36935555;
+ x ^= x >>> 16;
+ return x;
+ }
+
+ public PooledObjectLinkedOpenHashSet<E> getAddCache(final E element) {
+ final E currentAdd = this.lastAddObject.get();
+
+ if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) {
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> map = this.lastAddMap.get();
+ if (map == null || map.referenceCount == 0) {
+ // we need to ret null if ref count is zero as calling code will assume the map is in use
+ return null;
+ }
+
+ return map;
+ }
+
+ public PooledObjectLinkedOpenHashSet<E> getRemoveCache(final E element) {
+ final E currentRemove = this.lastRemoveObject.get();
+
+ if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) {
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> map = this.lastRemoveMap.get();
+ if (map == null || map.referenceCount == 0) {
+ // we need to ret null if ref count is zero as calling code will assume the map is in use
+ return null;
+ }
+
+ return map;
+ }
+
+ public void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
+ this.lastAddObject = new WeakReference<>(element);
+ this.lastAddMap = new WeakReference<>(map);
+ }
+
+ public void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
+ this.lastRemoveObject = new WeakReference<>(element);
+ this.lastRemoveMap = new WeakReference<>(map);
+ }
+
+ boolean add(final E element) {
+ boolean added = this.set.add(element);
+
+ if (added) {
+ this.hash += hash0(element.hashCode());
+ }
+
+ return added;
+ }
+
+ boolean remove(Object element) {
+ boolean removed = this.set.remove(element);
+
+ if (removed) {
+ this.hash -= hash0(element.hashCode());
+ }
+
+ return removed;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return this.set.iterator();
+ }
+
+ @Override
+ public int hashCode() {
+ return this.hash;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (!(other instanceof PooledObjectLinkedOpenHashSet)) {
+ return false;
+ }
+ if (this.referenceCount == 0) {
+ return other == this;
+ } else {
+ if (other == this) {
+ // Unfortunately we are never equal to our own instance while in use!
+ return false;
+ }
+ return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " +
+ this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString();
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 15b514aa20370a010ff3ab891d875dc70e41d75a..be9ae4f0f19da069ae44e45a772096bb09918219 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -145,6 +145,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
private final Long2ByteMap chunkTypeCache;
private final Queue<Runnable> unloadQueue;
int viewDistance;
+ public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
public final CallbackExecutor callbackExecutor = new CallbackExecutor();
@@ -343,8 +344,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), null, true, false); // unloaded, loaded
});
// Paper end - no-tick view distance
+ this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper
}
+ // Paper start
+ public void updatePlayerMobTypeMap(Entity entity) {
+ if (!this.level.paperConfig.perPlayerMobSpawns) {
+ return;
+ }
+ int chunkX = (int)Math.floor(entity.getX()) >> 4;
+ int chunkZ = (int)Math.floor(entity.getZ()) >> 4;
+ int index = entity.getType().getCategory().ordinal();
+
+ for (ServerPlayer player : this.playerMobDistanceMap.getPlayersInRange(chunkX, chunkZ)) {
+ ++player.mobCounts[index];
+ }
+ }
+
+ public int getMobCountNear(ServerPlayer entityPlayer, net.minecraft.world.entity.MobCategory mobCategory) {
+ return entityPlayer.mobCounts[mobCategory.ordinal()];
+ }
+ // Paper end
+
private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8);
double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8);
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 5d920d7df52c12f5f1d1d8111340800cbddaac78..9f71dc37b0cef2284d6abc051b379cfa1b7e1eb5 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -912,7 +912,22 @@ public class ServerChunkCache extends ChunkSource {
this.level.getProfiler().push("naturalSpawnCount");
this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
int l = this.distanceManager.getNaturalSpawnChunkCount();
- NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk);
+ // Paper start - per player mob spawning
+ NaturalSpawner.SpawnState spawnercreature_d; // moved down
+ if (this.chunkMap.playerMobDistanceMap != null) {
+ // update distance map
+ this.level.timings.playerMobDistanceMapUpdate.startTiming();
+ this.chunkMap.playerMobDistanceMap.update(this.level.players, this.chunkMap.viewDistance);
+ this.level.timings.playerMobDistanceMapUpdate.stopTiming();
+ // re-set mob counts
+ for (ServerPlayer player : this.level.players) {
+ Arrays.fill(player.mobCounts, 0);
+ }
+ spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, true);
+ } else {
+ spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, false);
+ }
+ // Paper end
this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
this.lastSpawnState = spawnercreature_d;
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 97d6963b6c7f0fb71324ac760df940fbf03e321f..4ca8f2f63e04da730a85a36d57058a481d1f9104 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -224,6 +224,11 @@ public class ServerPlayer extends Player {
public boolean queueHealthUpdatePacket = false;
public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
// Paper end
+ // Paper start - mob spawning rework
+ public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length;
+ public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper
+ public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleMobDistanceMap;
+ // Paper end
// CraftBukkit start
public String displayName;
@@ -313,6 +318,7 @@ public class ServerPlayer extends Player {
this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper
this.bukkitPickUpLoot = true;
this.maxHealthCache = this.getMaxHealth();
+ this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
}
// Yes, this doesn't match Vanilla, but it's the best we can do for now.
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index 461b64232c0f04e17e168f1e7f7857ee555200fc..4d8251a961a9c52456db997506dd9691beaec022 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@@ -17,6 +17,7 @@ import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.Tag;
@@ -65,7 +66,12 @@ public final class NaturalSpawner {
private NaturalSpawner() {}
+ // Paper start - add countMobs parameter
public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource) {
+ return createState(spawningChunkCount, entities, chunkSource, false);
+ }
+ public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource, boolean countMobs) {
+ // Paper end - add countMobs parameter
PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator();
Object2IntOpenHashMap<MobCategory> object2intopenhashmap = new Object2IntOpenHashMap();
Iterator iterator = entities.iterator();
@@ -103,6 +109,11 @@ public final class NaturalSpawner {
}
object2intopenhashmap.addTo(enumcreaturetype, 1);
+ // Paper start
+ if (countMobs) {
+ chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
+ }
+ // Paper end
});
}
}
@@ -166,13 +177,31 @@ public final class NaturalSpawner {
continue;
}
- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && info.canSpawnForCategory(enumcreaturetype, limit)) {
+ // Paper start - only allow spawns upto the limit per chunk and update count afterwards
+ int currEntityCount = info.mobCategoryCounts.getInt(enumcreaturetype);
+ int k1 = limit * info.getSpawnableChunkCount() / NaturalSpawner.MAGIC_NUMBER;
+ int difference = k1 - currEntityCount;
+
+ if (world.paperConfig.perPlayerMobSpawns) {
+ int minDiff = Integer.MAX_VALUE;
+ for (ServerPlayer entityplayer : world.getChunkSource().chunkMap.playerMobDistanceMap.getPlayersInRange(chunk.getPos())) {
+ minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear(entityplayer, enumcreaturetype), minDiff);
+ }
+ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
+ }
+ // Paper end
+
+ // Paper start - per player mob spawning
+ if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && difference > 0) {
// CraftBukkit end
Objects.requireNonNull(info);
NaturalSpawner.SpawnPredicate spawnercreature_c = info::canSpawn;
Objects.requireNonNull(info);
- NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn);
+ int spawnCount = NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn,
+ difference, world.paperConfig.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
+ info.mobCategoryCounts.mergeInt(enumcreaturetype, spawnCount, Integer::sum);
+ // Paper end - per player mob spawning
}
}
@@ -180,12 +209,18 @@ public final class NaturalSpawner {
world.getProfiler().pop();
}
+ // Paper start - add parameters and int ret type
public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
+ spawnCategoryForChunk(group, world, chunk, checker, runner);
+ }
+ public static int spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer<Entity> trackEntity) {
+ // Paper end - add parameters and int ret type
BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk);
if (blockposition.getY() >= world.getMinBuildHeight() + 1) {
- NaturalSpawner.spawnCategoryForPosition(group, world, (ChunkAccess) chunk, blockposition, checker, runner);
+ return NaturalSpawner.spawnCategoryForPosition(group, world, (ChunkAccess) chunk, blockposition, checker, runner, maxSpawns, trackEntity); // Paper
}
+ return 0; // Paper
}
@VisibleForDebug
@@ -196,15 +231,21 @@ public final class NaturalSpawner {
});
}
+ // Paper start - add maxSpawns parameter and return spawned mobs
public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
+ spawnCategoryForPosition(group, world,chunk, pos, checker, runner);
+ }
+ public static int spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer<Entity> trackEntity) {
+ // Paper end - add maxSpawns parameter and return spawned mobs
StructureFeatureManager structuremanager = world.structureFeatureManager();
ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
int i = pos.getY();
BlockState iblockdata = world.getTypeIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
+ int j = 0; // Paper - moved up
if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
- int j = 0;
+ // Paper - moved up
int k = 0;
while (k < 3) {
@@ -246,14 +287,14 @@ public final class NaturalSpawner {
// Paper start
Boolean doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
if (doSpawning == null) {
- return;
+ return j; // Paper
}
if (doSpawning && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
// Paper end
Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type);
if (entityinsentient == null) {
- return;
+ return j; // Paper
}
entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F);
@@ -265,10 +306,15 @@ public final class NaturalSpawner {
++j;
++k1;
runner.run(entityinsentient, chunk);
+ // Paper start
+ if (trackEntity != null) {
+ trackEntity.accept(entityinsentient);
+ }
+ // Paper end
}
// CraftBukkit end
- if (j >= entityinsentient.getMaxSpawnClusterSize()) {
- return;
+ if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper
+ return j; // Paper
}
if (entityinsentient.isMaxGroupSizeReached(k1)) {
@@ -290,6 +336,7 @@ public final class NaturalSpawner {
}
}
+ return j; // Paper
}
private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) {

View file

@ -1,142 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: CullanP <cullanpage@gmail.com>
Date: Thu, 3 Mar 2016 02:13:38 -0600
Subject: [PATCH] Avoid hopper searches if there are no items
Hoppers searching for items and minecarts is the most expensive part of hopper ticking.
We keep track of the number of minecarts and items in a chunk.
If there are no items in the chunk, we skip searching for items.
If there are no minecarts in the chunk, we skip searching for them.
Usually hoppers aren't near items, so we can skip most item searches.
And since minecart hoppers are used _very_ rarely near we can avoid alot of searching there.
Combined, this adds up a lot.
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index f196a184c05d5f87faee78323343d1fe19287c07..8306d6628c5b7507ee80cb2bff660e0badf84660 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -985,7 +985,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
}
- });
+ }, predicate == net.minecraft.world.entity.EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper
return list;
}
diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySection.java b/src/main/java/net/minecraft/world/level/entity/EntitySection.java
index 9a7b2602c2549dd03ad097c4a922a10a5e869645..5342eafe25614d262eeb39fa517a242e27d998f6 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntitySection.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntitySection.java
@@ -12,6 +12,10 @@ public class EntitySection<T> {
protected static final Logger LOGGER = LogManager.getLogger();
private final ClassInstanceMultiMap<T> storage;
private Visibility chunkStatus;
+ // Paper start - track number of items and minecarts
+ public int itemCount;
+ public int inventoryEntityCount;
+ // Paper end
public EntitySection(Class<T> entityClass, Visibility status) {
this.chunkStatus = status;
@@ -19,10 +23,24 @@ public class EntitySection<T> {
}
public void add(T obj) {
+ // Paper start
+ if (obj instanceof net.minecraft.world.entity.item.ItemEntity) {
+ this.itemCount++;
+ } else if (obj instanceof net.minecraft.world.Container) {
+ this.inventoryEntityCount++;
+ }
+ // Paper end
this.storage.add(obj);
}
public boolean remove(T obj) {
+ // Paper start
+ if (obj instanceof net.minecraft.world.entity.item.ItemEntity) {
+ this.itemCount--;
+ } else if (obj instanceof net.minecraft.world.Container) {
+ this.inventoryEntityCount--;
+ }
+ // Paper end
return this.storage.remove(obj);
}
@@ -39,7 +57,7 @@ public class EntitySection<T> {
for(T object : this.storage.find(type.getBaseClass())) {
U object2 = (U)type.tryCast(object);
if (object2 != null && filter.test(object2)) {
- action.accept((T)object2);
+ action.accept(object2); // Paper - decompile fix
}
}
diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java
index 067138e3983959347d19754e668bb7a1f702bad0..24552500307c42f9f3dc5c4d9ba73a84a787423a 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java
@@ -105,7 +105,7 @@ public class EntitySectionStorage<T extends EntityAccess> {
public LongSet getAllChunksWithExistingSections() {
LongSet longSet = new LongOpenHashSet();
- this.sections.keySet().forEach((sectionPos) -> {
+ this.sections.keySet().forEach((java.util.function.LongConsumer) (sectionPos) -> { // Paper - decompile fix
longSet.add(getChunkKeyFromSectionKey(sectionPos));
});
return longSet;
@@ -118,13 +118,20 @@ public class EntitySectionStorage<T extends EntityAccess> {
}
public void getEntities(AABB box, Consumer<T> action) {
+ // Paper start
+ this.getEntities(box, action, false);
+ }
+ public void getEntities(AABB box, Consumer<T> action, boolean isContainerSearch) {
+ // Paper end
this.forEachAccessibleSection(box, (entitySection) -> {
+ if (isContainerSearch && entitySection.inventoryEntityCount <= 0) return; // Paper
entitySection.getEntities(createBoundingBoxCheck(box), action);
});
}
public <U extends T> void getEntities(EntityTypeTest<T, U> filter, AABB box, Consumer<U> action) {
this.forEachAccessibleSection(box, (entitySection) -> {
+ if (filter.getBaseClass() == net.minecraft.world.entity.item.ItemEntity.class && entitySection.itemCount <= 0) return; // Paper
entitySection.getEntities(filter, createBoundingBoxCheck(box), action);
});
}
diff --git a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java
index 9723a0ad61548c8c6c4c5ef20a150d5b17d80afd..da1ad0b2679e392ed81b50c15f012c63cb5c939e 100644
--- a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java
+++ b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java
@@ -17,6 +17,7 @@ public interface LevelEntityGetter<T extends EntityAccess> {
<U extends T> void get(EntityTypeTest<T, U> filter, Consumer<U> action);
void get(AABB box, Consumer<T> action);
+ void get(AABB box, Consumer<T> action, boolean isContainerSearch); // Paper
<U extends T> void get(EntityTypeTest<T, U> filter, AABB box, Consumer<U> action);
}
diff --git a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java
index d5129c12c79eb6fe6b7e5f8eed4d24226423f5fd..3b13f6ea36a3bfecabe09221eb5c48dddab119db 100644
--- a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java
+++ b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java
@@ -38,7 +38,13 @@ public class LevelEntityGetterAdapter<T extends EntityAccess> implements LevelEn
@Override
public void get(AABB box, Consumer<T> action) {
- this.sectionStorage.getEntities(box, action);
+ // Paper start
+ this.get(box, action, false);
+ }
+ @Override
+ public void get(AABB box, Consumer<T> action, boolean isContainerSearch) {
+ this.sectionStorage.getEntities(box, action, isContainerSearch);
+ // Paper end
}
@Override

View file

@ -1,34 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Sun, 26 Jan 2020 16:30:19 -0600
Subject: [PATCH] Bees get gravity in void. Fixes MC-167279
diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java
index 47be3e33f89bdd633aba381c8c3052cfebf64d48..2487d636338cb968496166c68c3155d2913d6c41 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java
@@ -143,7 +143,22 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
public Bee(EntityType<? extends Bee> type, Level world) {
super(type, world);
this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60);
- this.moveControl = new FlyingMoveControl(this, 20, true);
+ // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279
+ class BeeFlyingMoveControl extends FlyingMoveControl {
+ public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) {
+ super(entity, maxPitchChange, noGravity);
+ }
+
+ @Override
+ public void tick() {
+ if (this.mob.getY() <= Bee.this.level.getMinBuildHeight()) {
+ this.mob.setNoGravity(false);
+ }
+ super.tick();
+ }
+ }
+ this.moveControl = new BeeFlyingMoveControl(this, 20, true);
+ // Paper end
this.lookControl = new Bee.BeeLookControl(this);
this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F);
this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F);

View file

@ -1,66 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 25 Jan 2020 17:04:35 -0800
Subject: [PATCH] Optimise getChunkAt calls for loaded chunks
bypass the need to get a player chunk, then get the either,
then unwrap it...
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 9f71dc37b0cef2284d6abc051b379cfa1b7e1eb5..9b69e1fe5432b25f0908dc977188d283ae9feac4 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -609,6 +609,12 @@ public class ServerChunkCache extends ChunkSource {
return this.getChunk(x, z, leastStatus, create);
}, this.mainThreadProcessor).join();
} else {
+ // Paper start - optimise for loaded chunks
+ LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z);
+ if (ifLoaded != null) {
+ return ifLoaded;
+ }
+ // Paper end
ProfilerFiller gameprofilerfiller = this.level.getProfiler();
gameprofilerfiller.incrementCounter("getChunk");
@@ -660,39 +666,7 @@ public class ServerChunkCache extends ChunkSource {
if (Thread.currentThread() != this.mainThread) {
return null;
} else {
- this.level.getProfiler().incrementCounter("getChunkNow");
- long k = ChunkPos.asLong(chunkX, chunkZ);
-
- for (int l = 0; l < 4; ++l) {
- if (k == this.lastChunkPos[l] && this.lastChunkStatus[l] == ChunkStatus.FULL) {
- ChunkAccess ichunkaccess = this.lastChunk[l];
-
- return ichunkaccess instanceof LevelChunk ? (LevelChunk) ichunkaccess : null;
- }
- }
-
- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
-
- if (playerchunk == null) {
- return null;
- } else {
- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = (Either) playerchunk.getFutureIfPresent(ChunkStatus.FULL).getNow(null); // CraftBukkit - decompile error
-
- if (either == null) {
- return null;
- } else {
- ChunkAccess ichunkaccess1 = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error
-
- if (ichunkaccess1 != null) {
- this.storeInCache(k, ichunkaccess1, ChunkStatus.FULL);
- if (ichunkaccess1 instanceof LevelChunk) {
- return (LevelChunk) ichunkaccess1;
- }
- }
-
- return null;
- }
- }
+ return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - optimise for loaded chunks
}
}

View file

@ -1,335 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 19 Jul 2019 03:29:14 -0700
Subject: [PATCH] Add debug for sync chunk loads
This patch adds a tool to find calls to getChunkAt which would load
chunks, however it must be enabled by setting the startup flag
-Dpaper.debug-sync-loads=true
- To get a debug log for sync loads, the command is
/paper syncloadinfo
- To clear clear the currently stored sync load info, use
/paper syncloadinfo clear
diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
index 3091c100eaf5a86ba270ef0d96de1852a2a0ac9e..51e469146f0712a509071c8438ff6b69f961f945 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
@@ -1,11 +1,17 @@
package com.destroystokyo.paper;
+import com.destroystokyo.paper.io.SyncLoadFinder;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gson.JsonObject;
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonWriter;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.MCUtil;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
@@ -30,6 +36,9 @@ import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.io.StringWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayDeque;
@@ -47,7 +56,7 @@ import java.util.stream.Collectors;
public class PaperCommand extends Command {
private static final String BASE_PERM = "bukkit.command.paper.";
- private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight").build();
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo").build();
public PaperCommand(String name) {
super(name);
@@ -90,6 +99,11 @@ public class PaperCommand extends Command {
return getListMatchingLast(sender, args, worldNames);
}
break;
+ case "syncloadinfo":
+ if (args.length == 2) {
+ return getListMatchingLast(sender, args, "clear");
+ }
+ break;
}
return Collections.emptyList();
}
@@ -165,6 +179,9 @@ public class PaperCommand extends Command {
case "fixlight":
this.doFixLight(sender, args);
break;
+ case "syncloadinfo":
+ this.doSyncLoadInfo(sender, args);
+ break;
case "ver":
if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set)
case "version":
@@ -182,6 +199,47 @@ public class PaperCommand extends Command {
return true;
}
+ private void doSyncLoadInfo(CommandSender sender, String[] args) {
+ if (!SyncLoadFinder.ENABLED) {
+ sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.");
+ return;
+ }
+
+ if (args.length > 1 && args[1].equals("clear")) {
+ SyncLoadFinder.clear();
+ sender.sendMessage(ChatColor.GRAY + "Sync load data cleared.");
+ return;
+ }
+
+ File file = new File(new File(new File("."), "debug"),
+ "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt");
+ file.getParentFile().mkdirs();
+ sender.sendMessage(ChatColor.GREEN + "Writing sync load info to " + file.toString());
+
+
+ try {
+ final JsonObject data = SyncLoadFinder.serialize();
+
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.setIndent(" ");
+ jsonWriter.setLenient(false);
+ Streams.write(data, jsonWriter);
+
+ String fileData = stringWriter.toString();
+
+ try (
+ PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")
+ ) {
+ out.print(fileData);
+ }
+ sender.sendMessage(ChatColor.GREEN + "Successfully written sync load information!");
+ } catch (Throwable thr) {
+ sender.sendMessage(ChatColor.RED + "Failed to write sync load information");
+ thr.printStackTrace();
+ }
+ }
+
private void doChunkInfo(CommandSender sender, String[] args) {
List<org.bukkit.World> worlds;
if (args.length < 2 || args[1].equals("*")) {
diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bb4aaa546939b67a5d22865190f30478a9337c1
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
@@ -0,0 +1,175 @@
+package com.destroystokyo.paper.io;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.mojang.datafixers.util.Pair;
+import it.unimi.dsi.fastutil.longs.Long2IntMap;
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import net.minecraft.world.level.Level;
+
+public class SyncLoadFinder {
+
+ public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads");
+
+ private static final WeakHashMap<Level, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> SYNC_LOADS = new WeakHashMap<>();
+
+ private static final class SyncLoadInformation {
+
+ public int times;
+
+ public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap();
+ }
+
+ public static void clear() {
+ SYNC_LOADS.clear();
+ }
+
+ public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) {
+ if (!ENABLED) {
+ return;
+ }
+
+ final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace());
+
+ SYNC_LOADS.compute(world, (final Level keyInMap, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation> map) -> {
+ if (map == null) {
+ map = new Object2ObjectOpenHashMap<>();
+ }
+
+ map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> {
+ if (valueInMap == null) {
+ valueInMap = new SyncLoadInformation();
+ }
+
+ ++valueInMap.times;
+
+ valueInMap.coordinateTimes.compute(IOUtil.getCoordinateKey(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> {
+ return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1);
+ });
+
+ return valueInMap;
+ });
+
+ return map;
+ });
+ }
+
+ public static JsonObject serialize() {
+ final JsonObject ret = new JsonObject();
+
+ final JsonArray worldsData = new JsonArray();
+
+ for (final Map.Entry<Level, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> entry : SYNC_LOADS.entrySet()) {
+ final Level world = entry.getKey();
+
+ final JsonObject worldData = new JsonObject();
+
+ worldData.addProperty("name", world.getWorld().getName());
+
+ final List<Pair<ThrowableWithEquals, SyncLoadInformation>> data = new ArrayList<>();
+
+ entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> {
+ data.add(new Pair<>(stacktrace, times));
+ });
+
+ data.sort((Pair<ThrowableWithEquals, SyncLoadInformation> pair1, Pair<ThrowableWithEquals, SyncLoadInformation> pair2) -> {
+ return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order
+ });
+
+ final JsonArray stacktraces = new JsonArray();
+
+ for (Pair<ThrowableWithEquals, SyncLoadInformation> pair : data) {
+ final JsonObject stacktrace = new JsonObject();
+
+ stacktrace.addProperty("times", pair.getSecond().times);
+
+ final JsonArray traces = new JsonArray();
+
+ for (StackTraceElement element : pair.getFirst().stacktrace) {
+ traces.add(String.valueOf(element));
+ }
+
+ stacktrace.add("stacktrace", traces);
+
+ final JsonArray coordinates = new JsonArray();
+
+ for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) {
+ final long key = coordinate.getLongKey();
+ final int times = coordinate.getIntValue();
+ coordinates.add("(" + IOUtil.getCoordinateX(key) + "," + IOUtil.getCoordinateZ(key) + "): " + times);
+ }
+
+ stacktrace.add("coordinates", coordinates);
+
+ stacktraces.add(stacktrace);
+ }
+
+
+ worldData.add("stacktraces", stacktraces);
+ worldsData.add(worldData);
+ }
+
+ ret.add("worlds", worldsData);
+
+ return ret;
+ }
+
+ static final class ThrowableWithEquals {
+
+ private final StackTraceElement[] stacktrace;
+ private final int hash;
+
+ public ThrowableWithEquals(final StackTraceElement[] stacktrace) {
+ this.stacktrace = stacktrace;
+ this.hash = ThrowableWithEquals.hash(stacktrace);
+ }
+
+ public static int hash(final StackTraceElement[] stacktrace) {
+ int hash = 0;
+
+ for (int i = 0; i < stacktrace.length; ++i) {
+ hash *= 31;
+ hash += stacktrace[i].hashCode();
+ }
+
+ return hash;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null || obj.getClass() != this.getClass()) {
+ return false;
+ }
+
+ final ThrowableWithEquals other = (ThrowableWithEquals)obj;
+ final StackTraceElement[] otherStackTrace = other.stacktrace;
+
+ if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) {
+ return false;
+ }
+
+ if (this == obj) {
+ return true;
+ }
+
+ for (int i = 0; i < this.stacktrace.length; ++i) {
+ if (!this.stacktrace[i].equals(otherStackTrace[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 9b69e1fe5432b25f0908dc977188d283ae9feac4..c6213a7dfcf9aeccdb546eaf74fa8eb119a6a32c 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -641,6 +641,7 @@ public class ServerChunkCache extends ChunkSource {
this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1);
// Paper end
+ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info
this.level.timings.syncChunkLoad.startTiming(); // Paper
chunkproviderserver_a.managedBlock(completablefuture::isDone);
com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 0df81cbddf2e7f164861b95cf572f9d6d3f031ca..207bcd82065e4c1832e4d7814635017210bd9d4d 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -366,6 +366,12 @@ public class ServerLevel extends Level implements WorldGenLevel {
};
public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager;
// Paper end
+ // Paper start
+ @Override
+ public boolean hasChunk(int chunkX, int chunkZ) {
+ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null;
+ }
+ // Paper end
// Paper start - optimise getPlayerByUUID
@Nullable

View file

@ -1,20 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <zach@zachbr.io>
Date: Sat, 8 Feb 2020 18:02:24 -0600
Subject: [PATCH] Allow overriding the java version check
-DPaper.IgnoreJavaVersion=true
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index 1da136f365664d4f8ace3d2d135b19eb97e55304..33fb61c219f5356a40c4e6e47187a3a606402536 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -185,7 +185,7 @@ public class Main {
}
if (javaVersion > 61.0) {
System.err.println("Unsupported Java detected (" + javaVersion + "). Only up to Java 17 is supported.");
- return;
+ if (!Boolean.getBoolean("Paper.IgnoreJavaVersion")) return; // Paper
}
try {

View file

@ -1,27 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Sun, 9 Feb 2020 00:19:05 -0600
Subject: [PATCH] Add ThrownEggHatchEvent
Adds a new event similar to PlayerEggThrowEvent, but without the Player requirement
(dispensers can throw eggs to hatch them, too).
diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java
index 4e083dcd07e5975c7379035e72ac2f3469e919fd..77941e3981e49cf5662b3e3c86a9c419080b17c8 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java
@@ -77,6 +77,14 @@ public class ThrownEgg extends ThrowableItemProjectile {
hatchingType = event.getHatchingType();
}
+ // Paper start
+ com.destroystokyo.paper.event.entity.ThrownEggHatchEvent event = new com.destroystokyo.paper.event.entity.ThrownEggHatchEvent((org.bukkit.entity.Egg) getBukkitEntity(), hatching, b0, hatchingType);
+ event.callEvent();
+
+ b0 = event.getNumHatches();
+ hatching = event.isHatching();
+ hatchingType = event.getHatchingType();
+ // Paper end
if (hatching) {
for (int i = 0; i < b0; ++i) {
Entity entity = level.getWorld().createEntity(new org.bukkit.Location(level.getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F), hatchingType.getEntityClass());

View file

@ -1,59 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: BillyGalbreath <Blake.Galbreath@GMail.com>
Date: Sat, 8 Feb 2020 23:26:11 -0600
Subject: [PATCH] Entity Jump API
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 7fc15f7731b9a249f5a1b82278c4ad61105462ce..3837cdc92174914e503dc573f496ea3464c7b566 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -3165,8 +3165,10 @@ public abstract class LivingEntity extends Entity {
} else if (this.isInLava() && (!this.onGround || d7 > d8)) {
this.jumpInLiquid((Tag) FluidTags.LAVA);
} else if ((this.onGround || flag && d7 <= d8) && this.noJumpDelay == 0) {
+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper
this.jumpFromGround();
this.noJumpDelay = 10;
+ } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop
}
} else {
this.noJumpDelay = 0;
diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java
index ddc32ee8112e318f913546fcaa1fd6b26d59a672..dadc202da2d5568dce051b9cb4aadf20cfdd2c4f 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java
@@ -514,7 +514,9 @@ public class Panda extends Animal {
Panda entitypanda = (Panda) iterator.next();
if (!entitypanda.isBaby() && entitypanda.onGround && !entitypanda.isInWater() && entitypanda.canPerformAction()) {
+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper
entitypanda.jumpFromGround();
+ } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
index 814363d0dee4a91c2e1d5f91e67530fec383c0cb..05c36f6dbd4da2cf29cc4087e3c8a78ecff41337 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
@@ -824,5 +824,19 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
public org.bukkit.inventory.EquipmentSlot getHandRaised() {
return getHandle().getUsedItemHand() == net.minecraft.world.InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND;
}
+
+ @Override
+ public boolean isJumping() {
+ return getHandle().jumping;
+ }
+
+ @Override
+ public void setJumping(boolean jumping) {
+ getHandle().setJumping(jumping);
+ if (jumping && getHandle() instanceof Mob) {
+ // this is needed to actually make a mob jump
+ ((Mob) getHandle()).getJumpControl().jump();
+ }
+ }
// Paper end
}

View file

@ -1,65 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Fri, 7 Feb 2020 14:36:56 -0600
Subject: [PATCH] Add option to nerf pigmen from nether portals
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index c95659ffe4722b50ea341f60c99262b60b8d0b81..8e2a2a069cd66605344a4d129dbb673976bf2f1f 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -526,6 +526,11 @@ public class PaperWorldConfig {
log("Hopper Ignore Occluding Blocks: " + (hoppersIgnoreOccludingBlocks ? "enabled" : "disabled"));
}
+ public boolean nerfNetherPortalPigmen = false;
+ private void nerfNetherPortalPigmen() {
+ nerfNetherPortalPigmen = getBoolean("game-mechanics.nerf-pigmen-from-nether-portals", nerfNetherPortalPigmen);
+ }
+
public int lightQueueSize = 20;
private void lightQueueSize() {
lightQueueSize = getInt("light-queue-size", lightQueueSize);
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 06597d10c640daca400da9d5a4c186cc95bca1bf..c8e1f38de2189c16cedfbf848593e4ae06e911f3 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -330,6 +330,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
public long activatedImmunityTick = Integer.MIN_VALUE; // Paper
public boolean isTemporarilyActive = false; // Paper
public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one
+ public boolean fromNetherPortal; // Paper
protected int numCollisions = 0; // Paper
public void inactiveTick() { }
// Spigot end
@@ -1892,6 +1893,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
if (spawnedViaMobSpawner) {
nbt.putBoolean("Paper.FromMobSpawner", true);
}
+ if (fromNetherPortal) {
+ nbt.putBoolean("Paper.FromNetherPortal", true);
+ }
// Paper end
return nbt;
} catch (Throwable throwable) {
@@ -2033,6 +2037,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
}
spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status
+ fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal");
if (nbt.contains("Paper.SpawnReason")) {
String spawnReasonName = nbt.getString("Paper.SpawnReason");
try {
diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java
index e34716f2a19eb578fef3f19182c124d359deb88f..cfea29f5bf1c5e74a0292c1344baaaa49c2f4403 100644
--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java
@@ -66,6 +66,8 @@ public class NetherPortalBlock extends Block {
if (entity != null) {
entity.setPortalCooldown();
+ entity.fromNetherPortal = true; // Paper
+ if (world.paperConfig.nerfNetherPortalPigmen) ((net.minecraft.world.entity.Mob) entity).aware = false; // Paper
}
}
}

View file

@ -1,398 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Sun, 2 Feb 2020 04:00:40 -0600
Subject: [PATCH] Make the GUI graph fancier
diff --git a/src/main/java/com/destroystokyo/paper/gui/GraphColor.java b/src/main/java/com/destroystokyo/paper/gui/GraphColor.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4e641fdcccd3efcd1a2865dc6dc28d50671b995
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/gui/GraphColor.java
@@ -0,0 +1,44 @@
+package com.destroystokyo.paper.gui;
+
+import java.awt.Color;
+
+public class GraphColor {
+ private static final Color[] colorLine = new Color[101];
+ private static final Color[] colorFill = new Color[101];
+
+ static {
+ for (int i = 0; i < 101; i++) {
+ Color color = createColor(i);
+ colorLine[i] = new Color(color.getRed() / 2, color.getGreen() / 2, color.getBlue() / 2, 255);
+ colorFill[i] = new Color(colorLine[i].getRed(), colorLine[i].getGreen(), colorLine[i].getBlue(), 125);
+ }
+ }
+
+ public static Color getLineColor(int percent) {
+ return colorLine[percent];
+ }
+
+ public static Color getFillColor(int percent) {
+ return colorFill[percent];
+ }
+
+ private static Color createColor(int percent) {
+ if (percent <= 50) {
+ return new Color(0X00FF00);
+ }
+
+ int value = 510 - (int) (Math.min(Math.max(0, ((percent - 50) / 50F)), 1) * 510);
+
+ int red, green;
+ if (value < 255) {
+ red = 255;
+ green = (int) (Math.sqrt(value) * 16);
+ } else {
+ green = 255;
+ value = value - 255;
+ red = 255 - (value * value / 255);
+ }
+
+ return new Color(red, green, 0);
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/gui/GraphData.java b/src/main/java/com/destroystokyo/paper/gui/GraphData.java
new file mode 100644
index 0000000000000000000000000000000000000000..186fc722965e403f76b1480e1c2381fc34e29049
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/gui/GraphData.java
@@ -0,0 +1,47 @@
+package com.destroystokyo.paper.gui;
+
+import java.awt.Color;
+
+public class GraphData {
+ private long total;
+ private long free;
+ private long max;
+ private long usedMem;
+ private int usedPercent;
+
+ public GraphData(long total, long free, long max) {
+ this.total = total;
+ this.free = free;
+ this.max = max;
+ this.usedMem = total - free;
+ this.usedPercent = usedMem == 0 ? 0 : (int) (usedMem * 100L / max);
+ }
+
+ public long getTotal() {
+ return total;
+ }
+
+ public long getFree() {
+ return free;
+ }
+
+ public long getMax() {
+ return max;
+ }
+
+ public long getUsedMem() {
+ return usedMem;
+ }
+
+ public int getUsedPercent() {
+ return usedPercent;
+ }
+
+ public Color getFillColor() {
+ return GraphColor.getFillColor(usedPercent);
+ }
+
+ public Color getLineColor() {
+ return GraphColor.getLineColor(usedPercent);
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java b/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java
new file mode 100644
index 0000000000000000000000000000000000000000..537bc6213545e8ff1b7b51bc4b27fd5b2a740883
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java
@@ -0,0 +1,41 @@
+package com.destroystokyo.paper.gui;
+
+import net.minecraft.server.MinecraftServer;
+
+import javax.swing.JPanel;
+import javax.swing.Timer;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+public class GuiStatsComponent extends JPanel {
+ private final Timer timer;
+ private final RAMGraph ramGraph;
+
+ public GuiStatsComponent(MinecraftServer server) {
+ super(new BorderLayout());
+
+ setOpaque(false);
+
+ ramGraph = new RAMGraph();
+ RAMDetails ramDetails = new RAMDetails(server);
+
+ add(ramGraph, "North");
+ add(ramDetails, "Center");
+
+ timer = new Timer(500, (event) -> {
+ ramGraph.update();
+ ramDetails.update();
+ });
+ timer.start();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(350, 200);
+ }
+
+ public void close() {
+ timer.stop();
+ ramGraph.stop();
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java
new file mode 100644
index 0000000000000000000000000000000000000000..23239679d6584f1088b2b94c46eb9a5c1f9ad91d
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java
@@ -0,0 +1,73 @@
+package com.destroystokyo.paper.gui;
+
+import net.minecraft.Util;
+import net.minecraft.server.MinecraftServer;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.JList;
+import javax.swing.border.EmptyBorder;
+import java.awt.Dimension;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+import java.util.Vector;
+
+public class RAMDetails extends JList<String> {
+ public static final DecimalFormat DECIMAL_FORMAT = Util.make(new DecimalFormat("########0.000"), (format)
+ -> format.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)));
+
+ private final MinecraftServer server;
+
+ public RAMDetails(MinecraftServer server) {
+ this.server = server;
+
+ setBorder(new EmptyBorder(0, 10, 0, 0));
+ setFixedCellHeight(20);
+ setOpaque(false);
+
+ DefaultListCellRenderer renderer = new DefaultListCellRenderer();
+ renderer.setOpaque(false);
+ setCellRenderer(renderer);
+
+ setSelectionModel(new DefaultListSelectionModel() {
+ @Override
+ public void setAnchorSelectionIndex(final int anchorIndex) {
+ }
+
+ @Override
+ public void setLeadAnchorNotificationEnabled(final boolean flag) {
+ }
+
+ @Override
+ public void setLeadSelectionIndex(final int leadIndex) {
+ }
+
+ @Override
+ public void setSelectionInterval(final int index0, final int index1) {
+ }
+ });
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(350, 100);
+ }
+
+ public void update() {
+ GraphData data = RAMGraph.DATA.peekLast();
+ Vector<String> vector = new Vector<>();
+ vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)");
+ vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb");
+ vector.add("Avg tick: " + DECIMAL_FORMAT.format(getAverage(server.tickTimes)) + " ms");
+ setListData(vector);
+ }
+
+ public double getAverage(long[] tickTimes) {
+ long total = 0L;
+ for (long value : tickTimes) {
+ total += value;
+ }
+ return ((double) total / (double) tickTimes.length) * 1.0E-6D;
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3e54da4ab6440811aab2f9dd1e218802ac13285
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java
@@ -0,0 +1,144 @@
+package com.destroystokyo.paper.gui;
+
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import javax.swing.ToolTipManager;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.MouseInfo;
+import java.awt.Point;
+import java.awt.PointerInfo;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.concurrent.TimeUnit;
+
+public class RAMGraph extends JComponent {
+ public static final LinkedList<GraphData> DATA = new LinkedList<GraphData>() {
+ @Override
+ public boolean add(GraphData data) {
+ if (size() >= 348) {
+ remove();
+ }
+ return super.add(data);
+ }
+ };
+
+ static {
+ GraphData empty = new GraphData(0, 0, 0);
+ for (int i = 0; i < 350; i++) {
+ DATA.add(empty);
+ }
+ }
+
+ private final Timer timer;
+ private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
+
+ private int currentTick;
+
+ public RAMGraph() {
+ ToolTipManager.sharedInstance().setInitialDelay(0);
+
+ addMouseListener(new MouseAdapter() {
+ final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay();
+ final int dismissDelayMinutes = (int) TimeUnit.MINUTES.toMillis(10);
+
+ @Override
+ public void mouseEntered(MouseEvent me) {
+ ToolTipManager.sharedInstance().setDismissDelay(dismissDelayMinutes);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent me) {
+ ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout);
+ }
+ });
+
+ timer = new Timer(50, (event) -> repaint());
+ timer.start();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(350, 110);
+ }
+
+ public void update() {
+ Runtime jvm = Runtime.getRuntime();
+ DATA.add(new GraphData(jvm.totalMemory(), jvm.freeMemory(), jvm.maxMemory()));
+
+ PointerInfo pointerInfo = MouseInfo.getPointerInfo();
+ if (pointerInfo != null) {
+ Point point = pointerInfo.getLocation();
+ if (point != null) {
+ Point loc = new Point(point);
+ SwingUtilities.convertPointFromScreen(loc, this);
+ if (this.contains(loc)) {
+ ToolTipManager.sharedInstance().mouseMoved(
+ new MouseEvent(this, -1, System.currentTimeMillis(), 0, loc.x, loc.y,
+ point.x, point.y, 0, false, 0));
+ }
+ }
+ }
+
+ currentTick++;
+ }
+
+ @Override
+ public void paint(Graphics graphics) {
+ graphics.setColor(new Color(0xFFFFFFFF));
+ graphics.fillRect(0, 0, 350, 100);
+
+ graphics.setColor(new Color(0x888888));
+ graphics.drawLine(1, 25, 348, 25);
+ graphics.drawLine(1, 50, 348, 50);
+ graphics.drawLine(1, 75, 348, 75);
+
+ int i = 0;
+ for (GraphData data : DATA) {
+ i++;
+ if ((i + currentTick) % 120 == 0) {
+ graphics.setColor(new Color(0x888888));
+ graphics.drawLine(i, 1, i, 99);
+ }
+ int used = data.getUsedPercent();
+ if (used > 0) {
+ Color color = data.getLineColor();
+ graphics.setColor(data.getFillColor());
+ graphics.fillRect(i, 100 - used, 1, used);
+ graphics.setColor(color);
+ graphics.fillRect(i, 100 - used, 1, 1);
+ }
+ }
+
+ graphics.setColor(new Color(0xFF000000));
+ graphics.drawRect(0, 0, 348, 100);
+
+ Point m = getMousePosition();
+ if (m != null && m.x > 0 && m.x < 348 && m.y > 0 && m.y < 100) {
+ GraphData data = DATA.get(m.x);
+ int used = data.getUsedPercent();
+ graphics.setColor(new Color(0x000000));
+ graphics.drawLine(m.x, 1, m.x, 99);
+ graphics.drawOval(m.x - 2, 100 - used - 2, 5, 5);
+ graphics.setColor(data.getLineColor());
+ graphics.fillOval(m.x - 2, 100 - used - 2, 5, 5);
+ setToolTipText(String.format("<html><body>Used: %s mb (%s%%)<br/>%s</body></html>",
+ Math.round(data.getUsedMem() / 1024F / 1024F),
+ used, getTime(m.x)));
+ }
+ }
+
+ public String getTime(int halfSeconds) {
+ int millis = (348 - halfSeconds) / 2 * 1000;
+ return TIME_FORMAT.format(new Date((System.currentTimeMillis() - millis)));
+ }
+
+ public void stop() {
+ timer.stop();
+ }
+}
diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
index 737ae68ab486a324628e994586862ef7397ae278..703d2bb93d6ab76fc117a320f155570addcc543c 100644
--- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
+++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
@@ -95,7 +95,7 @@ public class MinecraftServerGui extends JComponent {
private JComponent buildInfoPanel() {
JPanel jpanel = new JPanel(new BorderLayout());
- StatsComponent guistatscomponent = new StatsComponent(this.server);
+ com.destroystokyo.paper.gui.GuiStatsComponent guistatscomponent = new com.destroystokyo.paper.gui.GuiStatsComponent(this.server); // Paper
Collection<Runnable> collection = this.finalizers; // CraftBukkit - decompile error
Objects.requireNonNull(guistatscomponent);

View file

@ -1,30 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Trigary <trigary0@gmail.com>
Date: Sun, 1 Mar 2020 22:43:24 +0100
Subject: [PATCH] add hand to BlockMultiPlaceEvent
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 3ef52d7705f3c59e19c56aac6250f2e5b5712600..4c2ebb2eb93c1f8a805dbe0919f9726ccb4fff1f 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -344,13 +344,18 @@ public class CraftEventFactory {
}
org.bukkit.inventory.ItemStack item;
+ // Paper start - add hand to BlockMultiPlaceEvent
+ EquipmentSlot equipmentSlot;
if (hand == InteractionHand.MAIN_HAND) {
item = player.getInventory().getItemInMainHand();
+ equipmentSlot = EquipmentSlot.HAND;
} else {
item = player.getInventory().getItemInOffHand();
+ equipmentSlot = EquipmentSlot.OFF_HAND;
}
- BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild);
+ BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild, equipmentSlot);
+ // Paper end
craftServer.getPluginManager().callEvent(event);
return event;

View file

@ -1,21 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Tue, 3 Mar 2020 05:26:40 +0000
Subject: [PATCH] Prevent teleporting dead entities
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 98a0034f4e19a244c22619236cd8ab76b05df1bb..d4fac789cebd17e7b244002a7382d94cf0d80bb2 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1480,6 +1480,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
}
private void internalTeleport(double d0, double d1, double d2, float f, float f1, Set<ClientboundPlayerPositionPacket.RelativeArgument> set, boolean flag) {
+ if (player.isRemoved()) {
+ LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName());
+ return;
+ }
// CraftBukkit start
if (Float.isNaN(f)) {
f = 0;

View file

@ -1,18 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 7 Mar 2020 00:07:51 +0000
Subject: [PATCH] Validate tripwire hook placement before update
diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
index 3a5252afa9681fb1956493bead27e6cdb13679ca..0cfa1abca2c5744a4147b05905983ae4acaa569a 100644
--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
@@ -174,6 +174,7 @@ public class TripWireHookBlock extends Block {
this.playSound(world, pos, flag4, flag5, flag2, flag3);
if (!beingRemoved) {
+ if (world.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - validate
world.setBlock(pos, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection), 3);
if (flag1) {
this.notifyNeighbors(world, pos, enumdirection);

View file

@ -1,35 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Sat, 13 Apr 2019 16:50:58 -0500
Subject: [PATCH] Add option to allow iron golems to spawn in air
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 1eea1d2b0747b904ff30f0c6debb8f04fcbb6923..9ce95f6b6b53f6f30d3c9aeaee8938354fc0d3b5 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -423,6 +423,11 @@ public class PaperWorldConfig {
scanForLegacyEnderDragon = getBoolean("game-mechanics.scan-for-legacy-ender-dragon", true);
}
+ public boolean ironGolemsCanSpawnInAir = false;
+ private void ironGolemsCanSpawnInAir() {
+ ironGolemsCanSpawnInAir = getBoolean("iron-golems-can-spawn-in-air", ironGolemsCanSpawnInAir);
+ }
+
public boolean armorStandEntityLookups = true;
private void armorStandEntityLookups() {
armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true);
diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
index cc02d6324c61ce5e6387577fd08c6dfb549001bc..bf731a227944875320857e5838d50f15af13b0c1 100644
--- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
@@ -322,7 +322,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
BlockPos blockposition1 = blockposition.below();
BlockState iblockdata = world.getBlockState(blockposition1);
- if (!iblockdata.entityCanStandOn((BlockGetter) world, blockposition1, (Entity) this)) {
+ if (!iblockdata.entityCanStandOn((BlockGetter) world, blockposition1, (Entity) this) && !level.paperConfig.ironGolemsCanSpawnInAir) { // Paper
return false;
} else {
for (int i = 1; i < 3; ++i) {

View file

@ -1,45 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zero <zero@cock.li>
Date: Sat, 22 Feb 2020 16:10:31 -0500
Subject: [PATCH] Configurable chance of villager zombie infection
This allows you to solve an issue in vanilla behavior where:
* On easy difficulty your villagers will NEVER get infected, meaning they will always die.
* On normal difficulty they will have a 50% of getting infected or dying.
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index b8e58219847a243516754c845732d1b1e3f56f23..955cc760f3eabe2fa2d88fe277ca923944eb9aea 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -536,6 +536,11 @@ public class PaperWorldConfig {
nerfNetherPortalPigmen = getBoolean("game-mechanics.nerf-pigmen-from-nether-portals", nerfNetherPortalPigmen);
}
+ public double zombieVillagerInfectionChance = -1.0;
+ private void zombieVillagerInfectionChance() {
+ zombieVillagerInfectionChance = getDouble("zombie-villager-infection-chance", zombieVillagerInfectionChance);
+ }
+
public int lightQueueSize = 20;
private void lightQueueSize() {
lightQueueSize = getInt("light-queue-size", lightQueueSize);
diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
index c3fd3b354812dd690d68b45bc5a179778e4511e0..20d8e705ea432df64917b59f8290aa5400795f4b 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
@@ -452,10 +452,13 @@ public class Zombie extends Monster {
@Override
public void killed(ServerLevel world, LivingEntity other) {
super.killed(world, other);
- if ((world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager) {
- if (world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) {
+ if (level.paperConfig.zombieVillagerInfectionChance != 0.0 && (level.paperConfig.zombieVillagerInfectionChance != -1.0 || world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager) {
+ if (level.paperConfig.zombieVillagerInfectionChance == -1.0 && world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) {
return;
}
+ if (level.paperConfig.zombieVillagerInfectionChance != -1.0 && (this.random.nextDouble() * 100.0) > level.paperConfig.zombieVillagerInfectionChance) {
+ return;
+ } // Paper end
Villager entityvillager = (Villager) other;
// CraftBukkit start

View file

@ -1,63 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 14 Jan 2020 14:59:08 -0800
Subject: [PATCH] Optimise Chunk#getFluid
Removing the try catch and generally reducing ops should make it
faster on its own, however removing the try catch makes it
easier to inline due to code size
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index 83ed84f89a036d3768b22a36bc8a0bfc2bc29ec7..016c2302d8bcf121eafd1be7eb4f3b206dbdbeec 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -461,18 +461,20 @@ public class LevelChunk implements ChunkAccess {
}
public FluidState getFluidState(int x, int y, int z) {
- try {
- int l = this.getSectionIndex(y);
-
- if (l >= 0 && l < this.sections.length) {
- LevelChunkSection chunksection = this.sections[l];
-
- if (!LevelChunkSection.isEmpty(chunksection)) {
- return chunksection.getFluidState(x & 15, y & 15, z & 15);
+ // try { // Paper - remove try catch
+ // Paper start - reduce the number of ops in this call
+ int index = this.getSectionIndex(y);
+ if (index >= 0 && index < this.sections.length) {
+ LevelChunkSection chunksection = this.sections[index];
+
+ if (chunksection != null) {
+ return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState();
}
+ // Paper end
}
return Fluids.EMPTY.defaultFluidState();
+ /* // Paper - remove try catch
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state");
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got");
@@ -482,6 +484,7 @@ public class LevelChunk implements ChunkAccess {
});
throw new ReportedException(crashreport);
}
+ */ // Paper - remove try catch
}
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
index c9fefeef19bd46ade51b23eadb5eef3a88024ea1..cdac1f7b30e4c043dcb12ac9e29af926df8170bd 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -50,7 +50,7 @@ public class LevelChunkSection {
}
public FluidState getFluidState(int x, int y, int z) {
- return this.states.get(x, y, z).getFluidState();
+ return this.states.get(x, y, z).getFluidState(); // Paper - diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid.
}
public void acquire() {