apply minecraft server file patches

This commit is contained in:
granny
2025-11-25 22:27:20 -08:00
parent e6561c98ae
commit 757b8d5d42
251 changed files with 1060 additions and 2988 deletions

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/BaseSpawner.java
+++ b/net/minecraft/world/level/BaseSpawner.java
@@ -60,6 +_,7 @@
}
public boolean isNearPlayer(Level level, BlockPos pos) {
+ if (level.purpurConfig.spawnerDeactivateByRedstone && level.hasNeighborSignal(pos)) return false; // Purpur - Redstone deactivates spawners
return level.hasNearbyAlivePlayerThatAffectsSpawning(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange); // Paper - Affects Spawning API
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/EntityGetter.java
+++ b/net/minecraft/world/level/EntityGetter.java
@@ -182,7 +_,7 @@
default boolean hasNearbyAlivePlayer(double x, double y, double z, double distance) {
for (Player player : this.players()) {
- if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) {
+ if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) { // Purpur - AFK API
double d = player.distanceToSqr(x, y, z);
if (distance < 0.0 || d < distance * distance) {
return true;

View File

@@ -0,0 +1,82 @@
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -165,11 +_,55 @@
}
// Paper end - add paper world config
+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
private int tileTickPosition;
public final Map<ServerExplosion.CacheKey, Float> explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque<net.minecraft.world.level.block.RedstoneTorchBlock.Toggle> redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
+ // Purpur start - Add adjustable breeding cooldown to config
+ private com.google.common.cache.Cache<BreedingCooldownPair, Object> playerBreedingCooldowns;
+
+ private com.google.common.cache.Cache<BreedingCooldownPair, Object> getNewBreedingCooldownCache() {
+ return com.google.common.cache.CacheBuilder.newBuilder().expireAfterWrite(this.purpurConfig.animalBreedingCooldownSeconds, java.util.concurrent.TimeUnit.SECONDS).build();
+ }
+
+ public void resetBreedingCooldowns() {
+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache();
+ }
+
+ public boolean hasBreedingCooldown(java.util.UUID player, Class<? extends net.minecraft.world.entity.animal.Animal> animalType) { // Purpur
+ return this.playerBreedingCooldowns.getIfPresent(new BreedingCooldownPair(player, animalType)) != null;
+ }
+
+ public void addBreedingCooldown(java.util.UUID player, Class<? extends net.minecraft.world.entity.animal.Animal> animalType) {
+ this.playerBreedingCooldowns.put(new BreedingCooldownPair(player, animalType), new Object());
+ }
+
+ private static final class BreedingCooldownPair {
+ private final java.util.UUID playerUUID;
+ private final Class<? extends net.minecraft.world.entity.animal.Animal> animalType;
+
+ public BreedingCooldownPair(java.util.UUID playerUUID, Class<? extends net.minecraft.world.entity.animal.Animal> animalType) {
+ this.playerUUID = playerUUID;
+ this.animalType = animalType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BreedingCooldownPair that = (BreedingCooldownPair) o;
+ return playerUUID.equals(that.playerUUID) && animalType.equals(that.animalType);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(playerUUID, animalType);
+ }
+ }
+ // Purpur end - Add adjustable breeding cooldown to config
+
public CraftWorld getWorld() {
return this.world;
}
@@ -844,6 +_,8 @@
// Paper end - getblock optimisations - cache world height/sections
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName()); // Spigot
this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), environment); // Purpur - Purpur config files
+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - Add adjustable breeding cooldown to config
this.generator = generator;
this.world = new CraftWorld((ServerLevel) this, generator, biomeProvider, environment);
@@ -2071,4 +_,14 @@
return this.id;
}
}
+
+ // Purpur start - Add allow water in end world option
+ public boolean isNether() {
+ return getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER;
+ }
+
+ public boolean isTheEnd() {
+ return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END;
+ }
+ // Purpur end - Add allow water in end world option
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/net/minecraft/world/level/NaturalSpawner.java
@@ -209,7 +_,7 @@
mutableBlockPos.set(x, y, z);
double d = x + 0.5;
double d1 = z + 0.5;
- Player nearestPlayer = level.getNearestPlayer(d, y, d1, -1.0, false);
+ Player nearestPlayer = level.getNearestPlayer(d, y, d1, -1.0, level.purpurConfig.mobSpawningIgnoreCreativePlayers); // Purpur - mob spawning option to ignore creative players
if (nearestPlayer != null) {
double d2 = nearestPlayer.distanceToSqr(d, y, d1);
if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn

View File

@@ -0,0 +1,26 @@
--- a/net/minecraft/world/level/ServerExplosion.java
+++ b/net/minecraft/world/level/ServerExplosion.java
@@ -640,6 +_,23 @@
this.directMappedBlockCache = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
this.mutablePos = new BlockPos.MutableBlockPos();
// Paper end - collision optimisations
+ // Purpur start - add PreExplodeEvents
+ if (this.source != null) {
+ Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z);
+ if(!new org.purpurmc.purpur.event.entity.PreEntityExplodeEvent(this.source.getBukkitEntity(), location, this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, org.bukkit.craftbukkit.CraftExplosionResult.toExplosionResult(getBlockInteraction())).callEvent()) {
+ this.wasCanceled = true;
+ return 0;
+ }
+ } else {
+ Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z);
+ org.bukkit.block.Block block = location.getBlock();
+ org.bukkit.block.BlockState blockState = (this.damageSource.causingBlockSnapshot() != null) ? this.damageSource.causingBlockSnapshot() : block.getState();
+ if(!new org.purpurmc.purpur.event.PreBlockExplodeEvent(location.getBlock(), this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, blockState, org.bukkit.craftbukkit.CraftExplosionResult.toExplosionResult(getBlockInteraction())).callEvent()) {
+ this.wasCanceled = true;
+ return 0;
+ }
+ }
+ // Purpur end - Add PreExplodeEvents
this.level.gameEvent(this.source, GameEvent.EXPLODE, this.center);
List<BlockPos> list = this.calculateExplodedPositions();
this.hurtEntities();

View File

