99/103 rejected minecraft source files applied

This commit is contained in:
granny
2026-03-13 17:54:48 -07:00
parent 1f72458912
commit 2df686555d
83 changed files with 1079 additions and 1548 deletions

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
+++ b/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
@@ -29,6 +_,7 @@
@Override
public double sanitizeValue(final double value) {
+ if (!org.purpurmc.purpur.PurpurConfig.clampAttributes) return Double.isNaN(value) ? this.minValue : value; // Purpur - Add attribute clamping and armor limit config
return Double.isNaN(value) ? this.minValue : Mth.clamp(value, this.minValue, this.maxValue);
}
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -89,7 +_,7 @@
}
};
Set<Pair<Holder<PoiType>, BlockPos>> poiPositions = poiManager.findAllClosestFirstWithType(
- poiType, cacheTest, body.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE
+ poiType, cacheTest, body.blockPosition(), level.purpurConfig.villagerAcquirePoiSearchRadius, PoiManager.Occupancy.HAS_SPACE // Purpur - Configurable villager search radius
)
.limit(5L)
.filter(px -> validPoi.test(level, (BlockPos)px.getSecond()))

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
+++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
@@ -53,7 +_,7 @@
Node toNode = path.getNextNode();
BlockPos fromPos = fromNode.asBlockPos();
BlockState fromState = level.getBlockState(fromPos);
- if (fromState.is(BlockTags.MOB_INTERACTABLE_DOORS, s -> s.getBlock() instanceof DoorBlock)) {
+ if (fromState.is(BlockTags.MOB_INTERACTABLE_DOORS, s -> s.getBlock() instanceof DoorBlock)&& !DoorBlock.requiresRedstone(entity.level(), blockState, blockPos)) { // Purpur - Option to make doors require redstone
DoorBlock fromBlock = (DoorBlock)fromState.getBlock();
if (!fromBlock.isOpen(fromState)) {
// CraftBukkit start - entities opening doors
@@ -70,7 +_,7 @@
BlockPos toPos = toNode.asBlockPos();
BlockState toState = level.getBlockState(toPos);
- if (toState.is(BlockTags.MOB_INTERACTABLE_DOORS, s -> s.getBlock() instanceof DoorBlock)) {
+ if (toState.is(BlockTags.MOB_INTERACTABLE_DOORS, s -> s.getBlock() instanceof DoorBlock) && !DoorBlock.requiresRedstone(entity.level(), blockState1, blockPos1)) { // Purpur - Option to make doors require redstone
DoorBlock door = (DoorBlock)toState.getBlock();
if (!door.isOpen(toState)) {
// CraftBukkit start - entities opening doors
@@ -117,7 +_,7 @@
iterator.remove();
} else {
BlockState state = level.getBlockState(doorPos);
- if (!state.is(BlockTags.MOB_INTERACTABLE_DOORS, s -> s.getBlock() instanceof DoorBlock)) {
+ if (!state.is(BlockTags.MOB_INTERACTABLE_DOORS, s -> s.getBlock() instanceof DoorBlock) || DoorBlock.requiresRedstone(entity.level(), blockState, blockPos)) { // Purpur - Option to make doors require redstone
iterator.remove();
} else {
DoorBlock block = (DoorBlock)state.getBlock();

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
+++ b/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
@@ -41,6 +_,7 @@
@Override
public boolean canStillUse(final ServerLevel level, final Villager body, final long timestamp) {
+ if (!body.level().purpurConfig.villagerDisplayTradeItem) return false; // Purpur - Option for villager display trade item
return this.checkExtraStartConditions(level, body) && this.lookTime > 0 && body.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).isPresent();
}

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/world/entity/ai/behavior/SleepInBed.java
+++ b/net/minecraft/world/entity/ai/behavior/SleepInBed.java
@@ -89,7 +_,14 @@
InteractWithDoor.closeDoorsThatIHaveOpenedOrPassedThrough(level, body, null, null, doors, nearestEntities);
}
- body.startSleeping(body.getBrain().getMemory(MemoryModuleType.HOME).get().pos());
+ // Purpur start - Option for beds to explode on villager sleep
+ net.minecraft.core.BlockPos bedPosition = body.getBrain().getMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.HOME).get().pos();
+ if (level.purpurConfig.bedExplodeOnVillagerSleep && body.is(net.minecraft.world.entity.EntityType.VILLAGER) && level.getBlockState(bedPosition).getBlock() instanceof net.minecraft.world.level.block.BedBlock) {
+ level.explode(null, (double) bedPosition.getX() + 0.5D, (double) bedPosition.getY() + 0.5D, (double) bedPosition.getZ() + 0.5D, (float) level.purpurConfig.bedExplosionPower, level.purpurConfig.bedExplosionFire, level.purpurConfig.bedExplosionEffect);
+ return;
+ }
+ body.startSleeping(bedPosition);
+ // Purpur end - Option for beds to explode on villager sleep
brain.setMemory(MemoryModuleType.LAST_SLEPT, timestamp);
brain.eraseMemory(MemoryModuleType.WALK_TARGET);
brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);

View File

@@ -0,0 +1,54 @@
--- a/net/minecraft/world/entity/ai/behavior/TransportItemsBetweenContainers.java
+++ b/net/minecraft/world/entity/ai/behavior/TransportItemsBetweenContainers.java
@@ -286,7 +_,7 @@
LevelChunk levelChunk = level.getChunkSource().getChunkNow(chunkPos.x(), chunkPos.z());
if (levelChunk != null) {
for (BlockEntity potentialTarget : levelChunk.getBlockEntities().values()) {
- if (potentialTarget instanceof ChestBlockEntity chestBlockEntity) {
+ if (potentialTarget instanceof net.minecraft.world.level.block.entity.BaseContainerBlockEntity chestBlockEntity) { // Purpur - copper golem can place items in barrels or shulkers option
double distance = chestBlockEntity.getBlockPos().distToCenterSqr(body.position());
if (distance < closestDistance) {
TransportItemsBetweenContainers.TransportItemTarget targetValidToPick = this.isTargetValidToPick(
@@ -375,7 +_,11 @@
}
private boolean isTargetBlocked(final Level level, final TransportItemsBetweenContainers.TransportItemTarget target) {
- return ChestBlock.isChestBlockedAt(level, target.pos);
+ // Purpur start - copper golem can place items in barrels or shulkers option
+ boolean isBarrelBlocked = !level.purpurConfig.copperGolemCanOpenBarrel && target.state.is(net.minecraft.world.level.block.Blocks.BARREL);
+ boolean isShulkerBlocked = !level.purpurConfig.copperGolemCanOpenShulker && target.blockEntity instanceof net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity shulkerBoxBlockEntity && !net.minecraft.world.level.block.ShulkerBoxBlock.canOpen(target.state, level, target.pos, shulkerBoxBlockEntity);
+ return target.state.is(net.minecraft.world.level.block.Blocks.BARREL) ? isBarrelBlocked : isShulkerBlocked || net.minecraft.world.level.block.ChestBlock.isChestBlockedAt(level, target.pos);
+ // Purpur end - copper golem can place items in barrels or shulkers option
}
private boolean targetHasNotChanged(final Level level, final TransportItemsBetweenContainers.TransportItemTarget target) {
@@ -455,7 +_,7 @@
}
private boolean isWantedBlock(final PathfinderMob mob, final BlockState block) {
- return isPickingUpItems(mob) ? this.sourceBlockType.test(block) : this.destinationBlockType.test(block);
+ return isPickingUpItems(mob) ? this.sourceBlockType.test(block) : (mob.level().purpurConfig.copperGolemCanOpenBarrel && state.is(net.minecraft.world.level.block.Blocks.BARREL)) || (mob.level().purpurConfig.copperGolemCanOpenShulker && state.is(net.minecraft.tags.BlockTags.SHULKER_BOXES)) || this.destinationBlockType.test(block); // Purpur - copper golem can place items in barrels or shulkers option
}
private static double getInteractionRange(final PathfinderMob body) {
@@ -507,6 +_,11 @@
}
private static boolean matchesLeavingItemsRequirement(final PathfinderMob body, final Container container) {
+ // Purpur start - copper golem can place items in barrels or shulkers option
+ if (body.level().purpurConfig.copperGolemCanOpenShulker && container instanceof net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity && body.getMainHandItem().is(net.minecraft.tags.ItemTags.SHULKER_BOXES)) {
+ return false;
+ }
+ // Purpur end - copper golem can place items in barrels or shulkers option
return container.isEmpty() || hasItemMatchingHandItem(body, container);
}
@@ -544,7 +_,7 @@
int slot = 0;
for (ItemStack itemStack : container) {
- if (!itemStack.isEmpty()) {
+ if (!itemStack.isEmpty() && (!(container instanceof net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity) || !itemStack.is(net.minecraft.tags.ItemTags.SHULKER_BOXES))) { // Purpur - copper golem can place items in barrels or shulkers option
int itemCount = Math.min(itemStack.getCount(), 16);
return container.removeItem(slot, itemCount);
}

View File

@@ -0,0 +1,17 @@
--- a/net/minecraft/world/entity/ai/goal/SwellGoal.java
+++ b/net/minecraft/world/entity/ai/goal/SwellGoal.java
@@ -46,6 +_,14 @@
this.creeper.setSwellDir(-1);
} else {
this.creeper.setSwellDir(1);
+ // Purpur start - option to allow creeper to encircle target when fusing
+ if (this.creeper.level().purpurConfig.creeperEncircleTarget) {
+ net.minecraft.world.phys.Vec3 relative = this.creeper.position().subtract(this.target.position());
+ relative = relative.yRot((float) Math.PI / 3).normalize().multiply(2, 2, 2);
+ net.minecraft.world.phys.Vec3 destination = this.target.position().add(relative);
+ this.creeper.getNavigation().moveTo(destination.x, destination.y, destination.z, 1);
+ }
+ // Purpur end - option to allow creeper to encircle target when fusing
}
} else {
this.creeper.setSwellDir(-1);

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
@@ -54,9 +_,9 @@
}
};
Set<Pair<Holder<PoiType>, BlockPos>> pois = poiManager.findAllWithType(
- e -> e.is(PoiTypes.HOME), cacheTest, body.blockPosition(), 48, PoiManager.Occupancy.ANY
+ e -> e.is(PoiTypes.HOME), cacheTest, body.blockPosition(), level.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY
)
- .collect(Collectors.toSet());
+ .collect(Collectors.toSet()); // Purpur - Configurable villager search radius
Path path = AcquirePoi.findPathToPois(body, pois);
if (path != null && path.canReach()) {
BlockPos targetPos = path.getTarget();

View File

@@ -0,0 +1,34 @@
--- a/net/minecraft/world/entity/animal/Animal.java
+++ b/net/minecraft/world/entity/animal/Animal.java
@@ -146,7 +_,7 @@
ItemStack itemStack = player.getItemInHand(hand);
if (this.isFood(itemStack)) {
int age = this.getAge();
- if (player instanceof ServerPlayer serverPlayer && age == 0 && this.canFallInLove()) {
+ if (player instanceof ServerPlayer serverPlayer && age == 0 && this.canFallInLove() && (this.level().purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.level().hasBreedingCooldown(player.getUUID(), this.getClass()))) { // Purpur - Add adjustable breeding cooldown to config
final ItemStack breedCopy = itemStack.copy(); // Paper - Fix EntityBreedEvent copying
this.usePlayerItem(player, hand, itemStack);
this.setInLove(serverPlayer, breedCopy); // Paper - Fix EntityBreedEvent copying
@@ -227,10 +_,20 @@
public void spawnChildFromBreeding(final ServerLevel level, final Animal partner) {
AgeableMob offspring = this.getBreedOffspring(level, partner);
if (offspring != null) {
- offspring.setBaby(true);
- offspring.snapTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+ //offspring.setBaby(true); // Purpur - Add adjustable breeding cooldown to config - moved down
+ //offspring.snapTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); // Purpur - Add adjustable breeding cooldown to config - moved down
// CraftBukkit start - Call EntityBreedEvent
ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> Optional.ofNullable(partner.getLoveCause())).orElse(null);
+ // Purpur start - Add adjustable breeding cooldown to config
+ if (breeder != null && level.purpurConfig.animalBreedingCooldownSeconds > 0) {
+ if (level.hasBreedingCooldown(breeder.getUUID(), this.getClass())) {
+ return;
+ }
+ level.addBreedingCooldown(breeder.getUUID(), this.getClass());
+ }
+ offspring.setBaby(true);
+ offspring.snapTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+ // Purpur end - Add adjustable breeding cooldown to config
int experience = this.getRandom().nextInt(7) + 1;
org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(offspring, this, partner, breeder, this.breedItem, experience);
if (entityBreedEvent.isCancelled()) {

View File

@@ -0,0 +1,73 @@
--- a/net/minecraft/world/entity/animal/bee/Bee.java
+++ b/net/minecraft/world/entity/animal/bee/Bee.java
@@ -172,7 +_,7 @@
// Paper end - Fix MC-167279
this.lookControl = new Bee.BeeLookControl(this);
this.setPathfindingMalus(PathType.FIRE_IN_NEIGHBOR, -1.0F);
- this.setPathfindingMalus(PathType.WATER, -1.0F);
+ if (this.level().purpurConfig.beeCanInstantlyStartDrowning) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - bee can instantly start drowning in water option
this.setPathfindingMalus(PathType.WATER_BORDER, 16.0F);
this.setPathfindingMalus(PathType.COCOA, -1.0F);
this.setPathfindingMalus(PathType.FENCE, -1.0F);
@@ -363,13 +_,19 @@
if (this.stayOutOfHiveCountdown <= 0 && !this.beePollinateGoal.isPollinating() && !this.hasStung() && this.getTarget() == null) {
boolean wantsToEnterHive = this.hasNectar()
|| this.isTiredOfLookingForNectar()
- || this.level().environmentAttributes().getValue(EnvironmentAttributes.BEES_STAY_IN_HIVE, this.position());
+ || this.level().environmentAttributes().getValue(EnvironmentAttributes.BEES_STAY_IN_HIVE, this.position()) || isNightOrRaining(this.level()); // Purpur - Bee can work when raining or at night
return wantsToEnterHive && !this.isHiveNearFire();
} else {
return false;
}
}
+ // Purpur start - Bee can work when raining or at night
+ public static boolean isNightOrRaining(Level level) {
+ return level.dimensionType().hasSkyLight() && (level.isDarkOutside() && !level.purpurConfig.beeCanWorkAtNight || level.isRaining() && !level.purpurConfig.beeCanWorkInRain); // Purpur - Bee can work when raining or at night
+ }
+ // Purpur end - Bee can work when raining or at night
+
public void setStayOutOfHiveCountdown(final int ticks) {
this.stayOutOfHiveCountdown = ticks;
}
@@ -390,7 +_,7 @@
@Override
protected void customServerAiStep(final ServerLevel level) {
boolean hasStung = this.hasStung();
- if (this.isInWater()) {
+ if (this.level().purpurConfig.beeCanInstantlyStartDrowning && this.isInWater()) { // Purpur - bee can instantly start drowning in water option
this.underWaterTicks++;
} else {
this.underWaterTicks = 0;
@@ -400,6 +_,7 @@
this.hurtServer(level, this.damageSources().drown(), 1.0F);
}
+ if (hasStung && !this.level().purpurConfig.beeDiesAfterSting) setHasStung(false); else // Purpur - Stop bees from dying after stinging
if (hasStung) {
this.timeSinceSting++;
if (this.timeSinceSting % 5 == 0 && this.random.nextInt(Mth.clamp(1200 - this.timeSinceSting, 1, 1200)) == 0) {
@@ -1168,6 +_,7 @@
Bee.this.savedFlowerPos = nearbyPos.get();
Bee.this.navigation
.moveTo(Bee.this.savedFlowerPos.getX() + 0.5, Bee.this.savedFlowerPos.getY() + 0.5, Bee.this.savedFlowerPos.getZ() + 0.5, 1.2F);
+ new org.purpurmc.purpur.event.entity.BeeFoundFlowerEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftLocation.toBukkit(Bee.this.savedFlowerPos, Bee.this.level())).callEvent(); // Purpur - Bee API
return true;
} else {
Bee.this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(Bee.this.random, 20, 60);
@@ -1214,6 +_,7 @@
this.pollinating = false;
Bee.this.navigation.stop();
Bee.this.remainingCooldownBeforeLocatingNewFlower = 200;
+ new org.purpurmc.purpur.event.entity.BeeStopPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), Bee.this.savedFlowerPos == null ? null : org.bukkit.craftbukkit.util.CraftLocation.toBukkit(Bee.this.savedFlowerPos, Bee.this.level()), Bee.this.hasNectar()).callEvent(); // Purpur - Bee API
}
@Override
@@ -1260,6 +_,7 @@
this.setWantedPos();
}
+ if (this.successfulPollinatingTicks == 0) new org.purpurmc.purpur.event.entity.BeeStartedPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftLocation.toBukkit(Bee.this.savedFlowerPos, Bee.this.level())).callEvent(); // Purpur - Bee API
this.successfulPollinatingTicks++;
if (Bee.this.random.nextFloat() < 0.05F && this.successfulPollinatingTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = this.successfulPollinatingTicks;

View File

@@ -0,0 +1,90 @@
--- a/net/minecraft/world/entity/animal/cow/AbstractCow.java
+++ b/net/minecraft/world/entity/animal/cow/AbstractCow.java
@@ -40,7 +_,7 @@
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PanicGoal(this, 2.0));
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0));
- this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, i -> i.is(ItemTags.COW_FOOD), false));
+ this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, i -> level().purpurConfig.cowFeedMushrooms > 0 && (i.is(net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) || i.is(net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem())) || i.is(ItemTags.COW_FOOD), false)); // Purpur - Cows eat mushrooms
this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
@@ -100,6 +_,10 @@
ItemStack bucketOrMilkBucket = ItemUtils.createFilledResult(itemStack, player, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
player.setItemInHand(hand, bucketOrMilkBucket);
return InteractionResult.SUCCESS;
+ // Purpur start - Cows eat mushrooms - feed mushroom to change to mooshroom
+ } else if (level().purpurConfig.cowFeedMushrooms > 0 && this.getType() != EntityType.MOOSHROOM && isMushroom(itemInHand)) {
+ return this.feedMushroom(player, itemInHand);
+ // Purpur end - Cows eat mushrooms
} else {
return super.mobInteract(player, hand);
}
@@ -109,4 +_,67 @@
public EntityDimensions getDefaultDimensions(final Pose pose) {
return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(pose);
}
+
+ // Purpur start - Cows eat mushrooms - feed mushroom to change to mooshroom
+ private int redMushroomsFed = 0;
+ private int brownMushroomsFed = 0;
+
+ private boolean isMushroom(ItemStack stack) {
+ return stack.getItem() == net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem() || stack.getItem() == net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem();
+ }
+
+ private int incrementFeedCount(ItemStack stack) {
+ if (stack.getItem() == net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) {
+ return ++redMushroomsFed;
+ } else {
+ return ++brownMushroomsFed;
+ }
+ }
+
+ private InteractionResult feedMushroom(Player player, ItemStack stack) {
+ level().broadcastEntityEvent(this, (byte) 18); // hearts
+ playSound(SoundEvents.COW_MILK, 1.0F, 1.0F);
+ if (incrementFeedCount(stack) < level().purpurConfig.cowFeedMushrooms) {
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+ return InteractionResult.CONSUME; // require 5 mushrooms to transform (prevents mushroom duping)
+ }
+ MushroomCow mooshroom = EntityType.MOOSHROOM.create(level(), net.minecraft.world.entity.EntitySpawnReason.CONVERSION);
+ if (mooshroom == null) {
+ return InteractionResult.PASS;
+ }
+ if (stack.getItem() == net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem()) {
+ mooshroom.setVariant(MushroomCow.Variant.BROWN);
+ } else {
+ mooshroom.setVariant(MushroomCow.Variant.RED);
+ }
+ mooshroom.snapTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+ mooshroom.setHealth(this.getHealth());
+ mooshroom.setAge(getAge());
+ mooshroom.copyPosition(this);
+ mooshroom.setYBodyRot(this.yBodyRot);
+ mooshroom.setYHeadRot(this.getYHeadRot());
+ mooshroom.yRotO = this.yRotO;
+ mooshroom.xRotO = this.xRotO;
+ if (this.hasCustomName()) {
+ mooshroom.setCustomName(this.getCustomName());
+ }
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) {
+ return InteractionResult.PASS;
+ }
+ this.level().addFreshEntity(mooshroom);
+ this.remove(RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+ for (int i = 0; i < 15; ++i) {
+ ((net.minecraft.server.level.ServerLevel) level()).sendParticlesSource(((net.minecraft.server.level.ServerLevel) level()).players(), null, net.minecraft.core.particles.ParticleTypes.HAPPY_VILLAGER,
+ false, true,
+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1,
+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0);
+ }
+ return InteractionResult.SUCCESS;
+ }
+ // Purpur end - Cows eat mushrooms
}

View File

@@ -0,0 +1,48 @@
--- a/net/minecraft/world/entity/animal/dolphin/Dolphin.java
+++ b/net/minecraft/world/entity/animal/dolphin/Dolphin.java
@@ -77,6 +_,7 @@
public static final float BABY_SCALE = 0.65F;
private static final boolean DEFAULT_GOT_FISH = false;
public @Nullable BlockPos treasurePos;
+ private boolean isNaturallyAggressiveToPlayers; // Purpur - Dolphins naturally aggressive to players chance
public Dolphin(final EntityType<? extends Dolphin> type, final Level level) {
super(type, level);
@@ -92,6 +_,7 @@
this.setAirSupply(this.getMaxAirSupply());
this.setXRot(0.0F);
SpawnGroupData spawnGroupData = Objects.requireNonNullElseGet(groupData, () -> new AgeableMob.AgeableMobGroupData(0.1F));
+ this.isNaturallyAggressiveToPlayers = level.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= level.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur - Dolphins naturally aggressive to players chance
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
}
@@ -157,18 +_,20 @@
protected void registerGoals() {
this.goalSelector.addGoal(0, new BreathAirGoal(this));
this.goalSelector.addGoal(0, new TryFindWaterGoal(this));
+ this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - Dolphins naturally aggressive to players chance
this.goalSelector.addGoal(1, new Dolphin.DolphinSwimToTreasureGoal(this));
this.goalSelector.addGoal(2, new Dolphin.DolphinSwimWithPlayerGoal(this, 4.0));
this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0, 10));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(5, new DolphinJumpGoal(this, 10));
- this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2F, true));
+ //this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2F, true)); // Purpur - moved up - Dolphins naturally aggressive to players chance
this.goalSelector.addGoal(8, new Dolphin.PlayWithItemsGoal());
this.goalSelector.addGoal(8, new FollowPlayerRiddenEntityGoal(this, AbstractBoat.class));
this.goalSelector.addGoal(8, new FollowPlayerRiddenEntityGoal(this, AbstractNautilus.class));
this.goalSelector.addGoal(9, new AvoidEntityGoal<>(this, Guardian.class, 8.0F, 1.0, 1.0));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Guardian.class).setAlertOthers());
+ this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur - Dolphins naturally aggressive to players chance
}
public static AttributeSupplier.Builder createAttributes() {
@@ -395,6 +_,7 @@
@Override
public boolean canUse() {
+ if (this.dolphin.level().purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur - Add option to disable dolphin treasure searching
return this.dolphin.gotFish() && this.dolphin.getAirSupply() >= 100;
}

View File

@@ -0,0 +1,42 @@
--- a/net/minecraft/world/entity/animal/equine/Llama.java
+++ b/net/minecraft/world/entity/animal/equine/Llama.java
@@ -75,6 +_,7 @@
private boolean didSpit;
private @Nullable Llama caravanHead;
public @Nullable Llama caravanTail; // Paper - public
+ public boolean shouldJoinCaravan = true; // Purpur - Llama API
public Llama(final EntityType<? extends Llama> type, final Level level) {
super(type, level);
@@ -104,6 +_,7 @@
super.addAdditionalSaveData(output);
output.store("Variant", Llama.Variant.LEGACY_CODEC, this.getVariant());
output.putInt("Strength", this.getStrength());
+ output.putBoolean("Purpur.ShouldJoinCaravan", shouldJoinCaravan); // Purpur - Llama API
}
@Override
@@ -111,6 +_,7 @@
this.setStrength(input.getIntOr("Strength", 0));
super.readAdditionalSaveData(input);
this.setVariant(input.read("Variant", Llama.Variant.LEGACY_CODEC).orElse(Llama.Variant.DEFAULT));
+ this.shouldJoinCaravan = input.getBooleanOr("Purpur.ShouldJoinCaravan", true); // Purpur - Llama API
}
@Override
@@ -387,6 +_,7 @@
public void leaveCaravan() {
if (this.caravanHead != null) {
+ new org.purpurmc.purpur.event.entity.LlamaLeaveCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity()).callEvent(); // Purpur - Llama API
this.caravanHead.caravanTail = null;
}
@@ -394,6 +_,7 @@
}
public void joinCaravan(final Llama tail) {
+ if (!this.level().purpurConfig.llamaJoinCaravans || !shouldJoinCaravan || !new org.purpurmc.purpur.event.entity.LlamaJoinCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity(), (org.bukkit.entity.Llama) caravanHead.getBukkitEntity()).callEvent()) return; // Purpur - Llama API // Purpur - Config to disable Llama caravans
this.caravanHead = tail;
this.caravanHead.caravanTail = this;
}

