From b26ba5bc8acd8357c16a3960de3e2f1ce338be78 Mon Sep 17 00:00:00 2001 From: Omer Uddin Date: Tue, 15 Jun 2021 01:44:37 -0400 Subject: [PATCH] port some patches (#5837) --- .../0154-PreSpawnerSpawnEvent.patch} | 0 ...0312-Add-PufferFishStateChangeEvent.patch} | 0 ...e-Biome-Mob-Lookups-for-Mob-Spawning.patch | 99 --------------- .../1.17/0290-PreSpawnerSpawnEvent.patch | 31 ----- ...PaletteBlock-instead-of-ReentrantLoc.patch | 117 ------------------ .../server/0290-PreSpawnerSpawnEvent.patch | 29 +++++ ...tedContainer-instead-of-ReentrantLoc.patch | 92 ++++++++++++++ ...e-Biome-Mob-Lookups-for-Mob-Spawning.patch | 57 +++++++++ 8 files changed, 178 insertions(+), 247 deletions(-) rename patches/{removed/1.17/0153-PreSpawnerSpawnEvent.patch => api/0154-PreSpawnerSpawnEvent.patch} (100%) rename patches/api/{0313-Add-PufferFishStateChangeEvent.patch => 0312-Add-PufferFishStateChangeEvent.patch} (100%) delete mode 100644 patches/removed/1.17/0288-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch delete mode 100644 patches/removed/1.17/0290-PreSpawnerSpawnEvent.patch delete mode 100644 patches/removed/1.17/0362-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch create mode 100644 patches/server/0290-PreSpawnerSpawnEvent.patch create mode 100644 patches/server/0684-Synchronize-PalettedContainer-instead-of-ReentrantLoc.patch create mode 100644 patches/server/0690-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch diff --git a/patches/removed/1.17/0153-PreSpawnerSpawnEvent.patch b/patches/api/0154-PreSpawnerSpawnEvent.patch similarity index 100% rename from patches/removed/1.17/0153-PreSpawnerSpawnEvent.patch rename to patches/api/0154-PreSpawnerSpawnEvent.patch diff --git a/patches/api/0313-Add-PufferFishStateChangeEvent.patch b/patches/api/0312-Add-PufferFishStateChangeEvent.patch similarity index 100% rename from patches/api/0313-Add-PufferFishStateChangeEvent.patch rename to patches/api/0312-Add-PufferFishStateChangeEvent.patch diff --git a/patches/removed/1.17/0288-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/removed/1.17/0288-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch deleted file mode 100644 index 5fe08a22a2..0000000000 --- a/patches/removed/1.17/0288-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch +++ /dev/null @@ -1,99 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 12 Sep 2018 21:47:01 -0400 -Subject: [PATCH] Optimize Biome Mob Lookups for Mob Spawning - -Uses an EnumMap as well as a Set paired List for O(1) contains calls. - - -TODO 1.17: Does not look relevant now -diff --git a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java -index 58ee27a994b4cd845b8bb28e80cc2102c860f097..528f42c63a1186b8827bfe7cf6193e14da938cb3 100644 ---- a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java -+++ b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java -@@ -30,19 +30,28 @@ public class MobSpawnSettings { - }, (enumcreaturetype) -> { - return ImmutableList.of(); - })), ImmutableMap.of(), false); -+ // Paper start- decompile error workaround -+ private static class bProxy extends MobSpawnSettings.MobSpawnCost { -+ private bProxy(double gravityLimit, double mass) { -+ super(gravityLimit, mass); -+ } -+ } -+ private static class cProxy extends MobSpawnSettings.SpawnerData { -+ public cProxy(EntityType type, int weight, int minGroupSize, int maxGroupSize) { -+ super(type, weight, minGroupSize, maxGroupSize); -+ } -+ }; -+ // Paper end - public static final MapCodec CODEC = RecordCodecBuilder.mapCodec((instance) -> { -- RecordCodecBuilder recordcodecbuilder = Codec.FLOAT.optionalFieldOf("creature_spawn_probability", 0.1F).forGetter((biomesettingsmobs) -> { -- return biomesettingsmobs.d; -+ RecordCodecBuilder recordcodecbuilder = Codec.FLOAT.optionalFieldOf("creature_spawn_probability", 0.1F).forGetter((biomesettingsmobs) -> { // Paper - add type to builder -+ return biomesettingsmobs.creatureGenerationProbability; - }); -- Codec codec = MobCategory.CODEC; -- Codec codec1 = BiomeSettingsMobs.c.b.listOf(); -- Logger logger = MobSpawnSettings.LOGGER; -- -- logger.getClass(); -- return instance.group(recordcodecbuilder, Codec.simpleMap(codec, codec1.promotePartial(Util.prefix("Spawn data: ", logger::error)), StringRepresentable.keys(MobCategory.values())).fieldOf("spawners").forGetter((biomesettingsmobs) -> { -- return biomesettingsmobs.e; -- }), Codec.simpleMap(Registry.ENTITY_TYPE, BiomeSettingsMobs.b.a, Registry.ENTITY_TYPE).fieldOf("spawn_costs").forGetter((biomesettingsmobs) -> { -- return biomesettingsmobs.f; -+ // Paper - remove unused vars -+ -+ return instance.group(recordcodecbuilder, Codec.simpleMap(MobCategory.CODEC, cProxy.CODEC.listOf().promotePartial(Util.prefix("Spawn data: ", MobSpawnSettings.LOGGER::error)), StringRepresentable.keys(MobCategory.values())).fieldOf("spawners").forGetter((biomesettingsmobs) -> { // Paper - inline codec, cProxy, LOGGER -+ return biomesettingsmobs.spawners; -+ }), Codec.simpleMap(Registry.ENTITY_TYPE, bProxy.CODEC, Registry.ENTITY_TYPE).fieldOf("spawn_costs").forGetter((biomesettingsmobs) -> { // Paper - decompile error - bProxy -+ return biomesettingsmobs.mobSpawnCosts; - }), Codec.BOOL.fieldOf("player_spawn_friendly").orElse(false).forGetter(MobSpawnSettings::playerSpawnFriendly)).apply(instance, MobSpawnSettings::new); - }); - private final float creatureGenerationProbability; -@@ -76,11 +85,43 @@ public class MobSpawnSettings { - - public static class Builder { - -- private final Map> spawners = (Map) Stream.of(MobCategory.values()).collect(ImmutableMap.toImmutableMap((enumcreaturetype) -> { -+ // Paper start - keep track of data in a pair set to give O(1) contains calls - we have to hook removals incase plugins mess with it -+ public static class MobList extends java.util.ArrayList { -+ java.util.Set biomes = new java.util.HashSet<>(); -+ -+ @Override -+ public boolean contains(Object o) { -+ return biomes.contains(o); -+ } -+ -+ @Override -+ public boolean add(MobSpawnSettings.SpawnerData BiomeSettingsMobs) { -+ biomes.add(BiomeSettingsMobs); -+ return super.add(BiomeSettingsMobs); -+ } -+ -+ @Override -+ public MobSpawnSettings.SpawnerData remove(int index) { -+ MobSpawnSettings.SpawnerData removed = super.remove(index); -+ if (removed != null) { -+ biomes.remove(removed); -+ } -+ return removed; -+ } -+ -+ @Override -+ public void clear() { -+ biomes.clear(); -+ super.clear(); -+ } -+ } -+ // use toImmutableEnumMap collector -+ private final Map> spawners = (Map) Stream.of(MobCategory.values()).collect(Maps.toImmutableEnumMap((enumcreaturetype) -> { - return enumcreaturetype; - }, (enumcreaturetype) -> { -- return Lists.newArrayList(); -+ return new MobList(); // Use MobList instead of ArrayList - })); -+ // Paper end - private final Map, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap(); - private float creatureGenerationProbability = 0.1F; - private boolean playerCanSpawn; diff --git a/patches/removed/1.17/0290-PreSpawnerSpawnEvent.patch b/patches/removed/1.17/0290-PreSpawnerSpawnEvent.patch deleted file mode 100644 index 592cfbd22d..0000000000 --- a/patches/removed/1.17/0290-PreSpawnerSpawnEvent.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Tue, 18 Sep 2018 23:53:23 +0100 -Subject: [PATCH] PreSpawnerSpawnEvent - -This adds a separate event before an entity is spawned by a spawner -which contains the location of the spawner too similarly to how the -SpawnerSpawnEvent gets called instead of the CreatureSpawnEvent for -spawners. - -Dropped as it does not apply due to the earlier PreCreatureSpawnEvent patch not being applied - -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index ac572eba10a7239d71dfae060f623b076d4252ce..1ce675d0d24ceb5724f5ac2d8f671e38f2735f74 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -132,11 +132,11 @@ public abstract class BaseSpawner { - - org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(key); - if (type != null) { -- com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; -- event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( -+ com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event; -+ event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent( - MCUtil.toLocation(world, d3, d4, d5), - type, -- org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER -+ MCUtil.toLocation(world, blockposition) - ); - if (!event.callEvent()) { - flag = true; diff --git a/patches/removed/1.17/0362-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch b/patches/removed/1.17/0362-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch deleted file mode 100644 index f172c356ec..0000000000 --- a/patches/removed/1.17/0362-Synchronize-DataPaletteBlock-instead-of-ReentrantLoc.patch +++ /dev/null @@ -1,117 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 29 May 2020 20:29:02 -0400 -Subject: [PATCH] Synchronize DataPaletteBlock instead of ReentrantLock -1.17: Mini have fun I don't want to die -Mojang has flaws in their logic about chunks being concurrently -wrote to. So we constantly see crashes around multiple threads writing. - -Additionally, java has optimized synchronization so well that its -in many times faster than trying to manage read wrote locks for low -contention situations. - -And this is extremely a low contention situation. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index 6d3dcd19ce1abc9d502903b8008949b5174a13c3..917b0a64083ebbe24321089b784b91f3af4918b9 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -8,7 +8,6 @@ import java.util.function.Function; - import java.util.function.Predicate; - import java.util.stream.Collectors; - import net.minecraft.CrashReport; --import net.minecraft.CrashReportCategory; - import net.minecraft.ReportedException; - import net.minecraft.core.IdMapper; - import net.minecraft.nbt.CompoundTag; -@@ -32,23 +31,23 @@ public class PalettedContainer implements PaletteResize { - private int bits; private int getBitsPerObject() { return this.bits; } // Paper - OBFHELPER - private final ReentrantLock lock = new ReentrantLock(); - -- public void acquire() { -- if (this.lock.isLocked() && !this.lock.isHeldByCurrentThread()) { -+ public void acquire() { /* // Paper start - disable this - use proper synchronization -+ if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) { - String s = (String) Thread.getAllStackTraces().keySet().stream().filter(Objects::nonNull).map((thread) -> { - return thread.getName() + ": \n\tat " + (String) Arrays.stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n\tat ")); - }).collect(Collectors.joining("\n")); - CrashReport crashreport = new CrashReport("Writing into PalettedContainer from multiple threads", new IllegalStateException()); -- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Thread dumps"); -+ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Thread dumps"); - -- crashreportsystemdetails.setDetail("Thread dumps", (Object) s); -+ crashreportsystemdetails.a("Thread dumps", (Object) s); - throw new ReportedException(crashreport); - } else { -- this.lock.lock(); -- } -+ this.j.lock(); -+ } */ // Paper end - } - - public void release() { -- this.lock.unlock(); -+ //this.j.unlock(); // Paper - disable this - } - - public PalettedContainer(Palette fallbackPalette, IdMapper idList, Function elementDeserializer, Function elementSerializer, T defaultElement) { -@@ -84,7 +83,7 @@ public class PalettedContainer implements PaletteResize { - } - - @Override -- public int onResize(int newSize, T objectAdded) { -+ public synchronized int onResize(int newSize, T objectAdded) { // Paper - synchronize - this.acquire(); - BitStorage databits = this.storage; - Palette datapalette = this.palette; -@@ -107,18 +106,18 @@ public class PalettedContainer implements PaletteResize { - } - - public T getAndSet(int x, int y, int z, T value) { -- this.acquire(); -- T t1 = this.getAndSet(getIndex(x, y, z), value); -+ //this.a(); // Paper - remove to reduce ops - synchronize handled below -+ return this.getAndSet(getIndex(x, y, z), value); // Paper - -- this.release(); -- return t1; -+ //this.b(); // Paper -+ //return t1; // PAper - } - - public T getAndSetUnchecked(int x, int y, int z, T value) { - return this.getAndSet(getIndex(x, y, z), value); - } - -- protected T getAndSet(int index, T value) { -+ protected synchronized T getAndSet(int index, T value) { // Paper - synchronize - writes - int j = this.palette.idFor(value); - int k = this.storage.getAndSet(index, j); - T t1 = this.palette.valueFor(k); -@@ -143,7 +142,7 @@ public class PalettedContainer implements PaletteResize { - } - - public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER -- public void write(FriendlyByteBuf buf) { -+ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize - this.acquire(); - buf.writeByte(this.bits); - this.palette.write(buf); -@@ -151,7 +150,7 @@ public class PalettedContainer implements PaletteResize { - this.release(); - } - -- public void read(ListTag paletteTag, long[] data) { -+ public synchronized void read(ListTag paletteTag, long[] data) { // Paper - synchronize - this.acquire(); - int i = Math.max(4, Mth.ceillog2(paletteTag.size())); - -@@ -184,7 +183,7 @@ public class PalettedContainer implements PaletteResize { - this.release(); - } - -- public void write(CompoundTag nbttagcompound, String s, String s1) { -+ public synchronized void write(CompoundTag nbttagcompound, String s, String s1) { // Paper - synchronize - this.acquire(); - HashMapPalette datapalettehash = new HashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer); - T t0 = this.defaultValue; diff --git a/patches/server/0290-PreSpawnerSpawnEvent.patch b/patches/server/0290-PreSpawnerSpawnEvent.patch new file mode 100644 index 0000000000..fd48f263be --- /dev/null +++ b/patches/server/0290-PreSpawnerSpawnEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 18 Sep 2018 23:53:23 +0100 +Subject: [PATCH] PreSpawnerSpawnEvent + +This adds a separate event before an entity is spawned by a spawner +which contains the location of the spawner too similarly to how the +SpawnerSpawnEvent gets called instead of the CreatureSpawnEvent for +spawners. + +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index c741f8e4a4b67d1bfed2d1bac36856c5688bb161..9228c0bc797fb95c8ac949bdc568eadafee84a80 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -138,11 +138,11 @@ public abstract class BaseSpawner { + + org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(key); + if (type != null) { +- com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; +- event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event; ++ event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent( + net.minecraft.server.MCUtil.toLocation(world, d0, d1, d2), + type, +- org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER ++ net.minecraft.server.MCUtil.toLocation(world, pos) + ); + if (!event.callEvent()) { + flag = true; diff --git a/patches/server/0684-Synchronize-PalettedContainer-instead-of-ReentrantLoc.patch b/patches/server/0684-Synchronize-PalettedContainer-instead-of-ReentrantLoc.patch new file mode 100644 index 0000000000..598b0de366 --- /dev/null +++ b/patches/server/0684-Synchronize-PalettedContainer-instead-of-ReentrantLoc.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 May 2020 20:29:02 -0400 +Subject: [PATCH] Synchronize PalettedContainer instead of ReentrantLock + +Mojang has flaws in their logic about chunks being concurrently +wrote to. So we constantly see crashes around multiple threads writing. + +Additionally, java has optimized synchronization so well that its +in many times faster than trying to manage read wrote locks for low +contention situations. + +And this is extremely a low contention situation. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index 5ac948b5b82f3144cdf402af440251cb8c7369d7..d8b7a7d9aa3ef47aa4e222c6ca85e83ce21f2444 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -36,16 +36,18 @@ public class PalettedContainer implements PaletteResize { + private final DebugBuffer> traces = null; + + public void acquire() { ++ /* // Paper start - disable this - use proper synchronization + if (this.traces != null) { + Thread thread = Thread.currentThread(); + this.traces.push(Pair.of(thread, thread.getStackTrace())); + } + + ThreadingDetector.checkAndLock(this.lock, this.traces, "PalettedContainer"); ++ */ // Paper end + } + + public void release() { +- this.lock.release(); ++ //this.lock.release(); // Paper - disable this + } + + public PalettedContainer(Palette fallbackPalette, IdMapper idList, Function elementDeserializer, Function elementSerializer, T defaultElement) { +@@ -96,7 +98,7 @@ public class PalettedContainer implements PaletteResize { + return this.palette.idFor(objectAdded); + } + +- public T getAndSet(int x, int y, int z, T value) { ++ public synchronized T getAndSet(int x, int y, int z, T value) { // Paper - synchronize + Object var6; + try { + this.acquire(); +@@ -120,7 +122,7 @@ public class PalettedContainer implements PaletteResize { + return (T)(object == null ? this.defaultValue : object); + } + +- public void set(int i, int j, int k, T object) { ++ public synchronized void set(int i, int j, int k, T object) { // Paper - synchronize + try { + this.acquire(); + this.set(getIndex(i, j, k), object); +@@ -144,7 +146,7 @@ public class PalettedContainer implements PaletteResize { + return (T)(object == null ? this.defaultValue : object); + } + +- public void read(FriendlyByteBuf buf) { ++ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize + try { + this.acquire(); + int i = buf.readByte(); +@@ -161,7 +163,7 @@ public class PalettedContainer implements PaletteResize { + } + + public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER +- public void write(FriendlyByteBuf buf) { ++ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize + try { + this.acquire(); + buf.writeByte(this.bits); +@@ -173,7 +175,7 @@ public class PalettedContainer implements PaletteResize { + + } + +- public void read(ListTag paletteNbt, long[] data) { ++ public synchronized void read(ListTag paletteNbt, long[] data) { // Paper - synchronize + try { + this.acquire(); + int i = Math.max(4, Mth.ceillog2(paletteNbt.size())); +@@ -206,7 +208,7 @@ public class PalettedContainer implements PaletteResize { + + } + +- public void write(CompoundTag nbt, String paletteKey, String dataKey) { ++ public synchronized void write(CompoundTag nbt, String paletteKey, String dataKey) { // Paper - synchronize + try { + this.acquire(); + HashMapPalette hashMapPalette = new HashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer); diff --git a/patches/server/0690-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/server/0690-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch new file mode 100644 index 0000000000..14e1c84060 --- /dev/null +++ b/patches/server/0690-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 12 Sep 2018 21:47:01 -0400 +Subject: [PATCH] Optimize Biome Mob Lookups for Mob Spawning + +Uses an EnumMap as well as a Set paired List for O(1) contains calls. + +diff --git a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java +index 4e06bb1fbda33e79044ac54758b559f7436882a7..86528ff031014e788d72a8bf7c1c9443512096bb 100644 +--- a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java ++++ b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java +@@ -70,11 +70,43 @@ public class MobSpawnSettings { + } + + public static class Builder { +- private final Map> spawners = Stream.of(MobCategory.values()).collect(ImmutableMap.toImmutableMap((mobCategory) -> { ++ // Paper start - keep track of data in a pair set to give O(1) contains calls - we have to hook removals incase plugins mess with it ++ public static class MobList extends java.util.ArrayList { ++ java.util.Set biomes = new java.util.HashSet<>(); ++ ++ @Override ++ public boolean contains(Object o) { ++ return biomes.contains(o); ++ } ++ ++ @Override ++ public boolean add(MobSpawnSettings.SpawnerData BiomeSettingsMobs) { ++ biomes.add(BiomeSettingsMobs); ++ return super.add(BiomeSettingsMobs); ++ } ++ ++ @Override ++ public MobSpawnSettings.SpawnerData remove(int index) { ++ MobSpawnSettings.SpawnerData removed = super.remove(index); ++ if (removed != null) { ++ biomes.remove(removed); ++ } ++ return removed; ++ } ++ ++ @Override ++ public void clear() { ++ biomes.clear(); ++ super.clear(); ++ } ++ } ++ // use toImmutableEnumMap collector ++ private final Map> spawners = (Map) Stream.of(MobCategory.values()).collect(Maps.toImmutableEnumMap((mobCategory) -> { + return mobCategory; + }, (mobCategory) -> { +- return Lists.newArrayList(); ++ return new MobList(); // Use MobList instead of ArrayList + })); ++ // Paper end + private final Map, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap(); + private float creatureGenerationProbability = 0.1F; + private boolean playerCanSpawn;