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,169 @@
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -153,6 +_,7 @@
import org.slf4j.Logger;
public abstract class Entity implements SyncedDataHolder, DebugValueSource, Nameable, ItemOwner, SlotProvider, EntityAccess, ScoreHolder, DataComponentGetter, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker
+ public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur - Configurable entity base attributes
// CraftBukkit start
private static final int CURRENT_LEVEL = 2;
static boolean isLevelAtLeast(ValueInput input, int level) {
@@ -282,8 +_,9 @@
public double xOld;
public double yOld;
public double zOld;
+ public float maxUpStep; // Purpur - Add option to set armorstand step height
public boolean noPhysics;
- public final RandomSource random = SHARED_RANDOM; // Paper - Share random for entities to make them more random
+ public final RandomSource random; // Paper - Share random for entities to make them more random // Add toggle for RNG manipulation
public int tickCount;
private int remainingFireTicks;
public boolean wasTouchingWater;
@@ -316,8 +_,8 @@
public @Nullable PortalProcessor portalProcess;
public int portalCooldown;
private boolean invulnerable;
- protected UUID uuid = Mth.createInsecureUUID(this.random);
- protected String stringUUID = this.uuid.toString();
+ protected UUID uuid; // Purpur - Add toggle for RNG manipulation
+ protected String stringUUID; // Purpur - Add toggle for RNG manipulation
private boolean hasGlowingTag;
private final Set<String> tags = new io.papermc.paper.util.SizeLimitedSet<>(new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(), MAX_ENTITY_TAG_COUNT); // Paper - fully limit tag size - replace set impl
private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0};
@@ -373,6 +_,7 @@
public long activatedTick = Integer.MIN_VALUE;
public boolean isTemporarilyActive;
public long activatedImmunityTick = Integer.MIN_VALUE;
+ public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API
public void inactiveTick() {
}
@@ -535,10 +_,21 @@
}
// Paper end - optimise entity tracker
+ // Purpur start - Add canSaveToDisk to Entity
+ public boolean canSaveToDisk() {
+ return true;
+ }
+ // Purpur end - Add canSaveToDisk to Entity
+
public Entity(EntityType<?> type, Level level) {
this.type = type;
this.level = level;
this.dimensions = type.getDimensions();
+ // Purpur start - Add toggle for RNG manipulation
+ this.random = level == null || level.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create();
+ this.uuid = Mth.createInsecureUUID(this.random);
+ this.stringUUID = this.uuid.toString();
+ // Purpur end - Add toggle for RNG manipulation
this.position = Vec3.ZERO;
this.blockPosition = BlockPos.ZERO;
this.chunkPosition = ChunkPos.ZERO;
@@ -931,6 +_,7 @@
&& this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v)
&& (!(this instanceof Player player) || !player.getAbilities().invulnerable))) {
// Paper end - Configurable nether ceiling damage
+ if (this.level.purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.level.levelData.getRespawnData().pos(), this.level)); else // Purpur - Add option to teleport to spawn on nether ceiling damage
this.onBelowWorld();
}
}
@@ -1956,7 +_,7 @@
}
public boolean fireImmune() {
- return this.getType().fireImmune();
+ return this.immuneToFire != null ? immuneToFire : this.getType().fireImmune(); // Purpur - add fire immune API
}
public boolean causeFallDamage(double fallDistance, float damageMultiplier, DamageSource damageSource) {
@@ -2654,6 +_,11 @@
output.putBoolean("Paper.FreezeLock", true);
}
// Paper end
+ // Purpur start - Fire immune API
+ if (immuneToFire != null) {
+ output.putBoolean("Purpur.FireImmune", immuneToFire);
+ }
+ // Purpur end - Fire immune API
} catch (Throwable var7) {
CrashReport crashReport = CrashReport.forThrowable(var7, "Saving entity NBT");
CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being saved");
@@ -2774,6 +_,9 @@
}
freezeLocked = input.getBooleanOr("Paper.FreezeLock", false);
// Paper end
+
+ immuneToFire = input.read("Purpur.FireImmune", com.mojang.serialization.Codec.BOOL).orElse(null); // Purpur - Fire immune API
+
} catch (Throwable var7) {
CrashReport crashReport = CrashReport.forThrowable(var7, "Loading entity NBT");
CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being loaded");
@@ -3039,6 +_,7 @@
if (this.isAlive() && this instanceof Leashable leashable2) {
if (leashable2.getLeashHolder() == player) {
if (!this.level().isClientSide()) {
+ if (hand == InteractionHand.OFF_HAND && (level().purpurConfig.villagerCanBeLeashed || level().purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.AbstractVillager) return InteractionResult.CONSUME; // Purpur - Allow leashing villagers
// Paper start - EntityUnleashEvent
if (!org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerUnleashEntityEvent(
leashable2, player, hand, !player.hasInfiniteMaterials(), true
@@ -3469,15 +_,18 @@
return Vec3.directionFromRotation(this.getRotationVector());
}
+ public BlockPos portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals
public void setAsInsidePortal(Portal portal, BlockPos pos) {
if (this.isOnPortalCooldown()) {
+ if (!(level().purpurConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(this.portalPos))) // Purpur - Fix stuck in portals
this.setPortalCooldown();
- } else {
+ } else if (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer) { // Purpur - Entities can use portals
if (this.portalProcess == null || !this.portalProcess.isSamePortal(portal)) {
this.portalProcess = new PortalProcessor(portal, pos.immutable());
} else if (!this.portalProcess.isInsidePortalThisTick()) {
this.portalProcess.updateEntryPosition(pos.immutable());
this.portalProcess.setAsInsidePortalThisTick(true);
+ this.portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals
}
}
}
@@ -3683,7 +_,7 @@
}
public int getMaxAirSupply() {
- return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+ return this.level == null? this.maxAirTicks : this.level().purpurConfig.drowningAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() // Purpur - Drowning Settings
}
public int getAirSupply() {
@@ -4218,7 +_,7 @@
}
public boolean canUsePortal(boolean allowPassengers) {
- return (allowPassengers || !this.isPassenger()) && this.isAlive();
+ return (allowPassengers || !this.isPassenger()) && this.isAlive() && (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Purpur - Entities can use portals
}
public boolean canTeleport(Level fromLevel, Level toLevel) {
@@ -4737,6 +_,12 @@
return Mth.lerp(partialTick, this.yRotO, this.yRot);
}
+ // Purpur start - Stop squids floating on top of water
+ public AABB getAxisForFluidCheck() {
+ return this.getBoundingBox().deflate(0.001D);
+ }
+ // Purpur end - Stop squids floating on top of water
+
// Paper start - optimise collisions
public boolean updateFluidHeightAndDoFluidPushing(final TagKey<Fluid> fluid, final double flowScale) {
if (this.touchingUnloadedChunk()) {
@@ -5157,7 +_,7 @@
}
public float maxUpStep() {
- return 0.0F;
+ return maxUpStep; // Purpur - Add option to set armorstand step height
}
public void onExplosionHit(@Nullable Entity entity) {

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/EntitySelector.java
+++ b/net/minecraft/world/entity/EntitySelector.java
@@ -28,6 +_,8 @@
return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks;
};
// Paper end - Ability to control player's insomnia and phantoms
+ public static Predicate<Player> notAfk = (player) -> !player.isAfk(); // Purpur - AFK API
+
// Paper start - Affects Spawning API
public static final Predicate<Entity> PLAYER_AFFECTS_SPAWNING = (entity) -> {
return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning;

View File

@@ -0,0 +1,44 @@
--- a/net/minecraft/world/entity/EntityType.java
+++ b/net/minecraft/world/entity/EntityType.java
@@ -1240,6 +_,16 @@
return register(vanillaEntityId(key), builder);
}
+ // Purpur start - PlayerSetSpawnerTypeWithEggEvent
+ public static EntityType<?> getFromBukkitType(org.bukkit.entity.EntityType bukkitType) {
+ return getFromKey(Identifier.parse(bukkitType.getKey().toString()));
+ }
+
+ public static EntityType<?> getFromKey(Identifier location) {
+ return BuiltInRegistries.ENTITY_TYPE.getValue(location);
+ }
+ // Purpur end - PlayerSetSpawnerTypeWithEggEvent
+
public static Identifier getKey(EntityType<?> entityType) {
return BuiltInRegistries.ENTITY_TYPE.getKey(entityType);
}
@@ -1467,6 +_,16 @@
return this.category;
}
+ // Purpur start - PlayerSetSpawnerTypeWithEggEvent
+ public String getName() {
+ return BuiltInRegistries.ENTITY_TYPE.getKey(this).getPath();
+ }
+
+ public String getTranslatedName() {
+ return getDescription().getString();
+ }
+ // Purpur end - PlayerSetSpawnerTypeWithEggEvent
+
public String getDescriptionId() {
return this.descriptionId;
}
@@ -1528,6 +_,7 @@
// Paper start - Add logging for debugging entity tags with invalid ids
() -> {
LOGGER.warn("Skipping Entity with id {}", input.getStringOr("id", "[invalid]"));
+ LOGGER.warn("Location: {} {}", level.getWorld().getName(), input.read("Pos", net.minecraft.world.phys.Vec3.CODEC).orElse(net.minecraft.world.phys.Vec3.ZERO)); // Purpur - log skipped entity's position
if ((DEBUG_ENTITIES_WITH_INVALID_IDS || level.getCraftServer().getServer().isDebugging()) && input instanceof TagValueInput tagInput) {
LOGGER.warn("Skipped entity tag: {}", tagInput.input);
}

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/world/entity/ExperienceOrb.java
+++ b/net/minecraft/world/entity/ExperienceOrb.java
@@ -355,7 +_,7 @@
public void playerTouch(Player entity) {
if (entity instanceof ServerPlayer serverPlayer) {
if (entity.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent
- entity.takeXpDelay = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerXpCooldownEvent(entity, 2, org.bukkit.event.player.PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entity.takeXpDelay = 2;
+ entity.takeXpDelay = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerXpCooldownEvent(entity, this.level().purpurConfig.playerExpPickupDelay, org.bukkit.event.player.PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entity.takeXpDelay = 2; // Purpur - Configurable player pickup exp delay
entity.take(this, 1);
int i = this.repairPlayerItems(serverPlayer, this.getValue());
if (i > 0) {
@@ -371,7 +_,7 @@
}
private int repairPlayerItems(ServerPlayer player, int value) {
- Optional<EnchantedItemInUse> randomItemWith = EnchantmentHelper.getRandomItemWith(
+ Optional<EnchantedItemInUse> randomItemWith = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) : EnchantmentHelper.getRandomItemWith( // Purpur - Add option to mend the most damaged equipment first
EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged
);
if (randomItemWith.isPresent()) {

View File

@@ -0,0 +1,188 @@
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -447,6 +_,12 @@
if (d < 0.0) {
double damagePerBlock = serverLevel1.getWorldBorder().getDamagePerBlock();
if (damagePerBlock > 0.0) {
+ // Purpur start - Add option to teleport to spawn if outside world border
+ if (this.level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) {
+ serverPlayer.teleport(org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.level().levelData.getRespawnData().pos(), this.level()));
+ return;
+ }
+ // Purpur end - Add option to teleport to spawn if outside world border
this.hurtServer(serverLevel1, this.damageSources().outOfBorder(), Math.max(1, Mth.floor(-d * damagePerBlock)));
}
}
@@ -462,7 +_,7 @@
if (this.shouldTakeDrowningDamage()) {
this.setAirSupply(0);
serverLevel1.broadcastEntityEvent(this, EntityEvent.DROWN_PARTICLES);
- this.hurtServer(serverLevel1, this.damageSources().drown(), 2.0F);
+ this.hurtServer(serverLevel1, this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur - Drowning Settings
}
} else if (this.getAirSupply() < this.getMaxAirSupply() && MobEffectUtil.shouldEffectsRefillAirsupply(this)) {
this.setAirSupply(this.increaseAirSupply(this.getAirSupply()));
@@ -522,7 +_,7 @@
}
protected boolean shouldTakeDrowningDamage() {
- return this.getAirSupply() <= -20;
+ return this.getAirSupply() <= -this.level().purpurConfig.drowningDamageInterval; // Purpur - Drowning Settings
}
@Override
@@ -1050,14 +_,32 @@
if (lookingEntity != null) {
ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD);
EntityType<?> type = lookingEntity.getType();
- if (type == EntityType.SKELETON && itemBySlot.is(Items.SKELETON_SKULL)
- || type == EntityType.ZOMBIE && itemBySlot.is(Items.ZOMBIE_HEAD)
- || type == EntityType.PIGLIN && itemBySlot.is(Items.PIGLIN_HEAD)
- || type == EntityType.PIGLIN_BRUTE && itemBySlot.is(Items.PIGLIN_HEAD)
- || type == EntityType.CREEPER && itemBySlot.is(Items.CREEPER_HEAD)) {
- d *= 0.5;
- }
- }
+ // Purpur start - Mob head visibility percent
+ if (type == EntityType.SKELETON && itemBySlot.is(Items.SKELETON_SKULL)) {
+ d *= lookingEntity.level().purpurConfig.skeletonHeadVisibilityPercent;
+ }
+ else if (type == EntityType.ZOMBIE && itemBySlot.is(Items.ZOMBIE_HEAD)) {
+ d *= lookingEntity.level().purpurConfig.zombieHeadVisibilityPercent;
+ }
+ else if ((type == EntityType.PIGLIN || type == EntityType.PIGLIN_BRUTE) && itemBySlot.is(Items.PIGLIN_HEAD)) {
+ d *= lookingEntity.level().purpurConfig.piglinHeadVisibilityPercent;
+ }
+ else if (type == EntityType.CREEPER && itemBySlot.is(Items.CREEPER_HEAD)) {
+ d *= lookingEntity.level().purpurConfig.creeperHeadVisibilityPercent;
+ }
+ // Purpur end - Mob head visibility percent
+ }
+
+ // Purpur start - Configurable mob blindness
+ if (lookingEntity instanceof LivingEntity entityliving) {
+ if (entityliving.hasEffect(MobEffects.BLINDNESS)) {
+ int amplifier = entityliving.getEffect(MobEffects.BLINDNESS).getAmplifier();
+ for (int i = 0; i < amplifier; i++) {
+ d *= this.level().purpurConfig.mobsBlindnessMultiplier;
+ }
+ }
+ }
+ // Purpur end - Configurable mob blindness
return d;
}
@@ -1104,6 +_,7 @@
Iterator<MobEffectInstance> iterator = this.activeEffects.values().iterator();
while (iterator.hasNext()) {
MobEffectInstance effect = iterator.next();
+ if (cause == EntityPotionEffectEvent.Cause.MILK && !this.level().purpurConfig.milkClearsBeneficialEffects && effect.getEffect().value().isBeneficial()) continue; // Purpur - Milk Keeps Beneficial Effects
EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED);
if (event.isCancelled()) {
continue;
@@ -1424,6 +_,24 @@
this.stopSleeping();
}
+ // Purpur start - One Punch Man!
+ if (damageSource.getEntity() instanceof net.minecraft.world.entity.player.Player player && damageSource.getEntity().level().purpurConfig.creativeOnePunch && !damageSource.is(DamageTypeTags.IS_PROJECTILE)) {
+ if (player.isCreative()) {
+ org.apache.commons.lang3.mutable.MutableDouble attackDamage = new org.apache.commons.lang3.mutable.MutableDouble();
+ player.getMainHandItem().forEachModifier(EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> {
+ if (attributeModifier.operation() == AttributeModifier.Operation.ADD_VALUE) {
+ attackDamage.addAndGet(attributeModifier.amount());
+ }
+ });
+
+ if (attackDamage.doubleValue() == 0.0D) {
+ // One punch!
+ amount = 9999F;
+ }
+ }
+ }
+ // Purpur end - One Punch Man!
+
this.noActionTime = 0;
if (amount < 0.0F) {
amount = 0.0F;
@@ -1685,10 +_,10 @@
protected @Nullable Player resolvePlayerResponsibleForDamage(DamageSource damageSource) {
Entity entity = damageSource.getEntity();
if (entity instanceof Player player) {
- this.setLastHurtByPlayer(player, 100);
+ this.setLastHurtByPlayer(player, this.level().purpurConfig.mobLastHurtByPlayerTime); // Purpur - Config for mob last hurt by player time
} else if (entity instanceof Wolf wolf && wolf.isTame()) {
if (wolf.getOwnerReference() != null) {
- this.setLastHurtByPlayer(wolf.getOwnerReference().getUUID(), 100);
+ this.setLastHurtByPlayer(wolf.getOwnerReference().getUUID(), this.level().purpurConfig.mobLastHurtByPlayerTime); // Purpur - Config for mob last hurt by player time
} else {
this.lastHurtByPlayer = null;
this.lastHurtByPlayerMemoryTime = 0;
@@ -1739,6 +_,30 @@
}
}
+ // Purpur start - Totems work in inventory
+ if (level().purpurConfig.totemOfUndyingWorksInInventory && this instanceof ServerPlayer player && (itemStack == null || itemStack.getItem() != Items.TOTEM_OF_UNDYING) && player.getBukkitEntity().hasPermission("purpur.inventory_totem")) {
+ for (ItemStack item : player.getInventory().getNonEquipmentItems()) {
+ if (item.getItem() == Items.TOTEM_OF_UNDYING) {
+ itemInHand = item;
+ itemStack = item.copy();
+ break;
+ }
+ }
+ }
+ // Purpur end - Totems work in inventory
+
+ // Purpur start - Totems work in inventory
+ if (level().purpurConfig.totemOfUndyingWorksInInventory && this instanceof ServerPlayer player && (itemStack == null || itemStack.getItem() != Items.TOTEM_OF_UNDYING) && player.getBukkitEntity().hasPermission("purpur.inventory_totem")) {
+ for (ItemStack item : player.getInventory().getNonEquipmentItems()) {
+ if (item.getItem() == Items.TOTEM_OF_UNDYING) {
+ itemInHand = item;
+ itemStack = item.copy();
+ break;
+ }
+ }
+ }
+ // Purpur end - Totems work in inventory
+
final org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null;
final EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot);
event.setCancelled(itemStack == null);
@@ -1920,6 +_,7 @@
boolean flag = this.lastHurtByPlayerMemoryTime > 0;
this.dropEquipment(level); // CraftBukkit - from below
if (this.shouldDropLoot(level)) {
+ if (!(damageSource.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level().purpurConfig.disableDropsOnCrammingDeath)) { // Purpur - Disable loot drops on death by cramming
this.dropFromLootTable(level, damageSource, flag);
// Paper start
final boolean prev = this.clearEquipmentSlots;
@@ -1928,6 +_,7 @@
// Paper end
this.dropCustomDeathLoot(level, damageSource, flag);
this.clearEquipmentSlots = prev; // Paper
+ } // Purpur - Disable loot drops on death by cramming
}
// CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment
@@ -3202,6 +_,7 @@
float f = (float)(d * 10.0 - 3.0);
if (f > 0.0F) {
this.playSound(this.getFallDamageSound((int)f), 1.0F, 1.0F);
+ if (level().purpurConfig.elytraKineticDamage) // Purpur - Toggle for kinetic damage
this.hurt(this.damageSources().flyIntoWall(), f);
}
}
@@ -4671,6 +_,12 @@
? slot == EquipmentSlot.MAINHAND && this.canUseSlot(EquipmentSlot.MAINHAND)
: slot == equippable.slot() && this.canUseSlot(equippable.slot()) && equippable.canBeEquippedBy(this.getType());
}
+
+ // Purpur start - Dispenser curse of binding protection
+ public @Nullable EquipmentSlot getEquipmentSlotForDispenserItem(ItemStack itemstack) {
+ return EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.BINDING_CURSE, itemstack) > 0 ? null : this.getEquipmentSlotForItem(itemstack);
+ }
+ // Purpur end - Dispenser curse of binding protection
private static SlotAccess createEquipmentSlotAccess(LivingEntity entity, EquipmentSlot slot) {
return slot != EquipmentSlot.HEAD && slot != EquipmentSlot.MAINHAND && slot != EquipmentSlot.OFFHAND

View File

@@ -0,0 +1,80 @@
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
@@ -150,6 +_,7 @@
private int homeRadius = -1;
public boolean aware = true; // CraftBukkit
public net.kyori.adventure.util.TriState despawnInPeacefulOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - allow changing despawnInPeaceful
+ public int ticksSinceLastInteraction; // Purpur - Entity lifespan
protected Mob(EntityType<? extends Mob> type, Level level) {
super(type, level);
@@ -292,6 +_,7 @@
target = null;
}
}
+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
this.target = target;
return true;
// CraftBukkit end
@@ -335,7 +_,27 @@
}
profilerFiller.pop();
- }
+ incrementTicksSinceLastInteraction(); // Purpur - Entity lifespan
+ }
+
+ // Purpur start - Entity lifespan
+ private void incrementTicksSinceLastInteraction() {
+ ++this.ticksSinceLastInteraction;
+ if (getRider() != null) {
+ this.ticksSinceLastInteraction = 0;
+ return;
+ }
+ if (this.level().purpurConfig.entityLifeSpan <= 0) {
+ return; // feature disabled
+ }
+ if (!this.removeWhenFarAway(0) || isPersistenceRequired() || requiresCustomPersistence() || hasCustomName()) {
+ return; // mob persistent
+ }
+ if (this.ticksSinceLastInteraction > this.level().purpurConfig.entityLifeSpan) {
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ }
+ }
+ // Purpur end - Entity lifespan
@Override
protected void playHurtSound(DamageSource damageSource) {
@@ -439,6 +_,7 @@
output.putString("Paper.DespawnInPeacefulOverride", this.despawnInPeacefulOverride.name());
}
// Paper end - allow changing despawnInPeaceful
+ output.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur - Entity lifespan
}
@Override
@@ -466,6 +_,7 @@
this.lootTableSeed = input.getLongOr("DeathLootTableSeed", 0L);
this.setNoAi(input.getBooleanOr("NoAI", false));
this.aware = input.getBooleanOr("Bukkit.Aware", true); // CraftBukkit
+ this.ticksSinceLastInteraction = input.getIntOr("Purpur.ticksSinceLastInteraction", 0); // Purpur- Entity lifespan
// Paper start - allow changing despawnInPeaceful
this.despawnInPeacefulOverride = readDespawnInPeacefulOverride(input);
}
@@ -1246,7 +_,7 @@
);
}
- this.setLeftHanded(random.nextFloat() < 0.05F);
+ this.setLeftHanded(random.nextFloat() < level.getLevel().purpurConfig.entityLeftHandedChance); // Purpur - Changeable Mob Left Handed Chance
return spawnGroupData;
}
@@ -1597,6 +_,7 @@
}
this.lungeForwardMaybe();
+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
return flag;
}

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(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
@@ -85,7 +_,7 @@
};
// Paper start - optimise POI access
final java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
- io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, acquirablePois, predicate1, mob.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes);
+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, acquirablePois, predicate1, mob.blockPosition(), level.purpurConfig.villagerAcquirePoiSearchRadius, level.purpurConfig.villagerAcquirePoiSearchRadius*level.purpurConfig.villagerAcquirePoiSearchRadius, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); // Purpur - Configurable villager search radius
final Set<Pair<Holder<PoiType>, BlockPos>> set = new java.util.HashSet<>(poiposes.size());
for (final Pair<Holder<PoiType>, BlockPos> poiPose : poiposes) {
if (predicate.test(level, poiPose.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
@@ -55,7 +_,7 @@
Node nextNode = path.getNextNode();
BlockPos blockPos = previousNode.asBlockPos();
BlockState blockState = level.getBlockState(blockPos);
- if (blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+ if (blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)&& !DoorBlock.requiresRedstone(entity.level(), blockState, blockPos)) { // Purpur - Option to make doors require redstone
DoorBlock doorBlock = (DoorBlock)blockState.getBlock();
if (!doorBlock.isOpen(blockState)) {
// CraftBukkit start - entities opening doors
@@ -72,7 +_,7 @@
BlockPos blockPos1 = nextNode.asBlockPos();
BlockState blockState1 = level.getBlockState(blockPos1);
- if (blockState1.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+ if (blockState1.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock) && !DoorBlock.requiresRedstone(entity.level(), blockState1, blockPos1)) { // Purpur - Option to make doors require redstone
DoorBlock doorBlock1 = (DoorBlock)blockState1.getBlock();
if (!doorBlock1.isOpen(blockState1)) {
// CraftBukkit start - entities opening doors
@@ -118,7 +_,7 @@
iterator.remove();
} else {
BlockState blockState = level.getBlockState(blockPos);
- if (!blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+ if (!blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock) || DoorBlock.requiresRedstone(entity.level(), blockState, blockPos)) { // Purpur - Option to make doors require redstone
iterator.remove();
} else {
DoorBlock doorBlock = (DoorBlock)blockState.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
@@ -45,6 +_,7 @@
@Override
public boolean canStillUse(ServerLevel level, Villager entity, long gameTime) {
+ if (!entity.level().purpurConfig.villagerDisplayTradeItem) return false; // Purpur - Option for villager display trade item
return this.checkExtraStartConditions(level, entity)
&& this.lookTime > 0
&& entity.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).isPresent();

View File

@@ -0,0 +1,33 @@
--- a/net/minecraft/world/entity/ai/behavior/TransportItemsBetweenContainers.java
+++ b/net/minecraft/world/entity/ai/behavior/TransportItemsBetweenContainers.java
@@ -285,7 +_,7 @@
LevelChunk chunkNow = level.getChunkSource().getChunkNow(chunkPos.x, chunkPos.z);
if (chunkNow != null) {
for (BlockEntity blockEntity : chunkNow.getBlockEntities().values()) {
- if (blockEntity instanceof ChestBlockEntity chestBlockEntity) {
+ if (blockEntity instanceof net.minecraft.world.level.block.entity.BaseContainerBlockEntity chestBlockEntity) { // Purpur - copper golem can place items in barrels or shulkers option
double d1 = chestBlockEntity.getBlockPos().distToCenterSqr(mob.position());
if (d1 < d) {
TransportItemsBetweenContainers.TransportItemTarget transportItemTarget1 = this.isTargetValidToPick(
@@ -369,7 +_,11 @@
}
private boolean isTargetBlocked(Level level, 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 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(Level level, TransportItemsBetweenContainers.TransportItemTarget target) {
@@ -446,7 +_,7 @@
}
private boolean isWantedBlock(PathfinderMob mob, BlockState state) {
- return isPickingUpItems(mob) ? this.sourceBlockType.test(state) : this.destinationBlockType.test(state);
+ return isPickingUpItems(mob) ? this.sourceBlockType.test(state) : (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(state); // Purpur - copper golem can place items in barrels or shulkers option
}
private static double getInteractionRange(PathfinderMob mob) {

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
+++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
@@ -22,6 +_,7 @@
@Override
public boolean canUse() {
+ if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur - Llama API // Purpur - Config to disable Llama caravans
if (!this.llama.isLeashed() && !this.llama.inCaravan()) {
List<Entity> entities = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0, 4.0, 9.0), entity1 -> {
EntityType<?> type = entity1.getType();
@@ -71,6 +_,7 @@
@Override
public boolean canContinueToUse() {
+ if (!this.llama.shouldJoinCaravan) return false; // Purpur - Llama API
if (this.llama.inCaravan() && this.llama.getCaravanHead().isAlive() && this.firstIsLeashed(this.llama, 0)) {
double d = this.llama.distanceToSqr(this.llama.getCaravanHead());
if (d > 676.0) {

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
+++ b/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
@@ -116,9 +_,9 @@
}
this.mob.lookAt(target, 30.0F, 30.0F);
- } else {
+ } //else { // Purpur - MC-121706 - Fix mobs not looking up and down when strafing
this.mob.getLookControl().setLookAt(target, 30.0F, 30.0F);
- }
+ //} // Purpur - MC-121706 - Fix mobs not looking up and down when strafing
if (this.mob.isUsingItem()) {
if (!hasLineOfSight && this.seeTime < -60) {

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
+++ b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
@@ -59,7 +_,7 @@
if (firstPassenger instanceof Player player) {
int temper = this.horse.getTemper();
int maxTemper = this.horse.getMaxTemper();
- if (maxTemper > 0 && this.horse.getRandom().nextInt(maxTemper) < temper && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horse, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent
+ if (((this.horse.level().purpurConfig.alwaysTameInCreative && player.hasInfiniteMaterials()) || (maxTemper > 0 && this.horse.getRandom().nextInt(maxTemper) < temper)) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horse, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent // Purpur - Config to always tame in Creative
this.horse.tameWithName(player);
return;
}

View File

@@ -0,0 +1,17 @@
--- a/net/minecraft/world/entity/ai/goal/SwellGoal.java
+++ b/net/minecraft/world/entity/ai/goal/SwellGoal.java
@@ -54,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
}
}
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
@@ -56,7 +_,7 @@
// Paper start - optimise POI access
java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
// don't ask me why it's unbounded. ask mojang.
- io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes);
+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), level.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur - Configurable villager search radius
Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
// Paper end - optimise POI access
if (path != null && path.canReach()) {

View File

@@ -0,0 +1,13 @@
--- a/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+++ b/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
@@ -63,6 +_,10 @@
return false;
} else if (this.selector != null && !this.selector.test(target, level)) {
return false;
+ // Purpur start - AFK API
+ } else if (!level.purpurConfig.idleTimeoutTargetPlayer && target instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) {
+ return false;
+ // Purpur end - AFK API
} else {
if (entity == null) {
if (this.isCombat && (!target.canBeSeenAsEnemy() || level.getDifficulty() == Difficulty.PEACEFUL)) {

View File

@@ -0,0 +1,34 @@
--- a/net/minecraft/world/entity/animal/Animal.java
+++ b/net/minecraft/world/entity/animal/Animal.java
@@ -140,7 +_,7 @@
ItemStack itemInHand = player.getItemInHand(hand);
if (this.isFood(itemInHand)) {
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 = itemInHand.copy(); // Paper - Fix EntityBreedEvent copying
this.usePlayerItem(player, hand, itemInHand);
this.setInLove(serverPlayer, breedCopy); // Paper - Fix EntityBreedEvent copying
@@ -221,10 +_,20 @@
public void spawnChildFromBreeding(ServerLevel level, Animal partner) {
AgeableMob breedOffspring = this.getBreedOffspring(level, partner);
if (breedOffspring != null) {
- breedOffspring.setBaby(true);
- breedOffspring.snapTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+ //breedOffspring.setBaby(true); // Purpur - Add adjustable breeding cooldown to config - moved down
+ //breedOffspring.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());
+ }
+ breedOffspring.setBaby(true);
+ breedOffspring.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(breedOffspring, this, partner, breeder, this.breedItem, experience);
if (entityBreedEvent.isCancelled()) {

View File

@@ -0,0 +1,63 @@
--- a/net/minecraft/world/entity/animal/bee/Bee.java
+++ b/net/minecraft/world/entity/animal/bee/Bee.java
@@ -171,7 +_,7 @@
// Paper end - Fix MC-167279
this.lookControl = new Bee.BeeLookControl(this);
this.setPathfindingMalus(PathType.DANGER_FIRE, -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);
@@ -367,6 +_,10 @@
}
}
+ /*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
+ }*/
+
public void setStayOutOfHiveCountdown(int stayOutOfHiveCountdown) {
this.stayOutOfHiveCountdown = stayOutOfHiveCountdown;
}
@@ -387,7 +_,7 @@
@Override
protected void customServerAiStep(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;
@@ -397,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) {
@@ -1130,6 +_,7 @@
Bee.this.savedFlowerPos = optional.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);
@@ -1176,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
@@ -1222,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, itemStack -> itemStack.is(ItemTags.COW_FOOD), false));
+ this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, itemStack -> level().purpurConfig.cowFeedMushrooms > 0 && (itemStack.is(net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) || itemStack.is(net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem())) || itemStack.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));
@@ -96,6 +_,10 @@
ItemStack itemStack = ItemUtils.createFilledResult(itemInHand, player, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
player.setItemInHand(hand, itemStack);
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);
}
@@ -105,4 +_,67 @@
public EntityDimensions getDefaultDimensions(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,16 @@
--- a/net/minecraft/world/entity/animal/cow/MushroomCow.java
+++ b/net/minecraft/world/entity/animal/cow/MushroomCow.java
@@ -199,6 +_,13 @@
level.playSound(null, this, SoundEvents.MOOSHROOM_SHEAR, source, 1.0F, 1.0F);
this.convertTo(EntityType.COW, ConversionParams.single(this, false, false), cow -> {
level.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5), this.getZ(), 1, 0.0, 0.0, 0.0, 0.0);
+ // Purpur start - Fix cow rotation when shearing mooshroom
+ cow.copyPosition(this);
+ cow.yBodyRot = this.yBodyRot;
+ cow.setYHeadRot(this.getYHeadRot());
+ cow.yRotO = this.yRotO;
+ cow.xRotO = this.xRotO;
+ // Purpur end - Fix cow rotation when shearing mooshroom
// Paper start - custom shear drops; moved drop generation to separate method
drops.forEach(drop -> {
this.spawnAtLocation(level, new ItemEntity(this.level(), this.getX(), this.getY(1.0), this.getZ(), drop));

View File

@@ -0,0 +1,47 @@
--- a/net/minecraft/world/entity/animal/dolphin/Dolphin.java
+++ b/net/minecraft/world/entity/animal/dolphin/Dolphin.java
@@ -75,6 +_,7 @@
public static final float BABY_SCALE = 0.65F;
private static final boolean DEFAULT_GOT_FISH = false;
@Nullable public BlockPos treasurePos;
+ private boolean isNaturallyAggressiveToPlayers; // Purpur - Dolphins naturally aggressive to players chance
public Dolphin(EntityType<? extends Dolphin> type, Level level) {
super(type, level);
@@ -90,6 +_,7 @@
this.setAirSupply(this.getMaxAirSupply());
this.setXRot(0.0F);
SpawnGroupData spawnGroupData1 = Objects.requireNonNullElseGet(spawnGroupData, () -> 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, spawnGroupData1);
}
@@ -155,17 +_,19 @@
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 FollowBoatGoal(this));
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() {
@@ -392,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
@@ -76,6 +_,7 @@
boolean didSpit;
private @Nullable Llama caravanHead;
public @Nullable Llama caravanTail; // Paper - public
+ public boolean shouldJoinCaravan = true; // Purpur - Llama API
public Llama(EntityType<? extends Llama> type, Level level) {
super(type, level);
@@ -105,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
@@ -112,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
@@ -388,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;
}
@@ -395,6 +_,7 @@
}
public void joinCaravan(Llama caravanHead) {
+ 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 = caravanHead;
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
@@ -354,6 +_,14 @@
return this.isTame() && otherAnimal instanceof Cat cat && cat.isTame() && super.canMate(otherAnimal);
}
+ // 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(
ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
@@ -451,7 +_,7 @@
}
private void tryToTame(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(LevelReader level) {
if (level.isUnobstructed(this) && !level.containsAnyLiquid(this.getBoundingBox())) {
BlockPos blockPos = this.blockPosition();
- if (blockPos.getY() < level.getSeaLevel()) {
+ if (!level().purpurConfig.ocelotSpawnUnderSeaLevel && blockPos.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
@@ -76,8 +_,7 @@
seaLevel = level.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(seaLevel);
i = level.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(i);
// Paper end - Make water animal spawn height configurable
- return pos.getY() >= i
- && pos.getY() <= seaLevel
+ return ((spawnReason == EntitySpawnReason.SPAWNER && level.getMinecraftWorld().purpurConfig.spawnerFixMC238526) || (pos.getY() >= i && 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,52 @@
--- a/net/minecraft/world/entity/animal/fox/Fox.java
+++ b/net/minecraft/world/entity/animal/fox/Fox.java
@@ -357,6 +_,11 @@
}
private void setTargetGoals() {
+ // Purpur start - Tulips change fox type - do not add duplicate goals
+ this.targetSelector.removeGoal(this.landTargetGoal);
+ this.targetSelector.removeGoal(this.turtleEggTargetGoal);
+ this.targetSelector.removeGoal(this.fishTargetGoal);
+ // Purpur end - Tulips change fox type
if (this.getVariant() == Fox.Variant.RED) {
this.targetSelector.addGoal(4, this.landTargetGoal);
this.targetSelector.addGoal(4, this.turtleEggTargetGoal);
@@ -384,6 +_,7 @@
public void setVariant(Fox.Variant variant) {
this.entityData.set(DATA_TYPE_ID, variant.getId());
+ this.setTargetGoals(); // Purpur - Tulips change fox type - fix API bug not updating pathfinders on type change
}
@Override
@@ -709,6 +_,29 @@
return slot == EquipmentSlot.MAINHAND;
}
// Paper end
+
+ // Purpur start - Tulips change fox type
+ @Override
+ public net.minecraft.world.InteractionResult mobInteract(Player player, net.minecraft.world.InteractionHand hand) {
+ if (level().purpurConfig.foxTypeChangesWithTulips) {
+ ItemStack itemstack = player.getItemInHand(hand);
+ if (getVariant() == Variant.RED && itemstack.getItem() == Items.WHITE_TULIP) {
+ setVariant(Variant.SNOW);
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ } else if (getVariant() == Variant.SNOW && itemstack.getItem() == Items.ORANGE_TULIP) {
+ setVariant(Variant.RED);
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ }
+ }
+ return super.mobInteract(player, hand);
+ }
+ // Purpur end - Tulips change fox type
@Override
// Paper start - Cancellable death event

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/entity/animal/goat/Goat.java
+++ b/net/minecraft/world/entity/animal/goat/Goat.java
@@ -389,6 +_,7 @@
// Paper start - Goat ram API
public void ram(net.minecraft.world.entity.LivingEntity entity) {
+ if(!new org.purpurmc.purpur.event.entity.GoatRamEntityEvent((org.bukkit.entity.Goat) getBukkitEntity(), entity.getBukkitLivingEntity()).callEvent()) return; // Purpur - Added goat ram event
Brain<Goat> brain = this.getBrain();
brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position());
brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS);

View File

@@ -0,0 +1,44 @@
--- a/net/minecraft/world/entity/animal/golem/CopperGolem.java
+++ b/net/minecraft/world/entity/animal/golem/CopperGolem.java
@@ -83,6 +_,7 @@
private final AnimationState interactionDropItemAnimationState = new AnimationState();
private final AnimationState interactionDropNoItemAnimationState = new AnimationState();
public static final EquipmentSlot EQUIPMENT_SLOT_ANTENNA = EquipmentSlot.SADDLE;
+ @Nullable private UUID summoner; // Purpur - Summoner API
public CopperGolem(EntityType<? extends AbstractGolem> type, Level level) {
super(type, level);
@@ -96,6 +_,17 @@
this.getBrain().setMemory(MemoryModuleType.TRANSPORT_ITEMS_COOLDOWN_TICKS, this.getRandom().nextInt(60, 100));
}
+ // Purpur start - Summoner API
+ @Nullable
+ public UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable UUID summoner) {
+ this.summoner = summoner;
+ }
+ // Purpur end - Summoner API
+
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.2F).add(Attributes.STEP_HEIGHT, 1.0).add(Attributes.MAX_HEALTH, 12.0);
}
@@ -171,6 +_,7 @@
super.addAdditionalSaveData(output);
output.putLong("next_weather_age", this.nextWeatheringTick);
output.store("weather_state", WeatheringCopper.WeatherState.CODEC, this.getWeatherState());
+ output.storeNullable("Purpur.Summoner", net.minecraft.core.UUIDUtil.CODEC, getSummoner()); // Purpur - Summoner API
}
@Override
@@ -178,6 +_,7 @@
super.readAdditionalSaveData(input);
this.nextWeatheringTick = input.getLongOr("next_weather_age", -1L);
this.setWeatherState(input.read("weather_state", WeatheringCopper.WeatherState.CODEC).orElse(WeatheringCopper.WeatherState.UNAFFECTED));
+ this.setSummoner(input.read("Purpur.Summoner", net.minecraft.core.UUIDUtil.CODEC).orElse(null)); // Purpur - Summoner API
}
@Override

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/entity/animal/golem/CopperGolemAi.java
+++ b/net/minecraft/world/entity/animal/golem/CopperGolemAi.java
@@ -43,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 = state -> state.is(BlockTags.COPPER_CHESTS);
- private static final Predicate<BlockState> TRANSPORT_ITEM_DESTINATION_BLOCK = state -> state.is(Blocks.CHEST) || state.is(Blocks.TRAPPED_CHEST);
+ private static final Predicate<BlockState> TRANSPORT_ITEM_DESTINATION_BLOCK = state -> state.is(Blocks.CHEST) || state.is(Blocks.TRAPPED_CHEST); // Purpur - copper golem can place items in barrels or shulkers option - diff on change
private static final ImmutableList<SensorType<? extends Sensor<? super CopperGolem>>> SENSOR_TYPES = ImmutableList.of(
SensorType.NEAREST_LIVING_ENTITIES, SensorType.HURT_BY
);
@@ -158,6 +_,11 @@
}
if (integer == 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(pathfinderMob)) {
container.stopOpen(copperGolem);
}

View File

@@ -0,0 +1,53 @@
--- a/net/minecraft/world/entity/animal/golem/IronGolem.java
+++ b/net/minecraft/world/entity/animal/golem/IronGolem.java
@@ -58,13 +_,26 @@
private static final UniformInt PERSISTENT_ANGER_TIME = TimeUtil.rangeOfSeconds(20, 39);
private long persistentAngerEndTime;
private @Nullable EntityReference<LivingEntity> persistentAngerTarget;
+ @Nullable private UUID summoner; // Purpur - Summoner API
public IronGolem(EntityType<? extends IronGolem> type, Level level) {
super(type, level);
}
+ // Purpur start - Summoner API
+ @Nullable
+ public UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@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(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(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 f = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F;
this.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0F, f);
itemInHand.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,62 @@
--- a/net/minecraft/world/entity/animal/golem/SnowGolem.java
+++ b/net/minecraft/world/entity/animal/golem/SnowGolem.java
@@ -46,15 +_,27 @@
private static final EntityDataAccessor<Byte> DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE);
private static final byte PUMPKIN_FLAG = 16;
private static final boolean DEFAULT_PUMPKIN = true;
+ @Nullable private java.util.UUID summoner; // Purpur - Summoner API
public SnowGolem(EntityType<? extends SnowGolem> type, Level level) {
super(type, level);
}
+ // Purpur start - Summoner API
+ @Nullable
+ public java.util.UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable java.util.UUID summoner) {
+ this.summoner = summoner;
+ }
+ // Purpur end - Summoner API
+
@Override
protected void registerGoals() {
- this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25, 20, 10.0F));
- this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0, 1.0000001E-5F));
+ this.goalSelector.addGoal(1, new RangedAttackGoal(this, level().purpurConfig.snowGolemAttackDistance, level().purpurConfig.snowGolemSnowBallMin, level().purpurConfig.snowGolemSnowBallMax, level().purpurConfig.snowGolemSnowBallModifier)); // Purpur - Snow Golem rate of fire config
+ this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D, 1.0000001E-5F));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entity, level) -> entity instanceof Enemy));
@@ -74,12 +_,14 @@
protected void addAdditionalSaveData(ValueOutput output) {
super.addAdditionalSaveData(output);
output.putBoolean("Pumpkin", this.hasPumpkin());
+ output.storeNullable("Purpur.Summoner", net.minecraft.core.UUIDUtil.CODEC, getSummoner()); // Purpur - Summoner API
}
@Override
protected void readAdditionalSaveData(ValueInput input) {
super.readAdditionalSaveData(input);
this.setPumpkin(input.getBooleanOr("Pumpkin", true));
+ this.setSummoner(input.read("Purpur.Summoner", net.minecraft.core.UUIDUtil.CODEC).orElse(null)); // Purpur - Summoner API
}
@Override
@@ -153,6 +_,14 @@
}
return InteractionResult.SUCCESS;
+ // Purpur start - Snowman drop and put back pumpkin
+ } else if (level().purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && itemInHand.getItem() == Blocks.CARVED_PUMPKIN.asItem()) {
+ setPumpkin(true);
+ if (!player.getAbilities().instabuild) {
+ itemInHand.shrink(1);
+ }
+ return InteractionResult.SUCCESS;
+ // Purpur end - Snowman drop and put back pumpkin
} else {
return InteractionResult.PASS;
}

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));
@@ -269,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 {
@@ -277,6 +_,7 @@
}
}
+ if (this.level().purpurConfig.parrotBreedable) return super.mobInteract(player, hand); // Purpur - Breedable parrots
return InteractionResult.SUCCESS;
} else if (!itemInHand.is(ItemTags.PARROT_POISONOUS_FOOD)) {
if (!this.isFlying() && this.isTame() && this.isOwnedBy(player)) {
@@ -301,7 +_,7 @@
@Override
public boolean isFood(ItemStack stack) {
- return false;
+ return this.level().purpurConfig.parrotBreedable && stack.is(ItemTags.PARROT_FOOD); // Purpur - Breedable parrots
}
public static boolean checkParrotSpawnRules(
@@ -316,12 +_,12 @@
@Override
public boolean canMate(Animal otherAnimal) {
- return false;
+ return super.canMate(otherAnimal); // Purpur - Breedable parrots
}
@Override
public @Nullable AgeableMob getBreedOffspring(ServerLevel level, 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
@@ -142,6 +_,19 @@
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
boolean isFood = this.isFood(player.getItemInHand(hand));
+ // Purpur start - Pigs give saddle back
+ if (level().purpurConfig.pigGiveSaddleBack && 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 - Pigs give saddle back
+
if (!isFood && 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
@@ -66,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(ServerLevel level, AgeableMob partner) {
return EntityType.POLAR_BEAR.create(level, EntitySpawnReason.BREEDING);
@@ -73,7 +_,7 @@
@Override
public boolean isFood(ItemStack stack) {
- return false;
+ return level().purpurConfig.polarBearBreedableItem != null && stack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur - Breedable Polar Bears
}
@Override
@@ -82,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, mob -> mob.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
@@ -404,10 +_,23 @@
}
this.setVariant(randomRabbitVariant);
+
+ // Purpur start - Special mobs naturally spawn
+ if (randomRabbitVariant != 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, spawnGroupData);
}
private static Rabbit.Variant getRandomRabbitVariant(LevelAccessor level, 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 randomInt = level.getRandom().nextInt(100);
if (biome.is(BiomeTags.SPAWNS_WHITE_RABBITS)) {

View File

@@ -0,0 +1,16 @@
--- a/net/minecraft/world/entity/animal/squid/GlowSquid.java
+++ b/net/minecraft/world/entity/animal/squid/GlowSquid.java
@@ -30,6 +_,13 @@
super(type, level);
}
+ // Purpur start - Flying squids! Oh my!
+ @Override
+ public boolean canFly() {
+ return this.level().purpurConfig.glowSquidsCanFly;
+ }
+ // Purpur end - Flying squids! Oh my!
+
@Override
protected ParticleOptions getInkParticle() {
return ParticleTypes.GLOW_SQUID_INK;

View File

@@ -0,0 +1,50 @@
--- a/net/minecraft/world/entity/animal/squid/Squid.java
+++ b/net/minecraft/world/entity/animal/squid/Squid.java
@@ -48,10 +_,29 @@
public Squid(EntityType<? extends Squid> type, 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));
@@ -128,6 +_,7 @@
}
if (this.isInWater()) {
+ if (canFly()) setNoGravity(!wasTouchingWater); // Purpur - Flying squids! Oh my!
if (this.tentacleMovement < (float) Math.PI) {
float f = this.tentacleMovement / (float) Math.PI;
this.tentacleAngle = Mth.sin(f * f * (float) Math.PI) * (float) Math.PI * 0.25F;
@@ -308,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 f = this.squid.getRandom().nextFloat() * (float) (Math.PI * 2);
this.squid.movementVector = new Vec3(Mth.cos(f) * 0.2F, -0.1F + this.squid.getRandom().nextFloat() * 0.2F, Mth.sin(f) * 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
@@ -100,6 +_,37 @@
EntityType<?> type = entity.getType();
return type == EntityType.SHEEP || type == EntityType.RABBIT || type == 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;
@@ -121,12 +_,47 @@
this.setPathfindingMalus(PathType.DANGER_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));
@@ -139,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));
@@ -229,6 +_,7 @@
protected void addAdditionalSaveData(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()
@@ -243,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(resourceKey -> this.registryAccess().lookupOrThrow(Registries.WOLF_SOUND_VARIANT).get((ResourceKey<WolfSoundVariant>)resourceKey))
@@ -266,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, spawnGroupData);
}
@@ -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;
@@ -517,13 +_,27 @@
itemInHand.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(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 (!damageSource.is(DamageTypeTags.IS_EXPLOSION)) {
+ if (shouldExplode()) {// Purpur - End crystal explosion options
DamageSource damageSource1 = damageSource.getEntity() != null ? this.damageSources().explosion(this, damageSource.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, damageSource1, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK);
+ level.explode(this, damageSource1, 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
@@ -957,6 +_,7 @@
@Override
protected boolean canRide(Entity entity) {
+ if (this.level().purpurConfig.enderDragonCanRideVehicles) return this.boardingCooldown <= 0; // Purpur - Configs for if Wither/Ender Dragon can ride vehicles
return false;
}
@@ -992,7 +_,7 @@
boolean flag = level.getGameRules().get(GameRules.MOB_DROPS);
int i = 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
i = 12000;
}

View File

@@ -0,0 +1,74 @@
--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
+++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
@@ -80,6 +_,7 @@
private static final TargetingConditions.Selector LIVING_ENTITY_SELECTOR = (entity, level) -> !entity.getType().is(EntityTypeTags.WITHER_FRIENDS)
&& entity.attackable();
private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0).selector(LIVING_ENTITY_SELECTOR);
+ @Nullable private java.util.UUID summoner; // Purpur - Summoner API
public WitherBoss(EntityType<? extends WitherBoss> type, Level level) {
super(type, level);
@@ -88,6 +_,17 @@
this.xpReward = 50;
}
+ // Purpur start - Summoner API
+ @Nullable
+ public java.util.UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable java.util.UUID summoner) {
+ this.summoner = summoner;
+ }
+ // Purpur end - Summoner API
+
@Override
protected PathNavigation createNavigation(Level level) {
FlyingPathNavigation flyingPathNavigation = new FlyingPathNavigation(this, level);
@@ -120,6 +_,7 @@
protected void addAdditionalSaveData(ValueOutput output) {
super.addAdditionalSaveData(output);
output.putInt("Invul", this.getInvulnerableTicks());
+ output.storeNullable("Purpur.Summoner", net.minecraft.core.UUIDUtil.CODEC, getSummoner()); // Purpur - Summoner API
}
@Override
@@ -129,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
@@ -272,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(1023, this.blockPosition(), 0);
int viewDistance = level.getCraftServer().getViewDistance() * 16;
@@ -379,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());
@@ -577,6 +_,7 @@
@Override
protected boolean canRide(Entity entity) {
+ 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,43 @@
--- a/net/minecraft/world/entity/decoration/ArmorStand.java
+++ b/net/minecraft/world/entity/decoration/ArmorStand.java
@@ -92,10 +_,13 @@
public boolean canTickSetByAPI = false;
private boolean noTickEquipmentDirty = false;
// Paper end - Allow ArmorStands not to tick
+ public boolean canMovementTick = true; // Purpur - Movement options for armor stands
public ArmorStand(EntityType<? extends ArmorStand> type, Level level) {
super(type, level);
if (level != null) this.canTick = level.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick
+ if (level != null) this.canMovementTick = level.purpurConfig.armorstandMovement; // Purpur - Movement options for armor stands
+ this.setShowArms(level != null && level.purpurConfig.armorstandPlaceWithArms); // Purpur - Config to show Armor Stand arms on spawn
}
public ArmorStand(Level level, double x, double y, double z) {
@@ -522,6 +_,7 @@
// Paper start - Allow ArmorStands not to tick
@Override
public void tick() {
+ maxUpStep = level().purpurConfig.armorstandStepHeight; // Purpur - Add option to set armorstand step height
if (!this.canTick) {
if (this.noTickEquipmentDirty) {
this.noTickEquipmentDirty = false;
@@ -810,4 +_,18 @@
}
}
// Paper end
+
+ // Purpur start - Movement options for armor stands
+ @Override
+ public void updateInWaterStateAndDoWaterCurrentPushing() {
+ if (this.level().purpurConfig.armorstandWaterMovement &&
+ (this.level().purpurConfig.armorstandWaterFence || !(level().getBlockState(blockPosition().below()).getBlock() instanceof net.minecraft.world.level.block.FenceBlock)))
+ super.updateInWaterStateAndDoWaterCurrentPushing();
+ }
+
+ @Override
+ public void aiStep() {
+ if (this.canMovementTick && this.canMove) super.aiStep();
+ }
+ // Purpur end - Movement options for armor stands
}

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(EntityType<? extends ItemEntity> type, Level level) {
super(type, level);
@@ -330,7 +_,16 @@
@Override
public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
- if (this.isInvulnerableToBase(damageSource)) {
+ // Purpur start - Item entity immunities
+ if (
+ (immuneToCactus && damageSource.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) ||
+ (immuneToFire && (damageSource.is(net.minecraft.tags.DamageTypeTags.IS_FIRE) || damageSource.is(net.minecraft.world.damagesource.DamageTypes.ON_FIRE) || damageSource.is(net.minecraft.world.damagesource.DamageTypes.IN_FIRE))) ||
+ (immuneToLightning && damageSource.is(net.minecraft.world.damagesource.DamageTypes.LIGHTNING_BOLT)) ||
+ (immuneToExplosion && damageSource.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION))
+ ) {
+ return false;
+ } else if (this.isInvulnerableToBase(damageSource)) {
+ // Purpur end - Item entity immunities
return false;
} else if (!level.getGameRules().get(GameRules.MOB_GRIEFING) && damageSource.getEntity() instanceof Mob) {
return false;
@@ -508,6 +_,12 @@
public void setItem(ItemStack stack) {
this.getEntityData().set(DATA_ITEM, stack);
this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate
+ // Purpur start - Item entity immunities
+ if (level().purpurConfig.itemImmuneToCactus.contains(stack.getItem())) immuneToCactus = true;
+ if (level().purpurConfig.itemImmuneToExplosion.contains(stack.getItem())) immuneToExplosion = true;
+ if (level().purpurConfig.itemImmuneToFire.contains(stack.getItem())) immuneToFire = true;
+ if (level().purpurConfig.itemImmuneToLightning.contains(stack.getItem())) immuneToLightning = true;
+ // level end - Item entity immunities
}
@Override

View File

@@ -0,0 +1,35 @@
--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -235,4 +_,32 @@
return !this.level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid();
}
// Paper end - Option to prevent TNT from moving in water
+
+ // Purpur start - Shears can defuse TNT
+ @Override
+ public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) {
+ Level world = this.level();
+
+ if (world instanceof ServerLevel serverWorld && level().purpurConfig.shearsCanDefuseTnt) {
+ final net.minecraft.world.item.ItemStack inHand = player.getItemInHand(hand);
+
+ if (!inHand.is(net.minecraft.world.item.Items.SHEARS) || !player.getBukkitEntity().hasPermission("purpur.tnt.defuse") ||
+ serverWorld.random.nextFloat() > serverWorld.purpurConfig.shearsCanDefuseTntChance) return net.minecraft.world.InteractionResult.PASS;
+
+ net.minecraft.world.entity.item.ItemEntity tntItem = new net.minecraft.world.entity.item.ItemEntity(serverWorld, getX(), getY(), getZ(),
+ new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.TNT));
+ tntItem.setPickUpDelay(10);
+
+ inHand.hurtAndBreak(1, player, hand.asEquipmentSlot());
+ serverWorld.addFreshEntity(tntItem, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CUSTOM);
+
+ this.playSound(net.minecraft.sounds.SoundEvents.SHEEP_SHEAR);
+
+ this.kill(serverWorld);
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ }
+
+ return super.interact(player, hand);
+ }
+ // Purpur end - Shears can defuse TNT
}

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(EntityType<? extends Creeper> type, 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, @Nullable net.minecraft.world.entity.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(DamageSource damageSource) {
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 serverLevel) {
float f = this.isPowered() ? 2.0F : 1.0F;
+ float multiplier = serverLevel.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 * f, false);
+ org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, (this.explosionRadius * f) * multiplier, false); // Purpur - Config for health to impact Creeper explosion radius
if (!event.isCancelled()) {
// CraftBukkit end
this.dead = true;
- serverLevel.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)
+ serverLevel.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) && 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(serverLevel, 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,61 @@
--- a/net/minecraft/world/entity/monster/EnderMan.java
+++ b/net/minecraft/world/entity/monster/EnderMan.java
@@ -101,7 +_,7 @@
this.goalSelector.addGoal(11, new EnderMan.EndermanTakeBlockGoal(this));
this.targetSelector.addGoal(1, new EnderMan.EndermanLookForPlayerGoal(this, this::isAngryAt));
this.targetSelector.addGoal(2, new HurtByTargetGoal(this));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, true, false));
+ this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, 10, true, false, (entityliving, ignored) -> entityliving.level().purpurConfig.endermanAggroEndermites && entityliving instanceof Endermite endermite && (!entityliving.level().purpurConfig.endermanAggroEndermitesOnlyIfPlayerSpawned || endermite.isPlayerSpawned()))); // Purpur
this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false));
}
@@ -220,7 +_,7 @@
boolean isBeingStaredBy(Player player) {
// Paper start - EndermanAttackPlayerEvent
- final boolean shouldAttack = this.isBeingStaredBy0(player);
+ final boolean shouldAttack = !this.level().purpurConfig.endermanDisableStareAggro && this.isBeingStaredBy0(player); // Purpur - Config to ignore Dragon Head wearers and stare aggro
final com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity());
event.setCancelled(!shouldAttack);
return event.callEvent();
@@ -375,6 +_,7 @@
public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
if (this.isInvulnerableTo(level, damageSource)) {
return false;
+ } else if (org.purpurmc.purpur.PurpurConfig.endermanShortHeight && damageSource.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)) { return false; // Purpur - no suffocation damage if short height - Short enderman height
} else {
AbstractThrownPotion abstractThrownPotion1 = damageSource.getDirectEntity() instanceof AbstractThrownPotion abstractThrownPotion
? abstractThrownPotion
@@ -391,6 +_,7 @@
} else {
boolean flag = abstractThrownPotion1 != null && this.hurtWithCleanWater(level, damageSource, abstractThrownPotion1, amount);
+ if (!flag && level.purpurConfig.endermanIgnoreProjectiles) return super.hurtServer(level, damageSource, amount); // Purpur - Config to disable Enderman teleport on projectile hit
if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - EndermanEscapeEvent
for (int i = 0; i < 64; i++) {
if (this.teleport()) {
@@ -434,7 +_,7 @@
@Override
public boolean requiresCustomPersistence() {
- return super.requiresCustomPersistence() || this.getCarriedBlock() != null;
+ return super.requiresCustomPersistence() || (!this.level().purpurConfig.endermanDespawnEvenWithBlock && this.getCarriedBlock() != null); // Purpur - Add config for allowing Endermen to despawn even while holding a block
}
static class EndermanFreezeWhenLookedAt extends Goal {
@@ -477,6 +_,7 @@
@Override
public boolean canUse() {
+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur - Add enderman and creeper griefing controls
return this.enderman.getCarriedBlock() != null
&& getServerLevel(this.enderman).getGameRules().get(GameRules.MOB_GRIEFING)
&& this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0;
@@ -625,6 +_,7 @@
@Override
public boolean canUse() {
+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur - Add enderman and creeper griefing controls
return this.enderman.getCarriedBlock() == null
&& getServerLevel(this.enderman).getGameRules().get(GameRules.MOB_GRIEFING)
&& this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0;

View File

@@ -0,0 +1,41 @@
--- a/net/minecraft/world/entity/monster/Endermite.java
+++ b/net/minecraft/world/entity/monster/Endermite.java
@@ -30,12 +_,23 @@
private static final int MAX_LIFE = 2400;
private static final int DEFAULT_LIFE = 0;
public int life = 0;
+ private boolean isPlayerSpawned; // Purpur - Add back player spawned endermite API
public Endermite(EntityType<? extends Endermite> type, Level level) {
super(type, level);
this.xpReward = 3;
}
+ // Purpur start - Add back player spawned endermite API
+ public boolean isPlayerSpawned() {
+ return this.isPlayerSpawned;
+ }
+
+ public void setPlayerSpawned(boolean playerSpawned) {
+ this.isPlayerSpawned = playerSpawned;
+ }
+ // Purpur end - Add back player spawned endermite API
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
@@ -81,12 +_,14 @@
protected void readAdditionalSaveData(ValueInput input) {
super.readAdditionalSaveData(input);
this.life = input.getIntOr("Lifetime", 0);
+ this.isPlayerSpawned = input.getBooleanOr("PlayerSpawned", false); // Purpur - Add back player spawned endermite API
}
@Override
protected void addAdditionalSaveData(ValueOutput output) {
super.addAdditionalSaveData(output);
output.putInt("Lifetime", this.life);
+ output.putBoolean("PlayerSpawned", this.isPlayerSpawned); // Purpur - Add back player spawned endermite API
}
@Override

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(
EntityType<Ghast> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, 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(entityType, level, spawnReason, pos, random);
}

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/monster/Guardian.java
+++ b/net/minecraft/world/entity/monster/Guardian.java
@@ -312,6 +_,11 @@
public static boolean checkGuardianSpawnRules(
EntityType<? extends Guardian> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, 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 (random.nextInt(20) == 0 || !level.canSeeSkyFromBelowWater(pos))
&& level.getDifficulty() != Difficulty.PEACEFUL
&& (EntitySpawnReason.isSpawner(spawnReason) || level.getFluidState(pos).is(FluidTags.WATER))

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/monster/MagmaCube.java
+++ b/net/minecraft/world/entity/monster/MagmaCube.java
@@ -31,6 +_,11 @@
public static boolean checkMagmaCubeSpawnRules(
EntityType<MagmaCube> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, 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;
}

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(ServerLevelAccessor level, BlockPos pos, 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 {
@@ -109,6 +_,11 @@
public static boolean checkAnyLightMonsterSpawnRules(
EntityType<? extends Monster> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, 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(entityType, level, spawnReason, pos, random);
}
@@ -146,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
@@ -166,7 +_,11 @@
ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
) {
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, spawnGroupData);
}

View File

@@ -0,0 +1,19 @@
--- a/net/minecraft/world/entity/monster/Ravager.java
+++ b/net/minecraft/world/entity/monster/Ravager.java
@@ -76,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.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));
@@ -154,7 +_,7 @@
)) {
BlockState blockState = serverLevel.getBlockState(blockPos);
Block block = blockState.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, blockPos, blockState.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
continue;

View File

@@ -0,0 +1,59 @@
--- a/net/minecraft/world/entity/monster/Shulker.java
+++ b/net/minecraft/world/entity/monster/Shulker.java
@@ -93,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));
@@ -454,11 +_,21 @@
private void hitByShulkerBullet() {
Vec3 vec3 = this.position();
AABB boundingBox = this.getBoundingBox();
- if (!this.isClosed() && this.teleportSomewhere()) {
- int size = this.level().getEntities(EntityType.SHULKER, boundingBox.inflate(8.0), Entity::isAlive).size();
- float f = (size - 1) / 5.0F;
- if (!(this.level().random.nextFloat() < f)) {
+ // Purpur start - Shulker spawn from bullet options
+ if ((!this.level().purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) {
+ float chance = this.level().purpurConfig.shulkerSpawnFromBulletBaseChance;
+ if (!this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) {
+ int nearby = this.level().getEntities((net.minecraft.world.level.entity.EntityTypeTest) EntityType.SHULKER, boundingBox.inflate(this.level().purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size();
+ try {
+ chance -= ((Number) scriptEngine.eval("let nearby = " + nearby + "; " + this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation)).floatValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ chance -= (nearby - 1) / 5.0F;
+ }
+ }
+ if (this.level().random.nextFloat() <= chance) {
Shulker shulker = EntityType.SHULKER.create(this.level(), EntitySpawnReason.BREEDING);
+ // Purpur end - Shulker spawn from bullet options
if (shulker != null) {
shulker.setVariant(this.getVariant());
shulker.snapTo(vec3);
@@ -565,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,14 @@
--- a/net/minecraft/world/entity/monster/Slime.java
+++ b/net/minecraft/world/entity/monster/Slime.java
@@ -302,6 +_,11 @@
public static boolean checkSlimeSpawnRules(
EntityType<Slime> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, 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
if (level.getDifficulty() != Difficulty.PEACEFUL) {
if (EntitySpawnReason.isSpawner(spawnReason)) {
return checkMobSpawnRules(entityType, level, spawnReason, pos, random);

View File

@@ -0,0 +1,21 @@
--- a/net/minecraft/world/entity/monster/Strider.java
+++ b/net/minecraft/world/entity/monster/Strider.java
@@ -390,6 +_,18 @@
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
boolean isFood = 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 (!isFood && 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/hoglin/Hoglin.java
+++ b/net/minecraft/world/entity/monster/hoglin/Hoglin.java
@@ -202,6 +_,11 @@
public static boolean checkHoglinSpawnRules(
EntityType<Hoglin> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, 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.getBlockState(pos.below()).is(Blocks.NETHER_WART_BLOCK);
}

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 spawnGroupData1;
}

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/monster/piglin/Piglin.java
+++ b/net/minecraft/world/entity/monster/piglin/Piglin.java
@@ -208,6 +_,11 @@
public static boolean checkPiglinSpawnRules(
EntityType<Piglin> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, 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.getBlockState(pos.below()).is(Blocks.NETHER_WART_BLOCK);
}

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+++ b/net/minecraft/world/entity/monster/piglin/PiglinAi.java
@@ -667,13 +_,23 @@
public static boolean isWearingSafeArmor(LivingEntity entity) {
for (EquipmentSlot equipmentSlot : EquipmentSlotGroup.ARMOR) {
- if (entity.getItemBySlot(equipmentSlot).is(ItemTags.PIGLIN_SAFE_ARMOR)) {
+ // Purpur start - piglins ignore gold-trimmed armor
+ net.minecraft.world.item.ItemStack itemStack = entity.getItemBySlot(equipmentSlot);
+ if (itemStack.is(ItemTags.PIGLIN_SAFE_ARMOR) || (entity.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(Piglin piglin) {
piglin.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
@@ -137,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);
}
@@ -184,7 +_,7 @@
double squareRoot = Math.sqrt(d * d + d2 * d2);
if (this.level() instanceof ServerLevel serverLevel) {
Projectile.Delayed<AbstractArrow> delayedEntity = Projectile.spawnProjectileUsingShootDelayed( // Paper - delayed
- arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4
+ arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 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,67 @@
--- a/net/minecraft/world/entity/monster/skeleton/Skeleton.java
+++ b/net/minecraft/world/entity/monster/skeleton/Skeleton.java
@@ -130,4 +_,64 @@
SoundEvent getStepSound() {
return SoundEvents.SKELETON_STEP;
}
+
+ // Purpur start - Skeletons eat wither roses
+ private int witherRosesFed = 0;
+
+ @Override
+ public net.minecraft.world.InteractionResult mobInteract(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) {
+ net.minecraft.world.item.ItemStack stack = player.getItemInHand(hand);
+
+ if (level().purpurConfig.skeletonFeedWitherRoses > 0 && this.getType() != EntityType.WITHER_SKELETON && stack.getItem() == net.minecraft.world.level.block.Blocks.WITHER_ROSE.asItem()) {
+ return this.feedWitherRose(player, stack);
+ }
+
+ return super.mobInteract(player, hand);
+ }
+
+ private net.minecraft.world.InteractionResult feedWitherRose(net.minecraft.world.entity.player.Player player, net.minecraft.world.item.ItemStack stack) {
+ if (++witherRosesFed < level().purpurConfig.skeletonFeedWitherRoses) {
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+
+ WitherSkeleton skeleton = EntityType.WITHER_SKELETON.create(level(), net.minecraft.world.entity.EntitySpawnReason.CONVERSION);
+ if (skeleton == null) {
+ return net.minecraft.world.InteractionResult.PASS;
+ }
+
+ skeleton.snapTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+ skeleton.setHealth(this.getHealth());
+ skeleton.setAggressive(this.isAggressive());
+ skeleton.copyPosition(this);
+ skeleton.setYBodyRot(this.yBodyRot);
+ skeleton.setYHeadRot(this.getYHeadRot());
+ skeleton.yRotO = this.yRotO;
+ skeleton.xRotO = this.xRotO;
+
+ if (this.hasCustomName()) {
+ skeleton.setCustomName(this.getCustomName());
+ }
+
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTransformEvent(this, skeleton, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) {
+ return net.minecraft.world.InteractionResult.PASS;
+ }
+
+ this.level().addFreshEntity(skeleton);
+ 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 net.minecraft.world.InteractionResult.SUCCESS;
+ }
+ // Purpur end - Skeletons eat wither roses
}

View File

@@ -0,0 +1,21 @@
--- a/net/minecraft/world/entity/monster/warden/WardenAi.java
+++ b/net/minecraft/world/entity/monster/warden/WardenAi.java
@@ -175,15 +_,16 @@
brain.addActivityAndRemoveMemoryWhenStopped(
Activity.FIGHT,
10,
- ImmutableList.of(
+ ImmutableList.copyOf(java.util.stream.Stream.<BehaviorControl<? super Warden>>of( // Purpur - configurable warden sonic boom
DIG_COOLDOWN_SETTER,
StopAttackingIfTargetInvalid.<Warden>create(
(level, entity) -> !warden.getAngerLevel().isAngry() || !warden.canTargetEntity(entity), WardenAi::onTargetInvalid, false
),
SetEntityLookTarget.create(entity -> isTarget(warden, entity), (float)warden.getAttributeValue(Attributes.FOLLOW_RANGE)),
SetWalkTargetFromAttackTargetIfTargetOutOfReach.create(1.2F),
- new SonicBoom(),
+ warden.level().purpurConfig.wardenCanUseSonicBoom ? new SonicBoom() : null, // Purpur - configurable warden sonic boom
MeleeAttack.create(18)
+ ).filter(java.util.Objects::nonNull).toList() // Purpur - configurable warden sonic boom
),
MemoryModuleType.ATTACK_TARGET
);

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/world/entity/monster/zombie/Drowned.java
+++ b/net/minecraft/world/entity/monster/zombie/Drowned.java
@@ -86,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, (entity, level) -> this.okTarget(entity)));
- 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,32 @@
--- a/net/minecraft/world/entity/monster/zombie/Zombie.java
+++ b/net/minecraft/world/entity/monster/zombie/Zombie.java
@@ -118,7 +_,19 @@
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
- if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot
+ // 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) { // Spigot
+ @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(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));
}
@@ -524,7 +_,7 @@
}
}
- 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() < level.getLevel().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);
}

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/world/entity/monster/zombie/ZombieVillager.java
+++ b/net/minecraft/world/entity/monster/zombie/ZombieVillager.java
@@ -137,10 +_,10 @@
public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack itemInHand = player.getItemInHand(hand);
if (itemInHand.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
itemInHand.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,40 @@
--- a/net/minecraft/world/entity/monster/zombie/ZombifiedPiglin.java
+++ b/net/minecraft/world/entity/monster/zombie/ZombifiedPiglin.java
@@ -109,6 +_,12 @@
this.maybeAlertOthers();
}
+ // Purpur start - Toggle for Zombified Piglin death always counting as player kill when angry
+ if (this.isAngry() && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) {
+ this.lastHurtByPlayerMemoryTime = this.tickCount;
+ }
+ // Purpur end - Toggle for Zombified Piglin death always counting as player kill when angry
+
super.customServerAiStep(level);
}
@@ -156,6 +_,12 @@
this.ticksUntilNextAlert = ALERT_INTERVAL.sample(this.random);
}
+ // Purpur start - Toggle for Zombified Piglin death always counting as player kill when angry
+ if (target instanceof Player player && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) {
+ this.setLastHurtByPlayer(player, this.tickCount);
+ }
+ // Purpur end - Toggle for Zombified Piglin death always counting as player kill when angry
+
return super.setTarget(target, reason); // CraftBukkit
}
@@ -176,6 +_,11 @@
public static boolean checkZombifiedPiglinSpawnRules(
EntityType<ZombifiedPiglin> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, 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 && !level.getBlockState(pos.below()).is(Blocks.NETHER_WART_BLOCK);
}

View File

@@ -0,0 +1,41 @@
--- a/net/minecraft/world/entity/npc/CatSpawner.java
+++ b/net/minecraft/world/entity/npc/CatSpawner.java
@@ -23,7 +_,7 @@
public void tick(ServerLevel level, boolean spawnEnemies) {
this.nextTick--;
if (this.nextTick <= 0) {
- this.nextTick = 1200;
+ this.nextTick = level.purpurConfig.catSpawnDelay; // Purpur - Cat spawning options
Player randomPlayer = level.getRandomPlayer();
if (randomPlayer != null) {
RandomSource randomSource = level.random;
@@ -45,9 +_,12 @@
}
private void spawnInVillage(ServerLevel level, BlockPos pos) {
- int i = 48;
- if (level.getPoiManager().getCountInRange(holder -> holder.is(PoiTypes.HOME), pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) {
- List<Cat> entitiesOfClass = level.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(48.0, 8.0, 48.0));
+ // Purpur start - Cat spawning options
+ int range = level.purpurConfig.catSpawnVillageScanRange;
+ if (range <= 0) return;
+ if (level.getPoiManager().getCountInRange(holder -> holder.is(PoiTypes.HOME), pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) {
+ List<Cat> entitiesOfClass = level.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range));
+ // Purpur end - Cat spawning options
if (entitiesOfClass.size() < 5) {
this.spawnCat(pos, level, false);
}
@@ -55,8 +_,11 @@
}
private void spawnInHut(ServerLevel level, BlockPos pos) {
- int i = 16;
- List<Cat> entitiesOfClass = level.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(16.0, 8.0, 16.0));
+ // Purpur start - Cat spawning options
+ int range = level.purpurConfig.catSpawnSwampHutScanRange;
+ if (range <= 0) return;
+ List<Cat> entitiesOfClass = level.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range));
+ // Purpur end - Cat spawning options
if (entitiesOfClass.isEmpty()) {
this.spawnCat(pos, level, true);
}

View File

@@ -0,0 +1,141 @@
--- a/net/minecraft/world/entity/npc/villager/Villager.java
+++ b/net/minecraft/world/entity/npc/villager/Villager.java
@@ -179,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(EntityType<? extends Villager> type, Level level) {
this(type, level, VillagerType.PLAINS);
@@ -197,6 +_,57 @@
this.setVillagerData(this.getVillagerData().withType(villagerType).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();
@@ -293,11 +_,22 @@
// Paper start - EAR 2
this.customServerAiStep(level, false);
}
- protected void customServerAiStep(ServerLevel level, final boolean inactive) {
+ protected void customServerAiStep(ServerLevel level, boolean inactive) { // Purpur - Lobotomize stuck villagers - not final
// Paper end - EAR 2
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("villagerBrain");
- if (!inactive) this.getBrain().tick(level, this); // Paper - EAR 2
+ // 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()) restock();
+ // Purpur end - Lobotomize stuck villagers
profilerFiller.pop();
if (this.assignProfessionWhenSpawned) {
this.assignProfessionWhenSpawned = false;
@@ -369,6 +_,7 @@
return InteractionResult.CONSUME;
}
+ if (this.level().purpurConfig.villagerAllowTrading) // Purpur - Add config for villager trading
this.startTrading(player);
}
@@ -500,7 +_,7 @@
public void updateDemand() {
for (MerchantOffer merchantOffer : this.getOffers()) {
- merchantOffer.updateDemand();
+ merchantOffer.updateDemand(this.level().purpurConfig.villagerMinimumDemand); // Purpur - Configurable minimum demand for trades
}
}
@@ -692,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() {
@@ -915,6 +_,7 @@
}
public void spawnGolemIfNeeded(ServerLevel level, long gameTime, int minVillagerAmount) {
+ if (level.purpurConfig.villagerSpawnIronGolemRadius > 0 && level.getEntitiesOfClass(net.minecraft.world.entity.animal.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(gameTime)) {
AABB aabb = this.getBoundingBox().inflate(10.0, 10.0, 10.0);
List<Villager> entitiesOfClass = level.getEntitiesOfClass(Villager.class, aabb);
@@ -982,6 +_,12 @@
@Override
public void startSleeping(BlockPos pos) {
+ // Purpur start - Option for beds to explode on villager sleep
+ if (level().purpurConfig.bedExplodeOnVillagerSleep && this.level().getBlockState(pos).getBlock() instanceof net.minecraft.world.level.block.BedBlock) {
+ this.level().explode(null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (float) this.level().purpurConfig.bedExplosionPower, this.level().purpurConfig.bedExplosionFire, this.level().purpurConfig.bedExplosionEffect);
+ return;
+ }
+ // Purpur end - Option for beds to explode on villager sleep
super.startSleeping(pos);
this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime());
this.brain.eraseMemory(MemoryModuleType.WALK_TARGET);

View File

@@ -0,0 +1,36 @@
--- a/net/minecraft/world/entity/npc/wanderingtrader/WanderingTrader.java
+++ b/net/minecraft/world/entity/npc/wanderingtrader/WanderingTrader.java
@@ -61,6 +_,13 @@
super(type, level);
}
+ // Purpur start - Allow leashing villagers
+ @Override
+ public boolean canBeLeashed() {
+ return level().purpurConfig.wanderingTraderCanBeLeashed;
+ }
+ // Purpur end - Allow leashing villagers
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
@@ -81,7 +_,7 @@
this,
new ItemStack(Items.MILK_BUCKET),
SoundEvents.WANDERING_TRADER_REAPPEARED,
- wanderingTrader -> this.canDrinkMilk && this.level().isBrightOutside() && wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API
+ wanderingTrader -> level().purpurConfig.milkClearsBeneficialEffects && this.canDrinkMilk && this.level().isBrightOutside() && wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API // // Purpur - Milk Keeps Beneficial Effects
)
);
this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this));
@@ -124,8 +_,10 @@
return InteractionResult.CONSUME;
}
+ if (this.level().purpurConfig.wanderingTraderAllowTrading) { // Purpur - Add config for villager trading
this.setTradingPlayer(player);
this.openTradingScreen(player, this.getDisplayName(), 1);
+ } // Purpur - Add config for villager trading
}
return InteractionResult.SUCCESS;

View File

@@ -0,0 +1,21 @@
--- a/net/minecraft/world/entity/npc/wanderingtrader/WanderingTraderSpawner.java
+++ b/net/minecraft/world/entity/npc/wanderingtrader/WanderingTraderSpawner.java
@@ -134,7 +_,17 @@
int i1 = pos.getX() + this.random.nextInt(maxDistance * 2) - maxDistance;
int i2 = pos.getZ() + this.random.nextInt(maxDistance * 2) - maxDistance;
int height = level.getHeight(Heightmap.Types.WORLD_SURFACE, i1, i2);
- BlockPos blockPos1 = new BlockPos(i1, height, i2);
+ // Purpur start - Allow toggling special MobSpawners per world - allow traders to spawn below nether roof
+ BlockPos.MutableBlockPos blockPos1 = new BlockPos.MutableBlockPos(i1, height, i2);
+ if (level.dimensionType().hasCeiling()) {
+ do {
+ blockPos1.relative(net.minecraft.core.Direction.DOWN);
+ } while (!level.getBlockState(blockPos1).isAir());
+ do {
+ blockPos1.relative(net.minecraft.core.Direction.DOWN);
+ } while (level.getBlockState(blockPos1).isAir() && blockPos1.getY() > 0);
+ }
+ // Purpur end - Allow toggling special MobSpawners per world
if (placementType.isSpawnPositionOk(level, blockPos1, EntityType.WANDERING_TRADER)) {
blockPos = blockPos1;
break;

View File

@@ -0,0 +1,111 @@
--- a/net/minecraft/world/entity/player/Player.java
+++ b/net/minecraft/world/entity/player/Player.java
@@ -179,11 +_,20 @@
private int currentImpulseContextResetGraceTime = 0;
public boolean affectsSpawning = true; // Paper - Affects Spawning API
public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage
+ public int burpDelay = 0; // Purpur - Burp delay
+ public boolean canPortalInstant = false; // Purpur - Add portal permission bypass
// CraftBukkit start
public boolean fauxSleeping;
public int oldLevel = -1;
+ // Purpur start - AFK API
+ public abstract void setAfk(boolean afk);
+
+ public boolean isAfk() {
+ return false;
+ }
+ // Purpur end - AFK API
@Override
public org.bukkit.craftbukkit.entity.CraftHumanEntity getBukkitEntity() {
return (org.bukkit.craftbukkit.entity.CraftHumanEntity) super.getBukkitEntity();
@@ -245,6 +_,12 @@
@Override
public void tick() {
+ // Purpur start - Burp delay
+ if (this.burpDelay > 0 && --this.burpDelay == 0) {
+ this.level().playSound(null, getX(), getY(), getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 1.0F, this.level().random.nextFloat() * 0.1F + 0.9F);
+ }
+ // Purpur end - Burp delay
+
this.noPhysics = this.isSpectator();
if (this.isSpectator() || this.isPassenger()) {
this.setOnGround(false);
@@ -302,6 +_,17 @@
this.turtleHelmetTick();
}
+ // Purpur start - Full netherite armor grants fire resistance
+ if (this.level().purpurConfig.playerNetheriteFireResistanceDuration > 0 && this.level().getGameTime() % 20 == 0) {
+ if (this.getItemBySlot(EquipmentSlot.HEAD).is(Items.NETHERITE_HELMET)
+ && this.getItemBySlot(EquipmentSlot.CHEST).is(Items.NETHERITE_CHESTPLATE)
+ && this.getItemBySlot(EquipmentSlot.LEGS).is(Items.NETHERITE_LEGGINGS)
+ && this.getItemBySlot(EquipmentSlot.FEET).is(Items.NETHERITE_BOOTS)) {
+ this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, this.level().purpurConfig.playerNetheriteFireResistanceDuration, this.level().purpurConfig.playerNetheriteFireResistanceAmplifier, this.level().purpurConfig.playerNetheriteFireResistanceAmbient, this.level().purpurConfig.playerNetheriteFireResistanceShowParticles, this.level().purpurConfig.playerNetheriteFireResistanceShowIcon), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.NETHERITE_ARMOR);
+ }
+ }
+ // Purpur end - Full netherite armor grants fire resistance
+
this.cooldowns.tick();
this.updatePlayerPose();
if (this.currentImpulseContextResetGraceTime > 0) {
@@ -510,7 +_,7 @@
List<Entity> list = Lists.newArrayList();
for (Entity entity : entities) {
- if (entity.getType() == EntityType.EXPERIENCE_ORB) {
+ if (entity.getType() == EntityType.EXPERIENCE_ORB && entity.level().purpurConfig.playerExpPickupDelay >= 0) { // Purpur - Configurable player pickup exp delay
list.add(entity);
} else if (!entity.isRemoved()) {
this.touch(entity);
@@ -1052,7 +_,7 @@
flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits
if (flag2) {
damageSource = damageSource.critical(); // Paper - critical damage API
- f *= 1.5F;
+ f *= (float) this.level().purpurConfig.playerCriticalDamageMultiplier; // Purpur - Add config change multiplier critical damage value
}
float f2 = f + f1;
@@ -1764,7 +_,23 @@
@Override
protected int getBaseExperienceReward(ServerLevel level) {
- return !level.getGameRules().get(GameRules.KEEP_INVENTORY) && !this.isSpectator() ? Math.min(this.experienceLevel * 7, 100) : 0;
+ // Purpur start - Add player death exp control options
+ if (!level.getGameRules().get(GameRules.KEEP_INVENTORY) && !this.isSpectator()) {
+ int toDrop;
+ try {
+ toDrop = Math.round(((Number) scriptEngine.eval("let expLevel = " + experienceLevel + "; " +
+ "let expTotal = " + totalExperience + "; " +
+ "let exp = " + experienceProgress + "; " +
+ level().purpurConfig.playerDeathExpDropEquation)).floatValue());
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ toDrop = experienceLevel * 7;
+ }
+ return Math.min(toDrop, level().purpurConfig.playerDeathExpDropMax);
+ } else {
+ return 0;
+ }
+ // Purpur end - Add player death exp control options
}
@Override
@@ -1807,6 +_,13 @@
public boolean addItem(ItemStack stack) {
return this.inventory.add(stack);
}
+
+ // Purpur start - Player ridable in water option
+ @Override
+ public boolean dismountsUnderwater() {
+ return !level().purpurConfig.playerRidableInWater;
+ }
+ // Purpur end - Player ridable in water option
public abstract @Nullable GameType gameMode();

View File

@@ -0,0 +1,32 @@
--- a/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java
+++ b/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java
@@ -78,6 +_,7 @@
private @Nullable List<Entity> piercedAndKilledEntities;
public ItemStack pickupItemStack = this.getDefaultPickupItem();
public @Nullable ItemStack firedFromWeapon = null;
+ public net.minecraft.world.item.enchantment.ItemEnchantments actualEnchantments = net.minecraft.world.item.enchantment.ItemEnchantments.EMPTY; // Purpur - Add an option to fix MC-3304 projectile looting
protected AbstractArrow(EntityType<? extends AbstractArrow> type, Level level) {
super(type, level);
@@ -359,7 +_,7 @@
this.setInGround(false);
Vec3 deltaMovement = this.getDeltaMovement();
this.setDeltaMovement(deltaMovement.multiply(this.random.nextFloat() * 0.2F, this.random.nextFloat() * 0.2F, this.random.nextFloat() * 0.2F));
- this.life = 0;
+ if (this.level().purpurConfig.arrowMovementResetsDespawnCounter) this.life = 0; // Purpur - Arrows should not reset despawn counter
}
public boolean isInGround() {
@@ -582,6 +_,12 @@
public @Nullable ItemStack getWeaponItem() {
return this.firedFromWeapon;
}
+
+ // Purpur start - Add an option to fix MC-3304 projectile looting
+ public void setActualEnchantments(net.minecraft.world.item.enchantment.ItemEnchantments actualEnchantments) {
+ this.actualEnchantments = actualEnchantments;
+ }
+ // Purpur end - Add an option to fix MC-3304 projectile looting
protected SoundEvent getDefaultHitGroundSoundEvent() {
return SoundEvents.ARROW_HIT;

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/projectile/arrow/ThrownTrident.java
+++ b/net/minecraft/world/entity/projectile/arrow/ThrownTrident.java
@@ -72,7 +_,7 @@
Entity owner = this.getOwner();
int i = this.entityData.get(ID_LOYALTY);
- if (i > 0 && (this.dealtDamage || this.isNoPhysics()) && owner != null) {
+ if (i > 0 && (this.dealtDamage || this.isNoPhysics() || (level().purpurConfig.tridentLoyaltyVoidReturnHeight < 0.0D && getY() < level().purpurConfig.tridentLoyaltyVoidReturnHeight)) && owner != null) { // Purpur - Add option to allow loyalty on tridents to work in the void
if (!this.isAcceptibleReturnOwner()) {
if (this.level() instanceof ServerLevel serverLevel && this.pickup == AbstractArrow.Pickup.ALLOWED) {
this.spawnAtLocation(serverLevel, this.getPickupItem(), 0.1F);

View File

@@ -0,0 +1,25 @@
--- a/net/minecraft/world/entity/projectile/hurtingprojectile/WitherSkull.java
+++ b/net/minecraft/world/entity/projectile/hurtingprojectile/WitherSkull.java
@@ -94,7 +_,7 @@
super.onHit(result);
if (!this.level().isClientSide()) {
// CraftBukkit start
- org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false);
+ org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent(this.getBukkitEntity(), this.level().purpurConfig.witherExplosionRadius, false); // Purpur - Config for wither explosion radius
if (event.callEvent()) {
this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
}
@@ -102,6 +_,13 @@
this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
}
}
+
+ // Purpur start - Add canSaveToDisk to Entity
+ @Override
+ public boolean canSaveToDisk() {
+ return false;
+ }
+ // Purpur end - Add canSaveToDisk to Entity
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {

View File

@@ -0,0 +1,43 @@
--- a/net/minecraft/world/entity/projectile/throwableitemprojectile/Snowball.java
+++ b/net/minecraft/world/entity/projectile/throwableitemprojectile/Snowball.java
@@ -53,9 +_,39 @@
protected void onHitEntity(EntityHitResult result) {
super.onHitEntity(result);
Entity entity = result.getEntity();
- int i = entity instanceof Blaze ? 3 : 0;
+ int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur - Add configurable snowball damage
entity.hurt(this.damageSources().thrown(this, this.getOwner()), i);
}
+
+ // Purpur start - options to extinguish fire blocks with snowballs - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire
+ @Override
+ protected void onHitBlock(net.minecraft.world.phys.BlockHitResult blockHitResult) {
+ super.onHitBlock(blockHitResult);
+
+ if (!this.level().isClientSide()) {
+ net.minecraft.core.BlockPos pos = blockHitResult.getBlockPos();
+ net.minecraft.core.BlockPos relativePos = pos.relative(blockHitResult.getDirection());
+
+ net.minecraft.world.level.block.state.BlockState blockState = this.level().getBlockState(pos);
+
+ if (this.level().purpurConfig.snowballExtinguishesFire && this.level().getBlockState(relativePos).is(net.minecraft.world.level.block.Blocks.FIRE)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, relativePos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) {
+ this.level().removeBlock(relativePos, false);
+ }
+ } else if (this.level().purpurConfig.snowballExtinguishesCandles && net.minecraft.world.level.block.AbstractCandleBlock.isLit(blockState)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, blockState.setValue(net.minecraft.world.level.block.AbstractCandleBlock.LIT, false))) {
+ net.minecraft.world.level.block.AbstractCandleBlock.extinguish(null, blockState, this.level(), pos);
+ }
+ } else if (this.level().purpurConfig.snowballExtinguishesCampfires && net.minecraft.world.level.block.CampfireBlock.isLitCampfire(blockState)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, blockState.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false))) {
+ this.level().levelEvent(null, 1009, pos, 0);
+ net.minecraft.world.level.block.CampfireBlock.dowse(this.getOwner(), this.level(), pos, blockState);
+ this.level().setBlockAndUpdate(pos, blockState.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false));
+ }
+ }
+ }
+ }
+ // Purpur end - options to extinguish fire blocks with snowballs
@Override
protected void onHit(HitResult result) {

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
+++ b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
@@ -112,9 +_,10 @@
return;
}
// CraftBukkit end
- if (this.random.nextFloat() < 0.05F && serverLevel.isSpawningMonsters()) {
+ if (this.random.nextFloat() < serverLevel.purpurConfig.enderPearlEndermiteChance && serverLevel.isSpawningMonsters()) { // Purpur - Configurable Ender Pearl RNG
Endermite endermite = EntityType.ENDERMITE.create(serverLevel, EntitySpawnReason.TRIGGERED);
if (endermite != null) {
+ endermite.setPlayerSpawned(true); // Purpur - Add back player spawned endermite API
endermite.snapTo(preTeleportX, preTeleportY, preTeleportZ, preTeleportYRot, preTeleportXRot); // Paper - spawn endermite at pre teleport position as teleport has been moved up
serverLevel.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL); // Paper - add reason
}
@@ -134,7 +_,7 @@
if (serverPlayer1 != null) {
serverPlayer1.resetFallDistance();
serverPlayer1.resetCurrentImpulseContext();
- serverPlayer1.hurtServer(serverPlayer.level(), this.damageSources().enderPearl().eventEntityDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API
+ serverPlayer1.hurtServer(serverPlayer.level(), this.damageSources().enderPearl().eventEntityDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - Configurable Ender Pearl damage
}
this.playSound(serverLevel, vec3);

View File

@@ -0,0 +1,42 @@
--- a/net/minecraft/world/entity/raid/Raids.java
+++ b/net/minecraft/world/entity/raid/Raids.java
@@ -31,6 +_,7 @@
public class Raids extends SavedData {
private static final String RAID_FILE_ID = "raids";
+ public final java.util.Map<java.util.UUID, Integer> playerCooldowns = com.google.common.collect.Maps.newHashMap(); // Purpur - Raid cooldown setting
public static final Codec<Raids> CODEC = RecordCodecBuilder.create(
instance -> instance.group(
Raids.RaidWithId.CODEC
@@ -82,6 +_,17 @@
public void tick(ServerLevel level) {
this.tick++;
+ // Purpur start - Raid cooldown setting
+ if (level.purpurConfig.raidCooldownSeconds != 0 && this.tick % 20 == 0) {
+ com.google.common.collect.ImmutableMap.copyOf(playerCooldowns).forEach((uuid, i) -> {
+ if (i < 1) {
+ playerCooldowns.remove(uuid);
+ } else {
+ playerCooldowns.put(uuid, i - 1);
+ }
+ });
+ }
+ // Purpur end - Raid cooldown setting
Iterator<Raid> iterator = this.raidMap.values().iterator();
while (iterator.hasNext()) {
@@ -144,11 +_,11 @@
// }
if (!raid.isStarted() || (raid.isInProgress() && raid.getRaidOmenLevel() < raid.getMaxRaidOmenLevel())) { // CraftBukkit - fixed a bug with raid: players could add up Bad Omen level even when the raid had finished
- // CraftBukkit start
+ if (serverLevel.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(player.getUUID())) return null; // Purpur - Raid cooldown setting// CraftBukkit start
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(serverLevel, raid, player)) {
player.removeEffect(net.minecraft.world.effect.MobEffects.RAID_OMEN);
return null;
- }
+ }if (serverLevel.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), serverLevel.purpurConfig.raidCooldownSeconds); // Purpur - Raid cooldown setting
if (!raid.isStarted() && !this.raidMap.containsValue(raid)) {
this.raidMap.put(this.getUniqueId(), raid);

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/entity/vehicle/boat/AbstractBoat.java
+++ b/net/minecraft/world/entity/vehicle/boat/AbstractBoat.java
@@ -431,6 +_,7 @@
float groundFriction = this.getGroundFriction();
if (groundFriction > 0.0F) {
this.landFriction = groundFriction;
+ if (level().purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur - Add option for boats to eject players on land
return AbstractBoat.Status.ON_LAND;
} else {
return AbstractBoat.Status.IN_AIR;

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/vehicle/minecart/NewMinecartBehavior.java
+++ b/net/minecraft/world/entity/vehicle/minecart/NewMinecartBehavior.java
@@ -391,7 +_,7 @@
private Vec3 calculateBoostTrackSpeed(Vec3 speed, BlockPos pos, BlockState state) {
if (state.is(Blocks.POWERED_RAIL) && state.getValue(PoweredRailBlock.POWERED)) {
if (speed.length() > 0.01) {
- return speed.normalize().scale(speed.length() + 0.06);
+ return speed.normalize().scale(speed.length() + this.level().purpurConfig.poweredRailBoostModifier); // Purpur - Configurable powered rail boost modifier
} else {
Vec3 redstoneDirection = this.minecart.getRedstoneDirection(pos);
return redstoneDirection.lengthSqr() <= 0.0 ? speed : redstoneDirection.scale(speed.length() + 0.2);

View File

@@ -0,0 +1,13 @@
--- a/net/minecraft/world/entity/vehicle/minecart/OldMinecartBehavior.java
+++ b/net/minecraft/world/entity/vehicle/minecart/OldMinecartBehavior.java
@@ -243,8 +_,8 @@
Vec3 deltaMovement1 = this.getDeltaMovement();
double d13 = deltaMovement1.horizontalDistance();
if (d13 > 0.01) {
- double d14 = 0.06;
- this.setDeltaMovement(deltaMovement1.add(deltaMovement1.x / d13 * 0.06, 0.0, deltaMovement1.z / d13 * 0.06));
+ double d14 = level.purpurConfig.poweredRailBoostModifier; // Purpur - Configurable powered rail boost modifier
+ this.setDeltaMovement(deltaMovement1.add(deltaMovement1.x / d13 * level.purpurConfig.poweredRailBoostModifier, 0.0, deltaMovement1.z / d13 * level.purpurConfig.poweredRailBoostModifier)); // Purpur - Configurable powered rail boost modifier
} else {
Vec3 deltaMovement2 = this.getDeltaMovement();
double d15 = deltaMovement2.x;