@@ -0,0 +1,56 @@
--- a/net/minecraft/world/level/block/AnvilBlock.java
+++ b/net/minecraft/world/level/block/AnvilBlock.java
@@ -54,6 +_,53 @@
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getClockWise());
}
+ // Purpur start - Anvil repair/damage options
+ @Override
+ protected net.minecraft.world.InteractionResult useItemOn(final net.minecraft.world.item.ItemStack stack, final BlockState state, final Level world, final BlockPos pos, final Player player, final net.minecraft.world.InteractionHand hand, final BlockHitResult hit) {
+ if (world.purpurConfig.anvilRepairIngotsAmount > 0 && stack.is(net.minecraft.world.item.Items.IRON_INGOT)) {
+ if (stack.getCount() < world.purpurConfig.anvilRepairIngotsAmount) {
+ // not enough iron ingots, play "error" sound and consume
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ if (state.is(Blocks.DAMAGED_ANVIL)) {
+ world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ } else if (state.is(Blocks.CHIPPED_ANVIL)) {
+ world.setBlock(pos, Blocks.ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ } else if (state.is(Blocks.ANVIL)) {
+ // anvil is already fully repaired, play "error" sound and consume
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(world.purpurConfig.anvilRepairIngotsAmount);
+ }
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_PLACE, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ if (world.purpurConfig.anvilDamageObsidianAmount > 0 && stack.is(net.minecraft.world.item.Items.OBSIDIAN)) {
+ if (stack.getCount() < world.purpurConfig.anvilDamageObsidianAmount) {
+ // not enough obsidian, play "error" sound and consume
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ if (state.is(Blocks.DAMAGED_ANVIL)) {
+ world.destroyBlock(pos, false);
+ } else if (state.is(Blocks.CHIPPED_ANVIL)) {
+ world.setBlock(pos, Blocks.DAMAGED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ } else if (state.is(Blocks.ANVIL)) {
+ world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ }
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(world.purpurConfig.anvilDamageObsidianAmount);
+ }
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_LAND, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ return net.minecraft.world.InteractionResult.TRY_WITH_EMPTY_HAND;
+ }
+ // Purpur end - Anvil repair/damage options
+
@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
if (!level.isClientSide()) {

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/level/block/AzaleaBlock.java
+++ b/net/minecraft/world/level/block/AzaleaBlock.java
@@ -50,6 +_,20 @@
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+ // Purpur start - Chance for azalea blocks to grow into trees naturally
+ growTree(level, random, pos, state);
+ }
+
+ @Override
+ public void randomTick(net.minecraft.world.level.block.state.BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+ double chance = state.getBlock() == Blocks.FLOWERING_AZALEA ? world.purpurConfig.floweringAzaleaGrowthChance : world.purpurConfig.azaleaGrowthChance;
+ if (chance > 0.0D && world.getMaxLocalRawBrightness(pos.above()) > 9 && random.nextDouble() < chance) {
+ growTree(world, random, pos, state);
+ }
+ }
+
+ private void growTree(ServerLevel level, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) {
+ // Purpur end - Chance for azalea blocks to grow into trees naturally
TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java
+++ b/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java
@@ -39,6 +_,7 @@
}
protected static boolean scanForWater(BlockState state, BlockGetter level, BlockPos pos) {
+ if (!((net.minecraft.world.level.LevelAccessor) level).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - Config to not let coral die
if (state.getValue(WATERLOGGED)) {
return true;
} else {

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/world/level/block/BedBlock.java
+++ b/net/minecraft/world/level/block/BedBlock.java
@@ -100,7 +_,7 @@
}
Vec3 center = pos.getCenter();
- level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK);
+ if (level.purpurConfig.bedExplode) level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, (float) level.purpurConfig.bedExplosionPower, level.purpurConfig.bedExplosionFire, level.purpurConfig.bedExplosionEffect); // Purpur - Implement bed explosion options
return InteractionResult.SUCCESS_SERVER;
} else if (state.getValue(OCCUPIED)) {
if (bedRule.explodes()) return this.explodeBed(state, level, pos); // Paper - check explode first
@@ -153,7 +_,7 @@
}
Vec3 center = pos.getCenter();
- level.explode(null, level.damageSources().badRespawnPointExplosion(center).causingBlockSnapshot(blockState), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state
+ if (level.purpurConfig.bedExplode) level.explode(null, level.damageSources().badRespawnPointExplosion(center).causingBlockSnapshot(blockState), null, center, (float) level.purpurConfig.bedExplosionPower, level.purpurConfig.bedExplosionFire, level.purpurConfig.bedExplosionEffect); // CraftBukkit - add state // Purpur - Implement bed explosion options
return InteractionResult.SUCCESS_SERVER;
}
// CraftBukkit end
@@ -170,7 +_,7 @@
@Override
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance) {
- super.fallOn(level, state, pos, entity, fallDistance * 0.5);
+ super.fallOn(level, state, pos, entity, fallDistance); // Purpur - Configurable block fall damage modifiers
}
@Override

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/block/BigDripleafBlock.java
+++ b/net/minecraft/world/level/block/BigDripleafBlock.java
@@ -251,7 +_,7 @@
playTiltSound(level, pos, sound);
}
- int _int = DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt);
+ int _int = level.purpurConfig.bigDripleafTiltDelay.getOrDefault(tilt, -1); // Purpur - Big dripleaf tilt delay
if (_int != -1) {
level.scheduleTick(pos, this, _int);
}

View File

@@ -0,0 +1,89 @@
--- a/net/minecraft/world/level/block/Block.java
+++ b/net/minecraft/world/level/block/Block.java
@@ -113,6 +_,10 @@
public static final int UPDATE_LIMIT = 512;
protected final StateDefinition<Block, BlockState> stateDefinition;
private BlockState defaultBlockState;
+ // Purpur start - Configurable block fall damage modifiers
+ public float fallDamageMultiplier = 1.0F;
+ public float fallDistanceMultiplier = 1.0F;
+ // Purpur end - Configurable block fall damage modifiers
// Paper start - Protect Bedrock and End Portal/Frames from being destroyed
public final boolean isDestroyable() {
return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits ||
@@ -395,7 +_,7 @@
event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping
event.callEvent();
for (org.bukkit.inventory.ItemStack drop : event.getDrops()) {
- popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop));
+ popResource(serverLevel, pos, applyLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur - Persistent BlockEntity Lore and DisplayName
}
state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping
block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping
@@ -413,7 +_,7 @@
public static void dropResources(BlockState state, LevelAccessor level, BlockPos pos, @Nullable BlockEntity blockEntity) {
if (level instanceof ServerLevel) {
- getDrops(state, (ServerLevel)level, pos, blockEntity).forEach(stack -> popResource((ServerLevel)level, pos, stack));
+ getDrops(state, (ServerLevel)level, pos, blockEntity).forEach(stack -> popResource((ServerLevel)level, pos, applyLoreFromTile(stack, blockEntity))); // Purpur - Persistent BlockEntity Lore and DisplayName
state.spawnAfterBreak((ServerLevel)level, pos, ItemStack.EMPTY, true);
}
}
@@ -425,11 +_,30 @@
public static void dropResources(BlockState state, Level level, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) {
// Paper end - Properly handle xp dropping
if (level instanceof ServerLevel) {
- getDrops(state, (ServerLevel)level, pos, blockEntity, entity, tool).forEach(stack -> popResource(level, pos, stack));
+ getDrops(state, (ServerLevel)level, pos, blockEntity, entity, tool).forEach(stack -> popResource(level, pos, applyLoreFromTile(stack, blockEntity))); // Purpur - Persistent BlockEntity Lore and DisplayName
state.spawnAfterBreak((ServerLevel)level, pos, tool, dropExperience); // Paper - Properly handle xp dropping
}
}
+ // Purpur start - Persistent BlockEntity Lore and DisplayName
+ private static ItemStack applyLoreFromTile(ItemStack stack, @Nullable BlockEntity blockEntity) {
+ if (stack.getItem() instanceof BlockItem) {
+ if (blockEntity != null && blockEntity.getLevel() instanceof ServerLevel) {
+ net.minecraft.world.item.component.ItemLore lore = blockEntity.getPersistentLore();
+ net.minecraft.core.component.DataComponentPatch.Builder builder = net.minecraft.core.component.DataComponentPatch.builder();
+ if (blockEntity.getLevel().purpurConfig.persistentTileEntityLore && lore != null) {
+ builder.set(net.minecraft.core.component.DataComponents.LORE, lore);
+ }
+ if (!blockEntity.getLevel().purpurConfig.persistentTileEntityDisplayName) {
+ builder.remove(net.minecraft.core.component.DataComponents.CUSTOM_NAME);
+ }
+ stack.applyComponents(builder.build());
+ }
+ }
+ return stack;
+ }
+ // Purpur end - Persistent BlockEntity Lore and DisplayName
+
public static void popResource(Level level, BlockPos pos, ItemStack stack) {
double d = EntityType.ITEM.getHeight() / 2.0;
double d1 = pos.getX() + 0.5 + Mth.nextDouble(level.random, -0.25, 0.25);
@@ -509,7 +_,15 @@
}
public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
- }
+ this.placer = placer; // Purpur - Store placer on Block when placed
+ }
+
+ // Purpur start - Store placer on Block when placed
+ @Nullable protected LivingEntity placer = null;
+ public void forgetPlacer() {
+ this.placer = null;
+ }
+ // Purpur end - Store placer on Block when placed
public boolean isPossibleToRespawnInThis(BlockState state) {
return !state.isSolid() && !state.liquid();
@@ -520,7 +_,7 @@
}
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance) {
- entity.causeFallDamage(fallDistance, 1.0F, entity.damageSources().fall());
+ entity.causeFallDamage(fallDistance * fallDistanceMultiplier, fallDamageMultiplier, entity.damageSources().fall()); // Purpur - Configurable block fall damage modifiers
}
public void updateEntityMovementAfterFallOn(BlockGetter level, Entity entity) {

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/world/level/block/Blocks.java
+++ b/net/minecraft/world/level/block/Blocks.java
@@ -6806,6 +_,7 @@
BlockBehaviour.Properties.of()
.mapColor(MapColor.PLANT)
.forceSolidOff()
+ .randomTicks() // Purpur - Chance for azalea blocks to grow into trees naturally
.instabreak()
.sound(SoundType.AZALEA)
.noOcclusion()
@@ -6817,6 +_,7 @@
BlockBehaviour.Properties.of()
.mapColor(MapColor.PLANT)
.forceSolidOff()
+ .randomTicks() // Purpur - Chance for azalea blocks to grow into trees naturally
.instabreak()
.sound(SoundType.FLOWERING_AZALEA)
.noOcclusion()

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/level/block/BubbleColumnBlock.java
+++ b/net/minecraft/world/level/block/BubbleColumnBlock.java
@@ -99,9 +_,9 @@
if (state.is(Blocks.BUBBLE_COLUMN)) {
return state;
} else if (state.is(Blocks.SOUL_SAND)) {
- return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, false);
+ return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, org.purpurmc.purpur.PurpurConfig.soulSandBlockReverseBubbleColumnFlow); // Purpur - Config to reverse bubble column flow
} else {
- return state.is(Blocks.MAGMA_BLOCK) ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, true) : Blocks.WATER.defaultBlockState();
+ return state.is(Blocks.MAGMA_BLOCK) ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, !org.purpurmc.purpur.PurpurConfig.magmaBlockReverseBubbleColumnFlow) : Blocks.WATER.defaultBlockState(); // Purpur - Config to reverse bubble column flow
}
}

View File

@@ -0,0 +1,55 @@
--- a/net/minecraft/world/level/block/CactusBlock.java
+++ b/net/minecraft/world/level/block/CactusBlock.java
@@ -22,7 +_,7 @@
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
-public class CactusBlock extends Block {
+public class CactusBlock extends Block implements BonemealableBlock { // Purpur - bonemealable cactus
public static final MapCodec<CactusBlock> CODEC = simpleCodec(CactusBlock::new);
public static final IntegerProperty AGE = BlockStateProperties.AGE_15;
public static final int MAX_AGE = 15;
@@ -117,7 +_,7 @@
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
for (Direction direction : Direction.Plane.HORIZONTAL) {
BlockState blockState = level.getBlockState(pos.relative(direction));
- if (blockState.isSolid() || level.getFluidState(pos.relative(direction)).is(FluidTags.LAVA)) {
+ if ((level.getWorldBorder().world.purpurConfig.cactusBreaksFromSolidNeighbors && blockState.isSolid()) || level.getFluidState(pos.relative(direction)).is(FluidTags.LAVA)) { // Purpur - Cactus breaks from solid neighbors config
return false;
}
}
@@ -141,4 +_,34 @@
protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) {
return false;
}
+
+ // Purpur start - bonemealable cactus
+ @Override
+ public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) {
+ if (!((Level) world).purpurConfig.cactusAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false;
+
+ int cactusHeight = 0;
+ while (world.getBlockState(pos.below(cactusHeight)).is(this)) {
+ cactusHeight++;
+ }
+
+ return cactusHeight < ((Level) world).paperConfig().maxGrowthHeight.cactus;
+ }
+
+ @Override
+ public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, BlockState state) {
+ return true;
+ }
+
+ @Override
+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ int cactusHeight = 0;
+ while (world.getBlockState(pos.below(cactusHeight)).is(this)) {
+ cactusHeight++;
+ }
+ for (int i = 0; i <= world.paperConfig().maxGrowthHeight.cactus - cactusHeight; i++) {
+ world.setBlockAndUpdate(pos.above(i), state.setValue(CactusBlock.AGE, 0));
+ }
+ }
+ // Purpur end - bonemealable cactus
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/CakeBlock.java
+++ b/net/minecraft/world/level/block/CakeBlock.java
@@ -109,6 +_,7 @@
org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, 2 + oldFoodLevel);
if (!event.isCancelled()) {
+ if (player.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) player.burpDelay = player.level().purpurConfig.playerBurpDelay; // Purpur - Burp after eating food fills hunger bar completely
player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 0.1F);
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/block/CampfireBlock.java
+++ b/net/minecraft/world/level/block/CampfireBlock.java
@@ -123,7 +_,7 @@
return this.defaultBlockState()
.setValue(WATERLOGGED, flag)
.setValue(SIGNAL_FIRE, this.isSmokeSource(level.getBlockState(clickedPos.below())))
- .setValue(LIT, !flag)
+ .setValue(LIT, level.getMinecraftWorld().purpurConfig.campFireLitWhenPlaced && !flag) // Purpur - Campfire option for lit when placed
.setValue(FACING, context.getHorizontalDirection());
}

View File