View File

@@ -0,0 +1,26 @@
--- a/net/minecraft/world/entity/animal/feline/Cat.java
+++ b/net/minecraft/world/entity/animal/feline/Cat.java
@@ -382,6 +_,14 @@
return this.isTame() && partner instanceof Cat cat && cat.isTame() && super.canMate(partner);
}
+ // Purpur start - Configurable default collar color
+ @Override
+ public void tame(Player player) {
+ setCollarColor(level().purpurConfig.catDefaultCollarColor);
+ super.tame(player);
+ }
+ // Purpur end - Configurable default collar color
+
@Override
public @Nullable SpawnGroupData finalizeSpawn(
final ServerLevelAccessor level, final DifficultyInstance difficulty, final EntitySpawnReason spawnReason, @Nullable SpawnGroupData groupData
@@ -476,7 +_,7 @@
}
private void tryToTame(final Player player) {
- if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
+ if (((this.level().purpurConfig.alwaysTameInCreative && player.hasInfiniteMaterials()) || this.random.nextInt(3) == 0) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit // Purpur - Config to always tame in Creative
this.tame(player);
this.setOrderedToSit(true);
this.level().broadcastEntityEvent(this, EntityEvent.TAMING_SUCCEEDED);

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/animal/feline/Ocelot.java
+++ b/net/minecraft/world/entity/animal/feline/Ocelot.java
@@ -234,7 +_,7 @@
public boolean checkSpawnObstruction(final LevelReader level) {
if (level.isUnobstructed(this) && !level.containsAnyLiquid(this.getBoundingBox())) {
BlockPos pos = this.blockPosition();
- if (pos.getY() < level.getSeaLevel()) {
+ if (!level().purpurConfig.ocelotSpawnUnderSeaLevel && pos.getY() < level.getSeaLevel()) { // Purpur - Option Ocelot Spawn Under Sea Level
return false;
}

View File

@@ -0,0 +1,12 @@
--- a/net/minecraft/world/entity/animal/fish/WaterAnimal.java
+++ b/net/minecraft/world/entity/animal/fish/WaterAnimal.java
@@ -80,8 +_,7 @@
seaLevel = level.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(seaLevel);
minSpawnLevel = level.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(minSpawnLevel);
// Paper end - Make water animal spawn height configurable
- return pos.getY() >= minSpawnLevel
- && pos.getY() <= seaLevel
+ return ((spawnReason == EntitySpawnReason.SPAWNER && level.getMinecraftWorld().purpurConfig.spawnerFixMC238526) || (pos.getY() >= minSpawnLevel && pos.getY() <= seaLevel)) // Purpur - MC-238526 - Fix spawner not spawning water animals correctly
&& level.getFluidState(pos.below()).is(FluidTags.WATER)
&& level.getBlockState(pos.above()).is(Blocks.WATER);
}

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/entity/animal/golem/CopperGolemAi.java
+++ b/net/minecraft/world/entity/animal/golem/CopperGolemAi.java
@@ -41,7 +_,7 @@
private static final int TICK_TO_START_ON_REACHED_INTERACTION = 1;
private static final int TICK_TO_PLAY_ON_REACHED_SOUND = 9;
private static final Predicate<BlockState> TRANSPORT_ITEM_SOURCE_BLOCK = block -> block.is(BlockTags.COPPER_CHESTS);
- private static final Predicate<BlockState> TRANSPORT_ITEM_DESTINATION_BLOCK = block -> block.is(Blocks.CHEST) || block.is(Blocks.TRAPPED_CHEST);
+ private static final Predicate<BlockState> TRANSPORT_ITEM_DESTINATION_BLOCK = block -> block.is(Blocks.CHEST) || block.is(Blocks.TRAPPED_CHEST); // Purpur - copper golem can place items in barrels or shulkers option - diff on change
protected static List<ActivityData<CopperGolem>> getActivities() {
return List.of(initCoreActivity(), initIdleActivity());
@@ -130,6 +_,11 @@
}
if (ticksSinceReachingTarget == 60) {
+ // Purpur start - copper golem can place items in barrels or shulkers option
+ if (container instanceof net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity shulkerBoxBlockEntity && shulkerBoxBlockEntity.openCount > 0) {
+ container.stopOpen(copperGolem);
+ }
+ // Purpur end - copper golem can place items in barrels or shulkers option
if (container.getEntitiesWithContainerOpen().contains(body)) {
container.stopOpen(copperGolem);
}

View File

@@ -0,0 +1,52 @@
--- a/net/minecraft/world/entity/animal/golem/IronGolem.java
+++ b/net/minecraft/world/entity/animal/golem/IronGolem.java
@@ -58,13 +_,25 @@
private static final UniformInt PERSISTENT_ANGER_TIME = TimeUtil.rangeOfSeconds(20, 39);
private long persistentAngerEndTime;
private @Nullable EntityReference<LivingEntity> persistentAngerTarget;
+ private java.util.@Nullable UUID summoner; // Purpur - Summoner API
public IronGolem(final EntityType<? extends IronGolem> type, final Level level) {
super(type, level);
}
+ // Purpur start - Summoner API
+ public java.util.@Nullable UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(java.util.@Nullable UUID summoner) {
+ this.summoner = summoner;
+ }
+ // Purpur end - Summoner API
+
@Override
protected void registerGoals() {
+ if (this.level().purpurConfig.ironGolemPoppyCalm) this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.ReceiveFlower(this)); // Purpur - Iron golem calm anger options
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0, true));
this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9, 32.0F));
this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6, false));
@@ -142,6 +_,7 @@
protected void addAdditionalSaveData(final ValueOutput output) {
super.addAdditionalSaveData(output);
output.putBoolean("PlayerCreated", this.isPlayerCreated());
+ output.storeNullable("Purpur.Summoner", net.minecraft.core.UUIDUtil.CODEC, getSummoner()); // Purpur - Summoner API
this.addPersistentAngerSaveData(output);
}
@@ -149,6 +_,7 @@
protected void readAdditionalSaveData(final ValueInput input) {
super.readAdditionalSaveData(input);
this.setPlayerCreated(input.getBooleanOr("PlayerCreated", false));
+ this.setSummoner(input.read("Purpur.Summoner", net.minecraft.core.UUIDUtil.CODEC).orElse(null)); // Purpur - Summoner API
this.readPersistentAngerSaveData(this.level(), input);
}
@@ -267,6 +_,7 @@
float pitch = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F;
this.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0F, pitch);
itemStack.consume(1, player);
+ if (this.level().purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur - Iron golem calm anger options
return InteractionResult.SUCCESS;
}
}

