--- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -212,6 +_,8 @@ private final StructureManager structureManager; private final StructureCheck structureCheck; private final boolean tickTime; + private double preciseTime; // Purpur - Configurable daylight cycle + private boolean forceTime; // Purpur - Configurable daylight cycle private final RandomSequences randomSequences; final LevelDebugSynchronizers debugSynchronizers = new LevelDebugSynchronizers(this); @@ -609,7 +_,24 @@ // CraftBukkit end this.tickTime = tickTime; this.server = server; - this.customSpawners = customSpawners; + // Purpur start - Allow toggling special MobSpawners per world + this.customSpawners = new ArrayList<>(); + if (purpurConfig.phantomSpawning) { + this.customSpawners.add(new net.minecraft.world.level.levelgen.PhantomSpawner()); + } + if (purpurConfig.patrolSpawning) { + this.customSpawners.add(new net.minecraft.world.level.levelgen.PatrolSpawner()); + } + if (purpurConfig.catSpawning) { + this.customSpawners.add(new net.minecraft.world.entity.npc.CatSpawner()); + } + if (purpurConfig.villageSiegeSpawning) { + this.customSpawners.add(new net.minecraft.world.entity.ai.village.VillageSiege()); + } + if (purpurConfig.villagerTraderSpawning) { + this.customSpawners.add(new net.minecraft.world.entity.npc.WanderingTraderSpawner(serverLevelData)); + } + // Purpur end - Allow toggling special MobSpawners per world this.serverLevelData = levelData; ChunkGenerator chunkGenerator = levelStem.generator(); // CraftBukkit start @@ -692,6 +_,7 @@ this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); // Paper end - rewrite chunk system this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle } // Paper start @@ -738,7 +_,7 @@ } int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); - if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { + if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { // Purpur - Config for skipping night // Paper start - create time skip event - move up calculations final long newDayTime = this.levelData.getDayTime() + 24000L; org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent( @@ -872,6 +_,13 @@ this.serverLevelData.getScheduledEvents().tick(this.server, l); Profiler.get().pop(); if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { + // Purpur start - Configurable daylight cycle + int incrementTicks = isBrightOutside() ? this.purpurConfig.daytimeTicks : this.purpurConfig.nighttimeTicks; + if (incrementTicks != 12000) { + this.preciseTime += 12000 / (double) incrementTicks; + this.setDayTime(this.preciseTime); + } else + // Purpur end - Configurable daylight cycle this.setDayTime(this.levelData.getDayTime() + 1L); } } @@ -879,6 +_,20 @@ public void setDayTime(long time) { this.serverLevelData.setDayTime(time); + // Purpur start - Configurable daylight cycle + this.preciseTime = time; + this.forceTime = false; + } + public void setDayTime(double i) { + this.serverLevelData.setDayTime((long) i); + this.forceTime = true; + // Purpur end - Configurable daylight cycle + } + + // Purpur start - Configurable daylight cycle + public boolean isForceTime() { + return this.forceTime; + // Purpur end - Configurable daylight cycle } public void tickCustomSpawners(boolean flag) { @@ -983,9 +_,17 @@ && this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01) // Paper - Configurable spawn chances for skeleton horses && !this.getBlockState(blockPos.below()).is(BlockTags.LIGHTNING_RODS); if (flag) { - SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); + // Purpur start - Special mobs naturally spawn + net.minecraft.world.entity.animal.horse.AbstractHorse skeletonHorse; + if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) { + skeletonHorse = EntityType.ZOMBIE_HORSE.create(this, EntitySpawnReason.EVENT); + } else { + skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); + if (skeletonHorse != null) ((SkeletonHorse) skeletonHorse).setTrap(true); + } + // Purpur end - Special mobs naturally spawn if (skeletonHorse != null) { - skeletonHorse.setTrap(true); + //skeletonHorse.setTrap(true); // Purpur - Special mobs naturally spawn - moved up skeletonHorse.setAge(0); skeletonHorse.setPos(blockPos.getX(), blockPos.getY(), blockPos.getZ()); this.addFreshEntity(skeletonHorse, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit @@ -1020,9 +_,35 @@ if (blockState.is(Blocks.SNOW)) { int layersValue = blockState.getValue(SnowLayerBlock.LAYERS); if (layersValue < Math.min(_int, 8)) { + // Purpur start - Smooth snow accumulation + boolean canSnow = true; + // Ensure snow doesn't get more than N layers taller than its neighbors + // We only need to check blocks that are taller than the minimum step height + if (org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep > 0 && layersValue >= org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep) { + int layersValueMin = layersValue - org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep; + for (Direction direction : Direction.Plane.HORIZONTAL) { + BlockPos blockPosNeighbor = heightmapPos.relative(direction); + BlockState blockStateNeighbor = this.getBlockState(blockPosNeighbor); + if (blockStateNeighbor.is(Blocks.SNOW)) { + // Special check for snow layers, if neighbors are too short, don't accumulate + int layersValueNeighbor = blockStateNeighbor.getValue(SnowLayerBlock.LAYERS); + if (layersValueNeighbor <= layersValueMin) { + canSnow = false; + break; + } + } else if (!Block.isFaceFull(blockStateNeighbor.getCollisionShape(this, blockPosNeighbor), direction.getOpposite())) { + // Since our layer is tall enough already, if we have a non-full neighbor block, don't accumulate + canSnow = false; + break; + } + } + } + if (canSnow) { + // Purpur end - Smooth snow accumulation BlockState blockState1 = blockState.setValue(SnowLayerBlock.LAYERS, layersValue + 1); Block.pushEntitiesUp(blockState, blockState1, this, heightmapPos); org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, heightmapPos, blockState1, 3, null); // CraftBukkit + } // Purpur - Smooth snow accumulation } } else { org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, heightmapPos, Blocks.SNOW.defaultBlockState(), 3, null); // CraftBukkit @@ -1043,7 +_,7 @@ holder -> holder.is(PoiTypes.LIGHTNING_ROD), blockPos -> blockPos.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockPos.getX(), blockPos.getZ()) - 1, pos, - 128, + org.purpurmc.purpur.PurpurConfig.lightningRodRange, // Purpur - Make lightning rod range configurable PoiManager.Occupancy.ANY ); return optional.map(blockPos -> blockPos.above(1)); @@ -1092,8 +_,26 @@ int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); Component component; if (this.sleepStatus.areEnoughSleeping(_int)) { + // Purpur start - Customizable sleeping actionbar messages + if (org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.isBlank()) { + return; + } + if (!org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.equalsIgnoreCase("default")) { + component = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepSkippingNight)); + } else + // Purpur end - Customizable sleeping actionbar messages component = Component.translatable("sleep.skipping_night"); } else { + // Purpur start - Customizable sleeping actionbar messages + if (org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.isBlank()) { + return; + } + if (!org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.equalsIgnoreCase("default")) { + component = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent, + net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("count", Integer.toString(this.sleepStatus.amountSleeping())), + net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("total", Integer.toString(this.sleepStatus.sleepersNeeded(_int))))); + } else + // Purpur end - Customizable sleeping actionbar messages component = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(_int)); } @@ -1230,6 +_,7 @@ @VisibleForTesting public void resetWeatherCycle() { // CraftBukkit start + if (this.purpurConfig.rainStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents // If we stop due to everyone sleeping we should reset the weather duration to some other random value. // Not that everyone ever manages to get the whole server to sleep at the same time.... @@ -1237,6 +_,7 @@ this.serverLevelData.setRainTime(0); } // CraftBukkit end + if (this.purpurConfig.thunderStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents // CraftBukkit start // If we stop due to everyone sleeping we should reset the weather duration to some other random value. @@ -2793,7 +_,7 @@ // Spigot start if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message // Paper start - Fix merchant inventory not closing on entity removal - if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { + if (!entity.level().purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur - Allow void trading merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); } // Paper end - Fix merchant inventory not closing on entity removal