@@ -0,0 +1,50 @@
--- a/net/minecraft/world/level/block/CarvedPumpkinBlock.java
+++ b/net/minecraft/world/level/block/CarvedPumpkinBlock.java
@@ -67,7 +_,7 @@
if (blockPatternMatch != null) {
SnowGolem snowGolem = EntityType.SNOW_GOLEM.create(level, EntitySpawnReason.TRIGGERED);
if (snowGolem != null) {
- spawnGolemInWorld(level, blockPatternMatch, snowGolem, blockPatternMatch.getBlock(0, 2, 0).getPos());
+ spawnGolemInWorld(level, blockPatternMatch, snowGolem, blockPatternMatch.getBlock(0, 2, 0).getPos(), this.placer); // Purpur - Summoner API
return;
}
}
@@ -77,7 +_,7 @@
IronGolem ironGolem = EntityType.IRON_GOLEM.create(level, EntitySpawnReason.TRIGGERED);
if (ironGolem != null) {
ironGolem.setPlayerCreated(true);
- spawnGolemInWorld(level, blockPatternMatch1, ironGolem, blockPatternMatch1.getBlock(1, 2, 0).getPos());
+ spawnGolemInWorld(level, blockPatternMatch1, ironGolem, blockPatternMatch1.getBlock(1, 2, 0).getPos(), this.placer); // Purpur - Summoner API
return;
}
}
@@ -86,7 +_,7 @@
if (blockPatternMatch2 != null) {
CopperGolem copperGolem = EntityType.COPPER_GOLEM.create(level, EntitySpawnReason.TRIGGERED);
if (copperGolem != null) {
- spawnGolemInWorld(level, blockPatternMatch2, copperGolem, blockPatternMatch2.getBlock(0, 0, 0).getPos());
+ spawnGolemInWorld(level, blockPatternMatch2, copperGolem, blockPatternMatch2.getBlock(0, 0, 0).getPos(), this.placer); // Purpur - Summoner API
if (!copperGolem.valid) return; // Paper - entityspawnevent - entity was not added to the world so prevent world mutation
this.replaceCopperBlockWithChest(level, blockPatternMatch2);
copperGolem.spawn(this.getWeatherStateFromPattern(blockPatternMatch2));
@@ -105,7 +_,20 @@
.getAge();
}
+ @io.papermc.paper.annotation.DoNotUse
private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch patternMatch, Entity golem, BlockPos pos) {
+ // Purpur start - Summoner API
+ spawnGolemInWorld(level, patternMatch, golem, pos, null);
+ }
+ private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch patternMatch, Entity golem, BlockPos pos, net.minecraft.world.entity.LivingEntity placer) {
+ java.util.UUID summoner = placer == null ? null : placer.getUUID();
+ switch (golem) {
+ case SnowGolem snowGolem -> snowGolem.setSummoner(summoner);
+ case IronGolem ironGolem -> ironGolem.setSummoner(summoner);
+ case CopperGolem copperGolem -> copperGolem.setSummoner(summoner);
+ default -> throw new IllegalStateException("Unexpected value: " + golem);
+ }
+ // Purpur end - Summoner API
// clearPatternBlocks(level, patternMatch); // Paper - moved down
golem.snapTo(pos.getX() + 0.5, pos.getY() + 0.05, pos.getZ() + 0.5, 0.0F, 0.0F);
// Paper start

View File

@@ -0,0 +1,13 @@
--- a/net/minecraft/world/level/block/CauldronBlock.java
+++ b/net/minecraft/world/level/block/CauldronBlock.java
@@ -32,8 +_,8 @@
protected static boolean shouldHandlePrecipitation(Level level, Biome.Precipitation precipitation) {
return precipitation == Biome.Precipitation.RAIN
- ? level.getRandom().nextFloat() < 0.05F
- : precipitation == Biome.Precipitation.SNOW && level.getRandom().nextFloat() < 0.1F;
+ ? level.getRandom().nextFloat() < level.purpurConfig.cauldronRainChance // Purpur - Cauldron fill chances
+ : precipitation == Biome.Precipitation.SNOW && level.getRandom().nextFloat() < level.purpurConfig.cauldronPowderSnowChance; // Purpur - Cauldron fill chances
}
@Override

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/level/block/CaveVinesBlock.java
+++ b/net/minecraft/world/level/block/CaveVinesBlock.java
@@ -92,4 +_,11 @@
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
level.setBlock(pos, state.setValue(BERRIES, true), Block.UPDATE_CLIENTS);
}
+
+ // Purpur start - cave vines configurable max growth age
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.caveVinesMaxGrowthAge;
+ }
+ // Purpur end - cave vines configurable max growth age
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/block/ChangeOverTimeBlock.java
+++ b/net/minecraft/world/level/block/ChangeOverTimeBlock.java
@@ -51,7 +_,7 @@
}
float f = (float)(i1 + 1) / (i1 + i + 1);
- float f1 = f * f * this.getChanceModifier();
+ float f1 = level.purpurConfig.disableOxidationProximityPenalty ? this.getChanceModifier() :f * f * this.getChanceModifier();// Purpur - option to disable the copper oxidation proximity penalty
return random.nextFloat() < f1 ? this.getNext(state) : Optional.empty();
}
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/ChestBlock.java
+++ b/net/minecraft/world/level/block/ChestBlock.java
@@ -370,6 +_,7 @@
}
public static boolean isBlockedChestByBlock(BlockGetter level, BlockPos pos) {
+ if (level instanceof Level level1 && level1.purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur - Option for chests to open even with a solid block on top
BlockPos blockPos = pos.above();
return level.getBlockState(blockPos).isRedstoneConductor(level, blockPos);
}

View File

@@ -0,0 +1,66 @@
--- a/net/minecraft/world/level/block/ComposterBlock.java
+++ b/net/minecraft/world/level/block/ComposterBlock.java
@@ -250,23 +_,52 @@
) {
int levelValue = state.getValue(LEVEL);
if (levelValue < 8 && COMPOSTABLES.containsKey(stack.getItem())) {
- if (levelValue < 7 && !level.isClientSide()) {
- BlockState blockState = addItem(player, state, level, pos, stack);
- // Paper start - handle cancelled events
- if (blockState == null) {
- return InteractionResult.PASS;
- }
- // Paper end
- level.levelEvent(LevelEvent.COMPOSTER_FILL, pos, state != blockState ? 1 : 0);
- player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
- stack.consume(1, player);
- }
+ // Purpur start - sneak to bulk process composter
+ BlockState newState = process(levelValue, player, state, level, pos, stack);
+ if (newState == null) {
+ return InteractionResult.PASS;
+ }
+ if (level.purpurConfig.composterBulkProcess && player.isShiftKeyDown() && newState != state) {
+ BlockState oldState;
+ int oldCount, newCount, oldLevel, newLevel;
+ do {
+ oldState = newState;
+ oldCount = stack.getCount();
+ oldLevel = oldState.getValue(ComposterBlock.LEVEL);
+ newState = process(oldLevel, player, oldState, level, pos, stack);
+ if (newState == null) {
+ return InteractionResult.PASS;
+ }
+ newCount = stack.getCount();
+ newLevel = newState.getValue(ComposterBlock.LEVEL);
+ } while (newCount > 0 && (newCount != oldCount || newLevel != oldLevel || newState != oldState));
+ }
+ // Purpur end - Sneak to bulk process composter
return InteractionResult.SUCCESS;
} else {
return super.useItemOn(stack, state, level, pos, player, hand, hitResult);
}
}
+
+ // Purpur start - sneak to bulk process composter
+ private static @Nullable BlockState process(int levelValue, Player player, BlockState state, Level level, BlockPos pos, ItemStack stack) {
+ if (levelValue < 7 && !level.isClientSide()) {
+ BlockState blockState = ComposterBlock.addItem(player, state, level, pos, stack);
+ // Paper start - handle cancelled events
+ if (blockState == null) {
+ return null;
+ }
+ // Paper end
+
+ level.levelEvent(LevelEvent.COMPOSTER_FILL, pos, state != blockState ? 1 : 0);
+ player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
+ stack.consume(1, player);
+ return blockState;
+ }
+ return state;
+ }
+ // Purpur end - Sneak to bulk process composter
@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/CoralBlock.java
+++ b/net/minecraft/world/level/block/CoralBlock.java
@@ -65,6 +_,7 @@
}
protected boolean scanForWater(BlockGetter level, BlockPos pos) {
+ if (!((net.minecraft.world.level.LevelAccessor) level).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - Config to not let coral die
for (Direction direction : Direction.values()) {
FluidState fluidState = level.getFluidState(pos.relative(direction));
if (fluidState.is(FluidTags.WATER)) {

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/world/level/block/CropBlock.java
+++ b/net/minecraft/world/level/block/CropBlock.java
@@ -169,7 +_,7 @@
@Override
protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity, InsideBlockEffectApplier effectApplier, boolean pastEdges) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
- if (level instanceof ServerLevel serverLevel && entity instanceof Ravager && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !serverLevel.getGameRules().get(GameRules.MOB_GRIEFING))) { // CraftBukkit
+ if (level instanceof ServerLevel serverLevel && entity instanceof Ravager && serverLevel.purpurConfig.ravagerGriefableBlocks.contains(serverLevel.getBlockState(pos).getBlock()) && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !serverLevel.getGameRules().get(GameRules.MOB_GRIEFING))) { // CraftBukkit // Purpur - Configurable ravager griefable blocks list
serverLevel.destroyBlock(pos, true, entity);
}
@@ -204,4 +_,15 @@
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(AGE);
}
+
+ // Purpur start - Ability for hoe to replant crops
+ @Override
+ public void playerDestroy(Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) {
+ if (world.purpurConfig.hoeReplantsCrops && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) {
+ super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, getBaseSeedId());
+ } else {
+ super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp);
+ }
+ }
+ // Purpur end - Ability for hoe to replant crops
}

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/world/level/block/DoorBlock.java
+++ b/net/minecraft/world/level/block/DoorBlock.java
@@ -198,6 +_,7 @@
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
if (!this.type.canOpenByHand()) {
return InteractionResult.PASS;
+ } else if (requiresRedstone(level, state, pos)) { return InteractionResult.CONSUME; // Purpur - Option to make doors require redstone
} else {
state = state.cycle(OPEN);
level.setBlock(pos, state, Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE);
@@ -286,4 +_,18 @@
public static boolean isWoodenDoor(BlockState state) {
return state.getBlock() instanceof DoorBlock doorBlock && doorBlock.type().canOpenByHand();
}
+
+ // Purpur start - Option to make doors require redstone
+ public static boolean requiresRedstone(Level level, BlockState state, BlockPos pos) {
+ if (level.purpurConfig.doorRequiresRedstone.contains(state.getBlock())) {
+ // force update client
+ BlockPos otherPos = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN);
+ BlockState otherState = level.getBlockState(otherPos);
+ level.sendBlockUpdated(pos, state, state, Block.UPDATE_ALL);
+ level.sendBlockUpdated(otherPos, otherState, otherState, Block.UPDATE_ALL);
+ return true;
+ }
+ return false;
+ }
+ // Purpur end - Option to make doors require redstone
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/DragonEggBlock.java
+++ b/net/minecraft/world/level/block/DragonEggBlock.java
@@ -46,6 +_,7 @@
}
private void teleport(BlockState state, Level level, BlockPos pos) {
+ if (!level.purpurConfig.dragonEggTeleport) return; // Purpur - Option to disable dragon egg teleporting
WorldBorder worldBorder = level.getWorldBorder();
for (int i = 0; i < 1000; i++) {

View File

@@ -0,0 +1,49 @@
--- a/net/minecraft/world/level/block/FarmBlock.java
+++ b/net/minecraft/world/level/block/FarmBlock.java
@@ -112,7 +_,7 @@
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance) {
super.fallOn(level, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage.
if (level instanceof ServerLevel serverLevel
- && level.random.nextFloat() < fallDistance - 0.5
+ && (serverLevel.purpurConfig.farmlandTrampleHeight >= 0D ? fallDistance >= serverLevel.purpurConfig.farmlandTrampleHeight : level.random.nextFloat() < fallDistance - 0.5) // Purpur - Configurable farmland trample height
&& entity instanceof LivingEntity
&& (entity instanceof Player || serverLevel.getGameRules().get(GameRules.MOB_GRIEFING))
&& entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) {
@@ -129,6 +_,28 @@
return;
}
+ if (level.purpurConfig.farmlandTramplingDisabled) return; // Purpur - Farmland trampling changes
+ if (level.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof Player)) return; // Purpur - Farmland trampling changes
+
+ // Purpur start - Ability to re-add farmland mechanics from Alpha
+ if (level.purpurConfig.farmlandAlpha) {
+ Block block = level.getBlockState(pos.below()).getBlock();
+ if (block instanceof FenceBlock || block instanceof WallBlock) {
+ return;
+ }
+ }
+ // Purpur end - Ability to re-add farmland mechanics from Alpha
+
+ // Purpur start - Farmland trampling changes
+ if (level.purpurConfig.farmlandTramplingFeatherFalling) {
+ net.minecraft.world.item.ItemStack bootsItem = ((net.minecraft.world.entity.LivingEntity) entity).getItemBySlot(net.minecraft.world.entity.EquipmentSlot.FEET);
+
+ if (bootsItem != net.minecraft.world.item.ItemStack.EMPTY && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, bootsItem) >= (int) entity.fallDistance) {
+ return;
+ }
+ }
+ // Purpur end - Farmland trampling changes
+
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) {
return;
}
@@ -177,7 +_,7 @@
}
}
- return false;
+ return ((ServerLevel) level).purpurConfig.farmlandGetsMoistFromBelow && level.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur - Allow soil to moisten from water directly under it
// Paper end - Perf: remove abstract block iteration
}