View File

@@ -0,0 +1,51 @@
--- a/net/minecraft/world/entity/animal/parrot/Parrot.java
+++ b/net/minecraft/world/entity/animal/parrot/Parrot.java
@@ -164,6 +_,7 @@
protected void registerGoals() {
this.goalSelector.addGoal(0, new TamableAnimal.TamableAnimalPanicGoal(1.25));
this.goalSelector.addGoal(0, new FloatGoal(this));
+ if (this.level().purpurConfig.parrotBreedable) this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D)); // Purpur - Breedable parrots
this.goalSelector.addGoal(1, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
this.goalSelector.addGoal(2, new FollowOwnerGoal(this, 1.0, 5.0F, 1.0F));
@@ -270,7 +_,7 @@
}
if (!this.level().isClientSide()) {
- if (this.random.nextInt(10) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
+ if (((this.level().purpurConfig.alwaysTameInCreative && player.hasInfiniteMaterials()) || this.random.nextInt(10) == 0) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit // Purpur - Config to always tame in Creative
this.tame(player);
this.level().broadcastEntityEvent(this, EntityEvent.TAMING_SUCCEEDED);
} else {
@@ -278,6 +_,7 @@
}
}
+ if (this.level().purpurConfig.parrotBreedable) return super.mobInteract(player, hand); // Purpur - Breedable parrots
return InteractionResult.SUCCESS;
} else if (!itemStack.is(ItemTags.PARROT_POISONOUS_FOOD)) {
if (!this.isFlying() && this.isTame() && this.isOwnedBy(player)) {
@@ -302,7 +_,7 @@
@Override
public boolean isFood(final ItemStack itemStack) {
- return false;
+ return this.level().purpurConfig.parrotBreedable && stack.is(ItemTags.PARROT_FOOD); // Purpur - Breedable parrots
}
public static boolean checkParrotSpawnRules(
@@ -317,12 +_,12 @@
@Override
public boolean canMate(final Animal partner) {
- return false;
+ return super.canMate(otherAnimal); // Purpur - Breedable parrots
}
@Override
public @Nullable AgeableMob getBreedOffspring(final ServerLevel level, final AgeableMob partner) {
- return null;
+ return level.purpurConfig.parrotBreedable ? EntityType.PARROT.create(level, EntitySpawnReason.BREEDING) : null; // Purpur - Breedable parrots
}
@Override

View File

@@ -0,0 +1,22 @@
--- a/net/minecraft/world/entity/animal/pig/Pig.java
+++ b/net/minecraft/world/entity/animal/pig/Pig.java
@@ -158,6 +_,19 @@
@Override
public InteractionResult mobInteract(final Player player, final InteractionHand hand) {
boolean hasFood = this.isFood(player.getItemInHand(hand));
+ // Purpur start - Pigs give saddle back
+ if (level().purpurConfig.pigGiveSaddleBack && player.isSecondaryUseActive() && !hasFood && isSaddled() && !isVehicle()) {
+ this.setItemSlot(EquipmentSlot.SADDLE, ItemStack.EMPTY);
+ if (!player.getAbilities().instabuild) {
+ ItemStack saddle = new ItemStack(Items.SADDLE);
+ if (!player.getInventory().add(saddle)) {
+ player.drop(saddle, false);
+ }
+ }
+ return InteractionResult.SUCCESS;
+ }
+ // Purpur end - Pigs give saddle back
+
if (!hasFood && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) {
if (!this.level().isClientSide()) {
player.startRiding(this);

View File

@@ -0,0 +1,54 @@
--- a/net/minecraft/world/entity/animal/polarbear/PolarBear.java
+++ b/net/minecraft/world/entity/animal/polarbear/PolarBear.java
@@ -67,6 +_,29 @@
super(type, level);
}
+ // Purpur start - Breedable Polar Bears
+ public boolean canMate(Animal other) {
+ if (other == this) {
+ return false;
+ } else if (this.isStanding()) {
+ return false;
+ } else if (this.getTarget() != null) {
+ return false;
+ } else if (!(other instanceof PolarBear)) {
+ return false;
+ } else {
+ PolarBear bear = (PolarBear) other;
+ if (bear.isStanding()) {
+ return false;
+ }
+ if (bear.getTarget() != null) {
+ return false;
+ }
+ return this.isInLove() && bear.isInLove();
+ }
+ }
+ // Purpur end - Breedable Polar Bears
+
@Override
public @Nullable AgeableMob getBreedOffspring(final ServerLevel level, final AgeableMob partner) {
return EntityType.POLAR_BEAR.create(level, EntitySpawnReason.BREEDING);
@@ -74,7 +_,7 @@
@Override
public boolean isFood(final ItemStack itemStack) {
- return false;
+ return level().purpurConfig.polarBearBreedableItem != null && itemStack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur - Breedable Polar Bears
}
@Override
@@ -83,6 +_,12 @@
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal());
this.goalSelector.addGoal(1, new PanicGoal(this, 2.0, bear -> bear.isBaby() ? DamageTypeTags.PANIC_CAUSES : DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES));
+ // Purpur start - Breedable Polar Bears
+ if (level().purpurConfig.polarBearBreedableItem != null) {
+ this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D));
+ this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, net.minecraft.world.item.crafting.Ingredient.of(level().purpurConfig.polarBearBreedableItem), false));
+ }
+ // Purpur end - Breedable Polar Bears
this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25));
this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));

