From 710267df2aa7d0b235c6d823da42d2abe32d83a0 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:23:16 +0800 Subject: [PATCH] Force peaceful mode switch (#88) --- .../api/0008-Force-peaceful-mode-switch.patch | 23 ++ .../0116-Force-peaceful-mode-switch.patch | 238 ++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 patches/api/0008-Force-peaceful-mode-switch.patch create mode 100644 patches/server/0116-Force-peaceful-mode-switch.patch diff --git a/patches/api/0008-Force-peaceful-mode-switch.patch b/patches/api/0008-Force-peaceful-mode-switch.patch new file mode 100644 index 00000000..becc354e --- /dev/null +++ b/patches/api/0008-Force-peaceful-mode-switch.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Thu, 3 Aug 2023 14:21:55 +0800 +Subject: [PATCH] Force peaceful mode switch + + +diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java +index 6917931966377c51db88a3364997a110dd987970..ff145aba03c3ba20cb1d905705ff350d77dbc336 100644 +--- a/src/main/java/org/bukkit/World.java ++++ b/src/main/java/org/bukkit/World.java +@@ -4000,6 +4000,12 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient + @NotNull + public Set getFeatureFlags(); + ++ // Leaves start - unsupported settings ++ public void setPeacefulModeSwitchTick(int tick); ++ ++ public int getPeacefulModeSwitchTick(int tick); ++ // Leaves end - unsupported settings ++ + /** + * Represents various map environment types that a world may be + */ diff --git a/patches/server/0116-Force-peaceful-mode-switch.patch b/patches/server/0116-Force-peaceful-mode-switch.patch new file mode 100644 index 00000000..6e7cb31a --- /dev/null +++ b/patches/server/0116-Force-peaceful-mode-switch.patch @@ -0,0 +1,238 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Thu, 3 Aug 2023 14:21:47 +0800 +Subject: [PATCH] Force peaceful mode switch + + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 47787f1f86b99cfcae6e1bb3dcf7648e9f04c936..d9da306d2675201beedddd43dcef18b31a2fa0e0 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -82,6 +82,12 @@ public class ServerChunkCache extends ChunkSource { + public final java.util.concurrent.atomic.AtomicBoolean spawnCountsReady = new java.util.concurrent.atomic.AtomicBoolean(false); + // Leaves end - optimize mob spawning + ++ // Leaves start - peaceful mode switch ++ public int peacefulModeSwitchTick = -1; ++ public int peacefulModeSwitchCount = -1; ++ private final List> peacefulModeSwitchEntityTypes = List.of(net.minecraft.world.entity.boss.wither.WitherBoss.class, net.minecraft.world.entity.monster.Shulker.class, net.minecraft.world.entity.monster.warden.Warden.class); ++ // Leaves end - peaceful mode switch ++ + private static int getChunkCacheKey(int x, int z) { + return x & 3 | ((z & 3) << 2); + } +@@ -563,6 +569,22 @@ public class ServerChunkCache extends ChunkSource { + this.level.resetIceAndSnowTick(); + } + // Leaves end - reset ice & snow tick random ++ ++ // Leaves start - peaceful mode switch ++ if (peacefulModeSwitchTick > 0) { ++ if (this.level.getLevelData().getGameTime() % peacefulModeSwitchTick == 0) { ++ peacefulModeSwitchCount = 0; ++ this.level.getAllEntities().forEach(entity -> { ++ if (peacefulModeSwitchEntityTypes.contains(entity.getClass())) { ++ peacefulModeSwitchCount++; ++ } ++ }); ++ } ++ } else { ++ peacefulModeSwitchCount = -1; ++ } ++ // Leaves end - peaceful mode switch ++ + int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); + boolean flag1 = level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && worlddata.getGameTime() % level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit + +@@ -624,6 +646,15 @@ public class ServerChunkCache extends ChunkSource { + iterator1 = shuffled.iterator(); + } + ++ // Leaves start - peaceful mode switch ++ boolean peacefulModeSwitch = false; ++ if (lastSpawnState != null && peacefulModeSwitchCount != -1) { ++ if (peacefulModeSwitchCount >= NaturalSpawner.globalLimitForCategory(level, net.minecraft.world.entity.MobCategory.MONSTER, lastSpawnState.getSpawnableChunkCount())) { ++ peacefulModeSwitch = true; ++ } ++ } ++ // Leaves end - peaceful mode switch ++ + int chunksTicked = 0; // Paper + try { + while (iterator1.hasNext()) { +@@ -638,7 +669,7 @@ public class ServerChunkCache extends ChunkSource { + chunk1.incrementInhabitedTime(j); + // Leaves start - optimize mob spawning + if ((top.leavesmc.leaves.LeavesConfig.skipUnnecessaryMobSpawningComputations ? flag2AndHasNaturalSpawn : flag2) && (!top.leavesmc.leaves.LeavesConfig.asyncMobSpawning || spawnCountsReady.get()) && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration // Leaves -skip unnecessary mob spawning computations +- NaturalSpawner.spawnForChunk(this.level, chunk1, lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag1); ++ NaturalSpawner.spawnForChunk(this.level, chunk1, lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag1, peacefulModeSwitch); // Leaves - peaceful mode switch + } + // Leaves end - optimize mob spawning + +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 9a5b31a3a8614ca2ff2d5bdb64dddccd70b22c3f..64fdfb3c124f8574fc0313b8fd764a5cd254b151 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -133,6 +133,12 @@ public final class NaturalSpawner { + } + + public static void spawnForChunk(ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnState info, boolean spawnAnimals, boolean spawnMonsters, boolean rareSpawn) { ++ // Leaves start - peaceful mode switch ++ spawnForChunk(world, chunk, info, spawnAnimals, spawnMonsters, rareSpawn, false); ++ } ++ ++ public static void spawnForChunk(ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnState info, boolean spawnAnimals, boolean spawnMonsters, boolean rareSpawn, boolean peacefulModeSwitch) { ++ // Leaves end - peaceful mode switch + world.getProfiler().push("spawner"); + world.timings.mobSpawn.startTiming(); // Spigot + MobCategory[] aenumcreaturetype = NaturalSpawner.SPAWNING_CATEGORIES; +@@ -142,6 +148,11 @@ public final class NaturalSpawner { + + for (int j = 0; j < i; ++j) { + MobCategory enumcreaturetype = aenumcreaturetype[j]; ++ // Leaves start - peaceful mode switch ++ if (enumcreaturetype == MobCategory.MONSTER && peacefulModeSwitch) { ++ continue; ++ } ++ // Leaves end - peaceful mode switch + // CraftBukkit start - Use per-world spawn limits + boolean spawnThisTick = true; + int limit = enumcreaturetype.getMaxInstancesPerChunk(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 550dcb7d595221b221e4710890d8a3cad789fc07..7b576936c5a29f3714b2fc16c24e8cdec0d43f9e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2300,6 +2300,18 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return CraftFeatureFlag.getFromNMS(this.getHandle().enabledFeatures()).stream().map(FeatureFlag.class::cast).collect(Collectors.toUnmodifiableSet()); + } + ++ // Leaves start - unsupported settings ++ @Override ++ public void setPeacefulModeSwitchTick(int tick) { ++ this.getHandle().chunkSource.peacefulModeSwitchTick = tick; ++ } ++ ++ @Override ++ public int getPeacefulModeSwitchTick(int tick) { ++ return this.getHandle().chunkSource.peacefulModeSwitchTick; ++ } ++ // Leaves end - unsupported settings ++ + public void storeBukkitValues(CompoundTag c) { + if (!this.persistentDataContainer.isEmpty()) { + c.put("BukkitValues", this.persistentDataContainer.toTagCompound()); +diff --git a/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java b/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java +index 78d3df6a15057d42466c266226f308a070f560ac..06f5859cd04fe2147e37d61ab18e8fc0ff30769b 100644 +--- a/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java ++++ b/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java +@@ -12,6 +12,7 @@ import org.bukkit.permissions.PermissionDefault; + import org.bukkit.plugin.PluginManager; + import org.checkerframework.checker.nullness.qual.Nullable; + import org.jetbrains.annotations.NotNull; ++import top.leavesmc.leaves.command.subcommands.PeacefulModeSwitchCommand; + import top.leavesmc.leaves.command.subcommands.UpdateCommand; + + import java.util.ArrayList; +@@ -33,6 +34,7 @@ public final class LeavesCommand extends Command { + private static final Map SUBCOMMANDS = Util.make(() -> { + final Map, LeavesSubcommand> commands = new HashMap<>(); + commands.put(Set.of("update"), new UpdateCommand()); ++ commands.put(Set.of("peaceful"), new PeacefulModeSwitchCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/top/leavesmc/leaves/command/subcommands/PeacefulModeSwitchCommand.java b/src/main/java/top/leavesmc/leaves/command/subcommands/PeacefulModeSwitchCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..50c883ba689a51c1383e26ead3eb183c79b9a543 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/command/subcommands/PeacefulModeSwitchCommand.java +@@ -0,0 +1,89 @@ ++package top.leavesmc.leaves.command.subcommands; ++ ++import io.papermc.paper.command.CommandUtil; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.JoinConfiguration; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.MobCategory; ++import net.minecraft.world.level.NaturalSpawner; ++import org.bukkit.Bukkit; ++import org.bukkit.World; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.entity.Player; ++import top.leavesmc.leaves.command.LeavesSubcommand; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++ ++public class PeacefulModeSwitchCommand implements LeavesSubcommand { ++ ++ @Override ++ public boolean execute(CommandSender sender, String subCommand, String[] args) { ++ World world; ++ if (args.length == 0) { ++ if (sender instanceof Player player) { ++ world = player.getWorld(); ++ } else { ++ sender.sendMessage(Component.text("Must specify a world! ex: '/leaves peaceful world'", NamedTextColor.RED)); ++ return true; ++ } ++ } else { ++ final String input = args[0]; ++ final World inputWorld = Bukkit.getWorld(input); ++ if (inputWorld == null) { ++ sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); ++ return true; ++ } else { ++ world = inputWorld; ++ } ++ } ++ ++ final ServerLevel level = ((CraftWorld) world).getHandle(); ++ int chunks = 0; ++ if (level.chunkSource.getLastSpawnState() != null) { ++ chunks = level.chunkSource.getLastSpawnState().getSpawnableChunkCount(); ++ } ++ ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Peaceful Mode Switch for world: "), ++ Component.text(world.getName(), NamedTextColor.AQUA) ++ )); ++ ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Refuses per "), ++ Component.text(level.chunkSource.peacefulModeSwitchTick, level.chunkSource.peacefulModeSwitchTick > 0 ? NamedTextColor.AQUA : NamedTextColor.GRAY), ++ Component.text(" tick") ++ )); ++ ++ int count = level.chunkSource.peacefulModeSwitchCount; ++ int limit = NaturalSpawner.globalLimitForCategory(level, MobCategory.MONSTER, chunks); ++ NamedTextColor color = count >= limit ? NamedTextColor.AQUA : NamedTextColor.GRAY; ++ ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Now count "), ++ Component.text(count, color), ++ Component.text("/", color), ++ Component.text(limit, color) ++ )); ++ ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return CommandUtil.getListMatchingLast(sender, args, this.suggestPeacefulModeSwitch(args)); ++ } ++ ++ private List suggestPeacefulModeSwitch(final String[] args) { ++ if (args.length == 1) { ++ final List worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); ++ worlds.add("*"); ++ return worlds; ++ } ++ ++ return Collections.emptyList(); ++ } ++}