View File

@@ -0,0 +1,63 @@
--- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
+++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
@@ -34,12 +_,12 @@
@Override
public BlockState getStateForPlacement(RandomSource random) {
- return this.defaultBlockState().setValue(AGE, random.nextInt(25));
+ return this.defaultBlockState().setValue(AGE, getMaxGrowthAge() == 0 ? 0 : random.nextInt(getMaxGrowthAge())); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
@Override
protected boolean isRandomlyTicking(BlockState state) {
- return state.getValue(AGE) < 25;
+ return state.getValue(AGE) < getMaxGrowthAge(); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
@Override
@@ -55,7 +_,7 @@
} else if (this == Blocks.CAVE_VINES) {
modifier = level.spigotConfig.caveVinesModifier;
}
- if (state.getValue(AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution
+ if (state.getValue(AGE) < getMaxGrowthAge() && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution // Purpur - kelp, cave, weeping, and twisting configurable max growth age
// Spigot end
BlockPos blockPos = pos.relative(this.growthDirection);
if (this.canGrowInto(level.getBlockState(blockPos))) {
@@ -75,11 +_,11 @@
}
public BlockState getMaxAgeState(BlockState state) {
- return state.setValue(AGE, 25);
+ return state.setValue(AGE, getMaxGrowthAge()); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
public boolean isMaxAge(BlockState state) {
- return state.getValue(AGE) == 25;
+ return state.getValue(AGE) >= getMaxGrowthAge(); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
protected BlockState updateBodyAfterConvertedFromHead(BlockState head, BlockState body) {
@@ -137,13 +_,13 @@
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
BlockPos blockPos = pos.relative(this.growthDirection);
- int min = Math.min(state.getValue(AGE) + 1, 25);
+ int min = Math.min(state.getValue(AGE) + 1, getMaxGrowthAge()); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
int blocksToGrowWhenBonemealed = this.getBlocksToGrowWhenBonemealed(random);
for (int i = 0; i < blocksToGrowWhenBonemealed && this.canGrowInto(level.getBlockState(blockPos)); i++) {
level.setBlockAndUpdate(blockPos, state.setValue(AGE, min));
blockPos = blockPos.relative(this.growthDirection);
- min = Math.min(min + 1, 25);
+ min = Math.min(min + 1, getMaxGrowthAge()); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
}
@@ -155,4 +_,6 @@
protected GrowingPlantHeadBlock getHeadBlock() {
return this;
}
+
+ public abstract int getMaxGrowthAge(); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/HayBlock.java
+++ b/net/minecraft/world/level/block/HayBlock.java
@@ -23,6 +_,6 @@
@Override
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance) {
- entity.causeFallDamage(fallDistance, 0.2F, level.damageSources().fall());
+ super.fallOn(level, state, pos, entity, fallDistance); // Purpur - Configurable block fall damage modifiers
}
}

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/level/block/KelpBlock.java
+++ b/net/minecraft/world/level/block/KelpBlock.java
@@ -71,4 +_,11 @@
protected FluidState getFluidState(BlockState state) {
return Fluids.WATER.getSource(false);
}
+
+ // Purpur start - kelp vines configurable max growth age
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.kelpMaxGrowthAge;
+ }
+ // Purpur end - kelp vines configurable max growth age
}

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/world/level/block/LiquidBlock.java
+++ b/net/minecraft/world/level/block/LiquidBlock.java
@@ -138,7 +_,7 @@
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
- if (this.shouldSpreadLiquid(level, pos, state)) {
+ if (level.purpurConfig.tickFluids && this.shouldSpreadLiquid(level, pos, state)) { // Purpur - Tick fluids config
level.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(level, pos)); // Paper - Configurable speed for water flowing over lava
}
}
@@ -175,7 +_,7 @@
BlockState neighborState,
RandomSource random
) {
- if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) {
+ if (level.getWorldBorder().world.purpurConfig.tickFluids && state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { // Purpur - Tick fluids config
scheduledTickAccess.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(level));
}
@@ -184,7 +_,7 @@
@Override
protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
- if (this.shouldSpreadLiquid(level, pos, state)) {
+ if (level.purpurConfig.tickFluids && this.shouldSpreadLiquid(level, pos, state)) { // Purpur - Tick fluids config
level.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(level, pos)); // Paper - Configurable speed for water flowing over lava
}
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/block/MagmaBlock.java
+++ b/net/minecraft/world/level/block/MagmaBlock.java
@@ -28,7 +_,7 @@
@Override
public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
- if (!entity.isSteppingCarefully() && entity instanceof LivingEntity) {
+ if ((!entity.isSteppingCarefully() || level.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity) { // Purpur - Configurable damage settings for magma blocks
entity.hurt(level.damageSources().hotFloor().eventBlockDamager(level, pos), 1.0F); // CraftBukkit
}

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/world/level/block/NetherPortalBlock.java
+++ b/net/minecraft/world/level/block/NetherPortalBlock.java
@@ -68,7 +_,7 @@
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (level.spigotConfig.enableZombiePigmenPortalSpawns && level.isSpawningMonsters() // Spigot
&& level.environmentAttributes().getValue(EnvironmentAttributes.NETHER_PORTAL_SPAWNS_PIGLINS, pos)
- && random.nextInt(2000) < level.getDifficulty().getId()
+ && random.nextInt(level.purpurConfig.piglinPortalSpawnModifier) < level.getDifficulty().getId() // Purpur - Piglin portal spawn modifier
&& level.anyPlayerCloseEnoughForSpawning(pos)) {
while (level.getBlockState(pos).is(this)) {
pos = pos.below();
@@ -126,7 +_,7 @@
@Override
public int getPortalTransitionTime(ServerLevel level, Entity entity) {
return entity instanceof Player player
- ? Math.max(
+ ? player.canPortalInstant ? 1 : Math.max( // Purpur - Add portal permission bypass
0,
level.getGameRules()
.get(player.getAbilities().invulnerable ? GameRules.PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.PLAYERS_NETHER_PORTAL_DEFAULT_DELAY)

View File

@@ -0,0 +1,46 @@
--- a/net/minecraft/world/level/block/NetherWartBlock.java
+++ b/net/minecraft/world/level/block/NetherWartBlock.java
@@ -16,7 +_,7 @@
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
-public class NetherWartBlock extends VegetationBlock {
+public class NetherWartBlock extends VegetationBlock implements BonemealableBlock { // Purpur - bonemealable netherwart
public static final MapCodec<NetherWartBlock> CODEC = simpleCodec(NetherWartBlock::new);
public static final int MAX_AGE = 3;
public static final IntegerProperty AGE = BlockStateProperties.AGE_3;
@@ -65,4 +_,34 @@
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(AGE);
}
+
+ // Purpur start - Ability for hoe to replant nether warts
+ @Override
+ public void playerDestroy(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) {
+ if (world.purpurConfig.hoeReplantsNetherWarts && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) {
+ super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, Items.NETHER_WART);
+ } else {
+ super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp);
+ }
+ }
+ // Purpur end - Ability for hoe to replant nether warts
+
+ // Purpur start - bonemealable netherwart
+ @Override
+ public boolean isValidBonemealTarget(final net.minecraft.world.level.LevelReader world, final BlockPos pos, final BlockState state) {
+ return ((net.minecraft.world.level.Level) world).purpurConfig.netherWartAffectedByBonemeal && state.getValue(NetherWartBlock.AGE) < 3;
+ }
+
+ @Override
+ public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) {
+ return true;
+ }
+
+ @Override
+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ int i = Math.min(3, state.getValue(NetherWartBlock.AGE) + 1);
+ state = state.setValue(NetherWartBlock.AGE, i);
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit
+ }
+ // Purpur end - bonemealable netherwart
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/block/NoteBlock.java
+++ b/net/minecraft/world/level/block/NoteBlock.java
@@ -101,7 +_,7 @@
}
private void playNote(@Nullable Entity entity, BlockState state, Level level, BlockPos pos) {
- if (state.getValue(INSTRUMENT).worksAboveNoteBlock() || level.getBlockState(pos.above()).isAir()) {
+ if (level.purpurConfig.noteBlockIgnoreAbove || state.getValue(INSTRUMENT).worksAboveNoteBlock() || level.getBlockState(pos.above()).isAir()) { // Purpur - Config to allow Note Block sounds when blocked
level.blockEvent(pos, this, 0, 0);
level.gameEvent(entity, GameEvent.NOTE_BLOCK_PLAY, pos);
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/ObserverBlock.java
+++ b/net/minecraft/world/level/block/ObserverBlock.java
@@ -81,6 +_,7 @@
RandomSource random
) {
if (state.getValue(FACING) == direction && !state.getValue(POWERED)) {
+ if (!level.getWorldBorder().world.purpurConfig.disableObserverClocks || !(neighborState.getBlock() instanceof ObserverBlock) || neighborState.getValue(ObserverBlock.FACING).getOpposite() != direction) // Purpur - Add Option for disable observer clocks
this.startSignal(level, scheduledTickAccess, pos);
}

View File

@@ -0,0 +1,26 @@
--- a/net/minecraft/world/level/block/PointedDripstoneBlock.java
+++ b/net/minecraft/world/level/block/PointedDripstoneBlock.java
@@ -194,20 +_,20 @@
@VisibleForTesting
public static void maybeTransferFluid(BlockState state, ServerLevel level, BlockPos pos, float randChance) {
- if (!(randChance > 0.17578125F) || !(randChance > 0.05859375F)) {
+ if (!(randChance > level.purpurConfig.cauldronDripstoneWaterFillChance) || !(randChance > level.purpurConfig.cauldronDripstoneLavaFillChance)) { // Purpur - Cauldron fill chances
if (isStalactiteStartPos(state, level, pos)) {
Optional<PointedDripstoneBlock.FluidInfo> fluidAboveStalactite = getFluidAboveStalactite(level, pos, state);
if (!fluidAboveStalactite.isEmpty()) {
Fluid fluid = fluidAboveStalactite.get().fluid;
float f;
if (fluid == Fluids.WATER) {
- f = 0.17578125F;
+ f = level.purpurConfig.cauldronDripstoneWaterFillChance; // Purpur - Cauldron fill chances
} else {
if (fluid != Fluids.LAVA) {
return;
}
- f = 0.05859375F;
+ f = level.purpurConfig.cauldronDripstoneLavaFillChance; // Purpur - Cauldron fill chances
}
if (!(randChance >= f)) {

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/block/PoweredRailBlock.java
+++ b/net/minecraft/world/level/block/PoweredRailBlock.java
@@ -28,7 +_,7 @@
}
protected boolean findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean searchForward, int recursionCount) {
- if (recursionCount >= 8) {
+ if (recursionCount >= level.purpurConfig.railActivationRange) { // Purpur - Config for powered rail activation distance
return false;
} else {
int x = pos.getX();

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/block/RespawnAnchorBlock.java
+++ b/net/minecraft/world/level/block/RespawnAnchorBlock.java
@@ -166,7 +_,7 @@
};
Vec3 center = pos2.getCenter();
level.explode(
- null, level.damageSources().badRespawnPointExplosion(center).causingBlockSnapshot(blockState), explosionDamageCalculator, center, 5.0F, true, Level.ExplosionInteraction.BLOCK // CraftBukkit - add state
+ null, level.damageSources().badRespawnPointExplosion(center).causingBlockSnapshot(blockState), explosionDamageCalculator, center, (float) level.purpurConfig.respawnAnchorExplosionPower, level.purpurConfig.respawnAnchorExplosionFire, level.purpurConfig.respawnAnchorExplosionEffect // CraftBukkit - add state // Purpur - Implement respawn anchor explosion options
);
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/block/SculkShriekerBlock.java
+++ b/net/minecraft/world/level/block/SculkShriekerBlock.java
@@ -116,7 +_,7 @@
@Override
public @Nullable BlockState getStateForPlacement(BlockPlaceContext context) {
- return this.defaultBlockState().setValue(WATERLOGGED, context.getLevel().getFluidState(context.getClickedPos()).getType() == Fluids.WATER);
+ return this.defaultBlockState().setValue(WATERLOGGED, context.getLevel().getFluidState(context.getClickedPos()).getType() == Fluids.WATER).setValue(SculkShriekerBlock.CAN_SUMMON, context.getLevel().purpurConfig.sculkShriekerCanSummonDefault); // Purpur - Config for sculk shrieker can_summon state
}
@Override

View File

@@ -0,0 +1,28 @@
--- a/net/minecraft/world/level/block/SlabBlock.java
+++ b/net/minecraft/world/level/block/SlabBlock.java
@@ -143,4 +_,25 @@
return false;
}
}
+
+ // Purpur start - Break individual slabs when sneaking
+ public boolean halfBreak(BlockState state, BlockPos pos, net.minecraft.server.level.ServerPlayer player) {
+ if (state.getValue(SlabBlock.TYPE) != SlabType.DOUBLE) {
+ return false;
+ }
+ net.minecraft.world.phys.HitResult result = player.getRayTrace(16, net.minecraft.world.level.ClipContext.Fluid.NONE);
+ if (result.getType() != net.minecraft.world.phys.HitResult.Type.BLOCK) {
+ return false;
+ }
+ double hitY = result.getLocation().y();
+ int blockY = org.bukkit.util.NumberConversions.floor(hitY);
+ player.level().setBlock(pos, state.setValue(SlabBlock.TYPE, (hitY - blockY > 0.5 || blockY - pos.getY() == 1) ? SlabType.BOTTOM : SlabType.TOP), 3);
+ if (!player.getAbilities().instabuild) {
+ net.minecraft.world.entity.item.ItemEntity item = new net.minecraft.world.entity.item.ItemEntity(player.level(), pos.getX(), pos.getY(), pos.getZ(), new ItemStack(asItem()));
+ item.setDefaultPickUpDelay();
+ player.level().addFreshEntity(item);
+ }
+ return true;
+ }
+ // Purpur end - Break individual slabs when sneaking
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/SnowLayerBlock.java
+++ b/net/minecraft/world/level/block/SnowLayerBlock.java
@@ -76,6 +_,7 @@
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
BlockState blockState = level.getBlockState(pos.below());
+ if (blockState.is(Blocks.BLUE_ICE) && !level.getWorldBorder().world.purpurConfig.snowOnBlueIce) return false; // Purpur - Add config for snow on blue ice
return !blockState.is(BlockTags.SNOW_LAYER_CANNOT_SURVIVE_ON)
&& (
blockState.is(BlockTags.SNOW_LAYER_CAN_SURVIVE_ON)

View File

@@ -0,0 +1,81 @@
--- a/net/minecraft/world/level/block/SpawnerBlock.java
+++ b/net/minecraft/world/level/block/SpawnerBlock.java
@@ -14,6 +_,7 @@
import org.jspecify.annotations.Nullable;
public class SpawnerBlock extends BaseEntityBlock {
+ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger();
public static final MapCodec<SpawnerBlock> CODEC = simpleCodec(SpawnerBlock::new);
@Override
@@ -37,6 +_,62 @@
);
}
+ // Purpur start - Silk touch spawners
+ @Override
+ public void playerDestroy(Level level, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack stack, boolean includeDrops, boolean dropExp) {
+ if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(level, stack)) {
+ ItemStack item = new ItemStack(Blocks.SPAWNER.asItem());
+
+ net.minecraft.world.level.SpawnData nextSpawnData = blockEntity instanceof SpawnerBlockEntity spawnerBlock ? spawnerBlock.getSpawner().nextSpawnData : null;
+ java.util.Optional<net.minecraft.world.entity.EntityType<?>> type = java.util.Optional.empty();
+ if (nextSpawnData != null) {
+ try (net.minecraft.util.ProblemReporter.ScopedCollector scopedCollector = new net.minecraft.util.ProblemReporter.ScopedCollector(blockEntity.problemPath(), LOGGER)) {
+ net.minecraft.world.level.storage.ValueInput valueInput = net.minecraft.world.level.storage.TagValueInput.create(scopedCollector, player.level().registryAccess(), nextSpawnData.entityToSpawn());
+ type = net.minecraft.world.entity.EntityType.by(valueInput);
+ }
+ net.minecraft.nbt.CompoundTag spawnDataTag = new net.minecraft.nbt.CompoundTag();
+ spawnDataTag.storeNullable("SpawnData", net.minecraft.world.level.SpawnData.CODEC, nextSpawnData);
+ item.set(net.minecraft.core.component.DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY.update(compoundTag -> compoundTag.put("Purpur.SpawnData", spawnDataTag)));
+ }
+
+ if (type.isPresent()) {
+ final net.kyori.adventure.text.Component mobName = io.papermc.paper.adventure.PaperAdventure.asAdventure(type.get().getDescription());
+
+ String name = level.purpurConfig.silkTouchSpawnerName;
+ if (name != null && !name.isEmpty() && !name.equals("Monster Spawner")) {
+ net.kyori.adventure.text.Component displayName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName));
+ if (name.startsWith("<reset>")) {
+ displayName = displayName.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false);
+ }
+ item.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName));
+ }
+
+ java.util.List<String> lore = level.purpurConfig.silkTouchSpawnerLore;
+ if (lore != null && !lore.isEmpty()) {
+
+ java.util.List<net.minecraft.network.chat.Component> loreComponentList = new java.util.ArrayList<>();
+ for (String line : lore) {
+ net.kyori.adventure.text.Component lineComponent = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(line, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName));
+ if (line.startsWith("<reset>")) {
+ lineComponent = lineComponent.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false);
+ }
+ loreComponentList.add(io.papermc.paper.adventure.PaperAdventure.asVanilla(lineComponent));
+ }
+
+ item.set(net.minecraft.core.component.DataComponents.LORE, new net.minecraft.world.item.component.ItemLore(loreComponentList, loreComponentList));
+ }
+ item.set(net.minecraft.core.component.DataComponents.TOOLTIP_DISPLAY, net.minecraft.world.item.component.TooltipDisplay.DEFAULT.withHidden(net.minecraft.core.component.DataComponents.BLOCK_ENTITY_DATA, true));
+ }
+ popResource(level, pos, item);
+ }
+ super.playerDestroy(level, player, pos, state, blockEntity, stack, includeDrops, dropExp);
+ }
+
+ private boolean isSilkTouch(Level level, ItemStack stack) {
+ return stack != null && level.purpurConfig.silkTouchTools.contains(stack.getItem()) && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) >= level.purpurConfig.minimumSilkTouchSpawnerRequire;
+ }
+ // Purpur end - Silk touch spawners
+
@Override
protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
super.spawnAfterBreak(state, level, pos, stack, dropExperience);
@@ -45,6 +_,7 @@
@Override
public int getExpDrop(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+ if (level.purpurConfig.silkTouchEnabled && isSilkTouch(level, stack)) return 0; // Purpur - Silk touch spawners
if (dropExperience) {
int i = 15 + level.random.nextInt(15) + level.random.nextInt(15);
// this.popExperience(level, pos, i);

View File

@@ -0,0 +1,33 @@
--- a/net/minecraft/world/level/block/SpongeBlock.java
+++ b/net/minecraft/world/level/block/SpongeBlock.java
@@ -53,8 +_,8 @@
org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(level); // CraftBukkit - Use BlockStateListPopulator
BlockPos.breadthFirstTraversal(
pos,
- 6,
- 65,
+ level.purpurConfig.spongeAbsorptionRadius, // Purpur - Configurable sponge absorption
+ level.purpurConfig.spongeAbsorptionArea, // Purpur - Configurable sponge absorption
(validPos, queueAdder) -> {
for (Direction direction : ALL_DIRECTIONS) {
queueAdder.accept(validPos.relative(direction));
@@ -68,7 +_,7 @@
BlockState blockState = blockList.getBlockState(blockPos);
FluidState fluidState = blockList.getFluidState(blockPos);
// CraftBukkit end
- if (!fluidState.is(FluidTags.WATER)) {
+ if (!fluidState.is(FluidTags.WATER) && (!level.purpurConfig.spongeAbsorbsLava || !fluidState.is(FluidTags.LAVA)) && (!level.purpurConfig.spongeAbsorbsWaterFromMud || !blockState.is(Blocks.MUD))) { // Purpur - Option for sponges to work on lava and mud
return BlockPos.TraversalNodeStatus.SKIP;
} else if (blockState.getBlock() instanceof BucketPickup bucketPickup
&& !bucketPickup.pickupBlock(null, blockList, blockPos, blockState).isEmpty()) { // CraftBukkit
@@ -76,6 +_,10 @@
} else {
if (blockState.getBlock() instanceof LiquidBlock) {
blockList.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit
+ // Purpur start - Option for sponges to work on lava and mud
+ } else if (blockState.is(Blocks.MUD)) {
+ blockList.setBlock(blockPos, Blocks.CLAY.defaultBlockState(), 3);
+ // Purpur end - Option for sponges to work on lava and mud
} else {
if (!blockState.is(Blocks.KELP)
&& !blockState.is(Blocks.KELP_PLANT)

View File

@@ -0,0 +1,17 @@
--- a/net/minecraft/world/level/block/StonecutterBlock.java
+++ b/net/minecraft/world/level/block/StonecutterBlock.java
@@ -92,4 +_,14 @@
protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) {
return false;
}
+
+ // Purpur start - Stonecutter damage
+ @Override
+ public void stepOn(Level level, BlockPos pos, BlockState state, net.minecraft.world.entity.Entity entity) {
+ if (level.purpurConfig.stonecutterDamage > 0.0F && entity instanceof net.minecraft.world.entity.LivingEntity) {
+ entity.hurtServer((net.minecraft.server.level.ServerLevel) level, entity.damageSources().stonecutter().eventBlockDamager(level, pos), level.purpurConfig.stonecutterDamage);
+ }
+ super.stepOn(level, pos, state, entity);
+ }
+ // Purpur end - Stonecutter damage
}

View File

@@ -0,0 +1,46 @@
--- a/net/minecraft/world/level/block/SugarCaneBlock.java
+++ b/net/minecraft/world/level/block/SugarCaneBlock.java
@@ -19,7 +_,7 @@
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
-public class SugarCaneBlock extends Block {
+public class SugarCaneBlock extends Block implements BonemealableBlock { // Purpur - bonemealable sugarcane
public static final MapCodec<SugarCaneBlock> CODEC = simpleCodec(SugarCaneBlock::new);
public static final IntegerProperty AGE = BlockStateProperties.AGE_15;
private static final VoxelShape SHAPE = Block.column(12.0, 0.0, 16.0);
@@ -112,4 +_,34 @@
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(AGE);
}
+
+ // Purpur start - bonemealable sugarcane
+ @Override
+ public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) {
+ if (!((net.minecraft.world.level.Level) world).purpurConfig.sugarCanAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false;
+
+ int reedHeight = 0;
+ while (world.getBlockState(pos.below(reedHeight)).is(this)) {
+ reedHeight++;
+ }
+
+ return reedHeight < ((net.minecraft.world.level.Level) world).paperConfig().maxGrowthHeight.reeds;
+ }
+
+ @Override
+ public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) {
+ return true;
+ }
+
+ @Override
+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ int reedHeight = 0;
+ while (world.getBlockState(pos.below(reedHeight)).is(this)) {
+ reedHeight++;
+ }
+ for (int i = 0; i <= world.paperConfig().maxGrowthHeight.reeds - reedHeight; i++) {
+ world.setBlockAndUpdate(pos.above(i), state.setValue(SugarCaneBlock.AGE, 0));
+ }
+ }
+ // Purpur end - bonemealable sugarcane
}

View File

@@ -0,0 +1,39 @@
--- a/net/minecraft/world/level/block/TurtleEggBlock.java
+++ b/net/minecraft/world/level/block/TurtleEggBlock.java
@@ -189,9 +_,32 @@
}
private boolean canDestroyEgg(ServerLevel level, Entity entity) {
- return !(entity instanceof Turtle)
- && !(entity instanceof Bat)
- && entity instanceof LivingEntity
- && (entity instanceof Player || level.getGameRules().get(GameRules.MOB_GRIEFING));
+ // Purpur start - Add turtle egg block options
+ if (entity instanceof Turtle || entity instanceof Bat) {
+ return false;
+ }
+ if (level.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof net.minecraft.world.entity.ExperienceOrb) {
+ return true;
+ }
+ if (level.purpurConfig.turtleEggsBreakFromItems && entity instanceof net.minecraft.world.entity.item.ItemEntity) {
+ return true;
+ }
+ if (level.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) {
+ return true;
+ }
+ if (!(entity instanceof LivingEntity)) {
+ return false;
+ }
+ // Purpur start - Option to disable turtle egg trampling with feather falling
+ if (level.purpurConfig.turtleEggsTramplingFeatherFalling) {
+ net.minecraft.world.item.ItemStack bootsItem = ((net.minecraft.world.entity.LivingEntity) entity).getItemBySlot(net.minecraft.world.entity.EquipmentSlot.FEET);
+
+ return bootsItem != net.minecraft.world.item.ItemStack.EMPTY || net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, bootsItem) < (int) entity.fallDistance;
+ }
+ // Purpur end - Option to disable turtle egg trampling with feather falling
+ if (entity instanceof Player) return true;
+
+ return level.getGameRules().get(GameRules.MOB_GRIEFING);
+ // Purpur end - Add turtle egg block options
}
}

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/level/block/TwistingVinesBlock.java
+++ b/net/minecraft/world/level/block/TwistingVinesBlock.java
@@ -34,4 +_,11 @@
protected boolean canGrowInto(BlockState state) {
return NetherVines.isValidGrowthState(state);
}
+
+ // Purpur start - twisting vines configurable max growth age
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.twistingVinesMaxGrowthAge;
+ }
+ // Purpur end - twisting vines configurable max growth age
}

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/world/level/block/VegetationBlock.java
+++ b/net/minecraft/world/level/block/VegetationBlock.java
@@ -61,4 +_,24 @@
protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) {
return pathComputationType == PathComputationType.AIR && !this.hasCollision || super.isPathfindable(state, pathComputationType);
}
+
+ // Purpur start - Ability for hoe to replant crops
+ public void playerDestroyAndReplant(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, net.minecraft.world.item.ItemStack itemInHand, net.minecraft.world.level.ItemLike itemToReplant) {
+ player.awardStat(net.minecraft.stats.Stats.BLOCK_MINED.get(this));
+ player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED);
+ java.util.List<net.minecraft.world.item.ItemStack> dropList = Block.getDrops(state, (net.minecraft.server.level.ServerLevel) world, pos, blockEntity, player, itemInHand);
+
+ boolean planted = false;
+ for (net.minecraft.world.item.ItemStack itemToDrop : dropList) {
+ if (!planted && itemToDrop.getItem() == itemToReplant) {
+ world.setBlock(pos, defaultBlockState(), 3);
+ itemToDrop.setCount(itemToDrop.getCount() - 1);
+ planted = true;
+ }
+ Block.popResource(world, pos, itemToDrop);
+ }
+
+ state.spawnAfterBreak((net.minecraft.server.level.ServerLevel) world, pos, itemInHand, true);
+ }
+ // Purpur end - Ability for hoe to replant crops
}

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/level/block/WeepingVinesBlock.java
+++ b/net/minecraft/world/level/block/WeepingVinesBlock.java
@@ -34,4 +_,11 @@
protected boolean canGrowInto(BlockState state) {
return NetherVines.isValidGrowthState(state);
}
+
+ // Purpur start - weeping vines configurable max growth age
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.weepingVinesMaxGrowthAge;
+ }
+ // Purpur end - weeping vines configurable max growth age
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/WitherSkullBlock.java
+++ b/net/minecraft/world/level/block/WitherSkullBlock.java
@@ -69,6 +_,7 @@
);
witherBoss.yBodyRot = blockPatternMatch.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F;
witherBoss.makeInvulnerable();
+ witherBoss.setSummoner(blockState.getBlock().placer == null ? null : blockState.getBlock().placer.getUUID()); // Purpur - Summoner API
// CraftBukkit start
if (!level.addFreshEntity(witherBoss, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_WITHER)) {
return;

View File

@@ -0,0 +1,33 @@
--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
@@ -187,6 +_,21 @@
}
ItemStack itemStack = furnace.items.get(1);
+ // Purpur start - Furnace uses lava from underneath
+ boolean usedLavaFromUnderneath = false;
+ if (level.purpurConfig.furnaceUseLavaFromUnderneath && !furnace.isLit() && itemStack.isEmpty() && !furnace.items.get(0).isEmpty() && level.getGameTime() % 20 == 0) {
+ BlockPos below = furnace.getBlockPos().below();
+ BlockState belowState = level.getBlockStateIfLoaded(below);
+ if (belowState != null && belowState.is(Blocks.LAVA)) {
+ net.minecraft.world.level.material.FluidState fluidState = belowState.getFluidState();
+ if (fluidState != null && fluidState.isSource()) {
+ level.setBlock(below, Blocks.AIR.defaultBlockState(), 3);
+ itemStack = Items.LAVA_BUCKET.getDefaultInstance();
+ usedLavaFromUnderneath = true;
+ }
+ }
+ }
+ // Purpur end - Furnace uses lava from underneath
ItemStack itemStack1 = furnace.items.get(0);
boolean flag1 = !itemStack1.isEmpty();
boolean flag2 = !itemStack.isEmpty();
@@ -270,6 +_,8 @@
if (flag) {
setChanged(level, pos, state);
}
+
+ if (usedLavaFromUnderneath) furnace.items.set(1, ItemStack.EMPTY); // Purpur - Furnace uses lava from underneath
}
private static boolean canBurn(

View File

@@ -0,0 +1,44 @@
--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
@@ -138,6 +_,16 @@
public double getEffectRange() {
if (this.effectRange < 0) {
+ // Purpur start - Beacon Activation Range Configurable
+ if (this.level != null) {
+ switch (this.levels) {
+ case 1: return this.level.purpurConfig.beaconLevelOne;
+ case 2: return this.level.purpurConfig.beaconLevelTwo;
+ case 3: return this.level.purpurConfig.beaconLevelThree;
+ case 4: return this.level.purpurConfig.beaconLevelFour;
+ }
+ }
+ // Purpur end - Beacon Activation Range Configurable
return this.levels * 10 + 10;
} else {
return effectRange;
@@ -166,6 +_,7 @@
int y = pos.getY();
int z = pos.getZ();
BlockPos blockPos;
+ boolean isTintedGlass = false; // Purpur - allow beacon effects when covered by tinted glass
if (blockEntity.lastCheckY < y) {
blockPos = pos;
blockEntity.checkingBeamSections = Lists.newArrayList();
@@ -195,6 +_,7 @@
}
}
} else {
+ if (level.purpurConfig.beaconAllowEffectsWithTintedGlass && blockState.getBlock().equals(Blocks.TINTED_GLASS)) {isTintedGlass = true;} // Purpur - allow beacon effects when covered by tinted glass
if (section == null || blockState.getLightBlock() >= 15 && !blockState.is(Blocks.BEDROCK)) {
blockEntity.checkingBeamSections.clear();
blockEntity.lastCheckY = height;
@@ -214,7 +_,7 @@
blockEntity.levels = updateBase(level, x, y, z);
}
- if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) {
+ if (blockEntity.levels > 0 && (!blockEntity.beamSections.isEmpty() || (level.purpurConfig.beaconAllowEffectsWithTintedGlass && isTintedGlass))) { // Purpur - allow beacon effects when covered by tinted glass
applyEffects(level, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges
playSound(level, pos, SoundEvents.BEACON_AMBIENT);
}

View File

@@ -0,0 +1,56 @@
--- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
@@ -79,7 +_,7 @@
"leash",
"UUID"
);
- public static final int MAX_OCCUPANTS = 3;
+ public static final int MAX_OCCUPANTS = org.purpurmc.purpur.PurpurConfig.beeInsideBeeHive; // Purpur - Config to change max number of bees
private static final int MIN_TICKS_BEFORE_REENTERING_HIVE = 400;
private static final int MIN_OCCUPATION_TICKS_NECTAR = 2400;
public static final int MIN_OCCUPATION_TICKS_NECTARLESS = 600;
@@ -153,11 +_,33 @@
return list;
}
+ // Purpur start - Stored Bee API
+ public List<Entity> releaseBee(BlockState iblockdata, BeehiveBlockEntity.BeeData data, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) {
+ List<Entity> list = Lists.newArrayList();
+
+ BeehiveBlockEntity.releaseOccupant(this.level, this.worldPosition, iblockdata, data.occupant, list, tileentitybeehive_releasestatus, this.savedFlowerPos, force);
+
+ if (!list.isEmpty()) {
+ stored.remove(data);
+
+ super.setChanged();
+ }
+
+ return list;
+ }
+ // Purpur end - Stored Bee API
+
@VisibleForDebug
public int getOccupantCount() {
return this.stored.size();
}
+ // Purpur start - Stored Bee API
+ public List<BeeData> getStored() {
+ return stored;
+ }
+ // Purpur end - Stored Bee API
+
// Paper start - Add EntityBlockStorage clearEntities
public void clearBees() {
this.stored.clear();
@@ -398,8 +_,8 @@
registrar.register(DebugSubscriptions.BEE_HIVES, () -> DebugHiveInfo.pack(this));
}
- static class BeeData {
- private final BeehiveBlockEntity.Occupant occupant;
+ public static class BeeData { // Purpur - make public - Stored Bee API
+ public final BeehiveBlockEntity.Occupant occupant; // Purpur - make public - Stored Bee API
private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts
private int ticksInHive;

View File

@@ -0,0 +1,43 @@
--- a/net/minecraft/world/level/block/entity/BlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BlockEntity.java
@@ -104,6 +_,10 @@
input.read("PublicBukkitValues", CompoundTag.CODEC)
.ifPresent(this.persistentDataContainer::putAll);
// Paper end - read persistent data container
+
+
+ this.persistentLore = input.read("Purpur.persistentLore", net.minecraft.world.item.component.ItemLore.CODEC).orElse(null); // Purpur - Persistent BlockEntity Lore and DisplayName
+
}
public final void loadWithComponents(ValueInput input) {
@@ -116,6 +_,11 @@
}
protected void saveAdditional(ValueOutput output) {
+ // Purpur start - Persistent BlockEntity Lore and DisplayName
+ if (this.persistentLore != null) {
+ output.store("Purpur.persistentLore", net.minecraft.world.item.component.ItemLore.CODEC, this.persistentLore);
+ }
+ // Purpur end - Persistent BlockEntity Lore and DisplayName
}
public final CompoundTag saveWithFullMetadata(HolderLookup.Provider registries) {
@@ -400,4 +_,17 @@
return this.blockEntity.getNameForReporting() + "@" + this.blockEntity.getBlockPos();
}
}
+
+ // Purpur start - Persistent BlockEntity Lore and DisplayName
+ @Nullable
+ private net.minecraft.world.item.component.ItemLore persistentLore = null;
+
+ public void setPersistentLore(net.minecraft.world.item.component.ItemLore lore) {
+ this.persistentLore = lore;
+ }
+
+ public @org.jetbrains.annotations.Nullable net.minecraft.world.item.component.ItemLore getPersistentLore() {
+ return this.persistentLore;
+ }
+ // Purpur end - Persistent BlockEntity Lore and DisplayName
}