View File

@@ -0,0 +1,26 @@
--- a/net/minecraft/world/entity/animal/rabbit/Rabbit.java
+++ b/net/minecraft/world/entity/animal/rabbit/Rabbit.java
@@ -419,10 +_,23 @@
}
this.setVariant(variant);
+
+ // Purpur start - Special mobs naturally spawn
+ if (variant != Variant.EVIL && level.getLevel().purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= level.getLevel().purpurConfig.rabbitNaturalToast) {
+ setCustomName(Component.translatable("Toast"));
+ }
+ // Purpur end - Special mobs naturally spawn
+
return super.finalizeSpawn(level, difficulty, spawnReason, groupData);
}
private static Rabbit.Variant getRandomRabbitVariant(final LevelAccessor level, final BlockPos pos) {
+ // Purpur start - Special mobs naturally spawn
+ Level world = level.getMinecraftWorld();
+ if (world.purpurConfig.rabbitNaturalKiller > 0D && world.getRandom().nextDouble() <= world.purpurConfig.rabbitNaturalKiller) {
+ return Rabbit.Variant.EVIL;
+ }
+ // Purpur end - Special mobs naturally spawn
Holder<Biome> biome = level.getBiome(pos);
int randomVal = level.getRandom().nextInt(100);
if (biome.is(BiomeTags.SPAWNS_WHITE_RABBITS)) {

View File

@@ -0,0 +1,50 @@
--- a/net/minecraft/world/entity/animal/squid/Squid.java
+++ b/net/minecraft/world/entity/animal/squid/Squid.java
@@ -51,10 +_,29 @@
public Squid(final EntityType<? extends Squid> type, final Level level) {
super(type, level);
- // this.random.setSeed(this.getId()); // Paper - Share random for entities to make them more random
+ if (!level.purpurConfig.entitySharedRandom) this.random.setSeed(this.getId()); // Paper - Share random for entities to make them more random // Purpur - Add toggle for RNG manipulation
this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F;
}
+ // Purpur start - Stop squids floating on top of water
+ @Override
+ public net.minecraft.world.phys.AABB getAxisForFluidCheck() {
+ // Stops squids from floating just over the water
+ return super.getAxisForFluidCheck().offsetY(level().purpurConfig.squidOffsetWaterCheck);
+ }
+ // Purpur end - Stop squids floating on top of water
+
+ // Purpur start - Flying squids! Oh my!
+ public boolean canFly() {
+ return this.level().purpurConfig.squidsCanFly;
+ }
+
+ @Override
+ public boolean isInWater() {
+ return this.wasTouchingWater || canFly();
+ }
+ // Purpur end - Flying squids! Oh my!
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new Squid.SquidRandomMovementGoal(this));
@@ -131,6 +_,7 @@
}
if (this.isInWater()) {
+ if (canFly()) setNoGravity(!wasTouchingWater); // Purpur - Flying squids! Oh my!
if (this.tentacleMovement < (float) Math.PI) {
float tentacleScale = this.tentacleMovement / (float) Math.PI;
this.tentacleAngle = Mth.sin(tentacleScale * tentacleScale * (float) Math.PI) * (float) Math.PI * 0.25F;
@@ -321,7 +_,7 @@
int noActionTime = this.squid.getNoActionTime();
if (noActionTime > 100) {
this.squid.movementVector = Vec3.ZERO;
- } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.wasTouchingWater || !this.squid.hasMovementVector()) {
+ } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.isInWater() || !this.squid.hasMovementVector()) { // Purpur - Flying squids! Oh my!
float angle = this.squid.getRandom().nextFloat() * (float) (Math.PI * 2);
this.squid.movementVector = new Vec3(Mth.cos(angle) * 0.2F, -0.1F + this.squid.getRandom().nextFloat() * 0.2F, Mth.sin(angle) * 0.2F);
}

