--- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -219,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 LevelDebugSynchronizers debugSynchronizers = new LevelDebugSynchronizers(this); // CraftBukkit start @@ -378,8 +_,25 @@ // CraftBukkit end this.tickTime = tickTime; this.server = server; - this.customSpawners = customSpawners; + this.customSpawners = new ArrayList<>(); // Purpur - Allow toggling special MobSpawners per world this.serverLevelData = levelData; + // Purpur start - Allow toggling special MobSpawners per world + 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.wanderingtrader.WanderingTraderSpawner(levelData)); + } + // Purpur end - Allow toggling special MobSpawners per world ChunkGenerator generator = levelStem.generator(); // CraftBukkit start this.serverLevelData.setWorld(this); @@ -464,6 +_,7 @@ this.environmentAttributes = EnvironmentAttributeSystem.builder().addDefaultLayers(this).build(); this.updateSkyBrightness(); this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle } // Paper start @@ -520,7 +_,7 @@ } int percentage = this.getGameRules().get(GameRules.PLAYERS_SLEEPING_PERCENTAGE); - if (this.sleepStatus.areEnoughSleeping(percentage) && this.sleepStatus.areEnoughDeepSleeping(percentage, this.players)) { + if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(percentage) && this.sleepStatus.areEnoughDeepSleeping(percentage, this.players)) { // Purpur - Config for skipping night Optional> defaultClock = this.dimensionType().defaultClock(); org.bukkit.event.world.TimeSkipEvent event = null; // Paper - time skip event if (this.getGameRules().get(GameRules.ADVANCE_TIME) && defaultClock.isPresent()) { @@ -728,9 +_,18 @@ && this.random.nextDouble() < difficulty.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01) // Paper - Configurable spawn chances for skeleton horses && !this.getBlockState(pos.below()).is(BlockTags.LIGHTNING_RODS); if (isTrap) { + // Purpur start - Special mobs naturally spawn + net.minecraft.world.entity.animal.equine.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 SkeletonHorse horse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); if (horse != null) { - horse.setTrap(true); + //horse.setTrap(true); // Purpur - Special mobs naturally spawn - moved up horse.setAge(0); horse.setPos(pos.getX(), pos.getY(), pos.getZ()); this.addFreshEntity(horse, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit @@ -765,9 +_,35 @@ if (state.is(Blocks.SNOW)) { int currentLayers = state.getValue(SnowLayerBlock.LAYERS); if (currentLayers < Math.min(maxHeight, 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 newState = state.setValue(SnowLayerBlock.LAYERS, currentLayers + 1); Block.pushEntitiesUp(state, newState, this, topPos); org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, topPos, newState, Block.UPDATE_ALL, null); // CraftBukkit + } // Purpur - Smooth snow accumulation } } else { org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, topPos, Blocks.SNOW.defaultBlockState(), Block.UPDATE_ALL, null); // CraftBukkit @@ -788,7 +_,7 @@ p -> p.is(PoiTypes.LIGHTNING_ROD), lightningRodPos -> lightningRodPos.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, lightningRodPos.getX(), lightningRodPos.getZ()) - 1, center, - 128, + org.purpurmc.purpur.PurpurConfig.lightningRodRange, // Purpur - Make lightning rod range configurable PoiManager.Occupancy.ANY ); return nearbyLightningRod.map(blockPos -> blockPos.above(1)); @@ -836,8 +_,26 @@ int percentage = this.getGameRules().get(GameRules.PLAYERS_SLEEPING_PERCENTAGE); Component message; if (this.sleepStatus.areEnoughSleeping(percentage)) { + // Purpur start - Customizable sleeping actionbar messages + if (org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.isBlank()) { + return; + } + if (!org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.equalsIgnoreCase("default")) { + message = 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 message = 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")) { + message = 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(i))))); + } else + // Purpur end - Customizable sleeping actionbar messages message = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(percentage)); } @@ -1003,6 +_,7 @@ public void resetWeatherCycle() { WeatherData weatherData = this.getWeatherData(); // CraftBukkit start + if (this.purpurConfig.rainStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep weatherData.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.... @@ -1010,6 +_,7 @@ weatherData.setRainTime(0); } // CraftBukkit end + if (this.purpurConfig.thunderStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep weatherData.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. @@ -1655,7 +_,7 @@ Explosion.BlockInteraction blockInteraction = switch (interactionType) { case NONE -> Explosion.BlockInteraction.KEEP; case BLOCK -> this.getDestroyType(GameRules.BLOCK_EXPLOSION_DROP_DECAY); - case MOB -> this.getGameRules().get(GameRules.MOB_GRIEFING) + case MOB -> ((source instanceof net.minecraft.world.entity.projectile.hurtingprojectile.LargeFireball) ? this.getGameRules().get(GameRules.MOB_GRIEFING, this.purpurConfig.fireballsMobGriefingOverride) : this.getGameRules().get(GameRules.MOB_GRIEFING)) // Purpur - Add mobGriefing override to everything affected ? this.getDestroyType(GameRules.MOB_EXPLOSION_DROP_DECAY) : Explosion.BlockInteraction.KEEP; case TNT -> this.getDestroyType(GameRules.TNT_EXPLOSION_DROP_DECAY); @@ -2561,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