View File

@@ -0,0 +1,66 @@
--- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
@@ -150,7 +_,7 @@
BlockPos blockPos1 = pos.offset(i, i1, i2x);
BlockState blockState = level.getBlockState(blockPos1);
- for (Block block : VALID_BLOCKS) {
+ for (Block block : level.purpurConfig.conduitBlocks) { // Purpur - Conduit behavior configuration
if (blockState.is(block)) {
positions.add(blockPos1);
}
@@ -165,13 +_,13 @@
private static void applyEffects(Level level, BlockPos pos, List<BlockPos> positions) {
// CraftBukkit start
- ConduitBlockEntity.applyEffects(level, pos, ConduitBlockEntity.getRange(positions));
+ ConduitBlockEntity.applyEffects(level, pos, ConduitBlockEntity.getRange(positions, level)); // Purpur - Conduit behavior configuration
}
- public static int getRange(List<BlockPos> positions) {
+ public static int getRange(List<BlockPos> positions, Level level) { // Purpur - Conduit behavior configuration
// CraftBukkit end
int size = positions.size();
- int i = size / 7 * 16;
+ int i = size / 7 * level.purpurConfig.conduitDistance; // Purpur - Conduit behavior configuration
// CraftBukkit start
return i;
}
@@ -201,7 +_,7 @@
EntityReference<LivingEntity> entityReference = updateDestroyTarget(blockEntity.destroyTarget, level, pos, canDestroy);
LivingEntity livingEntity = EntityReference.getLivingEntity(entityReference, level);
if (damageTarget && livingEntity != null) { // CraftBukkit
- if (livingEntity.hurtServer(level, level.damageSources().magic().eventBlockDamager(level, pos), 4.0F)) // CraftBukkit - move up
+ if (livingEntity.hurtServer(level, level.damageSources().magic().eventBlockDamager(level, pos), level.purpurConfig.conduitDamageAmount)) // CraftBukkit - move up // Purpur - Conduit behavior configuration
level.playSound(
null, livingEntity.getX(), livingEntity.getY(), livingEntity.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F
);
@@ -222,19 +_,25 @@
return selectNewTarget(level, pos);
} else {
LivingEntity livingEntity = EntityReference.getLivingEntity(destroyTarget, level);
- return livingEntity != null && livingEntity.isAlive() && pos.closerThan(livingEntity.blockPosition(), 8.0) ? destroyTarget : null;
+ return livingEntity != null && livingEntity.isAlive() && pos.closerThan(livingEntity.blockPosition(), level.purpurConfig.conduitDamageDistance) ? destroyTarget : null; // Purpur - Conduit behavior configuration
}
}
private static @Nullable EntityReference<LivingEntity> selectNewTarget(ServerLevel level, BlockPos pos) {
List<LivingEntity> entitiesOfClass = level.getEntitiesOfClass(
- LivingEntity.class, getDestroyRangeAABB(pos), livingEntity -> livingEntity instanceof Enemy && livingEntity.isInWaterOrRain()
+ LivingEntity.class, getDestroyRangeAABB(pos, level), livingEntity -> livingEntity instanceof Enemy && livingEntity.isInWaterOrRain() // Purpur - Conduit behavior configuration
);
return entitiesOfClass.isEmpty() ? null : EntityReference.of(Util.getRandom(entitiesOfClass, level.random));
}
public static AABB getDestroyRangeAABB(BlockPos pos) {
- return new AABB(pos).inflate(8.0);
+ // Purpur start - Conduit behavior configuration
+ return getDestroyRangeAABB(pos, null);
+ }
+
+ private static AABB getDestroyRangeAABB(BlockPos pos, Level level) {
+ // Purpur end - Conduit behavior configuration
+ return new AABB(pos).inflate(level == null ? 8.0 : level.purpurConfig.conduitDamageDistance); // Purpur - Conduit behavior configuration
}
private static void animationTick(Level level, BlockPos pos, List<BlockPos> positions, @Nullable Entity entity, int tickCount) {

View File

@@ -0,0 +1,48 @@
--- a/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java
@@ -30,6 +_,7 @@
public float tRot;
private static final RandomSource RANDOM = RandomSource.create();
private @Nullable Component name;
+ private int lapis = 0; // Purpur - Enchantment Table Persists Lapis
public EnchantingTableBlockEntity(BlockPos pos, BlockState blockState) {
super(BlockEntityType.ENCHANTING_TABLE, pos, blockState);
@@ -39,12 +_,14 @@
protected void saveAdditional(ValueOutput output) {
super.saveAdditional(output);
output.storeNullable("CustomName", ComponentSerialization.CODEC, this.name);
+ output.putInt("Purpur.Lapis", this.lapis); // Purpur - Enchantment Table Persists Lapis
}
@Override
protected void loadAdditional(ValueInput input) {
super.loadAdditional(input);
this.name = parseCustomNameSafe(input, "CustomName");
+ this.lapis = input.getIntOr("Purpur.Lapis", 0); // Purpur - Enchantment Table Persists Lapis
}
public static void bookAnimationTick(Level level, BlockPos pos, BlockState state, EnchantingTableBlockEntity enchantingTable) {
@@ -135,4 +_,22 @@
public void removeComponentsFromTag(ValueOutput output) {
output.discard("CustomName");
}
+
+ // Purpur start - Enchantment Table Persists Lapis
+ public int getLapis() {
+ return this.lapis;
+ }
+
+ public void setLapis(int lapis) {
+ this.lapis = lapis;
+ }
+
+ @Override
+ public void preRemoveSideEffects(BlockPos pos, BlockState state) {
+ super.preRemoveSideEffects(pos, state);
+ if (this.level != null && this.level.purpurConfig.enchantmentTableLapisPersists) {
+ net.minecraft.world.Containers.dropItemStack(this.level, pos.getX(), pos.getY(), pos.getZ(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.LAPIS_LAZULI, this.getLapis()));
+ }
+ }
+ // Purpur end - Enchantment Table Persists Lapis
}

View File

@@ -0,0 +1,65 @@
--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java
@@ -149,16 +_,32 @@
return this.setText(updater.apply(text), isFrontText);
}
+ // Purpur start - Signs allow color codes
+ private Component translateColors(org.bukkit.entity.Player player, String line, Style style) {
+ if (level.purpurConfig.signAllowColors) {
+ if (player.hasPermission("purpur.sign.color")) line = line.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1");
+ if (player.hasPermission("purpur.sign.style")) line = line.replaceAll("(?i)&([l-or])", "\u00a7$1");
+ if (player.hasPermission("purpur.sign.magic")) line = line.replaceAll("(?i)&([kr])", "\u00a7$1");
+
+ return io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(line));
+ } else {
+ return Component.literal(line).setStyle(style);
+ }
+ }
+ // Purpur end - Signs allow color codes
+
private SignText setMessages(Player player, List<FilteredText> filteredText, SignText text, boolean front) { // CraftBukkit
SignText originalText = text; // CraftBukkit
for (int i = 0; i < filteredText.size(); i++) {
FilteredText filteredText1 = filteredText.get(i);
Style style = text.getMessage(i, player.isTextFilteringEnabled()).getStyle();
+
+ org.bukkit.entity.Player craftPlayer = (org.bukkit.craftbukkit.entity.CraftPlayer) player.getBukkitEntity(); // Purpur - Signs allow color codes
if (player.isTextFilteringEnabled()) {
- text = text.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style)); // Paper - filter sign text to chat only
+ text = text.setMessage(i, translateColors(craftPlayer, net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty()), style)); // Paper - filter sign text to chat only // Purpur - Signs allow color codes
} else {
text = text.setMessage(
- i, Component.literal(filteredText1.raw()).setStyle(style), Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style) // Paper - filter sign text to chat only
+ i, translateColors(craftPlayer, net.minecraft.util.StringUtil.filterText(filteredText1.raw()), style), translateColors(craftPlayer, net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty()), style) // Paper - filter sign text to chat only // Purpur - Signs allow color codes
);
}
}
@@ -308,6 +_,27 @@
return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, level, LevelBasedPermissionSet.GAMEMASTER, string, component, level.getServer(), player
); // Paper - Fix commands from signs not firing command events
}
+
+ // Purpur start - Signs allow color codes
+ public ClientboundBlockEntityDataPacket getTranslatedUpdatePacket(boolean filtered, boolean front) {
+ try (net.minecraft.util.ProblemReporter.ScopedCollector scopedCollector = new net.minecraft.util.ProblemReporter.ScopedCollector(this.problemPath(), LOGGER)) {
+ net.minecraft.world.level.storage.TagValueOutput tagValueOutput = net.minecraft.world.level.storage.TagValueOutput.createWithContext(scopedCollector, this.getLevel().registryAccess());
+ this.saveAdditional(tagValueOutput);
+
+ final Component[] lines = front ? frontText.getMessages(filtered) : backText.getMessages(filtered);
+ final String side = front ? "front_text" : "back_text";
+ net.minecraft.world.level.storage.ValueOutput sideNbt = tagValueOutput.child(side);
+ net.minecraft.world.level.storage.ValueOutput.TypedOutputList<String> messagesNbt = sideNbt.list("messages", com.mojang.serialization.Codec.STRING);
+ for (int i = 0; i < 4; i++) {
+ final net.kyori.adventure.text.Component component = io.papermc.paper.adventure.PaperAdventure.asAdventure(lines[i]);
+ final String line = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(component);
+ messagesNbt.add(line);
+ }
+ tagValueOutput.putString("PurpurEditor", "true");
+ return ClientboundBlockEntityDataPacket.create(this, (blockEntity, registryAccess) -> tagValueOutput.buildResult());
+ }
+ }
+ // Purpur end - Signs allow color codes
@Override
public ClientboundBlockEntityDataPacket getUpdatePacket() {

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/world/level/block/piston/PistonStructureResolver.java
+++ b/net/minecraft/world/level/block/piston/PistonStructureResolver.java
@@ -81,7 +_,7 @@
return true;
} else {
int i = 1;
- if (i + this.toPush.size() > 12) {
+ if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit
return false;
} else {
while (isSticky(blockState)) {
@@ -95,7 +_,7 @@
break;
}
- if (++i + this.toPush.size() > 12) {
+ if (++i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit
return false;
}
}
@@ -140,7 +_,7 @@
return true;
}
- if (this.toPush.size() >= 12) {
+ if (this.toPush.size() >= this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit
return false;
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/chunk/storage/EntityStorage.java
+++ b/net/minecraft/world/level/chunk/storage/EntityStorage.java
@@ -108,6 +_,7 @@
}
// Paper end - Entity load/save limit per chunk
TagValueOutput tagValueOutput = TagValueOutput.createWithContext(scopedCollector.forChild(entity.problemPath()), entity.registryAccess());
+ if (!entity.canSaveToDisk()) return; // Purpur - Add canSaveToDisk to Entity
if (entity.save(tagValueOutput)) {
CompoundTag compoundTag1 = tagValueOutput.buildResult();
listTag.add(compoundTag1);

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -309,7 +_,7 @@
// Paper start
private static void printOversizedLog(String msg, Path file, int x, int z) {
- org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed.");
+ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PURPUR - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); // Purpur - Rebrand
}
private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException {

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/levelgen/DensityFunctions.java
+++ b/net/minecraft/world/level/levelgen/DensityFunctions.java
@@ -534,7 +_,7 @@
int i1 = z / 2;
int i2 = x % 2;
int i3 = z % 2;
- float f = 100.0F - Mth.sqrt((long)x * (long)x + (long)z * (long)z) * 8.0F; // Paper - cast ints to long to avoid integer overflow
+ float f = 100.0F - Mth.sqrt(org.purpurmc.purpur.PurpurConfig.generateEndVoidRings ? x * x + z * z : (long)x * (long)x + (long)z * (long)z) * 8.0F; // Paper - cast ints to long to avoid integer overflow // Purpur - Setting to reintroduce end void rings
f = Mth.clamp(f, -100.0F, 80.0F);
NoiseCache cache = noiseCache.get().computeIfAbsent(noise, noiseKey -> new NoiseCache()); // Paper - Perf: Optimize end generation

View File

@@ -0,0 +1,28 @@
--- a/net/minecraft/world/level/levelgen/PhantomSpawner.java
+++ b/net/minecraft/world/level/levelgen/PhantomSpawner.java
@@ -38,13 +_,13 @@
int spawnAttemptMaxSeconds = level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds;
this.nextTick += (spawnAttemptMinSeconds + randomSource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20;
// Paper end - Ability to control player's insomnia and phantoms
- if (level.getSkyDarken() >= 5 || !level.dimensionType().hasSkyLight()) {
+ if (level.getSkyDarken() >= level.purpurConfig.phantomSpawnMinSkyDarkness || !level.dimensionType().hasSkyLight()) { // Purpur - Add phantom spawning options
for (ServerPlayer serverPlayer : level.players()) {
if (!serverPlayer.isSpectator() && (!level.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !serverPlayer.isCreative())) { // Paper - Add phantom creative and insomniac controls
BlockPos blockPos = serverPlayer.blockPosition();
- if (!level.dimensionType().hasSkyLight() || blockPos.getY() >= level.getSeaLevel() && level.canSeeSky(blockPos)) {
+ if (!level.dimensionType().hasSkyLight() || (!level.purpurConfig.phantomSpawnOnlyAboveSeaLevel || blockPos.getY() >= level.getSeaLevel()) && (!level.purpurConfig.phantomSpawnOnlyWithVisibleSky || level.canSeeSky(blockPos))) { // Purpur - Add phantom spawning options
DifficultyInstance currentDifficultyAt = level.getCurrentDifficultyAt(blockPos);
- if (currentDifficultyAt.isHarderThan(randomSource.nextFloat() * 3.0F)) {
+ if (currentDifficultyAt.isHarderThan(randomSource.nextFloat() * (float) level.purpurConfig.phantomSpawnLocalDifficultyChance)) { // Purpur - Add phantom spawning options
ServerStatsCounter stats = serverPlayer.getStats();
int i = Mth.clamp(stats.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
int i1 = 24000;
@@ -56,7 +_,7 @@
FluidState fluidState = level.getFluidState(blockPos1);
if (NaturalSpawner.isValidEmptySpawnBlock(level, blockPos1, blockState, fluidState, EntityType.PHANTOM)) {
SpawnGroupData spawnGroupData = null;
- int i2 = 1 + randomSource.nextInt(currentDifficultyAt.getDifficulty().getId() + 1);
+ int i2 = level.purpurConfig.phantomSpawnMinPerAttempt + randomSource.nextInt((level.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? currentDifficultyAt.getDifficulty().getId() : level.purpurConfig.phantomSpawnMaxPerAttempt - level.purpurConfig.phantomSpawnMinPerAttempt) + 1); // Purpur - Add phantom spawning options
for (int i3 = 0; i3 < i2; i3++) {
// Paper start - PhantomPreSpawnEvent

View File

@@ -0,0 +1,24 @@
--- a/net/minecraft/world/level/material/FlowingFluid.java
+++ b/net/minecraft/world/level/material/FlowingFluid.java
@@ -233,7 +_,7 @@
}
}
- if (i1 >= 2 && this.canConvertToSource(level)) {
+ if (i1 >= this.getRequiredSources(level) && this.canConvertToSource(level)) { // Purpur - Implement infinite liquids
BlockState blockState1 = level.getBlockState(mutableBlockPos.setWithOffset(pos, Direction.DOWN));
FluidState fluidState1 = blockState1.getFluidState();
if (blockState1.isSolid() || this.isSourceBlockOfThisType(fluidState1)) {
@@ -320,6 +_,12 @@
}
protected abstract boolean canConvertToSource(ServerLevel level);
+
+ // Purpur start - Implement infinite liquids
+ protected int getRequiredSources(Level level) {
+ return 2;
+ }
+ // Purpur end - Implement infinite liquids
protected void spreadTo(LevelAccessor level, BlockPos pos, BlockState state, Direction direction, FluidState fluidState) {
if (state.getBlock() instanceof LiquidBlockContainer liquidBlockContainer) {

View File

@@ -0,0 +1,25 @@
--- a/net/minecraft/world/level/material/LavaFluid.java
+++ b/net/minecraft/world/level/material/LavaFluid.java
@@ -190,7 +_,7 @@
@Override
public int getTickDelay(LevelReader level) {
- return isFastLava(level) ? 10 : 30;
+ return isFastLava(level) ? level.getWorldBorder().world.purpurConfig.lavaSpeedNether : level.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur - Make lava flow speed configurable
}
@Override
@@ -211,6 +_,13 @@
private void fizz(LevelAccessor level, BlockPos pos) {
level.levelEvent(LevelEvent.LAVA_FIZZ, pos, 0);
}
+
+ // Purpur start - Implement infinite liquids
+ @Override
+ protected int getRequiredSources(Level level) {
+ return level.purpurConfig.lavaInfiniteRequiredSources;
+ }
+ // Purpur end - Implement infinite liquids
@Override
protected boolean canConvertToSource(ServerLevel level) {

View File

@@ -0,0 +1,16 @@
--- a/net/minecraft/world/level/material/WaterFluid.java
+++ b/net/minecraft/world/level/material/WaterFluid.java
@@ -77,6 +_,13 @@
return level.getGameRules().get(GameRules.WATER_SOURCE_CONVERSION);
}
+ // Purpur start - Implement infinite liquids
+ @Override
+ protected int getRequiredSources(Level level) {
+ return level.purpurConfig.waterInfiniteRequiredSources;
+ }
+ // Purpur end - Implement infinite liquids
+
// Paper start - Add BlockBreakBlockEvent
@Override
protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state, BlockPos source) {

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
+++ b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
@@ -239,7 +_,7 @@
if ((node == null || node.costMalus < 0.0F)
&& verticalDeltaLimit > 0
&& (cachedPathType != PathType.FENCE || this.canWalkOverFences())
- && cachedPathType != PathType.UNPASSABLE_RAIL
+ && (this.mob.level().purpurConfig.mobsIgnoreRails || cachedPathType != PathType.UNPASSABLE_RAIL) // Purpur - Config to allow mobs to pathfind over rails
&& cachedPathType != PathType.TRAPDOOR
&& cachedPathType != PathType.POWDER_SNOW) {
node = this.tryJumpOn(x, y, z, verticalDeltaLimit, nodeFloorLevel, direction, pathType, mutableBlockPos);
@@ -490,7 +_,7 @@
return PathType.TRAPDOOR;
} else if (blockState.is(Blocks.POWDER_SNOW)) {
return PathType.POWDER_SNOW;
- } else if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH)) {
+ } else if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH) || blockState.is(Blocks.STONECUTTER)) { // Purpur - Stonecutter damage
return PathType.DAMAGE_OTHER;
} else if (blockState.is(Blocks.HONEY_BLOCK)) {
return PathType.STICKY_HONEY;

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/portal/PortalShape.java
+++ b/net/minecraft/world/level/portal/PortalShape.java
@@ -29,7 +_,7 @@
public static final int MAX_WIDTH = 21;
private static final int MIN_HEIGHT = 3;
public static final int MAX_HEIGHT = 21;
- private static final BlockBehaviour.StatePredicate FRAME = (state, level, pos) -> state.is(Blocks.OBSIDIAN);
+ private static final BlockBehaviour.StatePredicate FRAME = (state, level, pos) -> state.is(Blocks.OBSIDIAN) || (org.purpurmc.purpur.PurpurConfig.cryingObsidianValidForPortalFrame && state.is(Blocks.CRYING_OBSIDIAN)); // Purpur - Crying obsidian valid for portal frames
private static final float SAFE_TRAVEL_MAX_ENTITY_XY = 4.0F;
private static final double SAFE_TRAVEL_MAX_VERTICAL_DELTA = 1.0;
private final Direction.Axis axis;

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
@@ -81,6 +_,7 @@
public final Map<String, MapDecoration> decorations = Maps.newLinkedHashMap();
private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
private int trackedDecorationCount;
+ public boolean isExplorerMap; // Purpur - Explorer Map API
// CraftBukkit start
public final org.bukkit.craftbukkit.map.CraftMapView mapView;

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java
+++ b/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java
@@ -66,6 +_,11 @@
Entity entity = context.getOptionalParameter(LootContextParams.ATTACKING_ENTITY);
if (entity instanceof LivingEntity livingEntity) {
int enchantmentLevel = EnchantmentHelper.getEnchantmentLevel(this.enchantment, livingEntity);
+ // Purpur start - Add an option to fix MC-3304 projectile looting
+ if (org.purpurmc.purpur.PurpurConfig.fixProjectileLootingTransfer && context.getOptionalParameter(LootContextParams.DIRECT_ATTACKING_ENTITY) instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) {
+ enchantmentLevel = arrow.actualEnchantments.getLevel(this.enchantment);
+ }
+ // Purpur end - Add an option to fix MC-3304 projectile looting
if (enchantmentLevel == 0) {
return stack;
}