View File

@@ -0,0 +1,168 @@
--- a/net/minecraft/world/entity/animal/wolf/Wolf.java
+++ b/net/minecraft/world/entity/animal/wolf/Wolf.java
@@ -97,6 +_,37 @@
public static final TargetingConditions.Selector PREY_SELECTOR = (target, level) -> target.is(EntityType.SHEEP)
|| target.is(EntityType.RABBIT)
|| target.is(EntityType.FOX);
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ private boolean isRabid = false;
+ private static final TargetingConditions.Selector RABID_PREDICATE = (entity, ignored) -> entity instanceof net.minecraft.server.level.ServerPlayer || entity instanceof net.minecraft.world.entity.Mob;
+ private final net.minecraft.world.entity.ai.goal.Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR);
+ private final net.minecraft.world.entity.ai.goal.Goal PATHFINDER_RABID = new NonTameRandomTargetGoal<>(this, LivingEntity.class, false, RABID_PREDICATE);
+ private static final class AvoidRabidWolfGoal extends AvoidEntityGoal<Wolf> {
+ private final Wolf wolf;
+
+ public AvoidRabidWolfGoal(Wolf wolf, float distance, double minSpeed, double maxSpeed) {
+ super(wolf, Wolf.class, distance, minSpeed, maxSpeed);
+ this.wolf = wolf;
+ }
+
+ @Override
+ public boolean canUse() {
+ return super.canUse() && !this.wolf.isRabid() && this.toAvoid != null && this.toAvoid.isRabid(); // wolves which are not rabid run away from rabid wolves
+ }
+
+ @Override
+ public void start() {
+ this.wolf.setTarget(null);
+ super.start();
+ }
+
+ @Override
+ public void tick() {
+ this.wolf.setTarget(null);
+ super.tick();
+ }
+ }
+ // Purpur end - Configurable chance for wolves to spawn rabid
private static final float START_HEALTH = 8.0F;
private static final float TAME_HEALTH = 40.0F;
private static final float ARMOR_REPAIR_UNIT = 0.125F;
@@ -118,12 +_,47 @@
this.setPathfindingMalus(PathType.ON_TOP_OF_POWDER_SNOW, -1.0F);
}
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ public boolean isRabid() {
+ return this.isRabid;
+ }
+
+ public void setRabid(boolean isRabid) {
+ this.isRabid = isRabid;
+ updatePathfinders(true);
+ }
+
+ public void updatePathfinders(boolean modifyEffects) {
+ this.targetSelector.removeGoal(PATHFINDER_VANILLA);
+ this.targetSelector.removeGoal(PATHFINDER_RABID);
+ if (this.isRabid) {
+ this.setOwnerReference(null);
+ setTame(false, true);
+ this.targetSelector.addGoal(5, PATHFINDER_RABID);
+ if (modifyEffects) this.addEffect(new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.NAUSEA, 1200));
+ } else {
+ this.targetSelector.addGoal(5, PATHFINDER_VANILLA);
+ this.stopBeingAngry();
+ if (modifyEffects) this.removeEffect(net.minecraft.world.effect.MobEffects.NAUSEA);
+ }
+ }
+ // Purpur end - Configurable chance for wolves to spawn rabid
+
+ // Purpur start - Configurable default collar color
+ @Override
+ public void tame(Player player) {
+ setCollarColor(level().purpurConfig.wolfDefaultCollarColor);
+ super.tame(player);
+ }
+ // Purpur end - Configurable default collar color
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5, DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES));
this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal<>(this, Llama.class, 24.0F, 1.5, 1.5));
+ this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur - Configurable chance for wolves to spawn rabid
this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F));
this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, true));
this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0, 10.0F, 2.0F));
@@ -136,7 +_,7 @@
this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this));
this.targetSelector.addGoal(3, new HurtByTargetGoal(this).setAlertOthers());
this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
- this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR));
+ //this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR)); // Purpur - Configurable chance for wolves to spawn rabid - moved to updatePathfinders()
this.targetSelector.addGoal(6, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
this.targetSelector.addGoal(7, new NearestAttackableTargetGoal<>(this, AbstractSkeleton.class, false));
this.targetSelector.addGoal(8, new ResetUniversalAngerTargetGoal<>(this, true));
@@ -231,6 +_,7 @@
protected void addAdditionalSaveData(final ValueOutput output) {
super.addAdditionalSaveData(output);
output.store("CollarColor", DyeColor.LEGACY_ID_CODEC, this.getCollarColor());
+ output.putBoolean("Purpur.IsRabid", this.isRabid); // Purpur - Configurable chance for wolves to spawn rabid
VariantUtils.writeVariant(output, this.getVariant());
this.addPersistentAngerSaveData(output);
this.getSoundVariant()
@@ -245,6 +_,10 @@
super.readAdditionalSaveData(input);
VariantUtils.readVariant(input, Registries.WOLF_VARIANT).ifPresent(this::setVariant);
this.setCollarColor(input.read("CollarColor", DyeColor.LEGACY_ID_CODEC).orElse(DEFAULT_COLLAR_COLOR));
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ this.isRabid = input.getBooleanOr("Purpur.IsRabid", false);
+ this.updatePathfinders(false);
+ // Purpur end - Configurable chance for wolves to spawn rabid
this.readPersistentAngerSaveData(this.level(), input);
input.read("sound_variant", ResourceKey.codec(Registries.WOLF_SOUND_VARIANT))
.flatMap(soundVariant -> this.registryAccess().lookupOrThrow(Registries.WOLF_SOUND_VARIANT).get((ResourceKey<WolfSoundVariant>)soundVariant))
@@ -268,6 +_,10 @@
}
this.setSoundVariant(WolfSoundVariants.pickRandomSoundVariant(this.registryAccess(), level.getRandom()));
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ this.isRabid = level.getLevel().purpurConfig.wolfNaturalRabid > 0.0D && random.nextDouble() <= level.getLevel().purpurConfig.wolfNaturalRabid;
+ this.updatePathfinders(false);
+ // Purpur end - Configurable chance for wolves to spawn rabid
return super.finalizeSpawn(level, difficulty, spawnReason, groupData);
}
@@ -316,6 +_,11 @@
public void tick() {
super.tick();
if (this.isAlive()) {
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ if (this.age % 300 == 0 && this.isRabid()) {
+ this.addEffect(new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.NAUSEA, 400));
+ }
+ // Purpur end - Configurable chance for wolves to spawn rabid
this.interestedAngleO = this.interestedAngle;
if (this.isInterested()) {
this.interestedAngle = this.interestedAngle + (1.0F - this.interestedAngle) * 0.4F;
@@ -504,13 +_,27 @@
itemStack.consume(1, player);
this.tryToTame(player);
return InteractionResult.SUCCESS_SERVER;
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ } else if (this.level().purpurConfig.wolfMilkCuresRabies && itemInHand.getItem() == Items.MILK_BUCKET && this.isRabid()) {
+ if (!player.isCreative()) {
+ player.setItemInHand(hand, new ItemStack(Items.BUCKET));
+ }
+ this.setRabid(false);
+ for (int i = 0; i < 10; ++i) {
+ ((ServerLevel) level()).sendParticlesSource(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER,
+ false, true,
+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 1.5), getZ() + random.nextFloat(), 1,
+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0);
+ }
+ return InteractionResult.SUCCESS_SERVER;
+ // Purpur end - Configurable chance for wolves to spawn rabid
}
return super.mobInteract(player, hand);
}
private void tryToTame(final Player player) {
- if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit - added event call
+ if (((this.level().purpurConfig.alwaysTameInCreative && player.hasInfiniteMaterials()) || this.random.nextInt(3) == 0) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit - added event call // Purpur - Config to always tame in Creative
this.tame(player);
this.navigation.stop();
this.setTarget(null);

View File

@@ -0,0 +1,56 @@
--- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
+++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
@@ -39,6 +_,24 @@
this.setPos(x, y, z);
}
+ // Purpur start - End crystal explosion options
+ public boolean shouldExplode() {
+ return showsBottom() ? level().purpurConfig.basedEndCrystalExplode : level().purpurConfig.baselessEndCrystalExplode;
+ }
+
+ public float getExplosionPower() {
+ return (float) (showsBottom() ? level().purpurConfig.basedEndCrystalExplosionPower : level().purpurConfig.baselessEndCrystalExplosionPower);
+ }
+
+ public boolean hasExplosionFire() {
+ return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionFire : level().purpurConfig.baselessEndCrystalExplosionFire;
+ }
+
+ public Level.ExplosionInteraction getExplosionEffect() {
+ return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionEffect : level().purpurConfig.baselessEndCrystalExplosionEffect;
+ }
+ // Purpur end - End crystal explosion options
+
@Override
protected Entity.MovementEmission getMovementEmission() {
return Entity.MovementEmission.NONE;
@@ -75,6 +_,8 @@
}
}
// Paper end - Fix invulnerable end crystals
+ if (this.level().purpurConfig.endCrystalCramming > 0 && this.level().getEntitiesOfClass(EndCrystal.class, getBoundingBox()).size() > this.level().purpurConfig.endCrystalCramming) this.hurt(this.damageSources().cramming(), 6.0F); // Purpur - End Crystal Cramming
+
}
@Override
@@ -115,15 +_,17 @@
}
// CraftBukkit end
if (!source.is(DamageTypeTags.IS_EXPLOSION)) {
+ if (shouldExplode()) {// Purpur - End crystal explosion options
DamageSource damageSource = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null;
// CraftBukkit start
- org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false);
+ org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, getExplosionPower(), hasExplosionFire()); // Purpur - End crystal explosion options
if (event.isCancelled()) {
return false;
}
this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // Paper - add Bukkit remove cause
- level.explode(this, damageSource, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK);
+ level.explode(this, damageSource, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), getExplosionEffect()); // Purpur - End crystal explosion options
+ } else this.unsetRemoved(); // Purpur - End crystal explosion options
} else {
this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause
// CraftBukkit end

View File

@@ -0,0 +1,19 @@
--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
@@ -964,6 +_,7 @@
@Override
protected boolean canRide(final Entity vehicle) {
+ if (this.level().purpurConfig.enderDragonCanRideVehicles) return this.boardingCooldown <= 0; // Purpur - Configs for if Wither/Ender Dragon can ride vehicles
return false;
}
@@ -999,7 +_,7 @@
boolean shouldDrop = level.getGameRules().get(GameRules.MOB_DROPS);
int xpCount = 500;
- if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
+ if (this.dragonFight != null && (level().purpurConfig.enderDragonAlwaysDropsFullExp || !this.dragonFight.hasPreviouslyKilledDragon())) { // Purpur - Ender dragon always drop full exp
xpCount = 12000;
}

View File

@@ -0,0 +1,73 @@
--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
+++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
@@ -82,6 +_,7 @@
private static final TargetingConditions.Selector LIVING_ENTITY_SELECTOR = (target, level) -> !target.is(EntityTypeTags.WITHER_FRIENDS)
&& target.attackable();
private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0).selector(LIVING_ENTITY_SELECTOR);
+ private java.util.@Nullable UUID summoner; // Purpur - Summoner API
public WitherBoss(final EntityType<? extends WitherBoss> type, final Level level) {
super(type, level);
@@ -90,6 +_,16 @@
this.xpReward = 50;
}
+ // Purpur start - Summoner API
+ public java.util.@Nullable UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(java.util.@Nullable UUID summoner) {
+ this.summoner = summoner;
+ }
+ // Purpur end - Summoner API
+
@Override
protected PathNavigation createNavigation(final Level level) {
FlyingPathNavigation flyingPathNavigation = new FlyingPathNavigation(this, level);
@@ -122,6 +_,7 @@
protected void addAdditionalSaveData(final ValueOutput output) {
super.addAdditionalSaveData(output);
output.putInt("Invul", this.getInvulnerableTicks());
+ output.storeNullable("Purpur.Summoner", net.minecraft.core.UUIDUtil.CODEC, getSummoner()); // Purpur - Summoner API
}
@Override
@@ -131,6 +_,7 @@
if (this.hasCustomName()) {
this.bossEvent.setName(this.getDisplayName());
}
+ this.setSummoner(input.read("Purpur.Summoner", net.minecraft.core.UUIDUtil.CODEC).orElse(null)); // Purpur - Summoner API
}
@Override
@@ -274,7 +_,7 @@
level.explode(this, this.getX(), this.getEyeY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
}
// CraftBukkit end
- if (!this.isSilent()) {
+ if (!this.isSilent() && level.purpurConfig.witherPlaySpawnSound) { // Purpur - Toggle for Wither's spawn sound
// CraftBukkit start - Use relative location for far away sounds
// level.globalLevelEvent(LevelEvent.SOUND_WITHER_BOSS_SPAWN, this.blockPosition(), 0);
int viewDistance = level.getCraftServer().getViewDistance() * 16;
@@ -378,8 +_,10 @@
}
}
- if (this.tickCount % 20 == 0) {
- this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ // Purpur start - Customizable wither health and healing - customizable heal rate and amount
+ if (this.tickCount % level().purpurConfig.witherHealthRegenDelay == 0) {
+ this.heal(level().purpurConfig.witherHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ // Purpur end - Customizable wither health and healing
}
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
@@ -576,6 +_,7 @@
@Override
protected boolean canRide(final Entity vehicle) {
+ if (this.level().purpurConfig.witherCanRideVehicles) return this.boardingCooldown <= 0; // Purpur - Configs for if Wither/Ender Dragon can ride vehicles
return false;
}

View File

@@ -0,0 +1,46 @@
--- a/net/minecraft/world/entity/item/ItemEntity.java
+++ b/net/minecraft/world/entity/item/ItemEntity.java
@@ -54,6 +_,12 @@
public boolean canMobPickup = true; // Paper - Item#canEntityPickup
private int despawnRate = -1; // Paper - Alternative item-despawn-rate
public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API
+ // Purpur start - Item entity immunities
+ public boolean immuneToCactus = false;
+ public boolean immuneToExplosion = false;
+ public boolean immuneToFire = false;
+ public boolean immuneToLightning = false;
+ // Purpur end - Item entity immunities
public ItemEntity(final EntityType<? extends ItemEntity> type, final Level level) {
super(type, level);
@@ -314,7 +_,16 @@
@Override
public final boolean hurtServer(final ServerLevel level, final DamageSource source, final float damage) {
- if (this.isInvulnerableToBase(source)) {
+ // Purpur start - Item entity immunities
+ if (
+ (immuneToCactus && source.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) ||
+ (immuneToFire && (source.is(net.minecraft.tags.DamageTypeTags.IS_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.ON_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.IN_FIRE))) ||
+ (immuneToLightning && source.is(net.minecraft.world.damagesource.DamageTypes.LIGHTNING_BOLT)) ||
+ (immuneToExplosion && source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION))
+ ) {
+ return false;
+ } else if (this.isInvulnerableToBase(source)) {
+ // Purpur end - Item entity immunities
return false;
} else if (!level.getGameRules().get(GameRules.MOB_GRIEFING) && source.getEntity() instanceof Mob) {
return false;
@@ -492,6 +_,12 @@
public void setItem(final ItemStack itemStack) {
this.getEntityData().set(DATA_ITEM, itemStack);
this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(itemStack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate
+ // Purpur start - Item entity immunities
+ if (level().purpurConfig.itemImmuneToCactus.contains(itemStack.getItem())) immuneToCactus = true;
+ if (level().purpurConfig.itemImmuneToExplosion.contains(itemStack.getItem())) immuneToExplosion = true;
+ if (level().purpurConfig.itemImmuneToFire.contains(itemStack.getItem())) immuneToFire = true;
+ if (level().purpurConfig.itemImmuneToLightning.contains(itemStack.getItem())) immuneToLightning = true;
+ // level end - Item entity immunities
}
public void setTarget(final @Nullable UUID target) {

View File

@@ -0,0 +1,65 @@
--- a/net/minecraft/world/entity/monster/Creeper.java
+++ b/net/minecraft/world/entity/monster/Creeper.java
@@ -56,6 +_,7 @@
public int explosionRadius = 3;
public boolean droppedSkulls;
public @Nullable Entity entityIgniter; // CraftBukkit
+ private boolean exploding = false; // Purpur - Config to make Creepers explode on death
public Creeper(final EntityType<? extends Creeper> type, final Level level) {
super(type, level);
@@ -159,6 +_,27 @@
return false; // CraftBukkit
}
+ // Purpur start - Special mobs naturally spawn
+ @Override
+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, net.minecraft.world.entity.@Nullable SpawnGroupData entityData) {
+ double chance = world.getLevel().purpurConfig.creeperChargedChance;
+ if (chance > 0D && random.nextDouble() <= chance) {
+ setPowered(true);
+ }
+ return super.finalizeSpawn(world, difficulty, spawnReason, entityData);
+ }
+ // Purpur end - Special mobs naturally spawn
+
+ // Purpur start - Config to make Creepers explode on death
+ @Override
+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
+ if (!this.exploding && this.level().purpurConfig.creeperExplodeWhenKilled && damageSource.getEntity() instanceof net.minecraft.server.level.ServerPlayer) {
+ this.explodeCreeper();
+ }
+ return super.dropAllDeathLoot(world, damageSource);
+ }
+ // Purpur end - Config to make Creepers explode on death
+
@Override
public SoundEvent getHurtSound(final DamageSource source) {
return SoundEvents.CREEPER_HURT;
@@ -243,14 +_,16 @@
}
public void explodeCreeper() {
+ this.exploding = true; // Purpur - Config to make Creepers explode on death
if (this.level() instanceof ServerLevel level) {
float explosionMultiplier = this.isPowered() ? 2.0F : 1.0F;
+ float multiplier = level.purpurConfig.creeperHealthRadius ? this.getHealth() / this.getMaxHealth() : 1; // Purpur - Config for health to impact Creeper explosion radius
// CraftBukkit start
- org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * explosionMultiplier, false);
+ org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, (this.explosionRadius * explosionMultiplier) * multiplier, false); // Purpur - Config for health to impact Creeper explosion radius
if (!event.isCancelled()) {
// CraftBukkit end
this.dead = true;
- level.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this)
+ level.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), level.getGameRules().get(net.minecraft.world.level.gamerules.GameRules.MOB_GRIEFING) && level().purpurConfig.creeperAllowGriefing ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.NONE); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) // Purpur - Add enderman and creeper griefing controls
this.spawnLingeringCloud();
this.triggerOnDeathMobEffects(level, Entity.RemovalReason.KILLED);
this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
@@ -261,6 +_,7 @@
}
// CraftBukkit end
}
+ this.exploding = false; // Purpur - Config to make Creepers explode on death
}
private void spawnLingeringCloud() {

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/monster/Ghast.java
+++ b/net/minecraft/world/entity/monster/Ghast.java
@@ -156,6 +_,11 @@
public static boolean checkGhastSpawnRules(
final EntityType<Ghast> type, final LevelAccessor level, final EntitySpawnReason spawnReason, final BlockPos pos, final RandomSource random
) {
+ // Purpur start - Config to disable hostile mob spawn on ice
+ if (net.minecraft.world.entity.monster.Monster.canSpawnInBlueAndPackedIce(level, pos)) {
+ return false;
+ }
+ // Purpur end - Config to disable hostile mob spawn on ice
return level.getDifficulty() != Difficulty.PEACEFUL && random.nextInt(20) == 0 && checkMobSpawnRules(type, level, spawnReason, pos, random);
}

View File

@@ -0,0 +1,39 @@
--- a/net/minecraft/world/entity/monster/Monster.java
+++ b/net/minecraft/world/entity/monster/Monster.java
@@ -84,6 +_,11 @@
}
public static boolean isDarkEnoughToSpawn(final ServerLevelAccessor level, final BlockPos pos, final RandomSource random) {
+ // Purpur start - Config to disable hostile mob spawn on ice
+ if (canSpawnInBlueAndPackedIce(level, pos)) {
+ return false;
+ }
+ // Purpur end - Config to disable hostile mob spawn on ice
if (level.getBrightness(LightLayer.SKY, pos) > random.nextInt(32)) {
return false;
} else {
@@ -113,6 +_,11 @@
public static boolean checkAnyLightMonsterSpawnRules(
final EntityType<? extends Monster> type, final LevelAccessor level, final EntitySpawnReason spawnReason, final BlockPos pos, final RandomSource random
) {
+ // Purpur start - Config to disable hostile mob spawn on ice
+ if (canSpawnInBlueAndPackedIce(level, pos)) {
+ return false;
+ }
+ // Purpur end - Config to disable hostile mob spawn on ice
return level.getDifficulty() != Difficulty.PEACEFUL && checkMobSpawnRules(type, level, spawnReason, pos, random);
}
@@ -154,4 +_,12 @@
return ItemStack.EMPTY;
}
}
+
+ // Purpur start - Config to disable hostile mob spawn on ice
+ public static boolean canSpawnInBlueAndPackedIce(LevelAccessor level, BlockPos pos) {
+ net.minecraft.world.level.block.state.BlockState spawnBlock = level.getBlockState(pos.below());
+
+ return (!level.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.PACKED_ICE)) || (!level.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.BLUE_ICE));
+ }
+ // Purpur end - Config to disable hostile mob spawn on ice
}

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/world/entity/monster/Phantom.java
+++ b/net/minecraft/world/entity/monster/Phantom.java
@@ -167,7 +_,11 @@
final ServerLevelAccessor level, final DifficultyInstance difficulty, final EntitySpawnReason spawnReason, final @Nullable SpawnGroupData groupData
) {
this.anchorPoint = this.blockPosition().above(5);
- this.setPhantomSize(0);
+ // Purpur start - Configurable phantom size
+ int min = level.getLevel().purpurConfig.phantomMinSize;
+ int max = level.getLevel().purpurConfig.phantomMaxSize;
+ this.setPhantomSize(min == max ? min : level.getRandom().nextInt(max + 1 - min) + min);
+ // Purpur end - Configurable phantom size
return super.finalizeSpawn(level, difficulty, spawnReason, groupData);
}

View File

@@ -0,0 +1,19 @@
--- a/net/minecraft/world/entity/monster/Ravager.java
+++ b/net/minecraft/world/entity/monster/Ravager.java
@@ -74,6 +_,7 @@
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ if (level().purpurConfig.ravagerAvoidRabbits) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.AvoidEntityGoal<>(this, net.minecraft.world.entity.animal.rabbit.Rabbit.class, 6.0F, 1.0D, 1.2D)); // Purpur - option to make ravagers afraid of rabbits
this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, true));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
@@ -152,7 +_,7 @@
)) {
BlockState state = serverLevel.getBlockState(pos);
Block block = state.getBlock();
- if (block instanceof LeavesBlock) {
+ if (this.level().purpurConfig.ravagerGriefableBlocks.contains(block)) { // Purpur - Configurable ravager griefable blocks list
// CraftBukkit start
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
continue;

View File

@@ -9,3 +9,60 @@
private float currentPeekAmountO;
private float currentPeekAmount;
private @Nullable BlockPos clientOldAttachPosition;
@@ -94,6 +_,21 @@
this.lookControl = new Shulker.ShulkerLookControl(this);
}
+ // Purpur start - Shulker change color with dye
+ @Override
+ protected net.minecraft.world.InteractionResult mobInteract(Player player, net.minecraft.world.InteractionHand hand) {
+ net.minecraft.world.item.ItemStack itemstack = player.getItemInHand(hand);
+ if (player.level().purpurConfig.shulkerChangeColorWithDye && itemstack.getItem() instanceof net.minecraft.world.item.DyeItem dye && dye.getDyeColor() != this.getColor()) {
+ this.setVariant(Optional.of(dye.getDyeColor()));
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ }
+ return super.mobInteract(player, hand);
+ }
+ // Purpur end - Shulker change color with dye
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new LookAtPlayerGoal(this, Player.class, 8.0F, 0.02F, true));
@@ -455,11 +_,21 @@
private void hitByShulkerBullet() {
Vec3 oldPosition = this.position();
AABB oldAabb = this.getBoundingBox();
- if (!this.isClosed() && this.teleportSomewhere()) {
- int shulkerCount = this.level().getEntities(EntityType.SHULKER, oldAabb.inflate(8.0), Entity::isAlive).size();
- float failureChance = (shulkerCount - 1) / 5.0F;
- if (!(this.level().getRandom().nextFloat() < failureChance)) {
+ // Purpur start - Shulker spawn from bullet options
+ if ((!this.level().purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) {
+ float failureChance = this.level().purpurConfig.shulkerSpawnFromBulletBaseChance;
+ if (!this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) {
+ int shulkerCount = this.level().getEntities((net.minecraft.world.level.entity.EntityTypeTest) EntityType.SHULKER, oldAabb.inflate(this.level().purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size();
+ try {
+ failureChance -= ((Number) scriptEngine.eval("let shulkerCount = " + shulkerCount + "; " + this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation)).floatValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ failureChance -= (shulkerCount - 1) / 5.0F;
+ }
+ }
+ if (this.level().getRandom().nextFloat() <= failureChance) {
Shulker baby = EntityType.SHULKER.create(this.level(), EntitySpawnReason.BREEDING);
+ // Purpur end - Shulker spawn from bullet options
if (baby != null) {
baby.setVariant(this.getVariant());
baby.snapTo(oldPosition);
@@ -566,7 +_,7 @@
}
public Optional<DyeColor> getVariant() {
- return Optional.ofNullable(this.getColor());
+ return Optional.ofNullable(this.level().purpurConfig.shulkerSpawnFromBulletRandomColor ? DyeColor.random(this.level().random) : this.getColor()); // Purpur - Shulker spawn from bullet options
}
public @Nullable DyeColor getColor() {

View File

@@ -0,0 +1,21 @@
--- a/net/minecraft/world/entity/monster/Strider.java
+++ b/net/minecraft/world/entity/monster/Strider.java
@@ -400,6 +_,18 @@
@Override
public InteractionResult mobInteract(final Player player, final InteractionHand hand) {
boolean hasFood = this.isFood(player.getItemInHand(hand));
+ // Purpur start
+ if (level().purpurConfig.striderGiveSaddleBack && player.isSecondaryUseActive() && !isFood && isSaddled() && !isVehicle()) {
+ this.setItemSlot(EquipmentSlot.SADDLE, ItemStack.EMPTY);
+ if (!player.getAbilities().instabuild) {
+ ItemStack saddle = new ItemStack(Items.SADDLE);
+ if (!player.getInventory().add(saddle)) {
+ player.drop(saddle, false);
+ }
+ }
+ return InteractionResult.SUCCESS;
+ }
+ // Purpur end
if (!hasFood && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) {
if (!this.level().isClientSide()) {
player.startRiding(this);

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/monster/illager/Vindicator.java
+++ b/net/minecraft/world/entity/monster/illager/Vindicator.java
@@ -131,6 +_,11 @@
RandomSource random = level.getRandom();
this.populateDefaultEquipmentSlots(random, difficulty);
this.populateDefaultEquipmentEnchantments(level, random, difficulty);
+ // Purpur start - Special mobs naturally spawn
+ if (level().purpurConfig.vindicatorJohnnySpawnChance > 0D && random.nextDouble() <= level().purpurConfig.vindicatorJohnnySpawnChance) {
+ setCustomName(Component.translatable("Johnny"));
+ }
+ // Purpur end - Special mobs naturally spawn
return spawnGroupData;
}

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+++ b/net/minecraft/world/entity/monster/piglin/PiglinAi.java
@@ -669,14 +_,24 @@
}
public static boolean isWearingSafeArmor(final LivingEntity livingEntity) {
- for (EquipmentSlot slot : EquipmentSlotGroup.ARMOR) {
- if (livingEntity.getItemBySlot(slot).is(ItemTags.PIGLIN_SAFE_ARMOR)) {
+ for (EquipmentSlot equipmentSlot : EquipmentSlotGroup.ARMOR) {
+ // Purpur start - piglins ignore gold-trimmed armor
+ net.minecraft.world.item.ItemStack itemStack = livingEntity.getItemBySlot(equipmentSlot);
+ if (itemStack.is(ItemTags.PIGLIN_SAFE_ARMOR) || (livingEntity.level().purpurConfig.piglinIgnoresArmorWithGoldTrim && isWearingGoldTrim(itemStack))) {
+ // Purpur end - piglins ignore gold-trimmed armor
return true;
}
}
return false;
}
+
+ // Purpur start - piglins ignore gold-trimmed armor
+ private static boolean isWearingGoldTrim(net.minecraft.world.item.ItemStack itemstack) {
+ net.minecraft.world.item.equipment.trim.ArmorTrim armorTrim = itemstack.getComponents().get(net.minecraft.core.component.DataComponents.TRIM);
+ return armorTrim != null && armorTrim.material().is(net.minecraft.world.item.equipment.trim.TrimMaterials.GOLD);
+ }
+ // Purpur end - piglins ignore gold-trimmed armor
private static void stopWalking(final Piglin body) {
body.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET);

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/world/entity/monster/skeleton/AbstractSkeleton.java
+++ b/net/minecraft/world/entity/monster/skeleton/AbstractSkeleton.java
@@ -142,7 +_,7 @@
this.populateDefaultEquipmentEnchantments(level, random, difficulty);
this.reassessWeaponGoal();
this.setCanPickUpLoot(level.getLevel().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || random.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot
- if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty() && SpecialDates.isHalloween() && random.nextFloat() < 0.25F) {
+ if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty() && (level.getLevel().purpurConfig.forceHalloweenSeason || SpecialDates.isHalloween()) && random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur - Halloween options and optimizations
this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(random.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN));
this.setDropChance(EquipmentSlot.HEAD, 0.0F);
}
@@ -189,7 +_,7 @@
double distanceToTarget = Math.sqrt(xd * xd + zd * zd);
if (this.level() instanceof ServerLevel serverLevel) {
Projectile.Delayed<AbstractArrow> delayedEntity = Projectile.spawnProjectileUsingShootDelayed( // Paper - delayed
- arrow, serverLevel, projectile, xd, yd + distanceToTarget * 0.2F, zd, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4
+ arrow, serverLevel, projectile, xd, yd + distanceToTarget * 0.2F, zd, 1.6F, serverLevel.purpurConfig.skeletonBowAccuracyMap.getOrDefault(serverLevel.getDifficulty().getId(), (float) (14 - serverLevel.getDifficulty().getId() * 4)) // Purpur - skeleton bow accuracy option
);
// Paper start - call EntityShootBowEvent

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/world/entity/monster/zombie/Drowned.java
+++ b/net/minecraft/world/entity/monster/zombie/Drowned.java
@@ -93,10 +_,23 @@
this.goalSelector.addGoal(2, new Drowned.DrownedAttackGoal(this, 1.0, false));
this.goalSelector.addGoal(5, new Drowned.DrownedGoToBeachGoal(this, 1.0));
this.goalSelector.addGoal(6, new Drowned.DrownedSwimUpGoal(this, 1.0, this.level().getSeaLevel()));
+ if (level().purpurConfig.drownedBreakDoors) this.goalSelector.addGoal(6, new net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors)); // Purpur - Option to make drowned break doors
this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Drowned.class).setAlertOthers(ZombifiedPiglin.class));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (target, level) -> this.okTarget(target)));
- if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config
+ // Purpur start - Add option to disable zombie aggressiveness towards villagers
+ if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Paper - Check drowned for villager aggression config
+ @Override
+ public boolean canUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canUse();
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse();
+ }
+ });
+ // Purpur end - Add option to disable zombie aggressiveness towards villagers
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false));
this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/world/entity/monster/zombie/ZombieVillager.java
+++ b/net/minecraft/world/entity/monster/zombie/ZombieVillager.java
@@ -170,10 +_,10 @@
public InteractionResult mobInteract(final Player player, final InteractionHand hand) {
ItemStack itemStack = player.getItemInHand(hand);
if (itemStack.is(Items.GOLDEN_APPLE)) {
- if (this.hasEffect(MobEffects.WEAKNESS)) {
+ if (this.hasEffect(MobEffects.WEAKNESS) && level().purpurConfig.zombieVillagerCureEnabled) { // Purpur - Add option to disable zombie villagers cure
itemStack.consume(1, player);
if (!this.level().isClientSide()) {
- this.startConverting(player.getUUID(), this.random.nextInt(2401) + 3600);
+ this.startConverting(player.getUUID(), this.random.nextInt(level().purpurConfig.zombieVillagerCuringTimeMax - level().purpurConfig.zombieVillagerCuringTimeMin + 1) + level().purpurConfig.zombieVillagerCuringTimeMin); // Purpur - Customizable Zombie Villager curing times
}
return InteractionResult.SUCCESS_SERVER;

View File

@@ -0,0 +1,123 @@
--- a/net/minecraft/world/entity/npc/villager/Villager.java
+++ b/net/minecraft/world/entity/npc/villager/Villager.java
@@ -181,6 +_,8 @@
MemoryModuleType.MEETING_POINT,
(villager, poiType) -> poiType.is(PoiTypes.MEETING)
);
+ private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur - Lobotomize stuck villagers
+ private int notLobotomizedCount = 0; // Purpur - Lobotomize stuck villagers
public Villager(final EntityType<? extends Villager> type, final Level level) {
this(type, level, VillagerType.PLAINS);
@@ -199,6 +_,57 @@
this.setVillagerData(this.getVillagerData().withType(type).withProfession(level.registryAccess(), VillagerProfession.NONE));
}
+ // Purpur start - Allow leashing villagers
+ @Override
+ public boolean canBeLeashed() {
+ return level().purpurConfig.villagerCanBeLeashed;
+ }
+ // Purpur end - Allow leashing villagers
+
+ // Purpur start - Lobotomize stuck villagers
+ private boolean checkLobotomized() {
+ int interval = this.level().purpurConfig.villagerLobotomizeCheckInterval;
+ boolean shouldCheckForTradeLocked = this.level().purpurConfig.villagerLobotomizeWaitUntilTradeLocked;
+ if (this.notLobotomizedCount > 3) {
+ // check half as often if not lobotomized for the last 3+ consecutive checks
+ interval *= 2;
+ }
+ if (this.level().getGameTime() % interval == 0) {
+ // offset Y for short blocks like dirt_path/farmland
+ this.isLobotomized = !(shouldCheckForTradeLocked && this.getVillagerXp() == 0) && !canTravelFrom(BlockPos.containing(this.position().x, this.getBoundingBox().minY + 0.0625D, this.position().z));
+
+ if (this.isLobotomized) {
+ this.notLobotomizedCount = 0;
+ } else {
+ this.notLobotomizedCount++;
+ }
+ }
+ return this.isLobotomized;
+ }
+
+ private boolean canTravelFrom(BlockPos pos) {
+ return canTravelTo(pos.east()) || canTravelTo(pos.west()) || canTravelTo(pos.north()) || canTravelTo(pos.south());
+ }
+
+ private boolean canTravelTo(BlockPos pos) {
+ net.minecraft.world.level.block.state.BlockState state = this.level().getBlockStateIfLoaded(pos);
+ if (state == null) {
+ // chunk not loaded
+ return false;
+ }
+ net.minecraft.world.level.block.Block bottom = state.getBlock();
+ if (bottom instanceof net.minecraft.world.level.block.FenceBlock ||
+ bottom instanceof net.minecraft.world.level.block.FenceGateBlock ||
+ bottom instanceof net.minecraft.world.level.block.WallBlock) {
+ // bottom block is too tall to get over
+ return false;
+ }
+ net.minecraft.world.level.block.Block top = level().getBlockState(pos.above()).getBlock();
+ // only if both blocks have no collision
+ return !bottom.hasCollision && !top.hasCollision;
+ }
+ // Purpur end - Lobotomize stuck villagers
+
@Override
public Brain<Villager> getBrain() {
return (Brain<Villager>) super.getBrain();
@@ -248,7 +_,18 @@
protected void customServerAiStep(final ServerLevel level) {
ProfilerFiller profiler = Profiler.get();
profiler.push("villagerBrain");
- this.getBrain().tick(level, this);
+ // Purpur start - Lobotomize stuck villagers
+ if (this.level().purpurConfig.villagerLobotomizeEnabled) {
+ // treat as inactive if lobotomized
+ inactive = inactive || checkLobotomized();
+ } else {
+ this.isLobotomized = false;
+ }
+ if (!inactive) {
+ this.getBrain().tick(level, this); // Paper - EAR 2
+ }
+ else if (this.isLobotomized && shouldRestock(level)) restock();
+ // Purpur end - Lobotomize stuck villagers
profiler.pop();
if (this.assignProfessionWhenSpawned) {
this.assignProfessionWhenSpawned = false;
@@ -319,6 +_,7 @@
return InteractionResult.CONSUME;
}
+ if (this.level().purpurConfig.villagerAllowTrading) // Purpur - Add config for villager trading
this.startTrading(player);
}
@@ -460,7 +_,7 @@
public void updateDemand() {
for (MerchantOffer offer : this.getOffers()) {
- offer.updateDemand();
+ offer.updateDemand(this.level().purpurConfig.villagerMinimumDemand); // Purpur - Configurable minimum demand for trades
}
}
@@ -660,7 +_,7 @@
@Override
public boolean canBreed() {
- return this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0;
+ return this.level().purpurConfig.villagerCanBreed && this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; // Purpur - Configurable villager breeding
}
private boolean hungry() {
@@ -867,6 +_,7 @@
}
public void spawnGolemIfNeeded(final ServerLevel level, final long timestamp, final int villagersNeededToAgree) {
+ if (level.purpurConfig.villagerSpawnIronGolemRadius > 0 && level.getEntitiesOfClass(net.minecraft.world.entity.animal.golem.IronGolem.class, getBoundingBox().inflate(level.purpurConfig.villagerSpawnIronGolemRadius)).size() > level.purpurConfig.villagerSpawnIronGolemLimit) return; // Purpur - Implement configurable search radius for villagers to spawn iron golems
if (this.wantsToSpawnGolem(timestamp)) {
AABB villagerSearchBox = this.getBoundingBox().inflate(10.0, 10.0, 10.0);
List<Villager> nearbyVillagers = level.getEntitiesOfClass(Villager.class, villagerSearchBox);

View File

@@ -0,0 +1,21 @@
--- a/net/minecraft/world/entity/npc/wanderingtrader/WanderingTraderSpawner.java
+++ b/net/minecraft/world/entity/npc/wanderingtrader/WanderingTraderSpawner.java
@@ -133,7 +_,17 @@
int xPosition = referencePosition.getX() + this.random.nextInt(radius * 2) - radius;
int zPosition = referencePosition.getZ() + this.random.nextInt(radius * 2) - radius;
int yPosition = level.getHeight(SpawnPlacements.getHeightmapType(EntityType.WANDERING_TRADER), xPosition, zPosition);
- BlockPos spawnPos = new BlockPos(xPosition, yPosition, zPosition);
+ // Purpur start - Allow toggling special MobSpawners per world - allow traders to spawn below nether roof
+ BlockPos.MutableBlockPos spawnPos = new BlockPos.MutableBlockPos(xPosition, yPosition, zPosition);
+ if (level.dimensionType().hasCeiling()) {
+ do {
+ spawnPos.relative(net.minecraft.core.Direction.DOWN);
+ } while (!level.getBlockState(spawnPos).isAir());
+ do {
+ spawnPos.relative(net.minecraft.core.Direction.DOWN);
+ } while (level.getBlockState(spawnPos).isAir() && spawnPos.getY() > 0);
+ }
+ // Purpur end - Allow toggling special MobSpawners per world
if (wanderingTraderSpawnType.isSpawnPositionOk(level, spawnPos, EntityType.WANDERING_TRADER)) {
spawnPosition = spawnPos;
break;