9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00
Files
Leaf/leaf-archived-patches/unapplied/server/minecraft-patches/features/0007-Purpur-Server-Minecraft-Changes.patch
Dreeam 236010caba Cooking Tutorial
1. Wet the drys
2. Dry the wets
3. Wet the drys
4. Dry the wets
5. Wet the drys
6. Now dust the wets
2025-03-28 03:11:27 -04:00

23327 lines
1.2 MiB

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Github Actions <no-reply@github.com>
Date: Thu, 16 Jan 2025 11:21:12 +0000
Subject: [PATCH] Purpur Server Minecraft Changes
Original license: MIT
Original project: https://github.com/PurpurMC/Purpur
Commit: b34d675fef91bae2df723705f2568c7afd552d2d
Patches listed below are removed in this patch, They exists in Gale or Leaf:
* "net/minecraft/CrashReport.java.patch"
- Rebrand
* "net/minecraft/commands/Commands.java.patch"
- Skip events if there's no listeners
* "net/minecraft/network/chat/SignedMessageChain.java.patch"
- Option to disable kick for out of order chat
* "net/minecraft/server/MinecraftServer.java.patch"
- Add 5 second tps average in /tps
- Configurable server mod name
* "net/minecraft/server/PlayerAdvancements.java.patch"
- Logger settings (suppressing pointless logs)
* "net/minecraft/server/gui/StatsComponent.java.patch"
- Add 5 second tps average in /tps
* "net/minecraft/server/level/WorldGenRegion.java.patch"
- Logger settings (suppressing pointless logs)
* "net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch"
- Alternative Keepalive Handling
* "net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch"
- Fix 'outdated server' showing in ping before server fully boots
* "net/minecraft/stats/ServerRecipeBook.java.patch"
- Logger settings (suppressing pointless logs)
* "net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java.patch"
- MC-121706 - Fix mobs not looking up and down when strafing
* "net/minecraft/world/entity/animal/MushroomCow.java.patch"
- Fix cow rotation when shearing mooshroom
* "net/minecraft/world/entity/animal/WaterAnimal.java.patch"
- MC-238526 - Fix spawner not spawning water animals correctly
* "net/minecraft/world/entity/projectile/AbstractArrow.java.patch"
- Arrows should not reset despawn counter
* "net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch"
- Rebrand
diff --git a/io/papermc/paper/entity/activation/ActivationRange.java b/io/papermc/paper/entity/activation/ActivationRange.java
index a9269356de964585028e69a3713ca64f67ba02bf..9a81a642698ade8a06c3a3bb4766f3c004a0b030 100644
--- a/io/papermc/paper/entity/activation/ActivationRange.java
+++ b/io/papermc/paper/entity/activation/ActivationRange.java
@@ -155,6 +155,8 @@ public final class ActivationRange {
continue;
}
+ if (!player.level().purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur - AFK API
+
final int worldHeight = world.getHeight();
ActivationRange.maxBB = player.getBoundingBox().inflate(maxRange, worldHeight, maxRange);
ActivationType.MISC.boundingBox = player.getBoundingBox().inflate(miscActivationRange, worldHeight, miscActivationRange);
@@ -312,6 +314,7 @@ public final class ActivationRange {
* @return
*/
public static boolean checkIfActive(final Entity entity) {
+ if (entity.level().purpurConfig.squidImmuneToEAR && entity instanceof net.minecraft.world.entity.animal.Squid) return true; // Purpur - Squid EAR immunity
// Never safe to skip fireworks or item gravity
if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Needed for item gravity, see ItemEntity tick
return true;
diff --git a/net/minecraft/commands/CommandSourceStack.java b/net/minecraft/commands/CommandSourceStack.java
index c2b7164a1395842ab95428540782eeda4c7960b0..59c70c567051bc7dba0d308387352d1b15f3c842 100644
--- a/net/minecraft/commands/CommandSourceStack.java
+++ b/net/minecraft/commands/CommandSourceStack.java
@@ -455,6 +455,19 @@ public class CommandSourceStack implements ExecutionCommandSource<CommandSourceS
}
// CraftBukkit end
+ // Purpur start - Gamemode extra permissions
+ public boolean testPermission(int i, String bukkitPermission) {
+ if (hasPermission(i, bukkitPermission)) {
+ return true;
+ }
+ net.kyori.adventure.text.Component permissionMessage = getLevel().getServer().server.permissionMessage();
+ if (!permissionMessage.equals(net.kyori.adventure.text.Component.empty())) {
+ sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(permissionMessage.replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("<permission>").replacement(bukkitPermission).build())));
+ }
+ return false;
+ }
+ // Purpur end - Gamemode extra permissions
+
public Vec3 getPosition() {
return this.worldPosition;
}
@@ -541,6 +554,30 @@ public class CommandSourceStack implements ExecutionCommandSource<CommandSourceS
}
}
+ // Purpur start - Purpur config files
+ public void sendSuccess(@Nullable String message) {
+ sendSuccess(message, false);
+ }
+
+ public void sendSuccess(@Nullable String message, boolean broadcastToOps) {
+ if (message == null) {
+ return;
+ }
+ sendSuccess(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), broadcastToOps);
+ }
+
+ public void sendSuccess(@Nullable net.kyori.adventure.text.Component message) {
+ sendSuccess(message, false);
+ }
+
+ public void sendSuccess(@Nullable net.kyori.adventure.text.Component message, boolean broadcastToOps) {
+ if (message == null) {
+ return;
+ }
+ sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps);
+ }
+ // Purpur end - Purpur config files
+
public void sendSuccess(Supplier<Component> messageSupplier, boolean allowLogging) {
boolean flag = this.source.acceptsSuccess() && !this.silent;
boolean flag1 = allowLogging && this.source.shouldInformAdmins() && !this.silent;
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index 66839bc2ac5746e355713cb006f249f71230a361..ee7bdfd8f9da8d5989c9cc25f8cbcc94640361c5 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -218,8 +218,8 @@ public class Commands {
JfrCommand.register(this.dispatcher);
}
- if (SharedConstants.IS_RUNNING_IN_IDE) {
- TestCommand.register(this.dispatcher);
+ if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands || SharedConstants.IS_RUNNING_IN_IDE) { // Purpur - register minecraft debug commands
+ if (!org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands) TestCommand.register(this.dispatcher); // Purpur - register minecraft debug commands
RaidCommand.register(this.dispatcher, context);
DebugPathCommand.register(this.dispatcher);
DebugMobSpawningCommand.register(this.dispatcher);
@@ -247,6 +247,14 @@ public class Commands {
StopCommand.register(this.dispatcher);
TransferCommand.register(this.dispatcher);
WhitelistCommand.register(this.dispatcher);
+ org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur - Add credits command
+ org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur - Add demo command
+ org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur - Add ping command
+ org.purpurmc.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur - Add uptime command
+ org.purpurmc.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur - Implement TPSBar
+ org.purpurmc.purpur.command.CompassCommand.register(this.dispatcher); // Purpur - Add compass command
+ org.purpurmc.purpur.command.RamBarCommand.register(this.dispatcher); // Purpur - Add rambar command
+ org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur - Add ram command
}
if (selection.includeIntegrated) {
diff --git a/net/minecraft/commands/arguments/selector/EntitySelector.java b/net/minecraft/commands/arguments/selector/EntitySelector.java
index 514f8fbdeb776087608665c35de95294aadf5cf0..b305ba9bab617bf4e52d0e6ddf160bacc5751a94 100644
--- a/net/minecraft/commands/arguments/selector/EntitySelector.java
+++ b/net/minecraft/commands/arguments/selector/EntitySelector.java
@@ -192,26 +192,27 @@ public class EntitySelector {
this.checkPermissions(source);
if (this.playerName != null) {
ServerPlayer playerByName = source.getServer().getPlayerList().getPlayerByName(this.playerName);
- return playerByName == null ? List.of() : List.of(playerByName);
+ return playerByName == null || !canSee(source, playerByName) ? List.of() : List.of(playerByName); // Purpur - Hide hidden players from entity selector
} else if (this.entityUUID != null) {
ServerPlayer playerByName = source.getServer().getPlayerList().getPlayer(this.entityUUID);
- return playerByName == null ? List.of() : List.of(playerByName);
+ return playerByName == null || !canSee(source, playerByName) ? List.of() : List.of(playerByName); // Purpur - Hide hidden players from entity selector
} else {
Vec3 vec3 = this.position.apply(source.getPosition());
AABB absoluteAabb = this.getAbsoluteAabb(vec3);
Predicate<Entity> predicate = this.getPredicate(vec3, absoluteAabb, null);
if (this.currentEntity) {
- return source.getEntity() instanceof ServerPlayer serverPlayer && predicate.test(serverPlayer) ? List.of(serverPlayer) : List.of();
+ return source.getEntity() instanceof ServerPlayer serverPlayer && predicate.test(serverPlayer) && canSee(source, serverPlayer) ? List.of(serverPlayer) : List.of(); // Purpur - Hide hidden players from entity selector
} else {
int resultLimit = this.getResultLimit();
List<ServerPlayer> players;
if (this.isWorldLimited()) {
players = source.getLevel().getPlayers(predicate, resultLimit);
+ players.removeIf(entityplayer3 -> !canSee(source, entityplayer3)); // Purpur - Hide hidden players from entity selector
} else {
players = new ObjectArrayList<>();
for (ServerPlayer serverPlayer1 : source.getServer().getPlayerList().getPlayers()) {
- if (predicate.test(serverPlayer1)) {
+ if (predicate.test(serverPlayer1) && canSee(source, serverPlayer1)) { // Purpur - Hide hidden players from entity selector
players.add(serverPlayer1);
if (players.size() >= resultLimit) {
return players;
@@ -270,4 +271,10 @@ public class EntitySelector {
public static Component joinNames(List<? extends Entity> names) {
return ComponentUtils.formatList(names, Entity::getDisplayName);
}
+
+ // Purpur start - Hide hidden players from entity selector
+ private boolean canSee(CommandSourceStack sender, ServerPlayer target) {
+ return !org.purpurmc.purpur.PurpurConfig.hideHiddenPlayersFromEntitySelector || !(sender.getEntity() instanceof ServerPlayer player) || player.getBukkitEntity().canSee(target.getBukkitEntity());
+ }
+ // Purpur end - Hide hidden players from entity selector
}
diff --git a/net/minecraft/core/BlockPos.java b/net/minecraft/core/BlockPos.java
index a81694a22e94cca6f7110f7d5b205d1303f4e071..6518d3fff6daf331b24a7bf5b39fa1920b73711d 100644
--- a/net/minecraft/core/BlockPos.java
+++ b/net/minecraft/core/BlockPos.java
@@ -63,6 +63,12 @@ public class BlockPos extends Vec3i {
public static final int MAX_HORIZONTAL_COORDINATE = 33554431;
// Paper end - Optimize Bit Operations by inlining
+ // Purpur start - Ridables
+ public BlockPos(net.minecraft.world.entity.Entity entity) {
+ super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ());
+ }
+ // Purpur end - Ridables
+
public BlockPos(int x, int y, int z) {
super(x, y, z);
}
diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java
index 717c84165d5e25cd384f56b7cb976abf6669b6f0..f576449e8bc6fd92963cbe3954b0c853a02def3c 100644
--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -892,5 +892,22 @@ public interface DispenseItemBehavior {
DispenserBlock.registerBehavior(Items.TNT_MINECART, new MinecartDispenseItemBehavior(EntityType.TNT_MINECART));
DispenserBlock.registerBehavior(Items.HOPPER_MINECART, new MinecartDispenseItemBehavior(EntityType.HOPPER_MINECART));
DispenserBlock.registerBehavior(Items.COMMAND_BLOCK_MINECART, new MinecartDispenseItemBehavior(EntityType.COMMAND_BLOCK_MINECART));
+ // Purpur start - Dispensers place anvils option
+ DispenserBlock.registerBehavior(Items.ANVIL, (new OptionalDispenseItemBehavior() {
+ @Override
+ public ItemStack execute(BlockSource dispenser, ItemStack stack) {
+ net.minecraft.world.level.Level level = dispenser.level();
+ if (!level.purpurConfig.dispenserPlaceAnvils) return super.execute(dispenser, stack);
+ Direction facing = dispenser.blockEntity().getBlockState().getValue(DispenserBlock.FACING);
+ BlockPos pos = dispenser.pos().relative(facing);
+ BlockState state = level.getBlockState(pos);
+ if (state.isAir()) {
+ level.setBlockAndUpdate(pos, Blocks.ANVIL.defaultBlockState().setValue(net.minecraft.world.level.block.AnvilBlock.FACING, facing.getAxis() == Direction.Axis.Y ? Direction.NORTH : facing.getClockWise()));
+ stack.shrink(1);
+ }
+ return stack;
+ }
+ }));
+ // Purpur end - Dispensers place anvils option
}
}
diff --git a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
index 3595bbd05fb3e8fe57e38d4e2df5c6237046b726..b91b2f5ea6a1da0477541dc65fdfbfa57b9af475 100644
--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
@@ -31,7 +31,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior {
return false;
} else {
LivingEntity livingEntity = entitiesOfClass.getFirst();
- EquipmentSlot equipmentSlotForItem = livingEntity.getEquipmentSlotForItem(item);
+ EquipmentSlot equipmentSlotForItem = blockSource.level().purpurConfig.dispenserApplyCursedArmor ? livingEntity.getEquipmentSlotForItem(item) : livingEntity.getEquipmentSlotForDispenserItem(item); if (equipmentSlotForItem == null) return false; // Purpur - Dispenser curse of binding protection
ItemStack itemStack = item.copyWithCount(1); // Paper - shrink below and single item in event
// CraftBukkit start
net.minecraft.world.level.Level world = blockSource.level();
diff --git a/net/minecraft/gametest/framework/GameTestHelper.java b/net/minecraft/gametest/framework/GameTestHelper.java
index fe4ae6bcdcbb55c47e9f9a4d63ead4c39e6d63cf..ec0998369158286fccb38c8e10c3cfa2a653a8aa 100644
--- a/net/minecraft/gametest/framework/GameTestHelper.java
+++ b/net/minecraft/gametest/framework/GameTestHelper.java
@@ -279,6 +279,10 @@ public class GameTestHelper {
return gameType.isCreative();
}
+ public void setAfk(final boolean afk) {} // Purpur - AFK API
+
+ public void resetLastActionTime() {} // Purpur - Ridables
+
@Override
public boolean isLocalPlayer() {
return true;
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 4ed9611994c5c8da01fede690197527c5b3a5731..00a82873d226f113278632a53c0faca420dd67d4 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -588,11 +588,20 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world
private static int joinAttemptsThisTick; // Paper - Buffer joins to world
private static int currTick; // Paper - Buffer joins to world
+ private static int tickSecond; // Purpur - Max joins per second
public void tick() {
this.flushQueue();
// Paper start - Buffer joins to world
if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) {
Connection.currTick = net.minecraft.server.MinecraftServer.currentTick;
+ // Purpur start - Max joins per second
+ if (org.purpurmc.purpur.PurpurConfig.maxJoinsPerSecond) {
+ if (++Connection.tickSecond > 20) {
+ Connection.tickSecond = 0;
+ Connection.joinAttemptsThisTick = 0;
+ }
+ } else
+ // Purpur end - Max joins per second
Connection.joinAttemptsThisTick = 0;
}
// Paper end - Buffer joins to world
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
index 344d5ead46c33e303ac375922aad298481253ff2..c5e8eff72f9f9fb49885bc21f61033990871c4fd 100644
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -108,6 +108,12 @@ public class Main {
JvmProfiler.INSTANCE.start(Environment.SERVER);
}
+ // Purpur start - Add toggle for enchant level clamping - load config files early
+ org.bukkit.configuration.file.YamlConfiguration purpurConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("purpur-settings"));
+ org.purpurmc.purpur.PurpurConfig.clampEnchantLevels = purpurConfiguration.getBoolean("settings.enchantment.clamp-levels", true);
+ org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands = purpurConfiguration.getBoolean("settings.register-minecraft-debug-commands"); // Purpur - register minecraft debug commands
+ // Purpur end - Add toggle for enchant level clamping - load config files early
+
org.dreeam.leaf.config.LeafConfig.loadConfig(); // Leaf - Leaf config
io.papermc.paper.plugin.PluginInitializerManager.load(optionSet); // Paper
Bootstrap.bootStrap();
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index a76b67a846b12a7b3d0c41b6ac4833d4f0372531..5fb7a76faf72f7d91122e5bf01c51853164a73c0 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -266,6 +266,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public joptsimple.OptionSet options;
public org.bukkit.command.ConsoleCommandSender console;
public static int currentTick; // Paper - improve tick loop
+ public static final long startTimeMillis = System.currentTimeMillis(); // Purpur - Add uptime command
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
// Paper - don't store the vanilla dispatcher
@@ -285,7 +286,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public final org.galemc.gale.configuration.GaleConfigurations galeConfigurations; // Gale - Gale configuration
public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
+ public boolean lagging = false; // Purpur - Lagging threshold
public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
+ protected boolean upnp = false; // Purpur - UPnP Port Forwarding
public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
@@ -983,6 +986,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
LOGGER.info("Stopping server");
Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Perf: Async command map building; Shutdown and don't bother finishing
+ // Purpur start - UPnP Port Forwarding
+ if (upnp) {
+ if (dev.omega24.upnp4j.UPnP4J.close(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
+ LOGGER.info("[UPnP] Port {} closed", this.getPort());
+ } else {
+ LOGGER.error("[UPnP] Failed to close port {}", this.getPort());
+ }
+ }
+ // Purpur end - UPnP Port Forwarding
// CraftBukkit start
if (this.server != null) {
this.server.spark.disable(); // Paper - spark
@@ -1075,6 +1087,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.safeShutdown(waitForServer, false);
}
public void safeShutdown(boolean waitForServer, boolean isRestarting) {
+ org.purpurmc.purpur.task.BossBarTask.stopAll(); // Purpur - Implement TPSBar
+ org.purpurmc.purpur.task.BeehiveTask.instance().unregister(); // Purpur - Give bee counts in beehives to Purpur clients
this.isRestarting = isRestarting;
this.hasLoggedStop = true; // Paper - Debugging
if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
@@ -1186,6 +1200,26 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
// Paper end - Add onboarding message for initial server start
+ // Purpur start - config for startup commands
+ if (!Boolean.getBoolean("Purpur.IReallyDontWantStartupCommands") && !org.purpurmc.purpur.PurpurConfig.startupCommands.isEmpty()) {
+ LOGGER.info("Purpur: Running startup commands specified in purpur.yml.");
+ for (final String startupCommand : org.purpurmc.purpur.PurpurConfig.startupCommands) {
+ LOGGER.info("Purpur: Running the following command: \"{}\"", startupCommand);
+ ((net.minecraft.server.dedicated.DedicatedServer) this).handleConsoleInput(startupCommand, this.createCommandSourceStack());
+ }
+ }
+ // Purpur end - config for startup commands
+
+ // Purpur start - Configurable void damage height and damage
+ if (org.purpurmc.purpur.configuration.transformation.VoidDamageHeightMigration.HAS_BEEN_REGISTERED) {
+ try {
+ org.purpurmc.purpur.PurpurConfig.config.save((java.io.File) this.options.valueOf("purpur-settings"));
+ } catch (IOException ex) {
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Could not save " + this.options.valueOf("purpur-settings"), ex);
+ }
+ }
+ // Purpur end - Configurable void damage height and damage
+
while (this.running) {
long l;
if (!this.isPaused() && this.tickRateManager.isSprinting() && this.tickRateManager.checkShouldSprintThisTick()) {
@@ -1219,6 +1253,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.recentTps[0] = tps1.getAverage();
this.recentTps[1] = tps5.getAverage();
this.recentTps[2] = tps15.getAverage();
+ lagging = recentTps[0] < org.purpurmc.purpur.PurpurConfig.laggingThreshold; // Purpur - Lagging threshold
tickSection = currentTime;
}
// Paper end - further improve server tick loop
@@ -1244,6 +1279,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.tickFrame.end();
this.mayHaveDelayedTasks = true;
this.delayedTasksMaxNextTickTimeNanos = Math.max(Util.getNanos() + l, this.nextTickTimeNanos);
+ // Purpur start - Configurable TPS Catchup
+ if (!org.purpurmc.purpur.PurpurConfig.tpsCatchup /*|| !gg.pufferfish.pufferfish.PufferfishConfig.tpsCatchup*/) { // Purpur - Configurable TPS Catchup
+ this.nextTickTimeNanos = currentTime + l;
+ this.delayedTasksMaxNextTickTimeNanos = nextTickTimeNanos;
+ }
+ // Purpur end - Configurable TPS Catchup
this.startMeasuringTaskExecutionTime();
this.waitUntilNextTick();
this.finishMeasuringTaskExecutionTime();
@@ -1659,7 +1700,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
long worldTime = level.getGameTime();
final ClientboundSetTimePacket worldPacket = new ClientboundSetTimePacket(worldTime, dayTime, doDaylight);
for (Player entityhuman : level.players()) {
- if (!(entityhuman instanceof ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
+ if (!(entityhuman instanceof ServerPlayer) || (!level.isForceTime() && (tickCount + entityhuman.getId()) % 20 != 0)) { // Purpur - Configurable daylight cycle
continue;
}
ServerPlayer entityplayer = (ServerPlayer) entityhuman;
@@ -1678,6 +1719,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
serverLevel.updateLagCompensationTick(); // Paper - lag compensation
net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
+ serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables
/* Drop global time updates
if (this.tickCount % 20 == 0) {
this.synchronizeTime(serverLevel);
diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java
index 2dd779b300d20ff3e58f78d59dd59c73f526c9a1..abccabb8a0a1a9730b7df070dd25f3ca215af362 100644
--- a/net/minecraft/server/PlayerAdvancements.java
+++ b/net/minecraft/server/PlayerAdvancements.java
@@ -195,6 +195,7 @@ public class PlayerAdvancements {
advancement.value().display().ifPresent(displayInfo -> {
// Paper start - Add Adventure message to PlayerAdvancementDoneEvent
if (event.message() != null && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
+ if (org.purpurmc.purpur.PurpurConfig.advancementOnlyBroadcastToAffectedPlayer) this.player.sendMessage(message); else // Purpur - Configurable broadcast settings
this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false);
// Paper end
}
diff --git a/net/minecraft/server/commands/EnchantCommand.java b/net/minecraft/server/commands/EnchantCommand.java
index fe86823f1a02d66df143756f00ee56fb9f634475..53accb9ec72100c8b95bbfe083009f4ad9c9e4de 100644
--- a/net/minecraft/server/commands/EnchantCommand.java
+++ b/net/minecraft/server/commands/EnchantCommand.java
@@ -70,7 +70,7 @@ public class EnchantCommand {
private static int enchant(CommandSourceStack source, Collection<? extends Entity> targets, Holder<Enchantment> enchantment, int level) throws CommandSyntaxException {
Enchantment enchantment1 = enchantment.value();
- if (level > enchantment1.getMaxLevel()) {
+ if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment1.getMaxLevel()) { // Purpur - Config to allow unsafe enchants
throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment1.getMaxLevel());
} else {
int i = 0;
@@ -81,7 +81,7 @@ public class EnchantCommand {
ItemStack mainHandItem = livingEntity.getMainHandItem();
if (!mainHandItem.isEmpty()) {
if (enchantment1.canEnchant(mainHandItem)
- && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(mainHandItem).keySet(), enchantment)) {
+ && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(mainHandItem).keySet(), enchantment) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !mainHandItem.hasEnchantment(enchantment))) { // Purpur - Config to allow unsafe enchants
mainHandItem.enchant(enchantment, level);
i++;
} else if (targets.size() == 1) {
diff --git a/net/minecraft/server/commands/GameModeCommand.java b/net/minecraft/server/commands/GameModeCommand.java
index c44cdbbdc06b25bd20a208386545a10af9b96df8..a88b8f999b181071ebb492bc1afa2d72fff3748e 100644
--- a/net/minecraft/server/commands/GameModeCommand.java
+++ b/net/minecraft/server/commands/GameModeCommand.java
@@ -51,6 +51,18 @@ public class GameModeCommand {
}
private static int setMode(CommandContext<CommandSourceStack> source, Collection<ServerPlayer> players, GameType gameType) {
+ // Purpur start - Gamemode extra permissions
+ if (org.purpurmc.purpur.PurpurConfig.commandGamemodeRequiresPermission) {
+ String gamemode = gameType.getName();
+ CommandSourceStack sender = source.getSource();
+ if (!sender.testPermission(2, "minecraft.command.gamemode." + gamemode)) {
+ return 0;
+ }
+ if (sender.getEntity() instanceof ServerPlayer player && (players.size() > 1 || !players.contains(player)) && !sender.testPermission(2, "minecraft.command.gamemode." + gamemode + ".other")) {
+ return 0;
+ }
+ }
+ // Purpur end - Gamemode extra permissions
int i = 0;
for (ServerPlayer serverPlayer : players) {
diff --git a/net/minecraft/server/commands/GiveCommand.java b/net/minecraft/server/commands/GiveCommand.java
index 8b7af734ca4ed3cafa810460b2cea6c1e6342a69..c394e4ea9b066895a8ad370615383a4a58d11c19 100644
--- a/net/minecraft/server/commands/GiveCommand.java
+++ b/net/minecraft/server/commands/GiveCommand.java
@@ -66,6 +66,7 @@ public class GiveCommand {
i1 -= min;
ItemStack itemStack1 = item.createItemStack(min, false);
boolean flag = serverPlayer.getInventory().add(itemStack1);
+ if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping
if (flag && itemStack1.isEmpty()) {
ItemEntity itemEntity = serverPlayer.drop(itemStack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event
if (itemEntity != null) {
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 649403ef1d5d898052412d6d47783769f291b94f..5151c24697ceb01b4728d7d3fda5fee31db682d7 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -106,6 +106,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
// CraftBukkit start
if (!org.bukkit.craftbukkit.Main.useConsole) return;
// Paper start - Use TerminalConsoleAppender
+ if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - GUI Improvements - has no GUI or has console (did not double-click)
new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
/*
jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader;
@@ -208,6 +209,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
org.spigotmc.SpigotConfig.registerCommands();
// Spigot end
io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc.
+ // Purpur start - Configurable void damage height and damage
+ try {
+ org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings"));
+ } catch (Exception e) {
+ DedicatedServer.LOGGER.error("Unable to load server configuration", e);
+ return false;
+ }
+ org.purpurmc.purpur.PurpurConfig.registerCommands();
+ // Purpur end - Configurable void damage height and damage
// Paper start - initialize global and world-defaults configuration
this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
@@ -230,6 +240,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
org.dreeam.leaf.command.LeafCommands.registerCommands(this); // Leaf - Leaf commands
this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+ /*// Purpur start - Purpur config files // Purpur - Configurable void damage height and damage
+ try {
+ org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings"));
+ } catch (Exception e) {
+ DedicatedServer.LOGGER.error("Unable to load server configuration", e);
+ return false;
+ }
+ org.purpurmc.purpur.PurpurConfig.registerCommands();
+ */// Purpur end - Purpur config files // Purpur - Configurable void damage height and damage
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
// Gale start - Pufferfish - SIMD support
@@ -284,6 +303,30 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
if (true) throw new IllegalStateException("Failed to bind to port", var10); // Paper - Propagate failed to bind to port error
return false;
}
+ // Purpur start - UPnP Port Forwarding
+ if (org.purpurmc.purpur.PurpurConfig.useUPnP) {
+ LOGGER.info("[UPnP] Attempting to start UPnP port forwarding service...");
+ if (dev.omega24.upnp4j.UPnP4J.isUPnPAvailable()) {
+ if (dev.omega24.upnp4j.UPnP4J.isOpen(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
+ this.upnp = false;
+ LOGGER.info("[UPnP] Port {} is already open", this.getPort());
+ } else if (dev.omega24.upnp4j.UPnP4J.open(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
+ this.upnp = true;
+ LOGGER.info("[UPnP] Successfully opened port {}", this.getPort());
+ } else {
+ this.upnp = false;
+ LOGGER.info("[UPnP] Failed to open port {}", this.getPort());
+ }
+
+ if (upnp) {
+ LOGGER.info("[UPnP] {}:{}", dev.omega24.upnp4j.UPnP4J.getExternalIP(), this.getPort());
+ }
+ } else {
+ this.upnp = false;
+ LOGGER.error("[UPnP] Service is unavailable");
+ }
+ }
+ // Purpur end - UPnP Port Forwarding
// CraftBukkit start
// this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up
@@ -365,6 +408,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) mobSpawnExecutor.start(); // Pufferfish
+ org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur - Implement TPSBar
+ if (org.purpurmc.purpur.PurpurConfig.beeCountPayload) org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur - Give bee counts in beehives to Purpur clients
return true;
}
}
diff --git a/net/minecraft/server/dedicated/DedicatedServerProperties.java b/net/minecraft/server/dedicated/DedicatedServerProperties.java
index f6518e29f805018c72222f5aaa7b662071665b65..5748658abf0b90812005ae9d426df92daf5532f0 100644
--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java
+++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java
@@ -49,6 +49,7 @@ public class DedicatedServerProperties extends Settings<DedicatedServerPropertie
public final boolean onlineMode = this.get("online-mode", true);
public final boolean preventProxyConnections = this.get("prevent-proxy-connections", false);
public final String serverIp = this.get("server-ip", "");
+ public final String serverName = this.get("server-name", "Unknown Server"); // Purpur - Bring back server name
public final boolean pvp = this.get("pvp", true);
public final boolean allowFlight = this.get("allow-flight", false);
public final String motd = this.get("motd", "A Minecraft Server");
diff --git a/net/minecraft/server/gui/MinecraftServerGui.java b/net/minecraft/server/gui/MinecraftServerGui.java
index f262a7c5ae4e7d56f16f5c0f4f145a2e428abbe4..614c7d9f673c926562acc8fa3b3788623900db41 100644
--- a/net/minecraft/server/gui/MinecraftServerGui.java
+++ b/net/minecraft/server/gui/MinecraftServerGui.java
@@ -39,6 +39,11 @@ public class MinecraftServerGui extends JComponent {
private Thread logAppenderThread;
private final Collection<Runnable> finalizers = Lists.newArrayList();
final AtomicBoolean isClosing = new AtomicBoolean();
+ // Purpur start - GUI Improvements
+ private final CommandHistory history = new CommandHistory();
+ private String currentCommand = "";
+ private int historyIndex = 0;
+ // Purpur end - GUI Improvements
public static MinecraftServerGui showFrameFor(final DedicatedServer server) {
try {
@@ -46,7 +51,7 @@ public class MinecraftServerGui extends JComponent {
} catch (Exception var3) {
}
- final JFrame jFrame = new JFrame("Minecraft server");
+ final JFrame jFrame = new JFrame("Purpur Minecraft server"); // Purpur - Improve GUI
final MinecraftServerGui minecraftServerGui = new MinecraftServerGui(server);
jFrame.setDefaultCloseOperation(2);
jFrame.add(minecraftServerGui);
@@ -54,7 +59,7 @@ public class MinecraftServerGui extends JComponent {
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
// Paper start - Improve ServerGUI
- jFrame.setName("Minecraft server");
+ jFrame.setName("Purpur Minecraft server"); // Purpur - Improve GUI
try {
jFrame.setIconImage(javax.imageio.ImageIO.read(java.util.Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png"))));
} catch (java.io.IOException ignore) {
@@ -64,7 +69,7 @@ public class MinecraftServerGui extends JComponent {
@Override
public void windowClosing(WindowEvent event) {
if (!minecraftServerGui.isClosing.getAndSet(true)) {
- jFrame.setTitle("Minecraft server - shutting down!");
+ jFrame.setTitle("Purpur Minecraft server - shutting down!"); // Purpur - Improve GUI
server.halt(true);
minecraftServerGui.runFinalizers();
}
@@ -112,7 +117,7 @@ public class MinecraftServerGui extends JComponent {
private JComponent buildChatPanel() {
JPanel jPanel = new JPanel(new BorderLayout());
- JTextArea jTextArea = new JTextArea();
+ org.purpurmc.purpur.gui.JColorTextPane jTextArea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur - GUI Improvements
JScrollPane jScrollPane = new JScrollPane(jTextArea, 22, 30);
jTextArea.setEditable(false);
jTextArea.setFont(MONOSPACED);
@@ -121,10 +126,43 @@ public class MinecraftServerGui extends JComponent {
String trimmed = jTextField.getText().trim();
if (!trimmed.isEmpty()) {
this.server.handleConsoleInput(trimmed, this.server.createCommandSourceStack());
+ // Purpur start - GUI Improvements
+ history.add(trimmed);
+ historyIndex = -1;
+ // Purpur end - GUI Improvements
}
jTextField.setText("");
});
+ // Purpur start - GUI Improvements
+ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up");
+ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down");
+ jTextField.getActionMap().put("up", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex < 0) {
+ currentCommand = jTextField.getText();
+ }
+ if (historyIndex < history.size() - 1) {
+ jTextField.setText(history.get(++historyIndex));
+ }
+ }
+ });
+ jTextField.getActionMap().put("down", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex >= 0) {
+ if (historyIndex == 0) {
+ --historyIndex;
+ jTextField.setText(currentCommand);
+ } else {
+ --historyIndex;
+ jTextField.setText(history.get(historyIndex));
+ }
+ }
+ }
+ });
+ // Purpur end - GUI Improvements
jTextArea.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent event) {
@@ -159,7 +197,7 @@ public class MinecraftServerGui extends JComponent {
}
private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper
- public void print(JTextArea textArea, JScrollPane scrollPane, String line) {
+ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String line) { // Purpur - GUI Improvements
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> this.print(textArea, scrollPane, line));
} else {
@@ -170,10 +208,11 @@ public class MinecraftServerGui extends JComponent {
flag = verticalScrollBar.getValue() + verticalScrollBar.getSize().getHeight() + MONOSPACED.getSize() * 4 > verticalScrollBar.getMaximum();
}
- try {
+ /*try { // Purpur - GUI Improvements
document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(line).replaceAll(""), null); // CraftBukkit
} catch (BadLocationException var8) {
- }
+ }*/ // Purpur - GUI Improvements
+ textArea.append(line); // Purpur - GUI Improvements
if (flag) {
verticalScrollBar.setValue(Integer.MAX_VALUE);
@@ -181,6 +220,18 @@ public class MinecraftServerGui extends JComponent {
}
}
+ // Purpur start - GUI Improvements
+ public static class CommandHistory extends java.util.LinkedList<String> {
+ @Override
+ public boolean add(String command) {
+ if (size() > 1000) {
+ remove();
+ }
+ return super.offerFirst(command);
+ }
+ }
+ // Purpur end - GUI Improvements
+
// Paper start - Add onboarding message for initial server start
private JComponent buildOnboardingPanel() {
String onboardingLink = "https://docs.papermc.io/paper/next-steps";
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 101f1a87a5fe920b57a5179da41cc91d88afa32e..94ee31a4a02edb003b98a09b0311355c1db4f547 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -205,6 +205,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
private final StructureManager structureManager;
private final StructureCheck structureCheck;
private final boolean tickTime;
+ private double preciseTime; // Purpur - Configurable daylight cycle
+ private boolean forceTime; // Purpur - Configurable daylight cycle
private final RandomSequences randomSequences;
// CraftBukkit start
@@ -213,6 +215,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent
public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent
private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current)
+ public boolean hasRidableMoveEvent = false; // Purpur - Ridables
public LevelChunk getChunkIfLoaded(int x, int z) {
return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately
@@ -593,7 +596,24 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
// CraftBukkit end
this.tickTime = tickTime;
this.server = server;
- this.customSpawners = customSpawners;
+ // Purpur start - Allow toggling special MobSpawners per world
+ this.customSpawners = new ArrayList<>();
+ if (purpurConfig.phantomSpawning) {
+ this.customSpawners.add(new net.minecraft.world.level.levelgen.PhantomSpawner());
+ }
+ if (purpurConfig.patrolSpawning) {
+ this.customSpawners.add(new net.minecraft.world.level.levelgen.PatrolSpawner());
+ }
+ if (purpurConfig.catSpawning) {
+ this.customSpawners.add(new net.minecraft.world.entity.npc.CatSpawner());
+ }
+ if (purpurConfig.villageSiegeSpawning) {
+ this.customSpawners.add(new net.minecraft.world.entity.ai.village.VillageSiege());
+ }
+ if (purpurConfig.villagerTraderSpawning) {
+ this.customSpawners.add(new net.minecraft.world.entity.npc.WanderingTraderSpawner(serverLevelData));
+ }
+ // Purpur end - Allow toggling special MobSpawners per world
this.serverLevelData = serverLevelData;
ChunkGenerator chunkGenerator = levelStem.generator();
// CraftBukkit start
@@ -679,6 +699,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler);
// Paper end - rewrite chunk system
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
+ this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle
}
// Paper start
@@ -721,7 +742,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
- if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) {
+ if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { // Purpur - Config for skipping night
// Paper start - create time skip event - move up calculations
final long newDayTime = this.levelData.getDayTime() + 24000L;
org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(
@@ -833,6 +854,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
this.serverLevelData.setGameTime(l);
this.serverLevelData.getScheduledEvents().tick(this.server, l);
if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
+ // Purpur start - Configurable daylight cycle
+ int incrementTicks = isDay() ? this.purpurConfig.daytimeTicks : this.purpurConfig.nighttimeTicks;
+ if (incrementTicks != 12000) {
+ this.preciseTime += 12000 / (double) incrementTicks;
+ this.setDayTime(this.preciseTime);
+ } else
+ // Purpur end - Configurable daylight cycle
this.setDayTime(this.levelData.getDayTime() + 1L);
}
}
@@ -840,8 +868,22 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
public void setDayTime(long time) {
this.serverLevelData.setDayTime(time);
+ // Purpur start - Configurable daylight cycle
+ this.preciseTime = time;
+ this.forceTime = false;
+ }
+ public void setDayTime(double i) {
+ this.serverLevelData.setDayTime((long) i);
+ this.forceTime = true;
+ // Purpur end - Configurable daylight cycle
}
+ // Purpur start - Configurable daylight cycle
+ public boolean isForceTime() {
+ return this.forceTime;
+ }
+ // Purpur end - Configurable daylight cycle
+
public void tickCustomSpawners(boolean spawnEnemies, boolean spawnFriendlies) {
for (CustomSpawner customSpawner : this.customSpawners) {
customSpawner.tick(this, spawnEnemies, spawnFriendlies);
@@ -919,9 +961,17 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
&& this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01) // Paper - Configurable spawn chances for skeleton horses
&& !this.getBlockState(blockPos.below()).is(Blocks.LIGHTNING_ROD);
if (flag) {
- SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT);
+ // Purpur start - Special mobs naturally spawn
+ net.minecraft.world.entity.animal.horse.AbstractHorse skeletonHorse;
+ if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) {
+ skeletonHorse = EntityType.ZOMBIE_HORSE.create(this, EntitySpawnReason.EVENT);
+ } else {
+ skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT);
+ if (skeletonHorse != null) ((SkeletonHorse) skeletonHorse).setTrap(true);
+ }
+ // Purpur end - Special mobs naturally spawn
if (skeletonHorse != null) {
- skeletonHorse.setTrap(true);
+ //skeletonHorse.setTrap(true); // Purpur - Special mobs naturally spawn - moved up
skeletonHorse.setAge(0);
skeletonHorse.setPos(blockPos.getX(), blockPos.getY(), blockPos.getZ());
this.addFreshEntity(skeletonHorse, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
@@ -989,7 +1039,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
pointOfInterestType -> pointOfInterestType.is(PoiTypes.LIGHTNING_ROD),
blockPos -> blockPos.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockPos.getX(), blockPos.getZ()) - 1,
pos,
- 128,
+ org.purpurmc.purpur.PurpurConfig.lightningRodRange, // Purpur - Make lightning rod range configurable
PoiManager.Occupancy.ANY
);
return optional.map(blockPos -> blockPos.above(1));
@@ -1037,8 +1087,26 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
Component component;
if (this.sleepStatus.areEnoughSleeping(_int)) {
+ // Purpur start - Customizable sleeping actionbar messages
+ if (org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.isBlank()) {
+ return;
+ }
+ if (!org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.equalsIgnoreCase("default")) {
+ component = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepSkippingNight));
+ } else
+ // Purpur end - Customizable sleeping actionbar messages
component = Component.translatable("sleep.skipping_night");
} else {
+ // Purpur start - Customizable sleeping actionbar messages
+ if (org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.isBlank()) {
+ return;
+ }
+ if (!org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.equalsIgnoreCase("default")) {
+ component = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent,
+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("count", Integer.toString(this.sleepStatus.amountSleeping())),
+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("total", Integer.toString(this.sleepStatus.sleepersNeeded(_int)))));
+ } else
+ // Purpur end - Customizable sleeping actionbar messages
component = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(_int));
}
@@ -1171,6 +1239,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@VisibleForTesting
public void resetWeatherCycle() {
// CraftBukkit start
+ if (this.purpurConfig.rainStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep
this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
// If we stop due to everyone sleeping we should reset the weather duration to some other random value.
// Not that everyone ever manages to get the whole server to sleep at the same time....
@@ -1178,6 +1247,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
this.serverLevelData.setRainTime(0);
}
// CraftBukkit end
+ if (this.purpurConfig.thunderStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep
this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
// CraftBukkit start
// If we stop due to everyone sleeping we should reset the weather duration to some other random value.
@@ -2660,7 +2730,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
// Spigot start
if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
// Paper start - Fix merchant inventory not closing on entity removal
- if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
+ if (!entity.level().purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur - Allow void trading
merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED);
}
// Paper end - Fix merchant inventory not closing on entity removal
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b9afd7ee7 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -399,6 +399,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent
public @Nullable String clientBrandName = null; // Paper - Brand support
public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
+ public boolean purpurClient = false; // Purpur - Purpur client support
+ private boolean tpsBar = false; // Purpur - Implement TPSBar
+ private boolean compassBar = false; // Purpur - Add compass command
+ private boolean ramBar = false; // Purpur - Implement rambar commands
// Paper start - rewrite chunk system
private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader;
@@ -567,6 +571,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
if (tag != null) {
BlockPos.CODEC.parse(NbtOps.INSTANCE, tag).resultOrPartial(LOGGER::error).ifPresent(pos -> this.raidOmenPosition = pos);
}
+
+ if (compound.contains("Purpur.TPSBar")) { this.tpsBar = compound.getBoolean("Purpur.TPSBar"); } // Purpur - Implement TPSBar
+ if (compound.contains("Purpur.CompassBar")) { this.compassBar = compound.getBoolean("Purpur.CompassBar"); } // Purpur - Add compass command
+ if (compound.contains("Purpur.RamBar")) { this.ramBar = compound.getBoolean("Purpur.RamBar"); } // Purpur - Implement rambar command
}
@Override
@@ -611,6 +619,9 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
}
this.saveEnderPearls(compound);
+ compound.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur - Implement TPSBar
+ compound.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur - Add compass command
+ compound.putBoolean("Purpur.RamBar", this.ramBar); // Purpur - Add rambar command
}
private void saveParentVehicle(CompoundTag tag) {
@@ -843,6 +854,15 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
this.trackEnteredOrExitedLavaOnVehicle();
this.updatePlayerAttributes();
this.advancements.flushDirty(this);
+
+ // Purpur start - Ridables
+ if (this.level().purpurConfig.useNightVisionWhenRiding && this.getVehicle() != null && this.getVehicle().getRider() == this && this.level().getGameTime() % 100 == 0) { // 5 seconds
+ MobEffectInstance nightVision = this.getEffect(MobEffects.NIGHT_VISION);
+ if (nightVision == null || nightVision.getDuration() <= 300) { // 15 seconds
+ this.addEffect(new MobEffectInstance(MobEffects.NIGHT_VISION, 400, 0)); // 20 seconds
+ }
+ }
+ // Purpur end - Ridables
}
private void updatePlayerAttributes() {
@@ -1130,6 +1150,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
)
);
Team team = this.getTeam();
+ if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(deathMessage); else // Purpur - Configurable broadcast settings
if (team == null || team.getDeathMessageVisibility() == Team.Visibility.ALWAYS) {
this.server.getPlayerList().broadcastSystemMessage(deathMessage, false);
} else if (team.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) {
@@ -1223,6 +1244,18 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
if (this.isInvulnerableTo(level, damageSource)) {
return false;
} else {
+ // Purpur start - Add boat fall damage config
+ if (damageSource.is(net.minecraft.tags.DamageTypeTags.IS_FALL)) {
+ // Purpur start - Minecart settings and WASD controls
+ if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.AbstractMinecart && level().purpurConfig.minecartControllable && !level().purpurConfig.minecartControllableFallDamage) {
+ return false;
+ }
+ // Purpur end - Minecart settings and WASD controls
+ if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.Boat && !level().purpurConfig.boatsDoFallDamage) {
+ return false;
+ }
+ }
+ // Purpur end - Add boat fall damage config
Entity entity = damageSource.getEntity();
if (!( // Paper - split the if statement. If below statement is false, hurtServer would not have been evaluated. Return false.
!(entity instanceof Player player && !this.canHarmPlayer(player))
@@ -1448,6 +1481,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
this.unsetRemoved();
// CraftBukkit end
+ this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur - Fix stuck in portals
this.setServerLevel(level);
this.connection.internalTeleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); // CraftBukkit - use internal teleport without event
this.connection.resetPosition();
@@ -1565,7 +1599,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
new AABB(vec3.x() - 8.0, vec3.y() - 5.0, vec3.z() - 8.0, vec3.x() + 8.0, vec3.y() + 5.0, vec3.z() + 8.0),
monster -> monster.isPreventingPlayerRest(this.serverLevel(), this)
);
- if (!entitiesOfClass.isEmpty()) {
+ if (!this.level().purpurConfig.playerSleepNearMonsters && !entitiesOfClass.isEmpty()) { // Purpur - Config to ignore nearby mobs when sleeping
return Either.left(Player.BedSleepingProblem.NOT_SAFE);
}
}
@@ -1602,7 +1636,19 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
CriteriaTriggers.SLEPT_IN_BED.trigger(this);
});
if (!this.serverLevel().canSleepThroughNights()) {
- this.displayClientMessage(Component.translatable("sleep.not_possible"), true);
+ // Purpur start - Customizable sleeping actionbar messages
+ Component clientMessage;
+ if (org.purpurmc.purpur.PurpurConfig.sleepNotPossible.isBlank()) {
+ clientMessage = null;
+ } else if (!org.purpurmc.purpur.PurpurConfig.sleepNotPossible.equalsIgnoreCase("default")) {
+ clientMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepNotPossible));
+ } else {
+ clientMessage = Component.translatable("sleep.not_possible");
+ }
+ if (clientMessage != null) {
+ this.displayClientMessage(clientMessage, true);
+ }
+ // Purpur end - Customizable sleeping actionbar messages
}
((ServerLevel)this.level()).updateSleepingPlayerList();
@@ -1710,6 +1756,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@Override
public void openTextEdit(SignBlockEntity signEntity, boolean isFrontText) {
+ if (level().purpurConfig.signAllowColors) this.connection.send(signEntity.getTranslatedUpdatePacket(textFilteringEnabled, isFrontText)); // Purpur - Signs allow color codes
this.connection.send(new ClientboundBlockUpdatePacket(this.level(), signEntity.getBlockPos()));
this.connection.send(new ClientboundOpenSignEditorPacket(signEntity.getBlockPos(), isFrontText));
}
@@ -2015,6 +2062,26 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
this.lastSentExp = -1; // CraftBukkit - Added to reset
}
+ // Purpur start - Component related conveniences
+ public void sendActionBarMessage(@Nullable String message) {
+ if (message != null && !message.isEmpty()) {
+ sendActionBarMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message));
+ }
+ }
+
+ public void sendActionBarMessage(@Nullable net.kyori.adventure.text.Component message) {
+ if (message != null) {
+ sendActionBarMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
+ }
+ }
+
+ public void sendActionBarMessage(@Nullable Component message) {
+ if (message != null) {
+ displayClientMessage(message, true);
+ }
+ }
+ // Purpur end - Component related conveniences
+
@Override
public void displayClientMessage(Component chatComponent, boolean actionBar) {
this.sendSystemMessage(chatComponent, actionBar);
@@ -2242,6 +2309,20 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
);
}
+ // Purpur start - Component related conveniences
+ public void sendMiniMessage(@Nullable String message) {
+ if (message != null && !message.isEmpty()) {
+ this.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message));
+ }
+ }
+
+ public void sendMessage(@Nullable net.kyori.adventure.text.Component message) {
+ if (message != null) {
+ this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
+ }
+ }
+ // Purpur end - Component related conveniences
+
public void sendSystemMessage(Component mesage) {
this.sendSystemMessage(mesage, false);
}
@@ -2380,8 +2461,68 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
public void resetLastActionTime() {
this.lastActionTime = Util.getMillis();
+ this.setAfk(false); // Purpur - AFK API
}
+ // Purpur start - AFK API
+ private boolean isAfk = false;
+
+ @Override
+ public void setAfk(boolean afk) {
+ if (this.isAfk == afk) {
+ return;
+ }
+
+ String msg = afk ? org.purpurmc.purpur.PurpurConfig.afkBroadcastAway : org.purpurmc.purpur.PurpurConfig.afkBroadcastBack;
+
+ org.purpurmc.purpur.event.PlayerAFKEvent event = new org.purpurmc.purpur.event.PlayerAFKEvent(this.getBukkitEntity(), afk, this.level().purpurConfig.idleTimeoutKick, msg, !org.bukkit.Bukkit.isPrimaryThread());
+ if (!event.callEvent() || event.shouldKick()) {
+ return;
+ }
+
+ this.isAfk = afk;
+
+ if (!afk) {
+ resetLastActionTime();
+ }
+
+ msg = event.getBroadcastMsg();
+ if (msg != null && !msg.isEmpty()) {
+ String playerName = this.getGameProfile().getName();
+ if (org.purpurmc.purpur.PurpurConfig.afkBroadcastUseDisplayName) {
+ net.kyori.adventure.text.Component playerDisplayNameComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(this.getBukkitEntity().getDisplayName());
+ playerName = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(playerDisplayNameComponent);
+ }
+ server.getPlayerList().broadcastMiniMessage(String.format(msg, playerName), false);
+ }
+
+ if (this.level().purpurConfig.idleTimeoutUpdateTabList) {
+ String scoreboardName = getScoreboardName();
+ String playerListName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().serialize(getBukkitEntity().playerListName());
+ String[] split = playerListName.split(scoreboardName);
+ String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, "");
+ String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, "");
+ if (afk) {
+ getBukkitEntity().setPlayerListName(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix + prefix + scoreboardName + suffix + org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, true);
+ } else {
+ getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix, true);
+ }
+ }
+
+ ((ServerLevel) this.level()).updateSleepingPlayerList();
+ }
+
+ @Override
+ public boolean isAfk() {
+ return this.isAfk;
+ }
+
+ @Override
+ public boolean canBeCollidedWith() {
+ return !this.isAfk() && super.canBeCollidedWith();
+ }
+ // Purpur end - AFK API
+
public ServerStatsCounter getStats() {
return this.stats;
}
@@ -3085,4 +3226,56 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
return (org.bukkit.craftbukkit.entity.CraftPlayer) super.getBukkitEntity();
}
// CraftBukkit end
+
+ // Purpur start - Add option to teleport to spawn if outside world border
+ public void teleport(org.bukkit.Location to) {
+ this.ejectPassengers();
+ this.stopRiding(true);
+
+ if (this.isSleeping()) {
+ this.stopSleepInBed(true, false);
+ }
+
+ if (this.containerMenu != this.inventoryMenu) {
+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT);
+ }
+
+ ServerLevel toLevel = ((org.bukkit.craftbukkit.CraftWorld) to.getWorld()).getHandle();
+ if (this.level() == toLevel) {
+ this.connection.teleport(to);
+ } else {
+ this.server.getPlayerList().respawn(this, true, RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH, to);
+ }
+ }
+ // Purpur end - Add option to teleport to spawn if outside world border
+
+ // Purpur start - Implement TPSBar
+ public boolean tpsBar() {
+ return this.tpsBar;
+ }
+
+ public void tpsBar(boolean tpsBar) {
+ this.tpsBar = tpsBar;
+ }
+ // Purpur end - Implement TPSBar
+
+ // Purpur start - Add compass command
+ public boolean compassBar() {
+ return this.compassBar;
+ }
+
+ public void compassBar(boolean compassBar) {
+ this.compassBar = compassBar;
+ }
+ // Purpur end - Add compass command
+
+ // Purpur start - Add rambar command
+ public boolean ramBar() {
+ return this.ramBar;
+ }
+
+ public void ramBar(boolean ramBar) {
+ this.ramBar = ramBar;
+ }
+ // Purpur end - Add rambar command
}
diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
index 623c069f1fe079e020c6391a3db1a3d95cd3dbf5..a660bad3dfdb442c6aca5eb939ee103e14d37b4b 100644
--- a/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -351,6 +351,7 @@ public class ServerPlayerGameMode {
}
return false;
}
+ if (this.player.level().purpurConfig.slabHalfBreak && this.player.isShiftKeyDown() && blockState.getBlock() instanceof net.minecraft.world.level.block.SlabBlock && ((net.minecraft.world.level.block.SlabBlock) blockState.getBlock()).halfBreak(blockState, pos, this.player)) return true; // Purpur - Break individual slabs when sneaking
}
// CraftBukkit end
@@ -464,6 +465,7 @@ public class ServerPlayerGameMode {
public InteractionHand interactHand;
public ItemStack interactItemStack;
public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
+ if (shiftClickMended(stack)) return InteractionResult.SUCCESS; // Purpur - Shift right click to use exp for mending
BlockPos blockPos = hitResult.getBlockPos();
BlockState blockState = level.getBlockState(blockPos);
boolean cancelledBlock = false;
@@ -506,7 +508,7 @@ public class ServerPlayerGameMode {
boolean flag = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty();
boolean flag1 = player.isSecondaryUseActive() && flag;
ItemStack itemStack = stack.copy();
- if (!flag1) {
+ if (!flag1 || (player.level().purpurConfig.composterBulkProcess && blockState.is(net.minecraft.world.level.block.Blocks.COMPOSTER))) { // Purpur - Sneak to bulk process composter
InteractionResult interactionResult = blockState.useItemOn(player.getItemInHand(hand), level, player, hand, hitResult);
if (interactionResult.consumesAction()) {
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockPos, itemStack);
@@ -552,4 +554,18 @@ public class ServerPlayerGameMode {
public void setLevel(ServerLevel serverLevel) {
this.level = serverLevel;
}
+
+ // Purpur start - Shift right click to use exp for mending
+ public boolean shiftClickMended(ItemStack itemstack) {
+ if (this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints > 0 && this.player.isShiftKeyDown() && this.player.getBukkitEntity().hasPermission("purpur.mending_shift_click")) {
+ int points = Math.min(this.player.totalExperience, this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints);
+ if (points > 0 && itemstack.isDamaged() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MENDING, itemstack) > 0) {
+ this.player.giveExperiencePoints(-points);
+ this.player.level().addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.player.level(), this.player.getX(), this.player.getY(), this.player.getZ(), points, org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN, this.player, this.player));
+ return true;
+ }
+ }
+ return false;
+ }
+ // Purpur end - Shift right click to use exp for mending
}
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index ea34bb4913e7357f5b76a64443f7e744abdf7b5e..de115ee71fa240440b54c553e0d3ddaf4c0dfca0 100644
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -54,6 +54,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
private static final long KEEPALIVE_LIMIT = KEEPALIVE_LIMIT_IN_SECONDS * 1000;
// Gale end - Purpur - send multiple keep-alive packets
protected static final net.minecraft.resources.ResourceLocation MINECRAFT_BRAND = net.minecraft.resources.ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
+ protected static final net.minecraft.resources.ResourceLocation PURPUR_CLIENT = net.minecraft.resources.ResourceLocation.fromNamespaceAndPath("purpur", "client"); // Purpur - Purpur client support
public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, net.minecraft.server.level.ServerPlayer player) { // CraftBukkit
this.server = server;
@@ -173,6 +174,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
ServerGamePacketListenerImpl.LOGGER.error("Couldn't register custom payload", ex);
this.disconnect(Component.literal("Invalid payload REGISTER!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
}
+ // Purpur start - Purpur client support
+ } else if (identifier.equals(PURPUR_CLIENT)) {
+ try {
+ player.purpurClient = true;
+ } catch (Exception ignore) {
+ }
+ // Purpur end - Purpur client support
} else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) {
try {
String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index a3d0d331d367b8ddfd0ac450acd143ce7d3f7a9a..5fcd389b5483c4c11e7a007b2b6abb9abc1db6b2 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -328,6 +328,20 @@ public class ServerGamePacketListenerImpl
this.tickEndEvent = new io.papermc.paper.event.packet.ClientTickEndEvent(player.getBukkitEntity()); // Paper - add client tick end event
}
+ // Purpur start - AFK API
+ private final com.google.common.cache.LoadingCache<org.bukkit.craftbukkit.entity.CraftPlayer, Boolean> kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder()
+ .maximumSize(1000)
+ .expireAfterWrite(1, java.util.concurrent.TimeUnit.MINUTES)
+ .build(
+ new com.google.common.cache.CacheLoader<>() {
+ @Override
+ public Boolean load(org.bukkit.craftbukkit.entity.CraftPlayer player) {
+ return player.hasPermission("purpur.bypassIdleKick");
+ }
+ }
+ );
+ // Purpur end - AFK API
+
@Override
public void tick() {
if (this.ackBlockChangesUpTo > -1) {
@@ -386,6 +400,12 @@ public class ServerGamePacketListenerImpl
if (this.player.getLastActionTime() > 0L
&& this.server.getPlayerIdleTimeout() > 0
&& Util.getMillis() - this.player.getLastActionTime() > this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits
+ // Purpur start - AFK API
+ this.player.setAfk(true);
+ if (!this.player.level().purpurConfig.idleTimeoutKick || (!Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")) && kickPermissionCache.getUnchecked(this.player.getBukkitEntity()))) {
+ return;
+ }
+ // Purpur end - AFK API
this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
}
@@ -631,6 +651,8 @@ public class ServerGamePacketListenerImpl
this.lastYaw = to.getYaw();
this.lastPitch = to.getPitch();
+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur - AFK API
+
Location oldTo = to.clone();
PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
this.cserver.getPluginManager().callEvent(event);
@@ -711,6 +733,7 @@ public class ServerGamePacketListenerImpl
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
if (packet.getId() == this.awaitingTeleport) {
if (this.awaitingPositionFromClient == null) {
+ ServerGamePacketListenerImpl.LOGGER.warn("Disconnected on accept teleport packet. Was not expecting position data from client at this time"); // Purpur - Add more logger output for invalid movement kicks
this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
return;
}
@@ -1184,6 +1207,10 @@ public class ServerGamePacketListenerImpl
final int maxBookPageSize = pageMax.intValue();
final double multiplier = Math.clamp(io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier, 0.3D, 1D);
long byteAllowed = maxBookPageSize;
+ // Purpur start - PlayerBookTooLargeEvent
+ int slot = packet.slot();
+ ItemStack itemstack = Inventory.isHotbarSlot(slot) || slot == Inventory.SLOT_OFFHAND ? this.player.getInventory().getItem(slot) : ItemStack.EMPTY;
+ // Purpur end - PlayerBookTooLargeEvent
for (final String page : pageList) {
final int byteLength = page.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
byteTotal += byteLength;
@@ -1208,7 +1235,8 @@ public class ServerGamePacketListenerImpl
}
if (byteTotal > byteAllowed) {
- ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size());
+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send too large of a book. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size());
+ org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur - PlayerBookTooLargeEvent
this.disconnectAsync(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect
return;
}
@@ -1227,31 +1255,45 @@ public class ServerGamePacketListenerImpl
Optional<String> optional = packet.title();
optional.ifPresent(list::add);
list.addAll(packet.pages());
+ // Purpur start - Allow color codes in books
+ boolean hasEditPerm = getCraftPlayer().hasPermission("purpur.book.color.edit");
+ boolean hasSignPerm = hasEditPerm || getCraftPlayer().hasPermission("purpur.book.color.sign");
+ // Purpur end - Allow color codes in books
Consumer<List<FilteredText>> consumer = optional.isPresent()
- ? texts -> this.signBook(texts.get(0), texts.subList(1, texts.size()), slot)
- : texts -> this.updateBookContents(texts, slot);
+ ? texts -> this.signBook(texts.get(0), texts.subList(1, texts.size()), slot, hasSignPerm) // Purpur - Allow color codes in books
+ : texts -> this.updateBookContents(texts, slot, hasEditPerm); // Purpur - Allow color codes in books
this.filterTextPacket(list).thenAcceptAsync(consumer, this.server);
}
}
private void updateBookContents(List<FilteredText> pages, int index) {
+ // Purpur start - Allow color codes in books
+ updateBookContents(pages, index, false);
+ }
+ private void updateBookContents(List<FilteredText> pages, int index, boolean hasPerm) {
+ // Purpur end - Allow color codes in books
// CraftBukkit start
ItemStack handItem = this.player.getInventory().getItem(index);
ItemStack item = handItem.copy();
// CraftBukkit end
if (item.has(DataComponents.WRITABLE_BOOK_CONTENT)) {
- List<Filterable<String>> list = pages.stream().map(this::filterableFromOutgoing).toList();
+ List<Filterable<String>> list = pages.stream().map(filteredText -> filterableFromOutgoing(filteredText).map(s -> color(s, hasPerm))).toList(); // Purpur - Allow color codes in books
item.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list));
this.player.getInventory().setItem(index, CraftEventFactory.handleEditBookEvent(this.player, index, handItem, item)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent)
}
}
private void signBook(FilteredText title, List<FilteredText> pages, int index) {
+ // Purpur start - Allow color codes in books
+ signBook(title, pages, index, false);
+ }
+ private void signBook(FilteredText title, List<FilteredText> pages, int index, boolean hasPerm) {
+ // Purpur end - Allow color codes in books
ItemStack item = this.player.getInventory().getItem(index);
if (item.has(DataComponents.WRITABLE_BOOK_CONTENT)) {
ItemStack itemStack = item.transmuteCopy(Items.WRITTEN_BOOK);
itemStack.remove(DataComponents.WRITABLE_BOOK_CONTENT);
- List<Filterable<Component>> list = pages.stream().map(filteredText -> this.filterableFromOutgoing(filteredText).<Component>map(Component::literal)).toList();
+ List<Filterable<Component>> list = pages.stream().map((filteredText) -> this.filterableFromOutgoing(filteredText).map(s -> hexColor(s, hasPerm))).toList(); // Purpur - Allow color codes in books
itemStack.set(
DataComponents.WRITTEN_BOOK_CONTENT,
new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list, true)
@@ -1265,6 +1307,16 @@ public class ServerGamePacketListenerImpl
return this.player.isTextFilteringEnabled() ? Filterable.passThrough(filteredText.filteredOrEmpty()) : Filterable.from(filteredText);
}
+ // Purpur start - Allow color codes in books
+ private Component hexColor(String str, boolean hasPerm) {
+ return hasPerm ? PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().deserialize(str)) : Component.literal(str);
+ }
+
+ private String color(String str, boolean hasPerm) {
+ return hasPerm ? org.bukkit.ChatColor.color(str, false) : str;
+ }
+ // Purpur end - Allow color codes in books
+
@Override
public void handleEntityTagQuery(ServerboundEntityTagQueryPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -1300,7 +1352,15 @@ public class ServerGamePacketListenerImpl
@Override
public void handleMovePlayer(ServerboundMovePlayerPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
- if (containsInvalidValues(packet.getX(0.0), packet.getY(0.0), packet.getZ(0.0), packet.getYRot(0.0F), packet.getXRot(0.0F))) {
+ // Purpur start - Add more logger output for invalid movement kicks
+ boolean invalidX = Double.isNaN(packet.getX(0.0));
+ boolean invalidY = Double.isNaN(packet.getY(0.0));
+ boolean invalidZ = Double.isNaN(packet.getZ(0.0));
+ boolean invalidYaw = !Floats.isFinite(packet.getYRot(0.0F));
+ boolean invalidPitch = !Floats.isFinite(packet.getXRot(0.0F));
+ if (invalidX || invalidY || invalidZ || invalidYaw || invalidPitch) {
+ ServerGamePacketListenerImpl.LOGGER.warn(String.format("Disconnected on move player packet. Invalid data: x=%b, y=%b, z=%b, yaw=%b, pitch=%b", invalidX, invalidY, invalidZ, invalidYaw, invalidPitch));
+ // Purpur end - Add more logger output for invalid movement kicks
this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
} else {
ServerLevel serverLevel = this.player.serverLevel();
@@ -1475,7 +1535,7 @@ public class ServerGamePacketListenerImpl
movedWrongly = true;
if (event.getLogWarning())
// Paper end
- LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
+ LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), verticalDelta); // Purpur - AFK API
} // Paper
}
@@ -1541,6 +1601,8 @@ public class ServerGamePacketListenerImpl
this.lastYaw = to.getYaw();
this.lastPitch = to.getPitch();
+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur - AFK API
+
Location oldTo = to.clone();
PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
this.cserver.getPluginManager().callEvent(event);
@@ -1597,6 +1659,13 @@ public class ServerGamePacketListenerImpl
this.player.tryResetCurrentImpulseContext();
}
+ // Purpur start - Dont run with scissors!
+ if (this.player.serverLevel().purpurConfig.dontRunWithScissors && this.player.isSprinting() && !(this.player.serverLevel().purpurConfig.ignoreScissorsInWater && this.player.isInWater()) && !(this.player.serverLevel().purpurConfig.ignoreScissorsInLava && this.player.isInLava()) && (isScissors(this.player.getItemInHand(InteractionHand.MAIN_HAND)) || isScissors(this.player.getItemInHand(InteractionHand.OFF_HAND))) && (int) (Math.random() * 10) == 0) {
+ this.player.hurtServer(this.player.serverLevel(), this.player.damageSources().scissors(), (float) this.player.serverLevel().purpurConfig.scissorsRunningDamage);
+ if (!org.purpurmc.purpur.PurpurConfig.dontRunWithScissors.isBlank()) this.player.sendActionBarMessage(org.purpurmc.purpur.PurpurConfig.dontRunWithScissors);
+ }
+ // Purpur end - Dont run with scissors!
+
this.player.checkMovementStatistics(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z);
this.lastGoodX = this.player.getX();
this.lastGoodY = this.player.getY();
@@ -1645,6 +1714,17 @@ public class ServerGamePacketListenerImpl
}
}
+ // Purpur start - Dont run with scissors!
+ public boolean isScissors(ItemStack stack) {
+ if (!stack.is(Items.SHEARS)) return false;
+
+ ResourceLocation itemModelReference = stack.get(net.minecraft.core.component.DataComponents.ITEM_MODEL);
+ if (itemModelReference != null && itemModelReference.equals(this.player.serverLevel().purpurConfig.dontRunWithScissorsItemModelReference)) return true;
+
+ return stack.getOrDefault(DataComponents.CUSTOM_MODEL_DATA, net.minecraft.world.item.component.CustomModelData.EMPTY).equals(net.minecraft.world.item.component.CustomModelData.EMPTY);
+ }
+ // Purpur end - Dont run with scissors!
+
// Paper start - optimise out extra getCubes
private boolean hasNewCollision(final ServerLevel level, final Entity entity, final AABB oldBox, final AABB newBox) {
final List<AABB> collisionsBB = new java.util.ArrayList<>();
@@ -2015,6 +2095,7 @@ public class ServerGamePacketListenerImpl
boolean cancelled;
if (hitResult == null || hitResult.getType() != HitResult.Type.BLOCK) {
+ if (this.player.gameMode.shiftClickMended(itemInHand)) return; // Purpur - Shift right click to use exp for mending
org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemInHand, hand);
cancelled = event.useItemInHand() == Event.Result.DENY;
} else {
@@ -2758,6 +2839,7 @@ public class ServerGamePacketListenerImpl
AABB boundingBox = target.getBoundingBox();
if (this.player.canInteractWithEntity(boundingBox, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0))) { // Paper - configurable lenience value for interact range
+ if (target instanceof net.minecraft.world.entity.Mob mob) mob.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
packet.dispatch(
new ServerboundInteractPacket.Handler() {
private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction, PlayerInteractEntityEvent event) { // CraftBukkit
@@ -2770,6 +2852,8 @@ public class ServerGamePacketListenerImpl
ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event);
+ player.processClick(hand); // Purpur - Ridables
+
// Entity in bucket - SPIGOT-4048 and SPIGOT-6859a
if ((target instanceof Bucketable && target instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) {
target.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index e802cecf30a2bfa4390ca70c45a9840032c8c046..7401200f6a11bf552d128d833f25d825f93f57c9 100644
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -320,7 +320,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!");
ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // Spigot
} else {
- ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
+ ServerLoginPacketListenerImpl.this.disconnect(org.purpurmc.purpur.PurpurConfig.unverifiedUsername.equals("default") ? Component.translatable("multiplayer.disconnect.unverified_username") : io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.unverifiedUsername))); // Purpur - Config for unverified username message
ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", string1);
}
} catch (AuthenticationUnavailableException var4) {
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 7abdd083d84a1bd1af086eb04c757e0a8ab5f5c8..13edcfd9debc68167a3ea45699229d525d931f68 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -407,6 +407,7 @@ public abstract class PlayerList {
scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam);
}
// Paper end - Configurable player collision
+ org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur - Implement TPSBar
if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.playerLoginLocations) { // Gale - JettPack - make logging login location configurable
PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), loggableAddress, player.getId(), serverLevel.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ());
// Gale start - JettPack - make logging login location configurable
@@ -518,6 +519,7 @@ public abstract class PlayerList {
}
public net.kyori.adventure.text.Component remove(ServerPlayer player, net.kyori.adventure.text.Component leaveMessage) {
// Paper end - Fix kick event leave message not being sent
+ org.purpurmc.purpur.task.BossBarTask.removeFromAll(player.getBukkitEntity()); // Purpur - Implement TPSBar
ServerLevel serverLevel = player.serverLevel();
player.awardStat(Stats.LEAVE_GAME);
// CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it
@@ -683,7 +685,7 @@ public abstract class PlayerList {
// return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)
// ? Component.translatable("multiplayer.disconnect.server_full")
// : null;
- if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)) {
+ if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameProfile))) { // Purpur - Allow player join full server by permission
event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure
}
}
@@ -988,6 +990,20 @@ public abstract class PlayerList {
}
}
+ // Purpur start - Component related conveniences
+ public void broadcastMiniMessage(@Nullable String message, boolean overlay) {
+ if (message != null && !message.isEmpty()) {
+ this.broadcastMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), overlay);
+ }
+ }
+
+ public void broadcastMessage(@Nullable net.kyori.adventure.text.Component message, boolean overlay) {
+ if (message != null) {
+ this.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), overlay);
+ }
+ }
+ // Purpur end - Component related conveniences
+
public void broadcastAll(Packet<?> packet, ResourceKey<Level> dimension) {
for (ServerPlayer serverPlayer : this.players) {
if (serverPlayer.level().dimension() == dimension) {
@@ -1071,6 +1087,7 @@ public abstract class PlayerList {
} else {
b = (byte)(24 + permLevel);
}
+ if (b < 28 && player.getBukkitEntity().hasPermission("purpur.debug.f3n")) b = 28; // Purpur - Add permission for F3+N debug
player.connection.send(new ClientboundEntityEventPacket(player, b));
}
@@ -1079,6 +1096,27 @@ public abstract class PlayerList {
player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
this.server.getCommands().sendCommands(player);
} // Paper - Add sendOpLevel API
+
+ // Purpur start - Barrels and enderchests 6 rows
+ if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows && org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) {
+ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = player.getBukkitEntity();
+ if (bukkit.hasPermission("purpur.enderchest.rows.six")) {
+ player.sixRowEnderchestSlotCount = 54;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.five")) {
+ player.sixRowEnderchestSlotCount = 45;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.four")) {
+ player.sixRowEnderchestSlotCount = 36;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.three")) {
+ player.sixRowEnderchestSlotCount = 27;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.two")) {
+ player.sixRowEnderchestSlotCount = 18;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.one")) {
+ player.sixRowEnderchestSlotCount = 9;
+ }
+ } else {
+ player.sixRowEnderchestSlotCount = -1;
+ }
+ // Purpur end - Barrels and enderchests 6 rows
}
public boolean isWhiteListed(GameProfile profile) {
diff --git a/net/minecraft/server/players/SleepStatus.java b/net/minecraft/server/players/SleepStatus.java
index 2a7ae521654ad5c9f392baa5562e64bb71b13097..3a3e6992563236141db687084aeec9684437a7db 100644
--- a/net/minecraft/server/players/SleepStatus.java
+++ b/net/minecraft/server/players/SleepStatus.java
@@ -15,7 +15,7 @@ public class SleepStatus {
public boolean areEnoughDeepSleeping(int requiredSleepPercentage, List<ServerPlayer> sleepingPlayers) {
// CraftBukkit start
- int i = (int) sleepingPlayers.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping).count();
+ int i = (int) sleepingPlayers.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping || (player.level().purpurConfig.idleTimeoutCountAsSleeping && player.isAfk())).count(); // Purpur - AFK API
boolean anyDeepSleep = sleepingPlayers.stream().anyMatch(Player::isSleepingLongEnough);
return anyDeepSleep && i >= this.sleepersNeeded(requiredSleepPercentage);
// CraftBukkit end
@@ -43,7 +43,7 @@ public class SleepStatus {
for (ServerPlayer serverPlayer : players) {
if (!serverPlayer.isSpectator()) {
this.activePlayers++;
- if (serverPlayer.isSleeping() || serverPlayer.fauxSleeping) { // CraftBukkit
+ if (serverPlayer.isSleeping() || serverPlayer.fauxSleeping || (serverPlayer.level().purpurConfig.idleTimeoutCountAsSleeping && serverPlayer.isAfk())) { // CraftBukkit // Purpur - AFK API
this.sleepingPlayers++;
}
// CraftBukkit start
diff --git a/net/minecraft/util/StringUtil.java b/net/minecraft/util/StringUtil.java
index 77947e6915facee44588943fcd3e5b513de37e77..c3a99fe7b49858bc0ca9a7f800b0db40465f6901 100644
--- a/net/minecraft/util/StringUtil.java
+++ b/net/minecraft/util/StringUtil.java
@@ -87,6 +87,7 @@ public class StringUtil {
// Paper start - Username validation
public static boolean isReasonablePlayerName(final String name) {
+ if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(name).matches(); // Purpur - Configurable valid characters for usernames
if (name.isEmpty() || name.length() > 16) {
return false;
}
diff --git a/net/minecraft/world/damagesource/CombatRules.java b/net/minecraft/world/damagesource/CombatRules.java
index d5524038314591a10c9f08a68e2ac91f6079a897..bf82de45bf98e8605a1fdb69803f75f471c4af43 100644
--- a/net/minecraft/world/damagesource/CombatRules.java
+++ b/net/minecraft/world/damagesource/CombatRules.java
@@ -15,7 +15,7 @@ public class CombatRules {
public static float getDamageAfterAbsorb(LivingEntity entity, float damage, DamageSource damageSource, float armorValue, float armorToughness) {
float f = 2.0F + armorToughness / 4.0F;
- float f1 = Mth.clamp(armorValue - damage / f, armorValue * 0.2F, 20.0F);
+ float f1 = Mth.clamp(armorValue - damage / f, armorValue * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - Add attribute clamping and armor limit config
float f2 = f1 / 25.0F;
ItemStack weaponItem = damageSource.getWeaponItem();
float f3;
@@ -30,7 +30,7 @@ public class CombatRules {
}
public static float getDamageAfterMagicAbsorb(float damage, float enchantModifiers) {
- float f = Mth.clamp(enchantModifiers, 0.0F, 20.0F);
+ float f = Mth.clamp(enchantModifiers, 0.0F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - Add attribute clamping and armor limit config
return damage * (1.0F - f / 25.0F);
}
}
diff --git a/net/minecraft/world/damagesource/CombatTracker.java b/net/minecraft/world/damagesource/CombatTracker.java
index d3de87eaf0eb84af77165391c7b94085d425f21d..edaa6f66f33b6a9bfb4862ec5557080bf702f4bd 100644
--- a/net/minecraft/world/damagesource/CombatTracker.java
+++ b/net/minecraft/world/damagesource/CombatTracker.java
@@ -54,7 +54,7 @@ public class CombatTracker {
private Component getMessageForAssistedFall(Entity entity, Component entityDisplayName, String hasWeaponTranslationKey, String noWeaponTranslationKey) {
ItemStack itemStack = entity instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY;
- return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME)
+ return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.has(DataComponents.CUSTOM_NAME)) // Purpur - always show item in player death messages
? Component.translatable(hasWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName, itemStack.getDisplayName())
: Component.translatable(noWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName);
}
@@ -98,6 +98,15 @@ public class CombatTracker {
Component component = ComponentUtils.wrapInSquareBrackets(Component.translatable(string + ".link")).withStyle(INTENTIONAL_GAME_DESIGN_STYLE);
return Component.translatable(string + ".message", this.mob.getDisplayName(), component);
} else {
+ // Purpur start - Dont run with scissors!
+ if (damageSource.isScissors()) {
+ return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgRunWithScissors, this.mob);
+ // Purpur start - Stonecutter damage
+ } else if (damageSource.isStonecutter()) {
+ return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgStonecutter, this.mob);
+ // Purpur end - Stonecutter damage
+ }
+ // Purpur end - Dont run with scissors!
return damageSource.getLocalizedDeathMessage(this.mob);
}
}
diff --git a/net/minecraft/world/damagesource/DamageSource.java b/net/minecraft/world/damagesource/DamageSource.java
index 0d9e5aab214df54a7a24bec45fcc8ad85f699710..bf9520bf4f314dacc66db9a7d30dbcc239db2c7d 100644
--- a/net/minecraft/world/damagesource/DamageSource.java
+++ b/net/minecraft/world/damagesource/DamageSource.java
@@ -30,6 +30,8 @@ public class DamageSource {
@Nullable
private org.bukkit.block.BlockState fromBlockSnapshot; // Captured block snapshot when the eventBlockDamager is not relevant (e.g. for bad respawn point explosions the block is already removed)
private boolean critical; // Supports arrows and sweeping damage
+ private boolean scissors = false; // Purpur - Dont run with scissors!
+ private boolean stonecutter = false; // Purpur - Stonecutter damage
public DamageSource knownCause(final org.bukkit.event.entity.EntityDamageEvent.DamageCause cause) {
final DamageSource damageSource = this.copy();
@@ -42,6 +44,30 @@ public class DamageSource {
return this.knownCause;
}
+ // Purpur start - Dont run with scissors!
+ public DamageSource scissors() {
+ this.knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.SUICIDE);
+ this.scissors = true;
+ return this;
+ }
+
+ public boolean isScissors() {
+ return this.scissors;
+ }
+ // Purpur end - Dont run with scissors!
+
+ // Purpur start - - Stonecutter damage
+ public DamageSource stonecutter() {
+ this.knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.CONTACT);
+ this.stonecutter = true;
+ return this;
+ }
+
+ public boolean isStonecutter() {
+ return this.stonecutter;
+ }
+ // Purpur end - Stonecutter damage
+
@Nullable
public Entity eventEntityDamager() {
return this.eventEntityDamager;
@@ -103,6 +129,8 @@ public class DamageSource {
damageSource.eventBlockDamager = this.eventBlockDamager;
damageSource.fromBlockSnapshot = this.fromBlockSnapshot;
damageSource.critical = this.critical;
+ damageSource.scissors = this.isScissors(); // Purpur - Dont run with scissors!
+ damageSource.stonecutter = this.isStonecutter(); // Purpur - Stonecutter damage
return damageSource;
}
// CraftBukkit end
@@ -169,12 +197,21 @@ public class DamageSource {
} else {
Component component = this.causingEntity == null ? this.directEntity.getDisplayName() : this.causingEntity.getDisplayName();
ItemStack itemStack = this.causingEntity instanceof LivingEntity livingEntity1 ? livingEntity1.getMainHandItem() : ItemStack.EMPTY;
- return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME)
+ return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.has(DataComponents.CUSTOM_NAME)) // Purpur - always show item in player death messages
? Component.translatable(string + ".item", livingEntity.getDisplayName(), component, itemStack.getDisplayName())
: Component.translatable(string, livingEntity.getDisplayName(), component);
}
}
+ // Purpur start - Component related conveniences
+ public Component getLocalizedDeathMessage(String str, LivingEntity entity) {
+ net.kyori.adventure.text.Component name = io.papermc.paper.adventure.PaperAdventure.asAdventure(entity.getDisplayName());
+ net.kyori.adventure.text.minimessage.tag.resolver.TagResolver template = net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("player", name);
+ net.kyori.adventure.text.Component component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(str, template);
+ return io.papermc.paper.adventure.PaperAdventure.asVanilla(component);
+ }
+ // Purpur end - Component related conveniences
+
public String getMsgId() {
return this.type().msgId();
}
diff --git a/net/minecraft/world/damagesource/DamageSources.java b/net/minecraft/world/damagesource/DamageSources.java
index cc206ecff2d95f0398ca424c178a336ad80cc396..40ab73fb626cbb8b9132b0fec939a66145272e2e 100644
--- a/net/minecraft/world/damagesource/DamageSources.java
+++ b/net/minecraft/world/damagesource/DamageSources.java
@@ -42,6 +42,8 @@ public class DamageSources {
private final DamageSource stalagmite;
private final DamageSource outsideBorder;
private final DamageSource genericKill;
+ private final DamageSource scissors; // Purpur - Dont run with scissors!
+ private final DamageSource stonecutter; // Purpur - Stonecutter damage
public DamageSources(RegistryAccess registry) {
this.damageTypes = registry.lookupOrThrow(Registries.DAMAGE_TYPE);
@@ -70,6 +72,8 @@ public class DamageSources {
this.stalagmite = this.source(DamageTypes.STALAGMITE);
this.outsideBorder = this.source(DamageTypes.OUTSIDE_BORDER);
this.genericKill = this.source(DamageTypes.GENERIC_KILL);
+ this.scissors = this.source(DamageTypes.MAGIC).scissors(); // Purpur - Dont run with scissors!
+ this.stonecutter = this.source(DamageTypes.MAGIC).stonecutter(); // Purpur - Stonecutter damage
}
private DamageSource source(ResourceKey<DamageType> damageTypeKey) {
@@ -84,6 +88,18 @@ public class DamageSources {
return new DamageSource(this.damageTypes.getOrThrow(damageTypeKey), causingEntity, directEntity);
}
+ // Purpur start - Dont run with scissors!
+ public DamageSource scissors() {
+ return this.scissors;
+ }
+ // Purpur end - Dont run with scissors!
+
+ // Purpur start - Stonecutter damage
+ public DamageSource stonecutter() {
+ return this.stonecutter;
+ }
+ // Purpur end - Stonecutter damage
+
public DamageSource inFire() {
return this.inFire;
}
diff --git a/net/minecraft/world/effect/HungerMobEffect.java b/net/minecraft/world/effect/HungerMobEffect.java
index 0890694ae96b6cd60079c34066e7a6e288f038e8..6c0e6bd2a171edc57dec71af178764454de73313 100644
--- a/net/minecraft/world/effect/HungerMobEffect.java
+++ b/net/minecraft/world/effect/HungerMobEffect.java
@@ -12,7 +12,7 @@ class HungerMobEffect extends MobEffect {
@Override
public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) {
if (entity instanceof Player player) {
- player.causeFoodExhaustion(0.005F * (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent
+ player.causeFoodExhaustion(entity.level().purpurConfig.humanHungerExhaustionAmount * (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur - Config MobEffect by world
}
return true;
diff --git a/net/minecraft/world/effect/PoisonMobEffect.java b/net/minecraft/world/effect/PoisonMobEffect.java
index 75327fd96858fd508ea63a6983e5cbc655a8800f..73cfc61ac3f8e33e6b9f4fd08a292266c0adb535 100644
--- a/net/minecraft/world/effect/PoisonMobEffect.java
+++ b/net/minecraft/world/effect/PoisonMobEffect.java
@@ -12,8 +12,8 @@ public class PoisonMobEffect extends MobEffect {
@Override
public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) {
- if (entity.getHealth() > 1.0F) {
- entity.hurtServer(level, entity.damageSources().magic().knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.POISON), 1.0F); // CraftBukkit
+ if (entity.getHealth() > entity.level().purpurConfig.entityMinimalHealthPoison) { // Purpur
+ entity.hurtServer(level, entity.damageSources().magic().knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.POISON), entity.level().purpurConfig.entityPoisonDegenerationAmount); // CraftBukkit // Purpur - Config MobEffect by world
}
return true;
diff --git a/net/minecraft/world/effect/RegenerationMobEffect.java b/net/minecraft/world/effect/RegenerationMobEffect.java
index 76cffa4d4d18d6c04749d941dbdf5eb60aed4095..81481267a1577721dcc405f39a4c350bd59ac9a2 100644
--- a/net/minecraft/world/effect/RegenerationMobEffect.java
+++ b/net/minecraft/world/effect/RegenerationMobEffect.java
@@ -11,7 +11,7 @@ class RegenerationMobEffect extends MobEffect {
@Override
public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) {
if (entity.getHealth() < entity.getMaxHealth()) {
- entity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit
+ entity.heal(entity.level().purpurConfig.entityHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur - Config MobEffect by world
}
return true;
diff --git a/net/minecraft/world/effect/SaturationMobEffect.java b/net/minecraft/world/effect/SaturationMobEffect.java
index c192165910f6b139df6f604d0bce989061efa9cb..622c23f4570d07de8bee9623bf900aabb3331ded 100644
--- a/net/minecraft/world/effect/SaturationMobEffect.java
+++ b/net/minecraft/world/effect/SaturationMobEffect.java
@@ -16,7 +16,8 @@ class SaturationMobEffect extends InstantenousMobEffect {
int oldFoodLevel = player.getFoodData().foodLevel;
org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, amplifier + 1 + oldFoodLevel);
if (!event.isCancelled()) {
- player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F);
+ if (player.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) player.burpDelay = player.level().purpurConfig.playerBurpDelay; // Purpur - Burp delay
+ player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, entity.level().purpurConfig.humanSaturationRegenAmount); // Purpur - Config MobEffect by world
}
((org.bukkit.craftbukkit.entity.CraftPlayer) player.getBukkitEntity()).sendHealthUpdate();
diff --git a/net/minecraft/world/effect/WitherMobEffect.java b/net/minecraft/world/effect/WitherMobEffect.java
index 1fc9e1ad541c46124183a401b2a7d99aea69cecf..881271f0bc77a8a8a7d31daad9a8188bebaca67b 100644
--- a/net/minecraft/world/effect/WitherMobEffect.java
+++ b/net/minecraft/world/effect/WitherMobEffect.java
@@ -12,7 +12,7 @@ public class WitherMobEffect extends MobEffect {
@Override
public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) {
- entity.hurtServer(level, entity.damageSources().wither(), 1.0F);
+ entity.hurtServer(level, entity.damageSources().wither(), entity.level().purpurConfig.entityWitherDegenerationAmount); // Purpur - Config MobEffect by world
return true;
}
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 63f94b2fdf1e8395d3e76aebd4466c916c73dc59..a5a8bdecddadac3de1b5a0c1a9849ce1cd52a530 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -134,7 +134,7 @@ import net.minecraft.world.scores.Team;
import org.slf4j.Logger;
public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, 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;
public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation
@@ -252,9 +252,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
public double xOld;
public double yOld;
public double zOld;
+ public float maxUpStep; // Purpur - Add option to set armorstand step height
public boolean noPhysics;
private boolean wasOnFire;
- 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 = -this.getFireImmuneTicks();
public boolean wasTouchingWater;
@@ -288,8 +289,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
public 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};
@@ -344,6 +345,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
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() {
}
@@ -525,10 +527,39 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
// Paper end - optimise entity tracker
+ // Purpur start - Add canSaveToDisk to Entity
+ public boolean canSaveToDisk() {
+ return true;
+ }
+ // Purpur end - Add canSaveToDisk to Entity
+
+ // Purpur start - copied from Mob - API for any mob to burn daylight
+ public boolean isSunBurnTick() {
+ if (this.level().isDay() && !this.level().isClientSide) {
+ float lightLevelDependentMagicValue = this.getLightLevelDependentMagicValue();
+ BlockPos blockPos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
+ boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow;
+ if (lightLevelDependentMagicValue > 0.5F
+ && this.random.nextFloat() * 30.0F < (lightLevelDependentMagicValue - 0.4F) * 2.0F
+ && !flag
+ && this.level().canSeeSky(blockPos)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ // Purpur end - copied from Mob - API for any mob to burn daylight
+
public Entity(EntityType<?> entityType, Level level) {
this.type = entityType;
this.level = level;
this.dimensions = entityType.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;
@@ -909,6 +940,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
&& 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(io.papermc.paper.util.MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur - Add option to teleport to spawn on nether ceiling damage
this.onBelowWorld();
}
}
@@ -1839,7 +1871,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
public boolean fireImmune() {
- return this.getType().fireImmune();
+ return this.immuneToFire != null ? immuneToFire : this.getType().fireImmune(); // Purpur - add fire immune API
}
public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) {
@@ -1908,7 +1940,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return this.isInWater() || flag;
}
- public void updateInWaterStateAndDoWaterCurrentPushing() {
+ public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - Movement options for armor stands - package-private -> public - TODO: use AT file
if (this.getVehicle() instanceof AbstractBoat abstractBoat && !abstractBoat.isUnderWater()) {
this.wasTouchingWater = false;
} else if (this.updateFluidHeightAndDoFluidPushing(FluidTags.WATER, 0.014)) {
@@ -2544,6 +2576,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
compound.putBoolean("Paper.FreezeLock", true);
}
// Paper end
+
+ // Purpur start - Fire immune API
+ if (immuneToFire != null) {
+ compound.putBoolean("Purpur.FireImmune", immuneToFire);
+ }
+ // Purpur end - Fire immune API
+
return compound;
} catch (Throwable var9) {
CrashReport crashReport = CrashReport.forThrowable(var9, "Saving entity NBT");
@@ -2693,6 +2732,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
freezeLocked = compound.getBoolean("Paper.FreezeLock");
}
// Paper end
+
+ // Purpur start - Fire immune API
+ if (compound.contains("Purpur.FireImmune")) {
+ immuneToFire = compound.getBoolean("Purpur.FireImmune");
+ }
+ // Purpur end - Fire immune API
+
} catch (Throwable var17) {
CrashReport crashReport = CrashReport.forThrowable(var17, "Loading entity NBT");
CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being loaded");
@@ -2939,6 +2985,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (this.isAlive() && this instanceof Leashable leashable) {
if (leashable.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
// CraftBukkit start - fire PlayerUnleashEntityEvent
// Paper start - Expand EntityUnleashEvent
org.bukkit.event.player.PlayerUnleashEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials());
@@ -3145,6 +3192,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.passengers = ImmutableList.copyOf(list);
}
+ // Purpur start - Ridables
+ if (isRidable() && this.passengers.get(0) == passenger && passenger instanceof Player player) {
+ onMount(player);
+ this.rider = player;
+ }
+ // Purpur end - Ridables
+
this.gameEvent(GameEvent.ENTITY_MOUNT, passenger);
}
}
@@ -3186,6 +3240,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return false;
}
// CraftBukkit end
+
+ // Purpur start - Ridables
+ if (this.rider != null && this.passengers.get(0) == this.rider) {
+ onDismount(this.rider);
+ this.rider = null;
+ }
+ // Purpur end - Ridables
+
if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) {
this.passengers = ImmutableList.of();
} else {
@@ -3264,15 +3326,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
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
}
}
}
@@ -3474,7 +3539,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
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() {
@@ -3962,7 +4027,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// CraftBukkit end
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) {
@@ -4499,6 +4564,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
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()) {
@@ -4907,7 +4978,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
public float maxUpStep() {
- return 0.0F;
+ return maxUpStep; // Purpur - Add option to set armorstand step height
}
public void onExplosionHit(@Nullable Entity entity) {
@@ -5105,4 +5176,44 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return ((ServerLevel) this.level).isPositionEntityTicking(this.blockPosition());
}
// Paper end - Expose entity id counter
+ // Purpur start - Ridables
+ @Nullable
+ private Player rider = null;
+
+ @Nullable
+ public Player getRider() {
+ return rider;
+ }
+
+ public boolean isRidable() {
+ return false;
+ }
+
+ public boolean isControllable() {
+ return true;
+ }
+
+ public void onMount(Player rider) {
+ if (this instanceof Mob) {
+ ((Mob) this).setTarget(null, null, false);
+ ((Mob) this).getNavigation().stop();
+ }
+ rider.setJumping(false); // fixes jump on mount
+ }
+
+ public void onDismount(Player player) {
+ }
+
+ public boolean onSpacebar() {
+ return false;
+ }
+
+ public boolean onClick(InteractionHand hand) {
+ return false;
+ }
+
+ public boolean processClick(InteractionHand hand) {
+ return false;
+ }
+ // Purpur end - Ridables
}
diff --git a/net/minecraft/world/entity/EntitySelector.java b/net/minecraft/world/entity/EntitySelector.java
index 2aa54ee4ae742a68b79b906062528696beedf60d..002ec5f1ec14411ca48ae04b3379db0c70f81942 100644
--- a/net/minecraft/world/entity/EntitySelector.java
+++ b/net/minecraft/world/entity/EntitySelector.java
@@ -28,6 +28,8 @@ public final class EntitySelector {
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;
diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java
index 303bd2d3ea5c313477c8ab48359a01f230327447..adeab8980b590e4a8b64b62cb60f623b2a842982 100644
--- a/net/minecraft/world/entity/EntityType.java
+++ b/net/minecraft/world/entity/EntityType.java
@@ -1084,6 +1084,16 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
return register(vanillaEntityId(key), builder);
}
+ // Purpur start - PlayerSetSpawnerTypeWithEggEvent
+ public static EntityType<?> getFromBukkitType(org.bukkit.entity.EntityType bukkitType) {
+ return getFromKey(ResourceLocation.parse(bukkitType.getKey().toString()));
+ }
+
+ public static EntityType<?> getFromKey(ResourceLocation location) {
+ return BuiltInRegistries.ENTITY_TYPE.getValue(location);
+ }
+ // Purpur end - PlayerSetSpawnerTypeWithEggEvent
+
public static ResourceLocation getKey(EntityType<?> entityType) {
return BuiltInRegistries.ENTITY_TYPE.getKey(entityType);
}
@@ -1313,6 +1323,16 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
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;
}
@@ -1371,7 +1391,14 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
entity.load(tag);
},
// Paper end - Don't fire sync event during generation
- () -> LOGGER.warn("Skipping Entity with id {}", tag.getString("id"))
+ // Purpur start - log skipped entity's position
+ () -> {LOGGER.warn("Skipping Entity with id {}", tag.getString("id"));
+ try {
+ ListTag pos = tag.getList("Pos", 6);
+ EntityType.LOGGER.warn("Location: {} {},{},{}", level.getWorld().getName(), pos.getDouble(0), pos.getDouble(1), pos.getDouble(2));
+ } catch (Throwable ignore) {}
+ }
+ // Purpur end - log skipped entity's position
);
}
diff --git a/net/minecraft/world/entity/ExperienceOrb.java b/net/minecraft/world/entity/ExperienceOrb.java
index a5fd13641d134eae9d8f1d998cfc456b8fccd140..a43e5190c0f9ae14ccecccd5b58dc0e17f18b0a1 100644
--- a/net/minecraft/world/entity/ExperienceOrb.java
+++ b/net/minecraft/world/entity/ExperienceOrb.java
@@ -323,7 +323,7 @@ public class ExperienceOrb extends Entity {
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 = CraftEventFactory.callPlayerXpCooldownEvent(entity, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2;
+ entity.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(entity, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur - Configurable player pickup exp delay
entity.take(this, 1);
int i = this.repairPlayerItems(serverPlayer, this.value);
if (i > 0) {
@@ -339,7 +339,7 @@ public class ExperienceOrb extends Entity {
}
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()) {
diff --git a/net/minecraft/world/entity/GlowSquid.java b/net/minecraft/world/entity/GlowSquid.java
index efee812785240c1ab1fd47514cfb236a3548f9cf..b982d4b7bdf39fcaf5f22cc889467d7b953e3a8e 100644
--- a/net/minecraft/world/entity/GlowSquid.java
+++ b/net/minecraft/world/entity/GlowSquid.java
@@ -25,6 +25,47 @@ public class GlowSquid extends Squid {
super(entityType, level);
}
+ // Purpur start - Flying squids! Oh my!
+ @Override
+ public boolean canFly() {
+ return this.level().purpurConfig.glowSquidsCanFly;
+ }
+ // Purpur end - Flying squids! Oh my!
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.glowSquidRidable;
+ }
+
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.glowSquidControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.glowSquidMaxHealth);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.glowSquidTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.glowSquidAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected ParticleOptions getInkParticle() {
return ParticleTypes.GLOW_SQUID_INK;
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
index 696ef08b2d897c91a20bc22987b1f5c7047615be..ac006d8738592bc5cb77033adc8c442ce302a476 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -248,9 +248,9 @@ public abstract class LivingEntity extends Entity implements Attackable {
protected float rotOffs;
public float lastHurt;
public boolean jumping;
- public float xxa;
- public float yya;
- public float zza;
+ public float xxa; public float getStrafeMot() { return xxa; } public void setStrafeMot(float strafe) { xxa = strafe; } // Purpur - OBFHELPER
+ public float yya; public float getVerticalMot() { return yya; } public void setVerticalMot(float vertical) { yya = vertical; } // Purpur - OBFHELPER
+ public float zza; public float getForwardMot() { return zza; } public void setForwardMot(float forward) { zza = forward; } // Purpur - OBFHELPER
protected int lerpSteps;
protected double lerpX;
protected double lerpY;
@@ -299,6 +299,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event
public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API
+ protected boolean shouldBurnInDay = false; public boolean shouldBurnInDay() { return this.shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Purpur - API for any mob to burn daylight
@Override
public float getBukkitYaw() {
@@ -308,7 +309,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
protected LivingEntity(EntityType<? extends LivingEntity> entityType, Level level) {
super(entityType, level);
- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(entityType));
+ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(entityType), this); // Purpur - Ridables
+ this.initAttributes(); // Purpur - Configurable entity base attributes
this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit
// CraftBukkit - this.setHealth(this.getMaxHealth()) inlined and simplified to skip the instanceof check for Player, as getBukkitEntity() is not initialized in constructor
this.entityData.set(LivingEntity.DATA_HEALTH_ID, this.getMaxHealth());
@@ -322,6 +324,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.brain = this.makeBrain(new Dynamic<>(nbtOps, nbtOps.createMap(ImmutableMap.of(nbtOps.createString("memories"), nbtOps.emptyMap()))));
}
+ protected void initAttributes() {}// Purpur - Configurable entity base attributes
+
public Brain<?> getBrain() {
return this.brain;
}
@@ -375,6 +379,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
.add(Attributes.MOVEMENT_EFFICIENCY)
.add(Attributes.ATTACK_KNOCKBACK);
}
+ public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur - Ridables
@Override
protected void checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos) {
@@ -458,6 +463,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (d < 0.0) {
double damagePerBlock = this.level().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(io.papermc.paper.util.MCUtil.toLocation(this.level(), this.level().getSharedSpawnPos()));
+ 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)));
}
}
@@ -471,7 +482,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
&& (!flag || !((Player)this).getAbilities().invulnerable);
if (flag1) {
this.setAirSupply(this.decreaseAirSupply(this.getAirSupply()));
- if (this.getAirSupply() == -20) {
+ if (this.getAirSupply() == -this.level().purpurConfig.drowningDamageInterval) { // Purpur - Drowning Settings
this.setAirSupply(0);
Vec3 deltaMovement = this.getDeltaMovement();
@@ -491,7 +502,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
);
}
- this.hurt(this.damageSources().drown(), 2.0F);
+ this.hurt(this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur - Drowning Settings
}
} else if (this.getAirSupply() < this.getMaxAirSupply()) {
this.setAirSupply(this.increaseAirSupply(this.getAirSupply()));
@@ -795,6 +806,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
});
DataResult<Tag> dataResult = this.brain.serializeStart(NbtOps.INSTANCE);
dataResult.resultOrPartial(LOGGER::error).ifPresent(brain -> compound.put("Brain", brain));
+ compound.putBoolean("Purpur.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - API for any mob to burn daylight
}
@Override
@@ -878,6 +890,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (compound.contains("Brain", 10)) {
this.brain = this.makeBrain(new Dynamic<>(NbtOps.INSTANCE, compound.get("Brain")));
}
+
+ // Purpur start - API for any mob to burn daylight
+ if (compound.contains("Purpur.ShouldBurnInDay")) {
+ this.shouldBurnInDay = compound.getBoolean("Purpur.ShouldBurnInDay");
+ }
+ // Purpur end - API for any mob to burn daylight
}
// CraftBukkit start
@@ -1005,15 +1023,30 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (lookingEntity != null) {
// Gale start - Petal - reduce skull ItemStack lookups for reduced visibility
EntityType<?> type = lookingEntity.getType();
- if (type == EntityType.SKELETON && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.SKELETON_SKULL)
- || type == EntityType.ZOMBIE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.ZOMBIE_HEAD)
- || type == EntityType.PIGLIN && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD)
- || type == EntityType.PIGLIN_BRUTE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD)
- || type == EntityType.CREEPER && this.getItemBySlot(EquipmentSlot.HEAD).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;
+ }
}
// Gale end - Petal - reduce skull ItemStack lookups for reduced visibility
}
+ // Purpur end - Configurable mob blindness
return d;
}
@@ -1060,6 +1093,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
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;
@@ -1375,6 +1409,24 @@ public abstract class LivingEntity extends Entity implements Attackable {
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;
@@ -1539,11 +1591,11 @@ public abstract class LivingEntity extends Entity implements Attackable {
protected Player resolvePlayerResponsibleForDamage(DamageSource damageSource) {
Entity entity = damageSource.getEntity();
if (entity instanceof Player player) {
- this.lastHurtByPlayerTime = 100;
+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - Config for mob last hurt by player time
this.lastHurtByPlayer = player;
return player;
} else if (entity instanceof Wolf wolf && wolf.isTame()) {
- this.lastHurtByPlayerTime = 100;
+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - Config for mob last hurt by player time
if (wolf.getOwner() instanceof Player player1) {
this.lastHurtByPlayer = player1;
} else {
@@ -1597,6 +1649,18 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
}
+ // 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().items) {
+ 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);
@@ -1762,7 +1826,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (this.level() instanceof ServerLevel serverLevel) {
boolean var6 = false;
if (this.dead && entitySource instanceof WitherBoss) { // Paper
- if (serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (serverLevel.purpurConfig.witherBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
BlockPos blockPos = this.blockPosition();
BlockState blockState = Blocks.WITHER_ROSE.defaultBlockState();
if (this.level().getBlockState(blockPos).isAir() && blockState.canSurvive(this.level(), blockPos)) {
@@ -1793,6 +1857,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
boolean flag = this.lastHurtByPlayerTime > 0;
this.dropEquipment(level); // CraftBukkit - from below
if (this.shouldDropLoot() && level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+ 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;
@@ -1801,6 +1866,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
// 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
@@ -3040,6 +3106,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
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);
}
}
@@ -3530,8 +3597,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.pushEntities();
// Paper start - Add EntityMoveEvent
- if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof Player)) {
- if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
+ // Purpur start - Ridables
+ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
+ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof Player)) {
+ // Purpur end - Ridables
Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO);
Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone());
@@ -3541,11 +3610,52 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch());
}
}
+ // Purpur start - Ridables
+ if (getRider() != null) {
+ getRider().resetLastActionTime();
+ if (((ServerLevel) level()).hasRidableMoveEvent && this instanceof Mob) {
+ Location from = new Location(level().getWorld(), xo, yo, zo, this.yRotO, this.xRotO);
+ Location to = new Location(level().getWorld(), getX(), getY(), getZ(), this.getYRot(), this.getXRot());
+ org.purpurmc.purpur.event.entity.RidableMoveEvent event = new org.purpurmc.purpur.event.entity.RidableMoveEvent((org.bukkit.entity.Mob) getBukkitLivingEntity(), (org.bukkit.entity.Player) getRider().getBukkitEntity(), from, to.clone());
+ if (!event.callEvent()) {
+ absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch());
+ } else if (!to.equals(event.getTo())) {
+ absMoveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
+ }
+ }
+ }
+ // Purpur end - Ridables
}
// Paper end - Add EntityMoveEvent
if (this.level() instanceof ServerLevel serverLevel && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
this.hurtServer(serverLevel, this.damageSources().drown(), 1.0F);
}
+
+ // Purpur start - copied from Zombie - API for any mob to burn daylight
+ if (this.isAlive()) {
+ boolean flag = this.shouldBurnInDay() && this.isSunBurnTick(); // Paper - shouldBurnInDay API // Purpur - use shouldBurnInDay() method to handle Phantoms properly - API for any mob to burn daylight
+ if (flag) {
+ ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD);
+ if (!itemBySlot.isEmpty()) {
+ if (itemBySlot.isDamageableItem()) {
+ Item item = itemBySlot.getItem();
+ itemBySlot.setDamageValue(itemBySlot.getDamageValue() + this.random.nextInt(2));
+ if (itemBySlot.getDamageValue() >= itemBySlot.getMaxDamage()) {
+ this.onEquippedItemBroken(item, EquipmentSlot.HEAD);
+ this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY);
+ }
+ }
+
+ flag = false;
+ }
+
+ if (flag) {
+ if (getRider() == null || !this.isControllable()) // Purpur - ignore mobs which are uncontrollable or without rider - API for any mob to burn daylight
+ this.igniteForSeconds(8.0F);
+ }
+ }
+ }
+ // Purpur end - copied from Zombie - API for any mob to burn daylight
}
public boolean isSensitiveToWater() {
@@ -3567,7 +3677,18 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (i1 % 2 == 0) {
List<EquipmentSlot> list = EquipmentSlot.VALUES.stream().filter(slot -> canGlideUsing(this.getItemBySlot(slot), slot)).toList();
EquipmentSlot equipmentSlot = Util.getRandom(list, this.random);
- this.getItemBySlot(equipmentSlot).hurtAndBreak(1, this, equipmentSlot);
+
+ // Purpur start - Implement elytra settings
+ int damage = level().purpurConfig.elytraDamagePerSecond;
+ if (level().purpurConfig.elytraDamageMultiplyBySpeed > 0) {
+ double speed = getDeltaMovement().lengthSqr();
+ if (speed > level().purpurConfig.elytraDamageMultiplyBySpeed) {
+ damage *= (int) speed;
+ }
+ }
+
+ this.getItemBySlot(equipmentSlot).hurtAndBreak(damage, this, equipmentSlot);
+ // Purpur end - Implement elytra settings
}
this.gameEvent(GameEvent.ELYTRA_GLIDE);
@@ -4450,6 +4571,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
: 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
? SlotAccess.forEquipmentSlot(entity, slot, stack -> stack.isEmpty() || entity.getEquipmentSlotForItem(stack) == slot)
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
index 4b4fa6674916d227500ce03823477a3958729190..fbcb803fb575cb1f81afa9d03d5fddbf6352155d 100644
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
@@ -143,13 +143,14 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
private BlockPos restrictCenter = BlockPos.ZERO;
private float restrictRadius = -1.0F;
public boolean aware = true; // CraftBukkit
+ public int ticksSinceLastInteraction; // Purpur - Entity lifespan
protected Mob(EntityType<? extends Mob> entityType, Level level) {
super(entityType, level);
this.goalSelector = new GoalSelector();
this.targetSelector = new GoalSelector();
- this.lookControl = new LookControl(this);
- this.moveControl = new MoveControl(this);
+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur - Ridables
+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur - Ridables
this.jumpControl = new JumpControl(this);
this.bodyRotationControl = this.createBodyControl();
this.navigation = this.createNavigation(level);
@@ -294,6 +295,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
target = null;
}
}
+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
this.target = target;
return true;
// CraftBukkit end
@@ -333,8 +335,29 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
this.resetAmbientSoundTime();
this.playAmbientSound();
}
+
+ 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 source) {
this.resetAmbientSoundTime();
@@ -482,6 +505,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
compound.putBoolean("NoAI", this.isNoAi());
}
compound.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
+ compound.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur - Entity lifespan
}
@Override
@@ -564,6 +588,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
this.aware = compound.getBoolean("Bukkit.Aware");
}
// CraftBukkit end
+ // Purpur start - Entity lifespan
+ if (compound.contains("Purpur.ticksSinceLastInteraction")) {
+ this.ticksSinceLastInteraction = compound.getInt("Purpur.ticksSinceLastInteraction");
+ }
+ // Purpur end - Entity lifespan
}
@Override
@@ -614,7 +643,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
&& this.canPickUpLoot()
&& this.isAlive()
&& !this.dead
- && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ && serverLevel.purpurConfig.entitiesPickUpLootBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
Vec3i pickupReach = this.getPickupReach();
for (ItemEntity itemEntity : this.level()
@@ -1257,7 +1286,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
);
}
- this.setLeftHanded(random.nextFloat() < 0.05F);
+ this.setLeftHanded(random.nextFloat() < level.getLevel().purpurConfig.entityLeftHandedChance); // Purpur - Changeable Mob Left Handed Chance
return spawnGroupData;
}
@@ -1354,7 +1383,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
}
protected InteractionResult mobInteract(Player player, InteractionHand hand) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur - Ridables
}
public boolean isWithinRestriction() {
@@ -1596,6 +1625,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
this.playAttackSound();
}
+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
return flag;
}
@@ -1608,26 +1638,8 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
// Gale end - JettPack - optimize sun burn tick - cache eye blockpos
public boolean isSunBurnTick() {
- if (this.level().isDay() && !this.level().isClientSide) {
- // Gale start - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
- if (this.cached_position != this.position) {
- this.cached_eye_blockpos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
- this.cached_position = this.position;
- }
-
- float lightLevelDependentMagicValue = this.getLightLevelDependentMagicValue(cached_eye_blockpos); // Pass BlockPos to getBrightness
-
- // Check brightness first
- if (lightLevelDependentMagicValue <= 0.5F) return false;
- if (this.random.nextFloat() * 30.0F >= (lightLevelDependentMagicValue - 0.4F) * 2.0F) return false;
- // Gale end - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
-
- boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow;
-
- return !flag && this.level().canSeeSky(this.cached_eye_blockpos); // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
- }
-
- return false; // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - diff on change
+ // Purpur - implemented in Entity - API for any mob to burn daylight
+ return super.isSunBurnTick();
}
@Override
@@ -1683,4 +1695,58 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
public float[] getArmorDropChances() {
return this.armorDropChances;
}
+
+ // Purpur start - Ridables
+ public double getMaxY() {
+ return level().getHeight();
+ }
+
+ public InteractionResult tryRide(Player player, InteractionHand hand) {
+ return tryRide(player, hand, InteractionResult.PASS);
+ }
+
+ public InteractionResult tryRide(Player player, InteractionHand hand, InteractionResult result) {
+ if (!isRidable()) {
+ return result;
+ }
+ if (hand != InteractionHand.MAIN_HAND) {
+ return InteractionResult.PASS;
+ }
+ if (player.isShiftKeyDown()) {
+ return InteractionResult.PASS;
+ }
+ if (!player.getItemInHand(hand).isEmpty()) {
+ return InteractionResult.PASS;
+ }
+ if (!passengers.isEmpty() || player.isPassenger()) {
+ return InteractionResult.PASS;
+ }
+ if (this instanceof TamableAnimal tamable) {
+ if (tamable.isTame() && !tamable.isOwnedBy(player)) {
+ return InteractionResult.PASS;
+ }
+ if (!tamable.isTame() && !level().purpurConfig.untamedTamablesAreRidable) {
+ return InteractionResult.PASS;
+ }
+ }
+ if (this instanceof AgeableMob ageable) {
+ if (ageable.isBaby() && !level().purpurConfig.babiesAreRidable) {
+ return InteractionResult.PASS;
+ }
+ }
+ if (!player.getBukkitEntity().hasPermission("allow.ride." + net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getKey(getType()).getPath())) {
+ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
+ serverPlayer.sendMiniMessage(org.purpurmc.purpur.PurpurConfig.cannotRideMob);
+ }
+ return InteractionResult.PASS;
+ }
+ player.setYRot(this.getYRot());
+ player.setXRot(this.getXRot());
+ if (player.startRiding(this)) {
+ return InteractionResult.SUCCESS;
+ } else {
+ return InteractionResult.PASS;
+ }
+ }
+ // Purpur end - Ridables
}
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
index df3724294a3297ebdc11aef3f935bf0cf36b9c95..89f4c5b2d61e27acd48063f9f24ce9ea91898b8b 100644
--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
@@ -26,15 +26,22 @@ public class AttributeMap {
// Gale end - Lithium - replace AI attributes with optimized collections
private final AttributeSupplier supplier;
private final java.util.function.Function<Holder<Attribute>, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations
+ private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables
public AttributeMap(AttributeSupplier supplier) {
- this.supplier = supplier;
+ // Purpur start - Ridables
+ this(supplier, null);
+ }
+ public AttributeMap(AttributeSupplier defaultAttributes, net.minecraft.world.entity.LivingEntity entity) {
+ this.entity = entity;
+ // Purpur end - Ridables
+ this.supplier = defaultAttributes;
this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations
}
private void onAttributeModified(AttributeInstance instance) {
this.attributesToUpdate.add(instance);
- if (instance.getAttribute().value().isClientSyncable()) {
+ if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables
this.attributesToSync.add(instance);
}
}
@@ -48,7 +55,7 @@ public class AttributeMap {
}
public Collection<AttributeInstance> getSyncableAttributes() {
- return this.attributes.values().stream().filter(instance -> instance.getAttribute().value().isClientSyncable()).collect(Collectors.toList());
+ return this.attributes.values().stream().filter(instance -> instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))).collect(Collectors.toList()); // Purpur - Ridables
}
@Nullable
diff --git a/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java
index 33527a1825119f3667fb3c7ccec318f2c7328ec9..e773c426567964fc8269237d71c3434a5473985c 100644
--- a/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java
+++ b/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java
@@ -131,7 +131,7 @@ public class DefaultAttributes {
.put(EntityType.OCELOT, Ocelot.createAttributes().build())
.put(EntityType.PANDA, Panda.createAttributes().build())
.put(EntityType.PARROT, Parrot.createAttributes().build())
- .put(EntityType.PHANTOM, Monster.createMonsterAttributes().build())
+ .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur - Ridables
.put(EntityType.PIG, Pig.createAttributes().build())
.put(EntityType.PIGLIN, Piglin.createAttributes().build())
.put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build())
@@ -162,7 +162,7 @@ public class DefaultAttributes {
.put(EntityType.VILLAGER, Villager.createAttributes().build())
.put(EntityType.VINDICATOR, Vindicator.createAttributes().build())
.put(EntityType.WARDEN, Warden.createAttributes().build())
- .put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build())
+ .put(EntityType.WANDERING_TRADER, net.minecraft.world.entity.npc.WanderingTrader.createAttributes().build()) // Purpur - Villagers follow emerald blocks
.put(EntityType.WITCH, Witch.createAttributes().build())
.put(EntityType.WITHER, WitherBoss.createAttributes().build())
.put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build())
diff --git a/net/minecraft/world/entity/ai/attributes/RangedAttribute.java b/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
index a87accd5fe14102e7a2938f991a8ed0b9accd1bb..c7515f7f24e39d6931dcf18574cb47d754983903 100644
--- a/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
+++ b/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
@@ -29,6 +29,7 @@ public class RangedAttribute extends Attribute {
@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);
}
}
diff --git a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
index 751e91a922b20c96f27885c3eb085ec4ae39091b..7f0975f8bd6d5f8ca28f503f93c8cb5c42557420 100644
--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -94,7 +94,7 @@ public class AcquirePoi {
};
// 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())) {
diff --git a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
index 4106549bd4dec1cc47d8765be8f5d119fe33bf56..56d49bc71cb0cb0a08ff771991fd77ab774b4b59 100644
--- a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
+++ b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
@@ -32,6 +32,7 @@ public class HarvestFarmland extends Behavior<Villager> {
private long nextOkStartTime;
private int timeWorkedSoFar;
private final List<BlockPos> validFarmlandAroundVillager = Lists.newArrayList();
+ private boolean clericWartFarmer = false; // Purpur - Option for Villager Clerics to farm Nether Wart
public HarvestFarmland() {
super(
@@ -48,11 +49,12 @@ public class HarvestFarmland extends Behavior<Villager> {
@Override
protected boolean checkExtraStartConditions(ServerLevel level, Villager owner) {
- if (!level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (!level.purpurConfig.villagerBypassMobGriefing == !level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
return false;
- } else if (owner.getVillagerData().getProfession() != VillagerProfession.FARMER) {
+ } else if (owner.getVillagerData().getProfession() != VillagerProfession.FARMER && !(level.purpurConfig.villagerClericsFarmWarts && owner.getVillagerData().getProfession() == VillagerProfession.CLERIC)) { // Purpur - Option for Villager Clerics to farm Nether Wart
return false;
} else {
+ if (!this.clericWartFarmer && owner.getVillagerData().getProfession() == VillagerProfession.CLERIC) this.clericWartFarmer = true; // Purpur - Option for Villager Clerics to farm Nether Wart
BlockPos.MutableBlockPos mutableBlockPos = owner.blockPosition().mutable();
this.validFarmlandAroundVillager.clear();
@@ -83,6 +85,7 @@ public class HarvestFarmland extends Behavior<Villager> {
BlockState blockState = serverLevel.getBlockState(pos);
Block block = blockState.getBlock();
Block block1 = serverLevel.getBlockState(pos.below()).getBlock();
+ if (this.clericWartFarmer) return block == net.minecraft.world.level.block.Blocks.NETHER_WART && blockState.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3 || blockState.isAir() && block1 == net.minecraft.world.level.block.Blocks.SOUL_SAND; // Purpur - Option for Villager Clerics to farm Nether Wart
return block instanceof CropBlock && ((CropBlock)block).isMaxAge(blockState) || blockState.isAir() && block1 instanceof FarmBlock;
}
@@ -109,19 +112,19 @@ public class HarvestFarmland extends Behavior<Villager> {
BlockState blockState = level.getBlockState(this.aboveFarmlandPos);
Block block = blockState.getBlock();
Block block1 = level.getBlockState(this.aboveFarmlandPos.below()).getBlock();
- if (block instanceof CropBlock && ((CropBlock)block).isMaxAge(blockState)) {
+ if (block instanceof CropBlock && ((CropBlock)block).isMaxAge(blockState) && !this.clericWartFarmer || this.clericWartFarmer && block == net.minecraft.world.level.block.Blocks.NETHER_WART && blockState.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3) { // Purpur - Option for Villager Clerics to farm Nether Wart
if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, blockState.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state
level.destroyBlock(this.aboveFarmlandPos, true, owner);
} // CraftBukkit
}
- if (blockState.isAir() && block1 instanceof FarmBlock && owner.hasFarmSeeds()) {
+ if (blockState.isAir() && block1 instanceof FarmBlock && !this.clericWartFarmer || this.clericWartFarmer && block1 == net.minecraft.world.level.block.Blocks.SOUL_SAND && owner.hasFarmSeeds()) { // Purpur - Option for Villager Clerics to farm Nether Wart
SimpleContainer inventory = owner.getInventory();
for (int i = 0; i < inventory.getContainerSize(); i++) {
ItemStack item = inventory.getItem(i);
boolean flag = false;
- if (!item.isEmpty() && item.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) && item.getItem() instanceof BlockItem blockItem) {
+ if (!item.isEmpty() && (item.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) || this.clericWartFarmer && item.getItem() == net.minecraft.world.item.Items.NETHER_WART) && item.getItem() instanceof BlockItem blockItem) { // Purpur - Option for Villager Clerics to farm Nether Wart
BlockState blockState1 = blockItem.getBlock().defaultBlockState();
if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, blockState1)) { // CraftBukkit
level.setBlockAndUpdate(this.aboveFarmlandPos, blockState1);
@@ -136,7 +139,7 @@ public class HarvestFarmland extends Behavior<Villager> {
this.aboveFarmlandPos.getX(),
this.aboveFarmlandPos.getY(),
this.aboveFarmlandPos.getZ(),
- SoundEvents.CROP_PLANTED,
+ this.clericWartFarmer ? SoundEvents.NETHER_WART_PLANTED : SoundEvents.CROP_PLANTED, // Purpur - Option for Villager Clerics to farm Nether Wart
SoundSource.BLOCKS,
1.0F,
1.0F
diff --git a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
index 296ecffbbce931b42c67ea523373a61cea23acf4..b2eec24be3635f2c19da9b147211fe6cb454c780 100644
--- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
+++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
@@ -55,7 +55,7 @@ public class InteractWithDoor {
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 +72,7 @@ public class InteractWithDoor {
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 +118,7 @@ public class InteractWithDoor {
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();
diff --git a/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java b/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
index 400e6d49144b3e5803901938dcd2ac4e52e9c131..45c45afeffcfba3558bdf46cbe39ff60004ffc01 100644
--- a/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
+++ b/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
@@ -46,6 +46,7 @@ public class ShowTradesToPlayer extends Behavior<Villager> {
@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();
diff --git a/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
index 243ac036f95794e7766aefb4630b635681ae5a5f..4d8523a43d60cd6b4fd5546ffb3a61417b2c475b 100644
--- a/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
+++ b/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
@@ -59,6 +59,12 @@ public class TradeWithVillager extends Behavior<Villager> {
throwHalfStack(owner, ImmutableSet.of(Items.WHEAT), villager);
}
+ // Purpur start - Option for Villager Clerics to farm Nether Wart
+ if (level.purpurConfig.villagerClericsFarmWarts && level.purpurConfig.villagerClericFarmersThrowWarts && owner.getVillagerData().getProfession() == VillagerProfession.CLERIC && owner.getInventory().countItem(Items.NETHER_WART) > Items.NETHER_WART.getDefaultMaxStackSize() / 2) {
+ throwHalfStack(owner, ImmutableSet.of(Items.NETHER_WART), villager);
+ }
+ // Purpur end - Option for Villager Clerics to farm Nether Wart
+
if (!this.trades.isEmpty() && owner.getInventory().hasAnyOf(this.trades)) {
throwHalfStack(owner, this.trades, villager);
}
diff --git a/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java b/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
index 84afd8646b05409c582f29d73f9fea4b09feb603..32779b121322688a4b14e460b1f902ef67770b32 100644
--- a/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
+++ b/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
@@ -74,8 +74,13 @@ public class VillagerGoalPackages {
}
public static ImmutableList<Pair<Integer, ? extends BehaviorControl<? super Villager>>> getWorkPackage(VillagerProfession profession, float speedModifier) {
+ // Purpur start - Option for Villager Clerics to farm Nether Wart
+ return getWorkPackage(profession, speedModifier, false);
+ }
+ public static ImmutableList<Pair<Integer, ? extends BehaviorControl<? super Villager>>> getWorkPackage(VillagerProfession profession, float speedModifier, boolean clericsFarmWarts) {
+ // Purpur end - Option for Villager Clerics to farm Nether Wart
WorkAtPoi workAtPoi;
- if (profession == VillagerProfession.FARMER) {
+ if (profession == VillagerProfession.FARMER || (clericsFarmWarts && profession == VillagerProfession.CLERIC)) { // Purpur - Option for Villager Clerics to farm Nether Wart
workAtPoi = new WorkAtComposter();
} else {
workAtPoi = new WorkAtPoi();
diff --git a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
index 8fe5bd54b5a4848da1f08ea65fe2bc3514bed8c8..54eeb72b638127b180470887a3b59d55773f3bc9 100644
--- a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
+++ b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
@@ -118,8 +118,10 @@ public class VillagerMakeLove extends Behavior<Villager> {
return Optional.empty();
}
// Move age setting down
- parent.setAge(6000);
- partner.setAge(6000);
+ // Purpur start - Make entity breeding times configurable
+ parent.setAge(level.purpurConfig.villagerBreedingTicks);
+ partner.setAge(level.purpurConfig.villagerBreedingTicks);
+ // Purpur end - Make entity breeding times configurable
level.addFreshEntityWithPassengers(breedOffspring, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING);
// CraftBukkit end - call EntityBreedEvent
level.broadcastEntityEvent(breedOffspring, (byte)12);
diff --git a/net/minecraft/world/entity/ai/control/MoveControl.java b/net/minecraft/world/entity/ai/control/MoveControl.java
index 0f9bf0cb0655a6ed449a86e99b17f89b4e3264df..1860b4ab2314f5da017313977c6423e735a4f96b 100644
--- a/net/minecraft/world/entity/ai/control/MoveControl.java
+++ b/net/minecraft/world/entity/ai/control/MoveControl.java
@@ -29,6 +29,20 @@ public class MoveControl implements Control {
this.mob = mob;
}
+ // Purpur start - Ridables
+ public void setSpeedModifier(double speed) {
+ this.speedModifier = speed;
+ }
+
+ public void setForward(float forward) {
+ this.strafeForwards = forward;
+ }
+
+ public void setStrafe(float strafe) {
+ this.strafeRight = strafe;
+ }
+ // Purpur end - Ridables
+
public boolean hasWanted() {
return this.operation == MoveControl.Operation.MOVE_TO;
}
diff --git a/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java b/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java
index d7f9b3b2b1077ea10e8f64b87c8f4c4354e90858..713f62b34a91fa76f40e49a5e390145f70755e58 100644
--- a/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java
+++ b/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java
@@ -3,7 +3,7 @@ package net.minecraft.world.entity.ai.control;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Mob;
-public class SmoothSwimmingLookControl extends LookControl {
+public class SmoothSwimmingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
private final int maxYRotFromCenter;
private static final int HEAD_TILT_X = 10;
private static final int HEAD_TILT_Y = 20;
@@ -14,7 +14,7 @@ public class SmoothSwimmingLookControl extends LookControl {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (this.lookAtCooldown > 0) {
this.lookAtCooldown--;
this.getYRotD().ifPresent(rotationWanted -> this.mob.yHeadRot = this.rotateTowards(this.mob.yHeadRot, rotationWanted + 20.0F, this.yMaxRotSpeed));
diff --git a/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
index e026e07ca86700c864a3dceda6817fb7b6cb11e9..f1dfe0bf047e7d331b2379a672ff7b8eae4c9c90 100644
--- a/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
+++ b/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
@@ -30,7 +30,7 @@ public class BreakDoorGoal extends DoorInteractGoal {
@Override
public boolean canUse() {
return super.canUse()
- && getServerLevel(this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)
+ && this.mob.level().purpurConfig.zombieBypassMobGriefing == !getServerLevel(this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) // Purpur - Add mobGriefing bypass to everything affected
&& this.isValidDifficulty(this.mob.level().getDifficulty())
&& !this.isOpen();
}
diff --git a/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
index e84893780b533b1ecb3675606b5c2daf7339b861..65eb4d8001b07cb3f7cda17565eea10a88a9c47c 100644
--- a/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
+++ b/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
@@ -67,7 +67,7 @@ public class EatBlockGoal extends Goal {
BlockPos blockPos = this.mob.blockPosition();
final BlockState blockState = this.level.getBlockState(blockPos); // Paper - fix wrong block state
if (IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state
- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state // Purpur - Add mobGriefing bypass to everything affected
this.level.destroyBlock(blockPos, false);
}
@@ -75,7 +75,7 @@ public class EatBlockGoal extends Goal {
} else {
BlockPos blockPos1 = blockPos.below();
if (this.level.getBlockState(blockPos1).is(Blocks.GRASS_BLOCK)) {
- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state // Purpur - Add mobGriefing bypass to everything affected
this.level.levelEvent(2001, blockPos1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()));
this.level.setBlock(blockPos1, Blocks.DIRT.defaultBlockState(), 2);
}
diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
index 6eaf0bd944349cd0c6084462ac385fa2caafe933..be59d0c27a83b329ec3f97c029cfb9c114e22472 100644
--- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
+++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
@@ -22,6 +22,7 @@ public class LlamaFollowCaravanGoal extends Goal {
@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 +72,7 @@ public class LlamaFollowCaravanGoal extends Goal {
@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) {
diff --git a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
index 579ca031d461ed4327fe4fb45c5289565322e64e..95fa516910a3834bbd4db6d11279e13a1f0dac41 100644
--- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
+++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
@@ -35,7 +35,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal {
@Override
public boolean canUse() {
- if (!getServerLevel(this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (!getServerLevel(this.removerMob).purpurConfig.zombieBypassMobGriefing == !getServerLevel(this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
return false;
} else if (this.nextStartTick > 0) {
this.nextStartTick--;
diff --git a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
index 878d7813b3f2f52bef336c6d855d738bc2f83491..d0f94f065d2ecf6ca6b47ac49422ffa656a18f55 100644
--- a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
+++ b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
@@ -58,7 +58,7 @@ public class RunAroundLikeCrazyGoal extends Goal {
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;
}
diff --git a/net/minecraft/world/entity/ai/goal/SwellGoal.java b/net/minecraft/world/entity/ai/goal/SwellGoal.java
index 243a552f6f0c8c2bd25c0209c95e3bca08734711..38fd0196a0f5a90e39fa4eb8592f89bf6b88ccf5 100644
--- a/net/minecraft/world/entity/ai/goal/SwellGoal.java
+++ b/net/minecraft/world/entity/ai/goal/SwellGoal.java
@@ -55,6 +55,14 @@ public class SwellGoal extends Goal {
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
}
}
}
diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java
index 438d6347778a94b4fe430320b268a2d67afa209a..f88f618d34fb343b31de3af1a875d6633703df71 100644
--- a/net/minecraft/world/entity/ai/goal/TemptGoal.java
+++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java
@@ -58,7 +58,7 @@ public class TemptGoal extends Goal {
}
private boolean shouldFollow(LivingEntity entity) {
- return this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem());
+ return (this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem())) && (!(this.mob instanceof net.minecraft.world.entity.npc.Villager villager) || !villager.isSleeping()); // Purpur - Villagers follow emerald blocks
}
@Override
diff --git a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
index 066faa704338c573472381e1ebd063e0d52aaaa4..1f96fd5085bacb4c584576c7cb9f51e7898e9b03 100644
--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
@@ -56,7 +56,7 @@ public class NearestBedSensor extends Sensor<Mob> {
// 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()) {
diff --git a/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
index d9318a5a9eb3de89f48efa23b944c6d9e2ba37d1..135e19e921c9592cec83f6f940f2abe47757fbb8 100644
--- a/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
+++ b/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
@@ -30,6 +30,13 @@ public class SecondaryPoiSensor extends Sensor<Villager> {
return;
}
// Gale end - Lithium - skip secondary POI sensor if absent
+ // Purpur start - Option for Villager Clerics to farm Nether Wart - make sure clerics don't wander to soul sand when the option is off
+ Brain<?> brain = entity.getBrain();
+ if (!level.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC) {
+ brain.eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE);
+ return;
+ }
+ // Purpur end - Option for Villager Clerics to farm Nether Wart
ResourceKey<Level> resourceKey = level.dimension();
BlockPos blockPos = entity.blockPosition();
List<GlobalPos> list = Lists.newArrayList();
@@ -46,7 +53,7 @@ public class SecondaryPoiSensor extends Sensor<Villager> {
}
}
- Brain<?> brain = entity.getBrain();
+ //Brain<?> brain = entity.getBrain(); // Purpur - Option for Villager Clerics to farm Nether Wart - moved up
if (!list.isEmpty()) {
brain.setMemory(MemoryModuleType.SECONDARY_JOB_SITE, list);
} else {
diff --git a/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
index 17a08a3af468093668a41f154c2beb69c6617efa..398a97a72dca785204f6b7b8fc4abe5cd64de51e 100644
--- a/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+++ b/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
@@ -64,6 +64,10 @@ public class TargetingConditions {
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)) {
diff --git a/net/minecraft/world/entity/ambient/Bat.java b/net/minecraft/world/entity/ambient/Bat.java
index 62085eecd2bb55721208c5fd126aaae56a50ed6b..e042a4df5a9ad16751f64ce71537969932ae81fd 100644
--- a/net/minecraft/world/entity/ambient/Bat.java
+++ b/net/minecraft/world/entity/ambient/Bat.java
@@ -42,11 +42,87 @@ public class Bat extends AmbientCreature {
public Bat(EntityType<? extends Bat> entityType, Level level) {
super(entityType, level);
+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.075F); // Purpur - Ridables
if (!level.isClientSide) {
this.setResting(true);
}
}
+ // Purpur start - Ridables
+ @Override
+ public boolean shouldSendAttribute(net.minecraft.world.entity.ai.attributes.Attribute attribute) { return attribute != Attributes.FLYING_SPEED.value(); } // Fixes log spam on clients
+
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.batRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.batRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.batControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.batMaxY;
+ }
+
+ @Override
+ public void onMount(net.minecraft.world.entity.player.Player rider) {
+ super.onMount(rider);
+ if (isResting()) {
+ setResting(false);
+ level().levelEvent(null, 1025, new BlockPos(this).above(), 0);
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2;
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.batMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.batScale);
+ this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.batFollowRange);
+ this.getAttribute(Attributes.KNOCKBACK_RESISTANCE).setBaseValue(this.level().purpurConfig.batKnockbackResistance);
+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.batMovementSpeed);
+ this.getAttribute(Attributes.FLYING_SPEED).setBaseValue(this.level().purpurConfig.batFlyingSpeed);
+ this.getAttribute(Attributes.ARMOR).setBaseValue(this.level().purpurConfig.batArmor);
+ this.getAttribute(Attributes.ARMOR_TOUGHNESS).setBaseValue(this.level().purpurConfig.batArmorToughness);
+ this.getAttribute(Attributes.ATTACK_KNOCKBACK).setBaseValue(this.level().purpurConfig.batAttackKnockback);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.batTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.batAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public boolean isFlapping() {
return !this.isResting() && this.tickCount % 10.0F == 0.0F;
@@ -98,7 +174,7 @@ public class Bat extends AmbientCreature {
}
public static AttributeSupplier.Builder createAttributes() {
- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0);
+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables
}
public boolean isResting() {
@@ -129,6 +205,14 @@ public class Bat extends AmbientCreature {
@Override
protected void customServerAiStep(ServerLevel level) {
+ // Purpur start - Ridables
+ if (getRider() != null && this.isControllable()) {
+ Vec3 mot = getDeltaMovement();
+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z());
+ return;
+ }
+ // Purpur end - Ridables
+
super.customServerAiStep(level);
BlockPos blockPos = this.blockPosition();
BlockPos blockPos1 = blockPos.above();
@@ -231,7 +315,7 @@ public class Bat extends AmbientCreature {
} else {
int maxLocalRawBrightness = level.getMaxLocalRawBrightness(pos);
int i = 4;
- if (isHalloween()) {
+ if (Bat.isHalloweenSeason(level.getMinecraftWorld())) { // Purpur - Halloween options and optimizations
i = 7;
} else if (randomSource.nextBoolean()) {
return false;
@@ -276,6 +360,8 @@ public class Bat extends AmbientCreature {
*/
private static long nextHalloweenEnd = 0;
+ public static boolean isHalloweenSeason(Level level) { return level.purpurConfig.forceHalloweenSeason || isHalloween(); } // Purpur - Halloween options and optimizations
+
// The Halloween begins at 10/20 0:00, and end with 11/04 0:00
// Only when the current Halloween period ends, the `nextHalloweenStart`
// and `nextHalloweenEnd` will adjust to the epoch ms of date of next year
diff --git a/net/minecraft/world/entity/animal/AbstractFish.java b/net/minecraft/world/entity/animal/AbstractFish.java
index c0997c8c0f8ee4474d3acdd5938b1879c4e589a2..28ae152125ed83d8917674b6068f227f87890f30 100644
--- a/net/minecraft/world/entity/animal/AbstractFish.java
+++ b/net/minecraft/world/entity/animal/AbstractFish.java
@@ -87,6 +87,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
@Override
protected void registerGoals() {
super.registerGoals();
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(0, new PanicGoal(this, 1.25));
this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 1.6, 1.4, EntitySelector.NO_SPECTATORS::test));
this.goalSelector.addGoal(4, new AbstractFish.FishSwimGoal(this));
@@ -100,7 +101,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
@Override
public void travel(Vec3 travelVector) {
if (this.isControlledByLocalInstance() && this.isInWater()) {
- this.moveRelative(0.01F, travelVector);
+ this.moveRelative(getRider() != null ? getSpeed() : 0.01F, travelVector); // Purpur - Ridables
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(0.9));
if (this.getTarget() == null) {
@@ -160,7 +161,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
protected void playStepSound(BlockPos pos, BlockState block) {
}
- static class FishMoveControl extends MoveControl {
+ static class FishMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - Ridables
private final AbstractFish fish;
FishMoveControl(AbstractFish mob) {
@@ -168,14 +169,22 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
this.fish = mob;
}
+ // Purpur start - Ridables
@Override
- public void tick() {
+ public void purpurTick(Player rider) {
+ super.purpurTick(rider);
+ fish.setDeltaMovement(fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
+ }
+ // Purpur end - Ridables
+
+ @Override
+ public void vanillaTick() { // Purpur - Ridables
if (this.fish.isEyeInFluid(FluidTags.WATER)) {
this.fish.setDeltaMovement(this.fish.getDeltaMovement().add(0.0, 0.005, 0.0));
}
if (this.operation == MoveControl.Operation.MOVE_TO && !this.fish.getNavigation().isDone()) {
- float f = (float)(this.speedModifier * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float f = (float)(this.getSpeedModifier() * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables
this.fish.setSpeed(Mth.lerp(0.125F, this.fish.getSpeed(), f));
double d = this.wantedX - this.fish.getX();
double d1 = this.wantedY - this.fish.getY();
diff --git a/net/minecraft/world/entity/animal/Animal.java b/net/minecraft/world/entity/animal/Animal.java
index 28697020ba838c86dde5b2b11b2fe8f347a147a7..452270f7f1c54ca98c34dcf9a9d29acae77737c8 100644
--- a/net/minecraft/world/entity/animal/Animal.java
+++ b/net/minecraft/world/entity/animal/Animal.java
@@ -40,6 +40,7 @@ public abstract class Animal extends AgeableMob {
@Nullable
public UUID loveCause;
public ItemStack breedItem; // CraftBukkit - Add breedItem variable
+ public abstract int getPurpurBreedTime(); // Purpur - Make entity breeding times configurable
protected Animal(EntityType<? extends Animal> entityType, Level level) {
super(entityType, level);
@@ -142,7 +143,7 @@ public abstract class Animal extends AgeableMob {
ItemStack itemInHand = player.getItemInHand(hand);
if (this.isFood(itemInHand)) {
int age = this.getAge();
- if (!this.level().isClientSide && age == 0 && this.canFallInLove()) {
+ if (!this.level().isClientSide && 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(player, breedCopy); // Paper - Fix EntityBreedEvent copying
@@ -239,10 +240,20 @@ public abstract class Animal extends AgeableMob {
public void spawnChildFromBreeding(ServerLevel level, Animal mate) {
AgeableMob breedOffspring = this.getBreedOffspring(level, mate);
if (breedOffspring != null) {
- breedOffspring.setBaby(true);
- breedOffspring.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+ //breedOffspring.setBaby(true); // Purpur - Add adjustable breeding cooldown to config - moved down
+ //breedOffspring.moveTo(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(mate.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.moveTo(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, mate, breeder, this.breedItem, experience);
if (entityBreedEvent.isCancelled()) {
@@ -272,8 +283,10 @@ public abstract class Animal extends AgeableMob {
player.awardStat(Stats.ANIMALS_BRED);
CriteriaTriggers.BRED_ANIMALS.trigger(player, this, animal, baby);
} // Paper - call EntityBreedEvent
- this.setAge(6000);
- animal.setAge(6000);
+ // Purpur start - Make entity breeding times configurable
+ this.setAge(this.getPurpurBreedTime());
+ animal.setAge(animal.getPurpurBreedTime());
+ // Purpur end - Make entity breeding times configurable
this.resetLove();
animal.resetLove();
level.broadcastEntityEvent(this, (byte)18);
diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java
index 94244b148533ef026bf5c56abbc2bb5cfa83c938..d5727999eb67ff30dbf47865d59452483338e170 100644
--- a/net/minecraft/world/entity/animal/Bee.java
+++ b/net/minecraft/world/entity/animal/Bee.java
@@ -145,6 +145,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
public Bee(EntityType<? extends Bee> entityType, Level level) {
super(entityType, level);
+ final org.purpurmc.purpur.controller.FlyingMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.25F, 1.0F, false); // Purpur - Ridables
// Paper start - Fix MC-167279
class BeeFlyingMoveControl extends FlyingMoveControl {
public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) {
@@ -153,22 +154,69 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
@Override
public void tick() {
+ // Purpur start - Ridables
+ if (mob.getRider() != null && mob.isControllable()) {
+ flyingController.purpurTick(mob.getRider());
+ return;
+ }
+ // Purpur end - Ridables
if (this.mob.getY() <= Bee.this.level().getMinY()) {
this.mob.setNoGravity(false);
}
super.tick();
}
+
+ // Purpur start - Ridables
+ @Override
+ public boolean hasWanted() {
+ return mob.getRider() != null || !mob.isControllable() || super.hasWanted();
+ }
+ // Purpur end - Ridables
}
this.moveControl = new BeeFlyingMoveControl(this, 20, true);
// 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 || isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - bee can instantly start drowning in water option // Purpur - Toggle for water sensitive mob damage
this.setPathfindingMalus(PathType.WATER_BORDER, 16.0F);
this.setPathfindingMalus(PathType.COCOA, -1.0F);
this.setPathfindingMalus(PathType.FENCE, -1.0F);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.beeRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.beeRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.beeControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.beeMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2;
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, speed, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end - Ridables
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
@@ -183,6 +231,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(0, new Bee.BeeAttackGoal(this, 1.4F, true));
this.goalSelector.addGoal(1, new Bee.BeeEnterHiveGoal());
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0));
@@ -200,6 +249,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
this.goalSelector.addGoal(7, new Bee.BeeGrowCropGoal());
this.goalSelector.addGoal(8, new Bee.BeeWanderGoal());
this.goalSelector.addGoal(9, new FloatGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new Bee.BeeHurtByOtherGoal(this).setAlertOthers(new Class[0]));
this.targetSelector.addGoal(2, new Bee.BeeBecomeAngryTargetGoal(this));
this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true));
@@ -365,7 +415,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
}
public static boolean isNightOrRaining(Level level) {
- return level.dimensionType().hasSkyLight() && (level.isNight() || level.isRaining());
+ return level.dimensionType().hasSkyLight() && (level.isNight() && !level.purpurConfig.beeCanWorkAtNight || level.isRaining() && !level.purpurConfig.beeCanWorkInRain); // Purpur - Bee can work when raining or at night
}
public void setStayOutOfHiveCountdown(int stayOutOfHiveCountdown) {
@@ -388,7 +438,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
@Override
protected void customServerAiStep(ServerLevel level) {
boolean hasStung = this.hasStung();
- if (this.isInWaterOrBubble()) {
+ if (this.level().purpurConfig.beeCanInstantlyStartDrowning && this.isInWaterOrBubble()) { // Purpur - bee can instantly start drowning in water option
this.underWaterTicks++;
} else {
this.underWaterTicks = 0;
@@ -398,6 +448,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
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) {
@@ -421,6 +472,35 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
return beehiveBlockEntity != null && beehiveBlockEntity.isFireNearby();
}
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.beeMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.beeScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.beeBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.beeTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.beeAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public int getRemainingPersistentAngerTime() {
return this.entityData.get(DATA_REMAINING_ANGER_TIME);
@@ -1083,15 +1163,15 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
}
}
- class BeeLookControl extends LookControl {
+ class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
BeeLookControl(final Mob mob) {
super(mob);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (!Bee.this.isAngry()) {
- super.tick();
+ super.vanillaTick(); // Purpur - Ridables
}
}
@@ -1136,6 +1216,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
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(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur - Bee API
return true;
} else {
Bee.this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(Bee.this.random, 20, 60);
@@ -1182,6 +1263,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
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 : io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos), Bee.this.hasNectar()).callEvent(); // Purpur - Bee API
}
@Override
@@ -1228,6 +1310,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
this.setWantedPos();
}
+ if (this.successfulPollinatingTicks == 0) new org.purpurmc.purpur.event.entity.BeeStartedPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur - Bee API
this.successfulPollinatingTicks++;
if (Bee.this.random.nextFloat() < 0.05F && this.successfulPollinatingTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = this.successfulPollinatingTicks;
diff --git a/net/minecraft/world/entity/animal/Cat.java b/net/minecraft/world/entity/animal/Cat.java
index 1a7a5c81a260cc740994d1a63c4775c41c238dea..e5d4dc41a72cdae15a08e6bb5425056a325c584c 100644
--- a/net/minecraft/world/entity/animal/Cat.java
+++ b/net/minecraft/world/entity/animal/Cat.java
@@ -93,10 +93,65 @@ public class Cat extends TamableAnimal implements VariantHolder<Holder<CatVarian
this.reassessTameGoals();
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.catRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.catRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.catControllable;
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setInSittingPose(false);
+ setLying(false);
+ setRelaxStateOne(false);
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.catMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.catScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.catBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.catTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.catAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.temptGoal = new Cat.CatTemptGoal(this, 0.6, itemStack -> itemStack.is(ItemTags.CAT_FOOD), true);
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5));
this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
this.goalSelector.addGoal(3, new Cat.CatRelaxOnOwnerGoal(this));
@@ -109,6 +164,7 @@ public class Cat extends TamableAnimal implements VariantHolder<Holder<CatVarian
this.goalSelector.addGoal(10, new BreedGoal(this, 0.8));
this.goalSelector.addGoal(11, new WaterAvoidingRandomStrollGoal(this, 0.8, 1.0000001E-5F));
this.goalSelector.addGoal(12, new LookAtPlayerGoal(this, Player.class, 10.0F));
+ this.targetSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Rabbit.class, false, null));
this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
}
@@ -332,6 +388,14 @@ public class Cat extends TamableAnimal implements VariantHolder<Holder<CatVarian
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
+
@Nullable
@Override
public SpawnGroupData finalizeSpawn(
@@ -352,6 +416,7 @@ public class Cat extends TamableAnimal implements VariantHolder<Holder<CatVarian
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
+ if (getRider() != null) return InteractionResult.PASS; // Purpur - Ridables
ItemStack itemInHand = player.getItemInHand(hand);
Item item = itemInHand.getItem();
if (this.isTame()) {
@@ -438,7 +503,7 @@ public class Cat extends TamableAnimal implements VariantHolder<Holder<CatVarian
}
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, (byte)7);
diff --git a/net/minecraft/world/entity/animal/Chicken.java b/net/minecraft/world/entity/animal/Chicken.java
index 1235e46776589d519351d380b57f86e530b881ab..4d0b172a9d54b1524c8052051859c7178774bef7 100644
--- a/net/minecraft/world/entity/animal/Chicken.java
+++ b/net/minecraft/world/entity/animal/Chicken.java
@@ -51,16 +51,76 @@ public class Chicken extends Animal {
this.setPathfindingMalus(PathType.WATER, 0.0F);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.chickenRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.chickenRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.chickenControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.chickenMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.chickenScale);
+ // Purpur start - Chickens can retaliate
+ if (level().purpurConfig.chickenRetaliate) {
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(2.0D);
+ }
+ // Purpur end - Chickens can retaliate
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.chickenBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.chickenTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.chickenAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
- this.goalSelector.addGoal(1, new PanicGoal(this, 1.4));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ //this.goalSelector.addGoal(1, new PanicGoal(this, 1.4)); // Purpur - Chickens can retaliate - moved down
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0));
this.goalSelector.addGoal(3, new TemptGoal(this, 1.0, itemStack -> itemStack.is(ItemTags.CHICKEN_FOOD), false));
this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.1));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
+ // Purpur start - Chickens can retaliate
+ if (level().purpurConfig.chickenRetaliate) {
+ this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.0D, false));
+ this.targetSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal(this));
+ } else {
+ this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
+ }
+ // Purpur end - Chickens can retaliate
}
@Override
@@ -69,7 +129,7 @@ public class Chicken extends Animal {
}
public static AttributeSupplier.Builder createAttributes() {
- return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0).add(Attributes.MOVEMENT_SPEED, 0.25);
+ return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0).add(Attributes.MOVEMENT_SPEED, 0.25).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur - Chickens can retaliate
}
@Override
diff --git a/net/minecraft/world/entity/animal/Cod.java b/net/minecraft/world/entity/animal/Cod.java
index 708bcc39e7242292d5d5bfcaf599e3738628df9b..b259de78198e0e3df9e5901019283ad246c8e044 100644
--- a/net/minecraft/world/entity/animal/Cod.java
+++ b/net/minecraft/world/entity/animal/Cod.java
@@ -13,6 +13,39 @@ public class Cod extends AbstractSchoolingFish {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.codRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.codControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.codMaxHealth);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.codTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.codAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public ItemStack getBucketItemStack() {
return new ItemStack(Items.COD_BUCKET);
diff --git a/net/minecraft/world/entity/animal/Cow.java b/net/minecraft/world/entity/animal/Cow.java
index befb99f0a96cb23f139061f92497737e9203a8fd..a0297ac3ba520122ed2095d6008c057d749b731e 100644
--- a/net/minecraft/world/entity/animal/Cow.java
+++ b/net/minecraft/world/entity/animal/Cow.java
@@ -32,22 +32,82 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
public class Cow extends Animal {
+ private boolean isNaturallyAggressiveToPlayers; // Purpur - Cows naturally aggressive to players chance
+
private static final EntityDimensions BABY_DIMENSIONS = EntityType.COW.getDimensions().scale(0.5F).withEyeHeight(0.665F);
public Cow(EntityType<? extends Cow> entityType, Level level) {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.cowRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.cowRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.cowControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.cowMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.cowScale);
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.cowNaturallyAggressiveToPlayersDamage); // Purpur - Cows naturally aggressive to players chance
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.cowBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.cowTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Cows naturally aggressive to players chance
+ @Override
+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, net.minecraft.world.entity.SpawnGroupData entityData) {
+ this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance;
+ return super.finalizeSpawn(world, difficulty, spawnReason, entityData);
+ }
+ // Purpur end - Cows naturally aggressive to players chance
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.cowAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new PanicGoal(this, 2.0));
+ this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - Cows naturally aggressive to players chance
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));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur - Cows naturally aggressive to players chance
}
@Override
@@ -56,7 +116,7 @@ public class Cow extends Animal {
}
public static AttributeSupplier.Builder createAttributes() {
- return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.MOVEMENT_SPEED, 0.2F);
+ return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.MOVEMENT_SPEED, 0.2F).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur - Cows naturally aggressive to players chance
}
@Override
@@ -86,19 +146,24 @@ public class Cow extends Animal {
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
+ if (getRider() != null) return InteractionResult.PASS; // Purpur - Ridables
ItemStack itemInHand = player.getItemInHand(hand);
if (itemInHand.is(Items.BUCKET) && !this.isBaby()) {
// CraftBukkit start - Got milk?
org.bukkit.event.player.PlayerBucketFillEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemInHand, Items.MILK_BUCKET, hand);
if (event.isCancelled()) {
player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur - Ridables
}
// CraftBukkit end
player.playSound(SoundEvents.COW_MILK, 1.0F, 1.0F);
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);
}
@@ -114,4 +179,67 @@ public class Cow extends Animal {
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(), 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.moveTo(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) {
+ ((ServerLevel) level()).sendParticlesSource(((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
}
diff --git a/net/minecraft/world/entity/animal/Dolphin.java b/net/minecraft/world/entity/animal/Dolphin.java
index 4141052dfd635804195a5cfa24dbd0394355a7da..7003b532182737a745491e397a967b72e6b308aa 100644
--- a/net/minecraft/world/entity/animal/Dolphin.java
+++ b/net/minecraft/world/entity/animal/Dolphin.java
@@ -71,14 +71,105 @@ public class Dolphin extends AgeableWaterCreature {
private static final int TOTAL_MOISTNESS_LEVEL = 2400;
public static final Predicate<ItemEntity> ALLOWED_ITEMS = itemEntity -> !itemEntity.hasPickUpDelay() && itemEntity.isAlive() && itemEntity.isInWater();
public static final float BABY_SCALE = 0.65F;
+ private boolean isNaturallyAggressiveToPlayers; // Purpur - Dolphins naturally aggressive to players chance
+ private int spitCooldown; // Purpur - Ridables
public Dolphin(EntityType<? extends Dolphin> entityType, Level level) {
super(entityType, level);
- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
+ // Purpur start - Ridables
+ class DolphinMoveControl extends SmoothSwimmingMoveControl {
+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterMoveControllerWASD;
+ private final Dolphin dolphin;
+
+ public DolphinMoveControl(Dolphin dolphin, int pitchChange, int yawChange, float speedInWater, float speedInAir, boolean buoyant) {
+ super(dolphin, pitchChange, yawChange, speedInWater, speedInAir, buoyant);
+ this.dolphin = dolphin;
+ this.waterMoveControllerWASD = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(dolphin);
+ }
+
+ @Override
+ public void tick() {
+ if (dolphin.getRider() != null && dolphin.isControllable()) {
+ purpurTick(dolphin.getRider());
+ } else {
+ super.tick();
+ }
+ }
+
+ public void purpurTick(Player rider) {
+ if (dolphin.getAirSupply() < 150) {
+ // if drowning override player WASD controls to find air
+ super.tick();
+ } else {
+ waterMoveControllerWASD.purpurTick(rider);
+ dolphin.setDeltaMovement(dolphin.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
+ }
+ }
+ };
+ this.moveControl = new DolphinMoveControl(this, 85, 10, 0.02F, 0.1F, true);
+ // Purpur end - Ridables
this.lookControl = new SmoothSwimmingLookControl(this, 10);
this.setCanPickUpLoot(true);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.dolphinRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.dolphinControllable;
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (spitCooldown == 0 && getRider() != null) {
+ spitCooldown = level().purpurConfig.dolphinSpitCooldown;
+
+ org.bukkit.craftbukkit.entity.CraftPlayer player = (org.bukkit.craftbukkit.entity.CraftPlayer) getRider().getBukkitEntity();
+ if (!player.hasPermission("allow.special.dolphin")) {
+ return false;
+ }
+
+ org.bukkit.Location loc = player.getEyeLocation();
+ loc.setPitch(loc.getPitch() - 10);
+ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(10).add(loc.toVector());
+
+ org.purpurmc.purpur.entity.projectile.DolphinSpit spit = new org.purpurmc.purpur.entity.projectile.DolphinSpit(level(), this);
+ spit.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), level().purpurConfig.dolphinSpitSpeed, 5.0F);
+
+ level().addFreshEntity(spit);
+ playSound(SoundEvents.DOLPHIN_ATTACK, 1.0F, 1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F);
+ return true;
+ }
+ return false;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.dolphinMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.dolphinScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.dolphinTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.dolphinAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Nullable
@Override
public SpawnGroupData finalizeSpawn(
@@ -87,6 +178,7 @@ public class Dolphin extends AgeableWaterCreature {
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);
}
@@ -169,17 +261,21 @@ public class Dolphin extends AgeableWaterCreature {
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(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
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(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
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() {
@@ -223,7 +319,7 @@ public class Dolphin extends AgeableWaterCreature {
@Override
protected boolean canRide(Entity entity) {
- return true;
+ return boardingCooldown <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs;
}
@Override
@@ -252,6 +348,11 @@ public class Dolphin extends AgeableWaterCreature {
@Override
public void tick() {
super.tick();
+ // Purpur start - Ridables
+ if (spitCooldown > 0) {
+ spitCooldown--;
+ }
+ // Purpur end - Ridables
if (this.isNoAi()) {
this.setAirSupply(this.getMaxAirSupply());
} else {
@@ -412,6 +513,7 @@ public class Dolphin extends AgeableWaterCreature {
@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;
}
diff --git a/net/minecraft/world/entity/animal/Fox.java b/net/minecraft/world/entity/animal/Fox.java
index b4f3bc7938060cf55b6b242759606bf5100a1f56..90452f0945e761077608692877677f522d38bccd 100644
--- a/net/minecraft/world/entity/animal/Fox.java
+++ b/net/minecraft/world/entity/animal/Fox.java
@@ -129,6 +129,73 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
this.getNavigation().setRequiredPathLength(32.0F);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.foxRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.foxRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.foxControllable;
+ }
+
+ @Override
+ public float getJumpPower() {
+ return getRider() != null && this.isControllable() ? 0.5F : super.getJumpPower();
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setCanPickUpLoot(false);
+ clearStates();
+ setIsPouncing(false);
+ spitOutItem(getItemBySlot(EquipmentSlot.MAINHAND));
+ setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
+ }
+
+ @Override
+ public void onDismount(Player rider) {
+ super.onDismount(rider);
+ setCanPickUpLoot(true);
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.foxMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.foxScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.foxBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.foxTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.foxAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
@@ -148,6 +215,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
this, AbstractFish.class, 20, false, false, (entity, level) -> entity instanceof AbstractSchoolingFish
);
this.goalSelector.addGoal(0, new Fox.FoxFloatGoal());
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(0, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
this.goalSelector.addGoal(1, new Fox.FaceplantGoal());
this.goalSelector.addGoal(2, new Fox.FoxPanicGoal(2.2));
@@ -175,6 +243,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
this.goalSelector.addGoal(11, new Fox.FoxSearchForItemsGoal());
this.goalSelector.addGoal(12, new Fox.FoxLookAtPlayerGoal(this, Player.class, 24.0F));
this.goalSelector.addGoal(13, new Fox.PerchAndSearchGoal());
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector
.addGoal(
3,
@@ -335,6 +404,11 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
}
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);
@@ -364,6 +438,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
@Override
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
}
List<UUID> getTrustedUUIDs() {
@@ -685,6 +760,29 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
}
// 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
protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel level, DamageSource damageSource) {
@@ -896,8 +994,10 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
CriteriaTriggers.BRED_ANIMALS.trigger(serverPlayer, this.animal, this.partner, fox);
}
- this.animal.setAge(6000);
- this.partner.setAge(6000);
+ // Purpur start - Make entity breeding times configurable
+ this.animal.setAge(this.animal.getPurpurBreedTime());
+ this.partner.setAge(this.partner.getPurpurBreedTime());
+ // Purpur end - Make entity breeding times configurable
this.animal.resetLove();
this.partner.resetLove();
serverLevel.addFreshEntityWithPassengers(fox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
@@ -952,7 +1052,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
}
protected void onReachedTarget() {
- if (getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (getServerLevel(Fox.this.level()).purpurConfig.foxBypassMobGriefing ^ getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
BlockState blockState = Fox.this.level().getBlockState(this.blockPos);
if (blockState.is(Blocks.SWEET_BERRY_BUSH)) {
this.pickSweetBerries(blockState);
@@ -1066,15 +1166,15 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
}
}
- public class FoxLookControl extends LookControl {
+ public class FoxLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
public FoxLookControl() {
super(Fox.this);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (!Fox.this.isSleeping()) {
- super.tick();
+ super.vanillaTick(); // Purpur - Ridables
}
}
@@ -1110,15 +1210,15 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
}
}
- class FoxMoveControl extends MoveControl {
+ class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
public FoxMoveControl() {
super(Fox.this);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (Fox.this.canMove()) {
- super.tick();
+ super.vanillaTick(); // Purpur - Ridables
}
}
}
diff --git a/net/minecraft/world/entity/animal/IronGolem.java b/net/minecraft/world/entity/animal/IronGolem.java
index 8e9ba307a0528eb1aef56bdc0f4ded0e71621253..ccadc9a151e258ff2c74c65c374b1f09d56d10ec 100644
--- a/net/minecraft/world/entity/animal/IronGolem.java
+++ b/net/minecraft/world/entity/animal/IronGolem.java
@@ -56,13 +56,67 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
private int remainingPersistentAngerTime;
@Nullable
private UUID persistentAngerTarget;
+ @Nullable private UUID summoner; // Purpur - Summoner API
public IronGolem(EntityType<? extends IronGolem> entityType, Level level) {
super(entityType, level);
}
+ // Purpur start - Summoner API
+ @Nullable
+ public UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable UUID summoner) {
+ this.summoner = summoner;
+ }
+ // Purpur end - Summoner API
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.ironGolemRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ironGolemRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.ironGolemControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ironGolemMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ironGolemScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.ironGolemTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.ironGolemAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@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
+ if (level().purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur - Ridables
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
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));
@@ -70,6 +124,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
this.goalSelector.addGoal(5, new OfferFlowerGoal(this));
this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new DefendVillageTargetGoal(this));
this.targetSelector.addGoal(2, new HurtByTargetGoal(this));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
@@ -140,6 +195,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putBoolean("PlayerCreated", this.isPlayerCreated());
+ if (getSummoner() != null) compound.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API
this.addPersistentAngerSaveData(compound);
}
@@ -147,6 +203,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
public void readAdditionalSaveData(CompoundTag compound) {
super.readAdditionalSaveData(compound);
this.setPlayerCreated(compound.getBoolean("PlayerCreated"));
+ if (compound.contains("Purpur.Summoner")) setSummoner(compound.getUUID("Purpur.Summoner")); // Purpur - Summoner API
this.readPersistentAngerSaveData(this.level(), compound);
}
@@ -256,16 +313,17 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
protected InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack itemInHand = player.getItemInHand(hand);
if (!itemInHand.is(Items.IRON_INGOT)) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur - Ridables
} else {
float health = this.getHealth();
this.heal(25.0F);
if (this.getHealth() == health) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur - Ridables
} else {
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;
}
}
diff --git a/net/minecraft/world/entity/animal/MushroomCow.java b/net/minecraft/world/entity/animal/MushroomCow.java
index 12099a5eb45ee21520d3ba68ef26909d5949206d..81a50166e5baaa3b059308b69107c430f2926966 100644
--- a/net/minecraft/world/entity/animal/MushroomCow.java
+++ b/net/minecraft/world/entity/animal/MushroomCow.java
@@ -55,6 +55,51 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.mooshroomRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.mooshroomRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.mooshroomControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.mooshroomMaxHealth);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.mooshroomBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.mooshroomTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.mooshroomAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public float getWalkTargetValue(BlockPos pos, LevelReader level) {
return level.getBlockState(pos.below()).is(Blocks.MYCELIUM) ? 10.0F : level.getPathfindingCostFromLightLevels(pos);
@@ -115,7 +160,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
java.util.List<ItemStack> drops = this.generateDefaultDrops(serverLevel, itemInHand);
org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemInHand, hand, drops);
if (event != null) {
- if (event.isCancelled()) return InteractionResult.PASS;
+ if (event.isCancelled()) return tryRide(player, hand); // Purpur - Ridables
drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
// Paper end - custom shear drops
}
diff --git a/net/minecraft/world/entity/animal/Ocelot.java b/net/minecraft/world/entity/animal/Ocelot.java
index 5b59f68141c2ceeaf7907bbf5e7b9e08cbe2239e..681f1256d8bbedc7731fd2906a7f439da4333552 100644
--- a/net/minecraft/world/entity/animal/Ocelot.java
+++ b/net/minecraft/world/entity/animal/Ocelot.java
@@ -62,6 +62,52 @@ public class Ocelot extends Animal {
this.reassessTrustingGoals();
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.ocelotRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ocelotRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.ocelotControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ocelotMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ocelotScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.ocelotBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.ocelotTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.ocelotAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public boolean isTrusting() {
return this.entityData.get(DATA_TRUSTING);
}
@@ -93,12 +139,14 @@ public class Ocelot extends Animal {
protected void registerGoals() {
this.temptGoal = new Ocelot.OcelotTemptGoal(this, 0.6, itemStack -> itemStack.is(ItemTags.OCELOT_FOOD), true);
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(3, this.temptGoal);
this.goalSelector.addGoal(7, new LeapAtTargetGoal(this, 0.3F));
this.goalSelector.addGoal(8, new OcelotAttackGoal(this));
this.goalSelector.addGoal(9, new BreedGoal(this, 0.8));
this.goalSelector.addGoal(10, new WaterAvoidingRandomStrollGoal(this, 0.8, 1.0000001E-5F));
this.goalSelector.addGoal(11, new LookAtPlayerGoal(this, Player.class, 10.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Chicken.class, false));
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, false, false, Turtle.BABY_ON_LAND_SELECTOR));
}
@@ -232,7 +280,7 @@ public class Ocelot extends Animal {
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;
}
diff --git a/net/minecraft/world/entity/animal/Panda.java b/net/minecraft/world/entity/animal/Panda.java
index 283ddf7d13a17c0a6df5a52b7fd26ed7b7a4826b..0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688 100644
--- a/net/minecraft/world/entity/animal/Panda.java
+++ b/net/minecraft/world/entity/animal/Panda.java
@@ -105,6 +105,62 @@ public class Panda extends Animal {
}
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.pandaRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pandaRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.pandaControllable;
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setForwardMot(0.0F);
+ sit(false);
+ eat(false);
+ setOnBack(false);
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pandaMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pandaScale);
+ setAttributes();
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.pandaBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.pandaTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.pandaAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) {
return slot == EquipmentSlot.MAINHAND && this.canPickUpLoot();
@@ -258,6 +314,7 @@ public class Panda extends Animal {
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(2, new Panda.PandaPanicGoal(this, 2.0));
this.goalSelector.addGoal(2, new Panda.PandaBreedGoal(this, 1.0));
this.goalSelector.addGoal(3, new Panda.PandaAttackGoal(this, 1.2F, true));
@@ -273,6 +330,7 @@ public class Panda extends Animal {
this.goalSelector.addGoal(12, new Panda.PandaRollGoal(this));
this.goalSelector.addGoal(13, new FollowParentGoal(this, 1.25));
this.goalSelector.addGoal(14, new WaterAvoidingRandomStrollGoal(this, 1.0));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new Panda.PandaHurtByTargetGoal(this).setAlertOthers(new Class[0]));
}
@@ -596,7 +654,11 @@ public class Panda extends Animal {
public void setAttributes() {
if (this.isWeak()) {
- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(10.0);
+ // Purpur start - Configurable entity base attributes
+ net.minecraft.world.entity.ai.attributes.AttributeInstance maxHealth = this.getAttribute(Attributes.MAX_HEALTH);
+ maxHealth.setBaseValue(maxHealth.getValue() / 2);
+ // Purpur end - Configurable entity base attributes
+
}
if (this.isLazy()) {
@@ -616,7 +678,7 @@ public class Panda extends Animal {
public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack itemInHand = player.getItemInHand(hand);
if (this.isScared()) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur - Ridables
} else if (this.isOnBack()) {
this.setOnBack(false);
return InteractionResult.SUCCESS;
@@ -652,7 +714,7 @@ public class Panda extends Animal {
return InteractionResult.SUCCESS_SERVER;
} else {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur - Ridables
}
}
@@ -964,7 +1026,7 @@ public class Panda extends Animal {
}
}
- static class PandaMoveControl extends MoveControl {
+ static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
private final Panda panda;
public PandaMoveControl(Panda mob) {
@@ -973,9 +1035,9 @@ public class Panda extends Animal {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (this.panda.canPerformAction()) {
- super.tick();
+ super.vanillaTick(); // Purpur - Ridables
}
}
}
diff --git a/net/minecraft/world/entity/animal/Parrot.java b/net/minecraft/world/entity/animal/Parrot.java
index 16cc69b14fba16a5a5dfc05d63a40a5112314031..4fa9f50ef6869b181e4e48ed66aaee210b303cd9 100644
--- a/net/minecraft/world/entity/animal/Parrot.java
+++ b/net/minecraft/world/entity/animal/Parrot.java
@@ -124,12 +124,97 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
public Parrot(EntityType<? extends Parrot> entityType, Level level) {
super(entityType, level);
- this.moveControl = new FlyingMoveControl(this, 10, false);
+ // Purpur start - Ridables
+ final org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F);
+ class ParrotMoveControl extends FlyingMoveControl {
+ public ParrotMoveControl(Mob entity, int maxPitchChange, boolean noGravity) {
+ super(entity, maxPitchChange, noGravity);
+ }
+
+ @Override
+ public void tick() {
+ if (mob.getRider() != null && mob.isControllable()) {
+ flyingController.purpurTick(mob.getRider());
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean hasWanted() {
+ return mob.getRider() != null && mob.isControllable() ? getForwardMot() != 0 || getStrafeMot() != 0 : super.hasWanted();
+ }
+ }
+ this.moveControl = new ParrotMoveControl(this, 10, false);
+ // Purpur end - Ridables
this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F);
this.setPathfindingMalus(PathType.DAMAGE_FIRE, -1.0F);
this.setPathfindingMalus(PathType.COCOA, -1.0F);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.parrotRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.parrotRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.parrotControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.parrotMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2;
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.parrotMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.parrotScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return 6000;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.parrotTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.parrotAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Nullable
@Override
public SpawnGroupData finalizeSpawn(
@@ -150,8 +235,11 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
@Override
protected void registerGoals() {
- this.goalSelector.addGoal(0, new TamableAnimal.TamableAnimalPanicGoal(1.25));
+ //this.goalSelector.addGoal(0, new TamableAnimal.TamableAnimalPanicGoal(1.25)); // Purpur - move down
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(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.25D)); // Purpur - Ridables
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));
@@ -257,7 +345,7 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
}
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, (byte)7);
} else {
@@ -265,6 +353,7 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
}
}
+ 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)) {
@@ -289,7 +378,7 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
@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(
@@ -304,13 +393,13 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
@Override
public boolean canMate(Animal otherAnimal) {
- return false;
+ return super.canMate(otherAnimal); // Purpur - Breedable parrots
}
@Nullable
@Override
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
- return null;
+ return level.purpurConfig.parrotBreedable ? EntityType.PARROT.create(level, EntitySpawnReason.BREEDING) : null; // Purpur - Breedable parrots
}
@Nullable
diff --git a/net/minecraft/world/entity/animal/Pig.java b/net/minecraft/world/entity/animal/Pig.java
index d203d9c63c13f40a913235ad78a24a3bf489a083..1a504a3d887f5294c5a2fec3f4bc6654d380996e 100644
--- a/net/minecraft/world/entity/animal/Pig.java
+++ b/net/minecraft/world/entity/animal/Pig.java
@@ -56,9 +56,56 @@ public class Pig extends Animal implements ItemSteerable, Saddleable {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.pigRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pigRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.pigControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pigMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pigScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.pigBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.pigTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.pigAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new PanicGoal(this, 1.25));
this.goalSelector.addGoal(3, new BreedGoal(this, 1.0));
this.goalSelector.addGoal(4, new TemptGoal(this, 1.2, itemStack -> itemStack.is(Items.CARROT_ON_A_STICK), false));
@@ -132,6 +179,19 @@ public class Pig extends Animal implements ItemSteerable, Saddleable {
@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.steering.setSaddle(false);
+ 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);
diff --git a/net/minecraft/world/entity/animal/PolarBear.java b/net/minecraft/world/entity/animal/PolarBear.java
index f568c385e1427e183aefb5819013838aca95407b..026e64bf743aa79ba6409fa5cd4374b368f98caa 100644
--- a/net/minecraft/world/entity/animal/PolarBear.java
+++ b/net/minecraft/world/entity/animal/PolarBear.java
@@ -59,11 +59,92 @@ public class PolarBear extends Animal implements NeutralMob {
private int remainingPersistentAngerTime;
@Nullable
private UUID persistentAngerTarget;
+ private int standTimer = 0; // Purpur - Ridables
public PolarBear(EntityType<? extends PolarBear> entityType, Level level) {
super(entityType, 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
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.polarBearRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.polarBearRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.polarBearControllable;
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (!isStanding()) {
+ if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0) {
+ setStanding(true);
+ playSound(SoundEvents.POLAR_BEAR_WARNING, 1.0F, 1.0F);
+ }
+ }
+ return false;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.polarBearMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.polarBearScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.polarBearBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.polarBearTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.polarBearAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Nullable
@Override
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
@@ -72,19 +153,27 @@ public class PolarBear extends Animal implements NeutralMob {
@Override
public boolean isFood(ItemStack stack) {
- return false;
+ return level().purpurConfig.polarBearBreedableItem != null && stack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur - Breedable Polar Bears
}
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
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));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new PolarBear.PolarBearHurtByTargetGoal());
this.targetSelector.addGoal(2, new PolarBear.PolarBearAttackPlayersGoal());
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
@@ -203,6 +292,12 @@ public class PolarBear extends Animal implements NeutralMob {
if (!this.level().isClientSide) {
this.updatePersistentAnger((ServerLevel)this.level(), true);
}
+
+ // Purpur start - Ridables
+ if (isStanding() && --standTimer <= 0) {
+ setStanding(false);
+ }
+ // Purpur end - Ridables
}
@Override
@@ -222,6 +317,7 @@ public class PolarBear extends Animal implements NeutralMob {
public void setStanding(boolean standing) {
this.entityData.set(DATA_STANDING_ID, standing);
+ standTimer = standing ? 20 : -1; // Purpur - Ridables
}
public float getStandingAnimationScale(float partialTick) {
diff --git a/net/minecraft/world/entity/animal/Pufferfish.java b/net/minecraft/world/entity/animal/Pufferfish.java
index d94a7cfcd0f7a15ce97d3b12daa8b2c71acf997a..5889a9ceb54a354a79f33c9e4fc338cbf912ddda 100644
--- a/net/minecraft/world/entity/animal/Pufferfish.java
+++ b/net/minecraft/world/entity/animal/Pufferfish.java
@@ -45,6 +45,39 @@ public class Pufferfish extends AbstractFish {
this.refreshDimensions();
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.pufferfishRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.pufferfishControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pufferfishMaxHealth);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.pufferfishTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.pufferfishAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
diff --git a/net/minecraft/world/entity/animal/Rabbit.java b/net/minecraft/world/entity/animal/Rabbit.java
index c5346b6e4ffe09ca1ec4b85e612c9ee52ae77329..da5b32a17283e540615373097acc511d928aeff5 100644
--- a/net/minecraft/world/entity/animal/Rabbit.java
+++ b/net/minecraft/world/entity/animal/Rabbit.java
@@ -83,6 +83,7 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
private boolean wasOnGround;
private int jumpDelayTicks;
public int moreCarrotTicks;
+ private boolean actualJump; // Purpur - Ridables
public Rabbit(EntityType<? extends Rabbit> entityType, Level level) {
super(entityType, level);
@@ -91,9 +92,84 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
//this.setSpeedModifier(0.0); // CraftBukkit
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.rabbitRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.rabbitRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.rabbitControllable;
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (onGround) {
+ actualJump = true;
+ jumpFromGround();
+ actualJump = false;
+ }
+ return true;
+ }
+
+ private void handleJumping() {
+ if (onGround) {
+ RabbitJumpControl jumpController = (RabbitJumpControl) jumpControl;
+ if (!wasOnGround) {
+ setJumping(false);
+ jumpController.setCanJump(false);
+ }
+ if (!jumpController.wantJump()) {
+ if (moveControl.hasWanted()) {
+ startJumping();
+ }
+ } else if (!jumpController.canJump()) {
+ jumpController.setCanJump(true);
+ }
+ }
+ wasOnGround = onGround;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.rabbitMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.rabbitScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.rabbitBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.rabbitTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.rabbitAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2));
this.goalSelector.addGoal(2, new BreedGoal(this, 0.8));
@@ -108,6 +184,14 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
@Override
protected float getJumpPower() {
+ // Purpur start - Ridables
+ if (getRider() != null && this.isControllable()) {
+ if (getForwardMot() < 0) {
+ setSpeed(getForwardMot() * 2F);
+ }
+ return actualJump ? 0.5F : 0.3F;
+ }
+ // Purpur end - Ridables
float f = 0.3F;
if (this.moveControl.getSpeedModifier() <= 0.6) {
f = 0.2F;
@@ -175,6 +259,12 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
@Override
public void customServerAiStep(ServerLevel level) {
+ // Purpur start - Ridables
+ if (getRider() != null && this.isControllable()) {
+ handleJumping();
+ return;
+ }
+ // Purpur end - Ridables
if (this.jumpDelayTicks > 0) {
this.jumpDelayTicks--;
}
@@ -376,10 +466,23 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
}
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)) {
@@ -470,7 +573,7 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
}
}
- static class RabbitMoveControl extends MoveControl {
+ static class RabbitMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
private final Rabbit rabbit;
private double nextJumpSpeed;
@@ -480,14 +583,14 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (this.rabbit.onGround() && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl)this.rabbit.jumpControl).wantJump()) {
this.rabbit.setSpeedModifier(0.0);
} else if (this.hasWanted() || this.operation == MoveControl.Operation.JUMPING) {
this.rabbit.setSpeedModifier(this.nextJumpSpeed);
}
- super.tick();
+ super.vanillaTick(); // Purpur - Ridables
}
@Override
@@ -531,7 +634,7 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
@Override
public boolean canUse() {
if (this.nextStartTick <= 0) {
- if (!getServerLevel(this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (!getServerLevel(this.rabbit).purpurConfig.rabbitBypassMobGriefing == !getServerLevel(this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
return false;
}
diff --git a/net/minecraft/world/entity/animal/Salmon.java b/net/minecraft/world/entity/animal/Salmon.java
index 41366f7b9af176a33b20ea26dd53d50994d2c600..5da2f14770aebb2286c3e8cbd9622a89a33e0e20 100644
--- a/net/minecraft/world/entity/animal/Salmon.java
+++ b/net/minecraft/world/entity/animal/Salmon.java
@@ -35,6 +35,39 @@ public class Salmon extends AbstractSchoolingFish implements VariantHolder<Salmo
this.refreshDimensions();
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.salmonRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.salmonControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.salmonMaxHealth);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.salmonTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.salmonAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public int getMaxSchoolSize() {
return 5;
diff --git a/net/minecraft/world/entity/animal/Sheep.java b/net/minecraft/world/entity/animal/Sheep.java
index e686c500e4b5f3e7b0e808af8b2e43ddbd163bef..c1760dda7b42471982e4ad78b6150b54a654ebf7 100644
--- a/net/minecraft/world/entity/animal/Sheep.java
+++ b/net/minecraft/world/entity/animal/Sheep.java
@@ -81,10 +81,57 @@ public class Sheep extends Animal implements Shearable {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.sheepRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.sheepRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.sheepControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.sheepMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.sheepScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.sheepBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.sheepTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.sheepAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.eatBlockGoal = new EatBlockGoal(this);
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new PanicGoal(this, 1.25));
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0));
this.goalSelector.addGoal(3, new TemptGoal(this, 1.1, stack -> stack.is(ItemTags.SHEEP_FOOD), false));
diff --git a/net/minecraft/world/entity/animal/SnowGolem.java b/net/minecraft/world/entity/animal/SnowGolem.java
index 8a63b656b86b98f3756807498721d4daac5a38ef..eccf5f67bdacaf018e368843448754bc28a54b98 100644
--- a/net/minecraft/world/entity/animal/SnowGolem.java
+++ b/net/minecraft/world/entity/animal/SnowGolem.java
@@ -44,17 +44,63 @@ import net.minecraft.world.phys.Vec3;
public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackMob {
private static final EntityDataAccessor<Byte> DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE);
private static final byte PUMPKIN_FLAG = 16;
+ @Nullable private java.util.UUID summoner; // Purpur - Summoner API
public SnowGolem(EntityType<? extends SnowGolem> entityType, Level level) {
super(entityType, 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
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.snowGolemRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.snowGolemRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.snowGolemControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.snowGolemMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.snowGolemScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.snowGolemAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@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(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ 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(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entity, level) -> entity instanceof Enemy));
}
@@ -72,6 +118,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putBoolean("Pumpkin", this.hasPumpkin());
+ if (getSummoner() != null) compound.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API
}
@Override
@@ -80,11 +127,12 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
if (compound.contains("Pumpkin")) {
this.setPumpkin(compound.getBoolean("Pumpkin"));
}
+ if (compound.contains("Purpur.Summoner")) setSummoner(compound.getUUID("Purpur.Summoner")); // Purpur - Summoner API
}
@Override
public boolean isSensitiveToWater() {
- return true;
+ return this.level().purpurConfig.snowGolemTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage
}
@Override
@@ -95,10 +143,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
this.hurtServer(serverLevel, this.damageSources().onFire().knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.MELTING), 1.0F); // CraftBukkit
}
- if (!serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (!serverLevel.purpurConfig.snowGolemBypassMobGriefing == !serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
return;
}
+ if (getRider() != null && this.isControllable() && !level().purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden
BlockState blockState = Blocks.SNOW.defaultBlockState();
for (int i = 0; i < 4; i++) {
@@ -141,7 +190,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemInHand, hand, drops);
if (event != null) {
if (event.isCancelled()) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur - Ridables
}
drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
// Paper end - custom shear drops
@@ -153,8 +202,16 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
}
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;
+ return tryRide(player, hand); // Purpur - Ridables
}
}
diff --git a/net/minecraft/world/entity/animal/Squid.java b/net/minecraft/world/entity/animal/Squid.java
index 687ac3f50ed517a0b4df70c0c0a01215691ac718..0a103e6f3f6b0ec05c5d22b1258eb6e2f776e7dd 100644
--- a/net/minecraft/world/entity/animal/Squid.java
+++ b/net/minecraft/world/entity/animal/Squid.java
@@ -46,13 +46,77 @@ public class Squid extends AgeableWaterCreature {
public Squid(EntityType<? extends Squid> entityType, Level level) {
super(entityType, 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!
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.squidRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.squidControllable;
+ }
+
+ protected static void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) {
+ double rad = Math.toRadians(degrees);
+ double cos = Math.cos(rad);
+ double sine = Math.sin(rad);
+ double x = vector.getX();
+ double z = vector.getZ();
+ vector.setX(cos * x - sine * z);
+ vector.setZ(sine * x + cos * z);
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.squidMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.squidScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.squidTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.squidAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new Squid.SquidRandomMovementGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new Squid.SquidFleeGoal());
}
@@ -127,6 +191,7 @@ public class Squid extends AgeableWaterCreature {
}
if (this.isInWaterOrBubble()) {
+ 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;
@@ -307,10 +372,41 @@ public class Squid extends AgeableWaterCreature {
@Override
public void tick() {
+ // Purpur start - Ridables
+ net.minecraft.world.entity.player.Player rider = squid.getRider();
+ if (rider != null && squid.isControllable()) {
+ if (rider.jumping) {
+ squid.onSpacebar();
+ }
+ float forward = rider.getForwardMot();
+ float strafe = rider.getStrafeMot();
+ float speed = (float) squid.getAttributeValue(Attributes.MOVEMENT_SPEED) * 10F;
+ if (forward < 0.0F) {
+ speed *= -0.5;
+ }
+ org.bukkit.util.Vector dir = rider.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(speed / 20.0F);
+ if (strafe != 0.0F) {
+ if (forward == 0.0F) {
+ dir.setY(0);
+ rotateVectorAroundY(dir, strafe > 0.0F ? -90 : 90);
+ } else if (forward < 0.0F) {
+ rotateVectorAroundY(dir, strafe > 0.0F ? 45 : -45);
+ } else {
+ rotateVectorAroundY(dir, strafe > 0.0F ? -45 : 45);
+ }
+ }
+ if (forward != 0.0F || strafe != 0.0F) {
+ squid.movementVector = new Vec3((float) dir.getX(), (float) dir.getY(), (float) dir.getZ());
+ } else {
+ squid.movementVector = Vec3.ZERO;
+ }
+ return;
+ }
+ // Purpur end - Ridables
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);
}
diff --git a/net/minecraft/world/entity/animal/TropicalFish.java b/net/minecraft/world/entity/animal/TropicalFish.java
index fa5f7f7d54083f9ea2095dd44362069d00e0b9a5..41074e7847583583331ef8d685a9f9b85ddf0243 100644
--- a/net/minecraft/world/entity/animal/TropicalFish.java
+++ b/net/minecraft/world/entity/animal/TropicalFish.java
@@ -67,6 +67,39 @@ public class TropicalFish extends AbstractSchoolingFish implements VariantHolder
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.tropicalFishRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.tropicalFishControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.tropicalFishMaxHealth);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.tropicalFishTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.tropicalFishAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static String getPredefinedName(int variantId) {
return "entity.minecraft.tropical_fish.predefined." + variantId;
}
diff --git a/net/minecraft/world/entity/animal/Turtle.java b/net/minecraft/world/entity/animal/Turtle.java
index 0e2d2dd3f2ef2783f3773a9a05c4f718991f7784..10477fea8fcd70bf0c1ba4b6e1113625be690e68 100644
--- a/net/minecraft/world/entity/animal/Turtle.java
+++ b/net/minecraft/world/entity/animal/Turtle.java
@@ -84,6 +84,52 @@ public class Turtle extends Animal {
this.moveControl = new Turtle.TurtleMoveControl(this);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.turtleRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.turtleRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.turtleControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.turtleMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.turtleScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.turtleBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.turtleTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.turtleAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public void setHomePos(BlockPos homePos) {
this.entityData.set(HOME_POS, homePos);
}
@@ -188,6 +234,7 @@ public class Turtle extends Animal {
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2));
this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0));
this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0));
@@ -368,8 +415,10 @@ public class Turtle extends Animal {
}
this.turtle.setHasEgg(true);
- this.animal.setAge(6000);
- this.partner.setAge(6000);
+ // Purpur start - Make entity breeding times configurable
+ this.animal.setAge(this.animal.getPurpurBreedTime());
+ this.partner.setAge(this.partner.getPurpurBreedTime());
+ // Purpur end - Make entity breeding times configurable
this.animal.resetLove();
this.partner.resetLove();
RandomSource random = this.animal.getRandom();
@@ -539,12 +588,14 @@ public class Turtle extends Animal {
}
}
- static class TurtleMoveControl extends MoveControl {
+ static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
private final Turtle turtle;
+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - Ridables
TurtleMoveControl(Turtle mob) {
super(mob);
this.turtle = mob;
+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(turtle, 0.25D); // Purpur - Ridables
}
private void updateSpeed() {
@@ -563,7 +614,7 @@ public class Turtle extends Animal {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
this.updateSpeed();
if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) {
double d = this.wantedX - this.turtle.getX();
@@ -577,7 +628,7 @@ public class Turtle extends Animal {
float f = (float)(Mth.atan2(d2, d) * 180.0F / (float)Math.PI) - 90.0F;
this.turtle.setYRot(this.rotlerp(this.turtle.getYRot(), f, 90.0F));
this.turtle.yBodyRot = this.turtle.getYRot();
- float f1 = (float)(this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float f1 = (float)(this.getSpeedModifier() * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables
this.turtle.setSpeed(Mth.lerp(0.125F, this.turtle.getSpeed(), f1));
this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0, this.turtle.getSpeed() * d1 * 0.1, 0.0));
}
diff --git a/net/minecraft/world/entity/animal/Wolf.java b/net/minecraft/world/entity/animal/Wolf.java
index 133e14efb19f8984061b8cb5922aa4f0c7aaf4d9..7cb292de6b27fa4ba3c5fce526a4e939c576789f 100644
--- a/net/minecraft/world/entity/animal/Wolf.java
+++ b/net/minecraft/world/entity/animal/Wolf.java
@@ -94,6 +94,37 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Hol
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;
@@ -115,12 +146,99 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Hol
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) {
+ setOwnerUUID(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.CONFUSION, 1200));
+ } else {
+ this.targetSelector.addGoal(5, PATHFINDER_VANILLA);
+ this.stopBeingAngry();
+ if (modifyEffects) this.removeEffect(net.minecraft.world.effect.MobEffects.CONFUSION);
+ }
+ }
+ // 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
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.wolfRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wolfRidableInWater;
+ }
+
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setInSittingPose(false);
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.wolfControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wolfMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.wolfScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.wolfBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.wolfTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.wolfAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
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));
@@ -129,11 +247,12 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Hol
this.goalSelector.addGoal(9, new BegGoal(this, 8.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(10, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new OwnerHurtByTargetGoal(this));
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));
@@ -182,6 +301,7 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Hol
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putByte("CollarColor", (byte)this.getCollarColor().getId());
+ compound.putBoolean("Purpur.IsRabid", this.isRabid); // Purpur - Configurable chance for wolves to spawn rabid
this.getVariant().unwrapKey().ifPresent(resourceKey -> compound.putString("variant", resourceKey.location().toString()));
this.addPersistentAngerSaveData(compound);
}
@@ -196,6 +316,10 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Hol
if (compound.contains("CollarColor", 99)) {
this.setCollarColor(DyeColor.byId(compound.getInt("CollarColor")));
}
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ this.isRabid = compound.getBoolean("Purpur.IsRabid");
+ this.updatePathfinders(false);
+ // Purpur end - Configurable chance for wolves to spawn rabid
this.readPersistentAngerSaveData(this.level(), compound);
}
@@ -215,6 +339,10 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Hol
}
this.setVariant(holder);
+ // 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);
}
@@ -263,6 +391,11 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Hol
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.CONFUSION, 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;
@@ -481,13 +614,27 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Hol
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 and isCancelled check.
+ 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 and isCancelled check. // Purpur - Config to always tame in Creative
this.tame(player);
this.navigation.stop();
this.setTarget(null);
diff --git a/net/minecraft/world/entity/animal/allay/Allay.java b/net/minecraft/world/entity/animal/allay/Allay.java
index 8f1d66d005413fe4eadb993b61568fa84336345a..10d2a1138d814b83ce4233205a7f0ab2ed1f399d 100644
--- a/net/minecraft/world/entity/animal/allay/Allay.java
+++ b/net/minecraft/world/entity/animal/allay/Allay.java
@@ -117,10 +117,23 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
private float spinningAnimationTicks;
private float spinningAnimationTicks0;
public boolean forceDancing = false; // CraftBukkit
+ private org.purpurmc.purpur.controller.FlyingMoveControllerWASD purpurController; // Purpur - Ridables
public Allay(EntityType<? extends Allay> entityType, Level level) {
super(entityType, level);
- this.moveControl = new FlyingMoveControl(this, 20, true);
+ // Purpur start - Ridables
+ this.purpurController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.1F, 0.5F);
+ this.moveControl = new FlyingMoveControl(this, 20, true) {
+ @Override
+ public void tick() {
+ if (mob.getRider() != null && mob.isControllable()) {
+ purpurController.purpurTick(mob.getRider());
+ } else {
+ super.tick();
+ }
+ }
+ };
+ // Purpur end - Ridables
this.setCanPickUpLoot(this.canPickUpLoot());
this.vibrationUser = new Allay.VibrationUser();
this.vibrationData = new VibrationSystem.Data();
@@ -136,6 +149,36 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
}
// CraftBukkit end
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.allayRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.allayRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.allayControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.allayMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.allayScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
@Override
protected Brain.Provider<Allay> brainProvider() {
return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
@@ -244,6 +287,7 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep(ServerLevel level) {
+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(level, this);
AllayAi.updateActivity(this);
diff --git a/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/net/minecraft/world/entity/animal/armadillo/Armadillo.java
index 86e78ce740b27f9714145a690e8b182a2ccb3fb9..a24ed1747fb8836927ac41b822dc666862701516 100644
--- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java
+++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java
@@ -78,6 +78,38 @@ public class Armadillo extends Animal {
return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 12.0).add(Attributes.MOVEMENT_SPEED, 0.14);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.armadilloRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.armadilloRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.armadilloControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.armadilloMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.armadilloScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.armadilloBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
diff --git a/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/net/minecraft/world/entity/animal/axolotl/Axolotl.java
index 4fb36e2a6d71b79219e10f5089eb0daebf830ee7..1323cedcacd3072cdf5f1eac644688cd098b53db 100644
--- a/net/minecraft/world/entity/animal/axolotl/Axolotl.java
+++ b/net/minecraft/world/entity/animal/axolotl/Axolotl.java
@@ -113,6 +113,52 @@ public class Axolotl extends Animal implements VariantHolder<Axolotl.Variant>, B
this.lookControl = new Axolotl.AxolotlLookControl(this, 20);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.axolotlRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.axolotlControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.axolotlMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.axolotlScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.axolotlBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.axolotlTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.axolotlAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public float getWalkTargetValue(BlockPos pos, LevelReader level) {
return 0.0F;
@@ -301,6 +347,7 @@ public class Axolotl extends Animal implements VariantHolder<Axolotl.Variant>, B
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep(ServerLevel level) {
+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(level, this);
AxolotlAi.updateActivity(this);
@@ -550,23 +597,31 @@ public class Axolotl extends Animal implements VariantHolder<Axolotl.Variant>, B
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (!Axolotl.this.isPlayingDead()) {
- super.tick();
+ super.vanillaTick(); // Purpur - Ridables
}
}
}
static class AxolotlMoveControl extends SmoothSwimmingMoveControl {
private final Axolotl axolotl;
+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - Ridables
public AxolotlMoveControl(Axolotl axolotl) {
super(axolotl, 85, 10, 0.1F, 0.5F, false);
this.axolotl = axolotl;
+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(axolotl, 0.5D); // Purpur - Ridables
}
@Override
public void tick() {
+ // Purpur start - Ridables
+ if (axolotl.getRider() != null && axolotl.isControllable()) {
+ waterController.purpurTick(axolotl.getRider());
+ return;
+ }
+ // Purpur end - Ridables
if (!this.axolotl.isPlayingDead()) {
super.tick();
}
diff --git a/net/minecraft/world/entity/animal/camel/Camel.java b/net/minecraft/world/entity/animal/camel/Camel.java
index 1ac4b13554d2699c3e04d41946e1adfd5e854a17..64ff0d2923f16a567aa753cad028a1b21c20101b 100644
--- a/net/minecraft/world/entity/animal/camel/Camel.java
+++ b/net/minecraft/world/entity/animal/camel/Camel.java
@@ -81,6 +81,20 @@ public class Camel extends AbstractHorse {
groundPathNavigation.setCanWalkOverFences(true);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.camelRidableInWater;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.camelBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
@Override
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
@@ -308,6 +322,23 @@ public class Camel extends AbstractHorse {
return this.dashCooldown;
}
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.camelMaxHealthMin, this.level().purpurConfig.camelMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.camelJumpStrengthMin, this.level().purpurConfig.camelJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(net.minecraft.util.RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.camelMovementSpeedMin, this.level().purpurConfig.camelMovementSpeedMax);
+ }
+ // Purpur end - Configurable entity base attributes
+
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.CAMEL_AMBIENT;
diff --git a/net/minecraft/world/entity/animal/frog/Frog.java b/net/minecraft/world/entity/animal/frog/Frog.java
index 10a0779bf8611ade19e64031bb00beb277e98598..aca0877319d507c3a672589bd4de5268d7a4c3dc 100644
--- a/net/minecraft/world/entity/animal/frog/Frog.java
+++ b/net/minecraft/world/entity/animal/frog/Frog.java
@@ -104,6 +104,8 @@ public class Frog extends Animal implements VariantHolder<Holder<FrogVariant>> {
public final AnimationState croakAnimationState = new AnimationState();
public final AnimationState tongueAnimationState = new AnimationState();
public final AnimationState swimIdleAnimationState = new AnimationState();
+ private org.purpurmc.purpur.controller.MoveControllerWASD purpurLandController; // Purpur - Ridables
+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur - Ridables
public Frog(EntityType<? extends Animal> entityType, Level level) {
super(entityType, level);
@@ -111,7 +113,62 @@ public class Frog extends Animal implements VariantHolder<Holder<FrogVariant>> {
this.setPathfindingMalus(PathType.WATER, 4.0F);
this.setPathfindingMalus(PathType.TRAPDOOR, -1.0F);
this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
+ // Purpur start - Ridables
+ this.purpurLandController = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.2F);
+ this.purpurWaterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F);
+ this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) {
+ @Override
+ public void tick() {
+ net.minecraft.world.entity.player.Player rider = mob.getRider();
+ if (rider != null && mob.isControllable()) {
+ if (mob.isInWater()) {
+ purpurWaterController.purpurTick(rider);
+ mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, -0.005D, 0.0D));
+ } else {
+ purpurLandController.purpurTick(rider);
+ }
+ } else {
+ super.tick();
+ }
+ }
+ };
+ // Purpur end - Ridables
+ }
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.frogRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.frogRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.frogControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ }
+
+ @Override
+ public float getJumpPower() {
+ return (getRider() != null && isControllable()) ? level().purpurConfig.frogRidableJumpHeight * this.getBlockJumpFactor() : super.getJumpPower();
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.frogBreedingTicks;
}
+ // Purpur end - Make entity breeding times configurable
@Override
protected Brain.Provider<Frog> brainProvider() {
@@ -185,6 +242,7 @@ public class Frog extends Animal implements VariantHolder<Holder<FrogVariant>> {
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep(ServerLevel level) {
+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(level, this);
FrogAi.updateActivity(this);
@@ -375,7 +433,7 @@ public class Frog extends Animal implements VariantHolder<Holder<FrogVariant>> {
return level.getBlockState(pos.below()).is(BlockTags.FROGS_SPAWNABLE_ON) && isBrightEnoughToSpawn(level, pos);
}
- class FrogLookControl extends LookControl {
+ class FrogLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
FrogLookControl(final Mob mob) {
super(mob);
}
diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java
index 77691e10f7c511eca4384f2974e538d78d55c2ca..0ca35514a920dddf230d749bc1a5fe15f1c7940a 100644
--- a/net/minecraft/world/entity/animal/frog/Tadpole.java
+++ b/net/minecraft/world/entity/animal/frog/Tadpole.java
@@ -61,13 +61,50 @@ public class Tadpole extends AbstractFish {
MemoryModuleType.IS_PANICKING
);
public boolean ageLocked; // Paper
+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurController; // Purpur - Ridables
public Tadpole(EntityType<? extends AbstractFish> entityType, Level level) {
super(entityType, level);
- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
+ // Purpur start - Ridables
+ this.purpurController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F);
+ this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) {
+ @Override
+ public void tick() {
+ Player rider = mob.getRider();
+ if (rider != null && mob.isControllable()) {
+ purpurController.purpurTick(rider);
+ mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, 0.002D, 0.0D));
+ } else {
+ super.tick();
+ }
+ }
+ };
+ // Purpur end - Ridables
this.lookControl = new SmoothSwimmingLookControl(this, 10);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.tadpoleRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.tadpoleRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.tadpoleControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ }
+ // Purpur end - Ridables
+
@Override
protected PathNavigation createNavigation(Level level) {
return new WaterBoundPathNavigation(this, level);
@@ -96,6 +133,7 @@ public class Tadpole extends AbstractFish {
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep(ServerLevel level) {
+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(level, this);
TadpoleAi.updateActivity(this);
diff --git a/net/minecraft/world/entity/animal/goat/Goat.java b/net/minecraft/world/entity/animal/goat/Goat.java
index dde7f05b8664d9a60edc716a370917d6cab03c18..35d1b99b873d9f8c9aa2a1178a449a8625b18406 100644
--- a/net/minecraft/world/entity/animal/goat/Goat.java
+++ b/net/minecraft/world/entity/animal/goat/Goat.java
@@ -109,6 +109,44 @@ public class Goat extends Animal {
.orElseGet(() -> new ItemStack(Items.GOAT_HORN));
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.goatRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.goatRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.goatControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.goatBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.goatTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.goatAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected Brain.Provider<Goat> brainProvider() {
return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
@@ -185,6 +223,7 @@ public class Goat extends Animal {
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep(ServerLevel level) {
+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(level, this);
GoatAi.updateActivity(this);
@@ -387,6 +426,7 @@ public class Goat extends Animal {
// 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);
diff --git a/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/net/minecraft/world/entity/animal/horse/AbstractHorse.java
index d52a8315f1e6876c26c732f4c4caa47bc6bebf6e..56dc7011ed07f0bd5870fbadde2b5c0c630c5edd 100644
--- a/net/minecraft/world/entity/animal/horse/AbstractHorse.java
+++ b/net/minecraft/world/entity/animal/horse/AbstractHorse.java
@@ -206,11 +206,61 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
protected AbstractHorse(EntityType<? extends AbstractHorse> entityType, Level level) {
super(entityType, level);
+ this.moveControl = new net.minecraft.world.entity.ai.control.MoveControl(this); // Purpur - use vanilla controller
+ this.lookControl = new net.minecraft.world.entity.ai.control.LookControl(this); // Purpur - use vanilla controller
this.createInventory();
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return false; // vanilla handles
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.generateMaxHealth(random));
+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.generateSpeed(random));
+ this.getAttribute(Attributes.JUMP_STRENGTH).setBaseValue(this.generateJumpStrength(random));
+ }
+
+ protected double generateMaxHealth(double min, double max) {
+ if (min == max) return min;
+ int diff = Mth.floor(max - min);
+ double base = max - diff;
+ int first = Mth.floor((double) diff / 2);
+ int rest = diff - first;
+ return base + random.nextInt(first + 1) + random.nextInt(rest + 1);
+ }
+
+ protected double generateJumpStrength(double min, double max) {
+ if (min == max) return min;
+ return min + (max - min) * this.random.nextDouble();
+ }
+
+ protected double generateSpeed(double min, double max) {
+ if (min == max) return min;
+ return min + (max - min) * this.random.nextDouble();
+ }
+
+ protected float generateMaxHealth(RandomSource random) {
+ return 15.0F + (float) random.nextInt(8) + (float) random.nextInt(9);
+ }
+
+ protected double generateJumpStrength(RandomSource random) {
+ return 0.4F + random.nextDouble() * 0.2 + random.nextDouble() * 0.2 + random.nextDouble() * 0.2;
+ }
+
+ protected double generateSpeed(RandomSource random) {
+ return (0.45F + random.nextDouble() * 0.3 + random.nextDouble() * 0.3 + random.nextDouble() * 0.3) * 0.25;
+ }
+ // Purpur end - Configurable entity base attributes
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new PanicGoal(this, 1.2));
this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2));
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0, AbstractHorse.class));
@@ -221,6 +271,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
if (this.canPerformRearing()) {
this.goalSelector.addGoal(9, new RandomStandGoal(this));
}
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - Ridables
this.addBehaviourGoals();
}
@@ -1207,7 +1258,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
spawnGroupData = new AgeableMob.AgeableMobGroupData(0.2F);
}
- this.randomizeAttributes(level.getRandom());
+ //this.randomizeAttributes(level.getRandom()); // Purpur - replaced by initAttributes()
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
}
diff --git a/net/minecraft/world/entity/animal/horse/Donkey.java b/net/minecraft/world/entity/animal/horse/Donkey.java
index 9b97f3d3675f5051b18a68ff7fa056d859a283e9..3e0181578a6f2d22d1da3776abf30bf97d124620 100644
--- a/net/minecraft/world/entity/animal/horse/Donkey.java
+++ b/net/minecraft/world/entity/animal/horse/Donkey.java
@@ -16,6 +16,51 @@ public class Donkey extends AbstractChestedHorse {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.donkeyRidableInWater;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.donkeyMaxHealthMin, this.level().purpurConfig.donkeyMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.donkeyJumpStrengthMin, this.level().purpurConfig.donkeyJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(net.minecraft.util.RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.donkeyMovementSpeedMin, this.level().purpurConfig.donkeyMovementSpeedMax);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.donkeyBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.donkeyTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.donkeyAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.DONKEY_AMBIENT;
diff --git a/net/minecraft/world/entity/animal/horse/Horse.java b/net/minecraft/world/entity/animal/horse/Horse.java
index c6d0700f29d6c8123e96efe225faf2d99202ac81..be0d636ca894c5995f28f59c196cd8e56dd228c4 100644
--- a/net/minecraft/world/entity/animal/horse/Horse.java
+++ b/net/minecraft/world/entity/animal/horse/Horse.java
@@ -43,6 +43,51 @@ public class Horse extends AbstractHorse implements VariantHolder<Variant> {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.horseRidableInWater;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public float generateMaxHealth(RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.horseMaxHealthMin, this.level().purpurConfig.horseMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.horseJumpStrengthMin, this.level().purpurConfig.horseJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.horseMovementSpeedMin, this.level().purpurConfig.horseMovementSpeedMax);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.horseBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.horseTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.horseAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void randomizeAttributes(RandomSource random) {
this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(generateMaxHealth(random::nextInt));
diff --git a/net/minecraft/world/entity/animal/horse/Llama.java b/net/minecraft/world/entity/animal/horse/Llama.java
index 58bb056ce934c793b16e63e44a4029be955c7caa..c21d558a6a3a61d6c54b8163f8cb4963846b2c26 100644
--- a/net/minecraft/world/entity/animal/horse/Llama.java
+++ b/net/minecraft/world/entity/animal/horse/Llama.java
@@ -72,12 +72,95 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
private Llama caravanHead;
@Nullable
public Llama caravanTail; // Paper
+ public boolean shouldJoinCaravan = true; // Purpur - Llama API
public Llama(EntityType<? extends Llama> entityType, Level level) {
super(entityType, level);
this.getNavigation().setRequiredPathLength(40.0F);
this.maxDomestication = 30; // Paper - Missing entity API; configure max temper instead of a hardcoded value
+ // Purpur start - Ridables
+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this) {
+ @Override
+ public void tick() {
+ if (entity.getRider() != null && entity.isControllable() && isSaddled()) {
+ purpurTick(entity.getRider());
+ } else {
+ vanillaTick();
+ }
+ }
+ };
+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) {
+ @Override
+ public void tick() {
+ if (entity.getRider() != null && entity.isControllable() && isSaddled()) {
+ purpurTick(entity.getRider());
+ } else {
+ vanillaTick();
+ }
+ }
+ };
+ // Purpur end - Ridables
+ }
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.llamaRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.llamaRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.llamaControllable;
+ }
+
+ @Override
+ public boolean isSaddled() {
+ return super.isSaddled() || (isTamed());
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public float generateMaxHealth(RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.llamaMaxHealthMin, this.level().purpurConfig.llamaMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.llamaJumpStrengthMin, this.level().purpurConfig.llamaJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.llamaMovementSpeedMin, this.level().purpurConfig.llamaMovementSpeedMax);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.llamaBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.llamaTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.llamaAlwaysDropExp;
}
+ // Purpur end - Mobs always drop experience
public boolean isTraderLlama() {
return false;
@@ -106,6 +189,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
super.addAdditionalSaveData(compound);
compound.putInt("Variant", this.getVariant().id);
compound.putInt("Strength", this.getStrength());
+ compound.putBoolean("Purpur.ShouldJoinCaravan", shouldJoinCaravan); // Purpur - Llama API
}
@Override
@@ -113,11 +197,13 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
this.setStrength(compound.getInt("Strength"));
super.readAdditionalSaveData(compound);
this.setVariant(Llama.Variant.byId(compound.getInt("Variant")));
+ if (compound.contains("Purpur.ShouldJoinCaravan")) this.shouldJoinCaravan = compound.getBoolean("Purpur.ShouldJoinCaravan"); // Purpur - Llama API
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.LlamaHasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2));
this.goalSelector.addGoal(2, new LlamaFollowCaravanGoal(this, 2.1F));
this.goalSelector.addGoal(3, new RangedAttackGoal(this, 1.25, 40, 20.0F));
@@ -128,6 +214,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.7));
this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(9, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.LlamaHasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new Llama.LlamaHurtByTargetGoal(this));
this.targetSelector.addGoal(2, new Llama.LlamaAttackWolfGoal(this));
}
@@ -386,6 +473,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
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;
}
@@ -393,6 +481,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
}
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;
}
diff --git a/net/minecraft/world/entity/animal/horse/Mule.java b/net/minecraft/world/entity/animal/horse/Mule.java
index 6ec7b1647d0bd31817e6fae3887849cc06756b63..3c56961efa743edba8a9a613b992d0afcd9ae415 100644
--- a/net/minecraft/world/entity/animal/horse/Mule.java
+++ b/net/minecraft/world/entity/animal/horse/Mule.java
@@ -15,6 +15,51 @@ public class Mule extends AbstractChestedHorse {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.muleRidableInWater;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.muleMaxHealthMin, this.level().purpurConfig.muleMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.muleJumpStrengthMin, this.level().purpurConfig.muleJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(net.minecraft.util.RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.muleMovementSpeedMin, this.level().purpurConfig.muleMovementSpeedMax);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.muleBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.muleTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.muleAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.MULE_AMBIENT;
diff --git a/net/minecraft/world/entity/animal/horse/SkeletonHorse.java b/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
index 96d54de3f6dad646175dfc6d80c2124f7932aa73..d5ee5b816efc1b3b17b94227f8ea05ab7a2ccad7 100644
--- a/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
+++ b/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
@@ -39,6 +39,51 @@ public class SkeletonHorse extends AbstractHorse {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isTamed() {
+ return super.isTamed() || this.level().purpurConfig.skeletonHorseRidable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public float generateMaxHealth(RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.skeletonHorseMaxHealthMin, this.level().purpurConfig.skeletonHorseMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.skeletonHorseJumpStrengthMin, this.level().purpurConfig.skeletonHorseJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.skeletonHorseMovementSpeedMin, this.level().purpurConfig.skeletonHorseMovementSpeedMax);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return 6000;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.skeletonHorseTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.skeletonHorseAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static AttributeSupplier.Builder createAttributes() {
return createBaseHorseAttributes().add(Attributes.MAX_HEALTH, 15.0).add(Attributes.MOVEMENT_SPEED, 0.2F);
}
@@ -58,6 +103,7 @@ public class SkeletonHorse extends AbstractHorse {
@Override
protected void addBehaviourGoals() {
+ if (level().purpurConfig.skeletonHorseCanSwim) goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur - Ridables
}
@Override
diff --git a/net/minecraft/world/entity/animal/horse/TraderLlama.java b/net/minecraft/world/entity/animal/horse/TraderLlama.java
index c5b11a63bb2ab660efcc386ad9b4697e2a5efc97..8faeb84f1c48b6a713867a1b95fed6402654fabb 100644
--- a/net/minecraft/world/entity/animal/horse/TraderLlama.java
+++ b/net/minecraft/world/entity/animal/horse/TraderLlama.java
@@ -29,6 +29,66 @@ public class TraderLlama extends Llama {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.traderLlamaRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.traderLlamaRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.traderLlamaControllable;
+ }
+
+ @Override
+ public boolean isSaddled() {
+ return super.isSaddled() || isTamed();
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.traderLlamaMaxHealthMin, this.level().purpurConfig.traderLlamaMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.traderLlamaJumpStrengthMin, this.level().purpurConfig.traderLlamaJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(net.minecraft.util.RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.traderLlamaMovementSpeedMin, this.level().purpurConfig.traderLlamaMovementSpeedMax);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.traderLlamaBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.traderLlamaTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.traderLlamaAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public boolean isTraderLlama() {
return true;
diff --git a/net/minecraft/world/entity/animal/horse/ZombieHorse.java b/net/minecraft/world/entity/animal/horse/ZombieHorse.java
index 6ed5bdb3c069d6d9a1a2321a49f6286824ede898..4a9c0242b5c01d1adda281058658134e7982e8c7 100644
--- a/net/minecraft/world/entity/animal/horse/ZombieHorse.java
+++ b/net/minecraft/world/entity/animal/horse/ZombieHorse.java
@@ -33,6 +33,56 @@ public class ZombieHorse extends AbstractHorse {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieHorseRidableInWater;
+ }
+
+ @Override
+ public boolean isTamed() {
+ return super.isTamed() || this.level().purpurConfig.zombieHorseRidable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public float generateMaxHealth(RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.zombieHorseMaxHealthMin, this.level().purpurConfig.zombieHorseMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.zombieHorseJumpStrengthMin, this.level().purpurConfig.zombieHorseJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.zombieHorseMovementSpeedMin, this.level().purpurConfig.zombieHorseMovementSpeedMax);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return 6000;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zombieHorseTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zombieHorseAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static AttributeSupplier.Builder createAttributes() {
return createBaseHorseAttributes().add(Attributes.MAX_HEALTH, 15.0).add(Attributes.MOVEMENT_SPEED, 0.2F);
}
@@ -78,6 +128,7 @@ public class ZombieHorse extends AbstractHorse {
@Override
protected void addBehaviourGoals() {
+ if (level().purpurConfig.zombieHorseCanSwim) goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur - Ridables
}
@Override
diff --git a/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/net/minecraft/world/entity/animal/sniffer/Sniffer.java
index 5f0efcfb88bee09f1cccc53cedbef22b14c5f554..68751f7ed123c3e99f56259ccc23121661f89bc1 100644
--- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java
+++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java
@@ -86,6 +86,38 @@ public class Sniffer extends Animal {
this.setPathfindingMalus(PathType.DAMAGE_CAUTIOUS, -1.0F);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.snifferRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.snifferRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.snifferControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.snifferMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.snifferScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.snifferBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
diff --git a/net/minecraft/world/entity/boss/EnderDragonPart.java b/net/minecraft/world/entity/boss/EnderDragonPart.java
index 31f064267514e590944ad809c95915b481e65aaa..c8bc09c3fe27e69360027698c41fd51a111ffa66 100644
--- a/net/minecraft/world/entity/boss/EnderDragonPart.java
+++ b/net/minecraft/world/entity/boss/EnderDragonPart.java
@@ -27,6 +27,13 @@ public class EnderDragonPart extends Entity {
this.name = name;
}
+ // Purpur start - Ridables
+ @Override
+ public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) {
+ return parentMob.isAlive() ? parentMob.tryRide(player, hand) : net.minecraft.world.InteractionResult.PASS;
+ }
+ // Purpur end - Ridables
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
}
diff --git a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
index ff1c84d37db48e1bd0283a900e199647c0e8eba1..d58829c88b86358a0c06a982b302fc9a31c15853 100644
--- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
+++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
@@ -26,6 +26,12 @@ public class EndCrystal extends Entity {
private static final EntityDataAccessor<Boolean> DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN);
public int time;
public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals
+ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms
+ private net.minecraft.world.entity.monster.Phantom targetPhantom;
+ private int phantomBeamTicks = 0;
+ private int phantomDamageCooldown = 0;
+ private int idleCooldown = 0;
+ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms
public EndCrystal(EntityType<? extends EndCrystal> entityType, Level level) {
super(entityType, level);
@@ -38,6 +44,24 @@ public class EndCrystal extends Entity {
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;
@@ -74,6 +98,51 @@ public class EndCrystal extends Entity {
}
}
// 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
+
+ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms
+ if (level().purpurConfig.phantomAttackedByCrystalRadius <= 0 || --idleCooldown > 0) {
+ return; // on cooldown
+ }
+
+ if (targetPhantom == null) {
+ for (net.minecraft.world.entity.monster.Phantom phantom : level().getEntitiesOfClass(net.minecraft.world.entity.monster.Phantom.class, getBoundingBox().inflate(level().purpurConfig.phantomAttackedByCrystalRadius))) {
+ if (phantom.hasLineOfSight(this)) {
+ attackPhantom(phantom);
+ break;
+ }
+ }
+ } else {
+ setBeamTarget(new BlockPos(targetPhantom).offset(0, -2, 0));
+ if (--phantomBeamTicks > 0 && targetPhantom.isAlive()) {
+ phantomDamageCooldown--;
+ if (targetPhantom.hasLineOfSight(this)) {
+ if (phantomDamageCooldown <= 0) {
+ phantomDamageCooldown = 20;
+ targetPhantom.hurt(targetPhantom.damageSources().indirectMagic(this, this), level().purpurConfig.phantomAttackedByCrystalDamage);
+ }
+ } else {
+ forgetPhantom(); // no longer in sight
+ }
+ } else {
+ forgetPhantom(); // attacked long enough
+ }
+ }
+ }
+
+ private void attackPhantom(net.minecraft.world.entity.monster.Phantom phantom) {
+ phantomDamageCooldown = 0;
+ phantomBeamTicks = 60;
+ targetPhantom = phantom;
+ }
+
+ private void forgetPhantom() {
+ targetPhantom = null;
+ setBeamTarget(null);
+ phantomBeamTicks = 0;
+ phantomDamageCooldown = 0;
+ idleCooldown = 60;
+ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms
}
@Override
@@ -119,15 +188,17 @@ public class EndCrystal extends Entity {
}
// 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
diff --git a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
index 403bcd056bf9c385599383983bf7a0cc117a1881..b2a0ba6faa117ad781aaa3e6932482d4d9c8a789 100644
--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
@@ -90,6 +90,7 @@ public class EnderDragon extends Mob implements Enemy {
private final net.minecraft.world.level.Explosion explosionSource; // Paper - reusable source for CraftTNTPrimed.getSource()
@Nullable private BlockPos podium;
// Paper end
+ private boolean hadRider; // Purpur - Ridables
public EnderDragon(EntityType<? extends EnderDragon> entityType, Level level) {
super(EntityType.ENDER_DRAGON, level);
@@ -106,6 +107,37 @@ public class EnderDragon extends Mob implements Enemy {
this.noPhysics = true;
this.phaseManager = new EnderDragonPhaseManager(this);
this.explosionSource = new net.minecraft.world.level.ServerExplosion(level.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, net.minecraft.world.level.Explosion.BlockInteraction.DESTROY); // Paper
+
+ // Purpur start - Ridables
+ this.moveControl = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this) {
+ @Override
+ public void vanillaTick() {
+ // dragon doesn't use the controller. do nothing
+ }
+ };
+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) {
+ @Override
+ public void vanillaTick() {
+ // dragon doesn't use the controller. do nothing
+ }
+
+ @Override
+ public void purpurTick(Player rider) {
+ setYawPitch(rider.getYRot() - 180F, rider.xRotO * 0.5F);
+ }
+ };
+ // Purpur end - Ridables
+ }
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.enderDragonRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.enderDragonRidableInWater;
}
public void setDragonFight(EndDragonFight dragonFight) {
@@ -120,6 +152,31 @@ public class EnderDragon extends Mob implements Enemy {
return this.fightOrigin;
}
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.enderDragonControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.enderDragonMaxY;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.enderDragonMaxHealth);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.enderDragonTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0);
}
@@ -169,6 +226,37 @@ public class EnderDragon extends Mob implements Enemy {
@Override
public void aiStep() {
+ // Purpur start - Ridables
+ boolean hasRider = getRider() != null && this.isControllable();
+ if (hasRider) {
+ if (!hadRider) {
+ hadRider = true;
+ noPhysics = false;
+ this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(4.0F, 2.0F);
+ }
+
+ // dragon doesn't use controllers, so must tick manually
+ moveControl.tick();
+ lookControl.tick();
+
+ moveRelative((float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F, new Vec3(-getStrafeMot(), getVerticalMot(), -getForwardMot()));
+ Vec3 mot = getDeltaMovement();
+ setDeltaMovement(mot);
+ move(MoverType.PLAYER, mot);
+
+ mot = mot.multiply(0.9F, 0.9F, 0.9F);
+ setDeltaMovement(mot);
+
+ // control wing flap speed on client
+ phaseManager.setPhase(mot.x() * mot.x() + mot.z() * mot.z() < 0.005F ? EnderDragonPhase.HOVERING : EnderDragonPhase.HOLDING_PATTERN);
+ } else if (hadRider) {
+ hadRider = false;
+ noPhysics = true;
+ this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(16.0F, 8.0F);
+ phaseManager.setPhase(EnderDragonPhase.HOLDING_PATTERN); // HoldingPattern
+ }
+ // Purpur end - Ridables
+
this.processFlappingMovement();
if (this.level().isClientSide) {
this.setHealth(this.getHealth());
@@ -197,6 +285,8 @@ public class EnderDragon extends Mob implements Enemy {
this.oFlapTime = this.flapTime;
if (this.isDeadOrDying()) {
+ if (hasRider) ejectPassengers(); // Purpur - Ridables
+
float f = (this.random.nextFloat() - 0.5F) * 8.0F;
float f1 = (this.random.nextFloat() - 0.5F) * 4.0F;
float f2 = (this.random.nextFloat() - 0.5F) * 8.0F;
@@ -206,9 +296,9 @@ public class EnderDragon extends Mob implements Enemy {
Vec3 deltaMovement = this.getDeltaMovement();
float f1 = 0.2F / ((float)deltaMovement.horizontalDistance() * 10.0F + 1.0F);
f1 *= (float)Math.pow(2.0, deltaMovement.y);
- if (this.phaseManager.getCurrentPhase().isSitting()) {
+ if (!hasRider && this.phaseManager.getCurrentPhase().isSitting()) { // Purpur - Ridables
this.flapTime += 0.1F;
- } else if (this.inWall) {
+ } else if (!hasRider && this.inWall) { // Purpur - Ridables
this.flapTime += f1 * 0.5F;
} else {
this.flapTime += f1;
@@ -219,7 +309,7 @@ public class EnderDragon extends Mob implements Enemy {
this.flapTime = 0.5F;
} else {
this.flightHistory.record(this.getY(), this.getYRot());
- if (this.level() instanceof ServerLevel serverLevel1) {
+ if (this.level() instanceof ServerLevel serverLevel1 && !hasRider) { // Purpur - Ridables
DragonPhaseInstance currentPhase = this.phaseManager.getCurrentPhase();
currentPhase.doServerTick(serverLevel1);
if (this.phaseManager.getCurrentPhase() != currentPhase) {
@@ -298,7 +388,7 @@ public class EnderDragon extends Mob implements Enemy {
this.tickPart(this.body, sin1 * 0.5F, 0.0, -cos1 * 0.5F);
this.tickPart(this.wing1, cos1 * 4.5F, 2.0, sin1 * 4.5F);
this.tickPart(this.wing2, cos1 * -4.5F, 2.0, sin1 * -4.5F);
- if (this.level() instanceof ServerLevel serverLevel2 && this.hurtTime == 0) {
+ if (this.level() instanceof ServerLevel serverLevel2 && this.hurtTime == 0 && !hasRider) { // Purpur - Ridables
this.knockBack(
serverLevel2,
serverLevel2.getEntities(
@@ -348,9 +438,9 @@ public class EnderDragon extends Mob implements Enemy {
}
if (this.level() instanceof ServerLevel serverLevel3) {
- this.inWall = this.checkWalls(serverLevel3, this.head.getBoundingBox())
+ this.inWall = !hasRider && this.checkWalls(serverLevel3, this.head.getBoundingBox())
| this.checkWalls(serverLevel3, this.neck.getBoundingBox())
- | this.checkWalls(serverLevel3, this.body.getBoundingBox());
+ | this.checkWalls(serverLevel3, this.body.getBoundingBox()); // Purpur - Ridables
if (this.dragonFight != null) {
this.dragonFight.updateDragon(this);
}
@@ -464,7 +554,7 @@ public class EnderDragon extends Mob implements Enemy {
BlockPos blockPos = new BlockPos(i, i1, i2);
BlockState blockState = level.getBlockState(blockPos);
if (!blockState.isAir() && !blockState.is(BlockTags.DRAGON_TRANSPARENT)) {
- if (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) {
+ if (level.purpurConfig.enderDragonBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) { // Purpur - Add mobGriefing bypass to everything affected
// CraftBukkit start - Add blocks to list rather than destroying them
//flag1 = level.removeBlock(blockPos, false) || flag1;
flag1 = true;
@@ -974,6 +1064,7 @@ public class EnderDragon extends Mob implements Enemy {
@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;
}
@@ -999,7 +1090,7 @@ public class EnderDragon extends Mob implements Enemy {
@Override
protected float sanitizeScale(float scale) {
- return 1.0F;
+ return 1.0F; // Purpur - Configurable entity base attributes
}
// CraftBukkit start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time.
@@ -1009,7 +1100,7 @@ public class EnderDragon extends Mob implements Enemy {
boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
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;
}
diff --git a/net/minecraft/world/entity/boss/wither/WitherBoss.java b/net/minecraft/world/entity/boss/wither/WitherBoss.java
index afe43600c4976e01e61d716034a2823d50fb55cb..2d9f64cddbec782de1775ec6da67b16203e3fd90 100644
--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
+++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
@@ -69,6 +69,7 @@ public class WitherBoss extends Monster implements RangedAttackMob {
private final int[] nextHeadUpdate = new int[2];
private final int[] idleHeadUpdates = new int[2];
private int destroyBlocksTick;
+ private int shootCooldown = 0; // Purpur - Ridables
private boolean canPortal = false; // Paper
public final ServerBossEvent bossEvent = (ServerBossEvent)new ServerBossEvent(
this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS
@@ -77,14 +78,161 @@ public class WitherBoss extends Monster implements RangedAttackMob {
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
+ private org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD purpurController; // Purpur - Ridables
public WitherBoss(EntityType<? extends WitherBoss> entityType, Level level) {
super(entityType, level);
+ // Purpur start - Ridables
+ this.purpurController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.1F);
+ this.moveControl = new FlyingMoveControl(this, 10, false) {
+ @Override
+ public void tick() {
+ if (mob.getRider() != null && mob.isControllable()) {
+ purpurController.purpurTick(mob.getRider());
+ } else {
+ super.tick();
+ }
+ }
+ };
+ // Purpur end - Ridables
this.moveControl = new FlyingMoveControl(this, 10, false);
this.setHealth(this.getMaxHealth());
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
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.witherRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.witherControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.witherMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 5F;
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.5, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ this.entityData.set(DATA_TARGETS.get(0), 0);
+ this.entityData.set(DATA_TARGETS.get(1), 0);
+ this.entityData.set(DATA_TARGETS.get(2), 0);
+ getNavigation().stop();
+ shootCooldown = 20;
+ }
+
+ @Override
+ public boolean onClick(net.minecraft.world.InteractionHand hand) {
+ return shoot(getRider(), hand == net.minecraft.world.InteractionHand.MAIN_HAND ? new int[]{1} : new int[]{2});
+ }
+
+ public boolean shoot(@Nullable Player rider, int[] heads) {
+ if (shootCooldown > 0) {
+ return false;
+ }
+
+ shootCooldown = 20;
+ if (rider == null) {
+ return false;
+ }
+
+ org.bukkit.craftbukkit.entity.CraftHumanEntity player = rider.getBukkitEntity();
+ if (!player.hasPermission("allow.special.wither")) {
+ return false;
+ }
+
+ net.minecraft.world.phys.HitResult rayTrace = getRayTrace(120, net.minecraft.world.level.ClipContext.Fluid.NONE);
+ if (rayTrace == null) {
+ return false;
+ }
+
+ Vec3 loc;
+ if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) {
+ BlockPos pos = ((net.minecraft.world.phys.BlockHitResult) rayTrace).getBlockPos();
+ loc = new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
+ } else if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.ENTITY) {
+ Entity target = ((net.minecraft.world.phys.EntityHitResult) rayTrace).getEntity();
+ loc = new Vec3(target.getX(), target.getY() + (target.getEyeHeight() / 2), target.getZ());
+ } else {
+ org.bukkit.block.Block block = player.getTargetBlock(null, 120);
+ loc = new Vec3(block.getX() + 0.5D, block.getY() + 0.5D, block.getZ() + 0.5D);
+ }
+
+ for (int head : heads) {
+ shoot(head, loc.x(), loc.y(), loc.z(), rider);
+ }
+
+ return true; // handled
+ }
+
+ public void shoot(int head, double x, double y, double z, Player rider) {
+ level().levelEvent(null, 1024, blockPosition(), 0);
+ double headX = getHeadX(head);
+ double headY = getHeadY(head);
+ double headZ = getHeadZ(head);
+ Vec3 vec3d = new Vec3(x - headX, y - headY, z - headZ);
+ WitherSkull skull = new WitherSkull(level(), this, vec3d.normalize());
+ skull.setPosRaw(headX, headY, headZ);
+ level().addFreshEntity(skull);
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.witherTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.witherAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected PathNavigation createNavigation(Level level) {
FlyingPathNavigation flyingPathNavigation = new FlyingPathNavigation(this, level);
@@ -95,11 +243,13 @@ public class WitherBoss extends Monster implements RangedAttackMob {
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(0, new WitherBoss.WitherDoNothingGoal());
this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0, 40, 20.0F));
this.goalSelector.addGoal(5, new WaterAvoidingRandomFlyingGoal(this, 1.0));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 0, false, false, LIVING_ENTITY_SELECTOR));
}
@@ -117,6 +267,7 @@ public class WitherBoss extends Monster implements RangedAttackMob {
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putInt("Invul", this.getInvulnerableTicks());
+ if (getSummoner() != null) compound.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API
}
@Override
@@ -126,6 +277,7 @@ public class WitherBoss extends Monster implements RangedAttackMob {
if (this.hasCustomName()) {
this.bossEvent.setName(this.getDisplayName());
}
+ if (compound.contains("Purpur.Summoner")) setSummoner(compound.getUUID("Purpur.Summoner")); // Purpur - Summoner API
}
@Override
@@ -257,6 +409,15 @@ public class WitherBoss extends Monster implements RangedAttackMob {
@Override
protected void customServerAiStep(ServerLevel level) {
+ // Purpur start - Ridables
+ if (getRider() != null && this.isControllable()) {
+ Vec3 mot = getDeltaMovement();
+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z());
+ }
+ if (shootCooldown > 0) {
+ shootCooldown--;
+ }
+ // Purpur end - Ridables
if (this.getInvulnerableTicks() > 0) {
int i = this.getInvulnerableTicks() - 1;
this.bossEvent.setProgress(1.0F - i / 220.0F);
@@ -269,7 +430,7 @@ public class WitherBoss extends Monster implements RangedAttackMob {
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;
@@ -294,7 +455,7 @@ public class WitherBoss extends Monster implements RangedAttackMob {
this.setInvulnerableTicks(i);
if (this.tickCount % 10 == 0) {
- this.heal(10.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit
+ this.heal(this.getMaxHealth() / 30, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit // Purpur - Configurable entity base attributes
}
} else {
super.customServerAiStep(level);
@@ -346,7 +507,7 @@ public class WitherBoss extends Monster implements RangedAttackMob {
if (this.destroyBlocksTick > 0) {
this.destroyBlocksTick--;
- if (this.destroyBlocksTick == 0 && level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (this.destroyBlocksTick == 0 && level.purpurConfig.witherBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
boolean flag = false;
int alternativeTarget = Mth.floor(this.getBbWidth() / 2.0F + 1.0F);
int floor = Mth.floor(this.getBbHeight());
@@ -376,8 +537,10 @@ public class WitherBoss extends Monster implements RangedAttackMob {
}
}
- 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());
@@ -561,11 +724,11 @@ public class WitherBoss extends Monster implements RangedAttackMob {
}
public int getAlternativeTarget(int head) {
- return this.entityData.get(DATA_TARGETS.get(head));
+ return getRider() != null && this.isControllable() ? 0 : this.entityData.get(DATA_TARGETS.get(head)); // Purpur - Ridables
}
public void setAlternativeTarget(int targetOffset, int newId) {
- this.entityData.set(DATA_TARGETS.get(targetOffset), newId);
+ if (getRider() == null || !this.isControllable()) this.entityData.set(DATA_TARGETS.get(targetOffset), newId); // Purpur - Ridables
}
public boolean isPowered() {
@@ -574,6 +737,7 @@ public class WitherBoss extends Monster implements RangedAttackMob {
@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;
}
diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java
index 3e0830c70c0b3fda8631679c7426cc303600d1fa..a31bbd8f3fff4fb4b1b33877d5835b93fc248f65 100644
--- a/net/minecraft/world/entity/decoration/ArmorStand.java
+++ b/net/minecraft/world/entity/decoration/ArmorStand.java
@@ -93,10 +93,13 @@ public class ArmorStand extends LivingEntity {
private boolean noTickPoseDirty = false;
public 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> entityType, Level level) {
super(entityType, 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) {
@@ -561,6 +564,7 @@ public class ArmorStand extends LivingEntity {
private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel level, DamageSource damageSource) { // Paper
ItemStack itemStack = new ItemStack(Items.ARMOR_STAND);
+ if (level.purpurConfig.persistentDroppableEntityDisplayNames) // Purpur - Apply display names from item forms of entities to entities and vice versa
itemStack.set(DataComponents.CUSTOM_NAME, this.getCustomName());
this.drops.add(new DefaultDrop(itemStack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior
return this.brokenByAnything(level, damageSource); // Paper
@@ -620,6 +624,7 @@ public class ArmorStand extends LivingEntity {
@Override
public void tick() {
+ maxUpStep = level().purpurConfig.armorstandStepHeight; // Purpur - Add option to set armorstand step height
// Paper start - Allow ArmorStands not to tick
if (!this.canTick) {
if (this.noTickPoseDirty) {
@@ -949,4 +954,18 @@ public class ArmorStand extends LivingEntity {
}
}
// 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
}
diff --git a/net/minecraft/world/entity/decoration/ItemFrame.java b/net/minecraft/world/entity/decoration/ItemFrame.java
index 65e1d7c5ac94b1cfb921fa009be59d3e5872f0b5..3ee1d8798db666ee8d83556047e40ff217cda732 100644
--- a/net/minecraft/world/entity/decoration/ItemFrame.java
+++ b/net/minecraft/world/entity/decoration/ItemFrame.java
@@ -223,7 +223,11 @@ public class ItemFrame extends HangingEntity {
this.removeFramedMap(item);
} else {
if (dropItem) {
- this.spawnAtLocation(level, this.getFrameItemStack());
+ // Purpur start - Apply display names from item forms of entities to entities and vice versa
+ final ItemStack itemFrame = this.getFrameItemStack();
+ if (!level.purpurConfig.persistentDroppableEntityDisplayNames) itemFrame.set(DataComponents.CUSTOM_NAME, null);
+ this.spawnAtLocation(level, itemFrame);
+ // Purpur end - Apply display names from item forms of entities to entities and vice versa
}
if (!item.isEmpty()) {
diff --git a/net/minecraft/world/entity/decoration/Painting.java b/net/minecraft/world/entity/decoration/Painting.java
index 5b905a4d49c44b04d5795c2bf297f3c69d183d7c..b6429a2bbb6fc1e08610ab20e50f8f0414f0ad26 100644
--- a/net/minecraft/world/entity/decoration/Painting.java
+++ b/net/minecraft/world/entity/decoration/Painting.java
@@ -162,7 +162,11 @@ public class Painting extends HangingEntity implements VariantHolder<Holder<Pain
if (level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
this.playSound(SoundEvents.PAINTING_BREAK, 1.0F, 1.0F);
if (!(entity instanceof Player player && player.hasInfiniteMaterials())) {
- this.spawnAtLocation(level, Items.PAINTING);
+ // Purpur start - Apply display names from item forms of entities to entities and vice versa
+ final ItemStack painting = new ItemStack(Items.PAINTING);
+ if (!this.level().purpurConfig.persistentDroppableEntityDisplayNames) painting.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, null);
+ this.spawnAtLocation(level, painting);
+ // Purpur end - Apply display names from item forms of entities to entities and vice versa
}
}
}
diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java
index 8e450c9d0111c0ce44feb386cf8b2715e407bf25..bbd3253dc6ab09f166447b163b19730244ff7443 100644
--- a/net/minecraft/world/entity/item/ItemEntity.java
+++ b/net/minecraft/world/entity/item/ItemEntity.java
@@ -52,6 +52,12 @@ public class ItemEntity extends Entity implements TraceableEntity {
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> entityType, Level level) {
super(entityType, level);
@@ -365,7 +371,16 @@ public class ItemEntity extends Entity implements TraceableEntity {
@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().getBoolean(GameRules.RULE_MOBGRIEFING) && damageSource.getEntity() instanceof Mob) {
return false;
@@ -567,6 +582,12 @@ public class ItemEntity extends Entity implements TraceableEntity {
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
diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java
index 40da052e7fea1306a007b3cb5c9daa33e0ef523e..40f5534b425ef57c435b365f156d3b988b74f911 100644
--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -251,4 +251,32 @@ public class PrimedTnt extends Entity implements TraceableEntity {
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, LivingEntity.getSlotForHand(hand));
+ 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
}
diff --git a/net/minecraft/world/entity/monster/AbstractSkeleton.java b/net/minecraft/world/entity/monster/AbstractSkeleton.java
index 37abc7769573e3cdda380166dd086551d5e7bd88..223739818e9ac6c9fe396b82bce53a3ab029610a 100644
--- a/net/minecraft/world/entity/monster/AbstractSkeleton.java
+++ b/net/minecraft/world/entity/monster/AbstractSkeleton.java
@@ -64,21 +64,24 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
AbstractSkeleton.this.setAggressive(true);
}
};
- private boolean shouldBurnInDay = true; // Paper - shouldBurnInDay API
+ //private boolean shouldBurnInDay = true; // Paper - shouldBurnInDay API // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight
protected AbstractSkeleton(EntityType<? extends AbstractSkeleton> entityType, Level level) {
super(entityType, level);
this.reassessWeaponGoal();
+ this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight
}
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(2, new RestrictSunGoal(this));
this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0));
this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Wolf.class, 6.0F, 1.0, 1.2));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
@@ -108,27 +111,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
@Override
public void aiStep() {
- boolean isSunBurnTick = this.shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API
- if (isSunBurnTick) {
- ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD);
- if (!itemBySlot.isEmpty()) {
- if (itemBySlot.isDamageableItem()) {
- Item item = itemBySlot.getItem();
- itemBySlot.setDamageValue(itemBySlot.getDamageValue() + this.random.nextInt(2));
- if (itemBySlot.getDamageValue() >= itemBySlot.getMaxDamage()) {
- this.onEquippedItemBroken(item, EquipmentSlot.HEAD);
- this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY);
- }
- }
-
- isSunBurnTick = false;
- }
-
- if (isSunBurnTick) {
- this.igniteForSeconds(8.0F);
- }
- }
-
+ // Purpur - implemented in LivingEntity - API for any mob to burn daylight
super.aiStep();
}
@@ -158,10 +141,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
this.reassessWeaponGoal();
this.setCanPickUpLoot(this.level().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()) {
- LocalDate localDate = LocalDate.now();
- int i = localDate.get(ChronoField.DAY_OF_MONTH);
- int i1 = localDate.get(ChronoField.MONTH_OF_YEAR);
- if (i1 == 10 && i == 31 && random.nextFloat() < 0.25F) {
+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(level.getMinecraftWorld()) && this.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.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F;
}
@@ -217,7 +197,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
if (event.getProjectile() == arrow.getBukkitEntity()) {
// CraftBukkit end
Projectile.spawnProjectileUsingShoot(
- 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
);
} // CraftBukkit
}
@@ -244,7 +224,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
super.readAdditionalSaveData(compound);
this.reassessWeaponGoal();
// Paper start - shouldBurnInDay API
- if (compound.contains("Paper.ShouldBurnInDay")) {
+ if (false && compound.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight
this.shouldBurnInDay = compound.getBoolean("Paper.ShouldBurnInDay");
}
// Paper end - shouldBurnInDay API
@@ -253,7 +233,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
@Override
public void addAdditionalSaveData(final net.minecraft.nbt.CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
- nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay);
+ //nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - implemented in LivingEntity - API for any mob to burn daylight
}
// Paper end - shouldBurnInDay API
diff --git a/net/minecraft/world/entity/monster/Blaze.java b/net/minecraft/world/entity/monster/Blaze.java
index 419c729502ee708bba9e750f1b951450eca82695..79f5f0dbf95ed72dd031495c75675d7b6c7dfc34 100644
--- a/net/minecraft/world/entity/monster/Blaze.java
+++ b/net/minecraft/world/entity/monster/Blaze.java
@@ -33,26 +33,78 @@ public class Blaze extends Monster {
public Blaze(EntityType<? extends Blaze> entityType, Level level) {
super(entityType, level);
- this.setPathfindingMalus(PathType.WATER, -1.0F);
+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur - Ridables
+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage
this.setPathfindingMalus(PathType.LAVA, 8.0F);
this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F);
this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F);
this.xpReward = 10;
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.blazeRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.blazeRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.blazeControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.blazeMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.blazeMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.blazeScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.blazeAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(4, new Blaze.BlazeAttackGoal(this));
this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0));
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0, 0.0F));
this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0);
+ return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables
}
@Override
@@ -112,11 +164,18 @@ public class Blaze extends Monster {
@Override
public boolean isSensitiveToWater() {
- return true;
+ return this.level().purpurConfig.blazeTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage
}
@Override
protected void customServerAiStep(ServerLevel level) {
+ // Purpur start - Ridables
+ if (getRider() != null && this.isControllable()) {
+ Vec3 mot = getDeltaMovement();
+ setDeltaMovement(mot.x(), getVerticalMot() > 0 ? 0.07D : -0.07D, mot.z());
+ return;
+ }
+ // Purpur end - Ridables
this.nextHeightOffsetChangeTick--;
if (this.nextHeightOffsetChangeTick <= 0) {
this.nextHeightOffsetChangeTick = 100;
diff --git a/net/minecraft/world/entity/monster/Bogged.java b/net/minecraft/world/entity/monster/Bogged.java
index f01670f7106a39957c9b37839fcca0d9f29208f0..2774602455b92745e789d83f17480c141eb89abf 100644
--- a/net/minecraft/world/entity/monster/Bogged.java
+++ b/net/minecraft/world/entity/monster/Bogged.java
@@ -41,6 +41,31 @@ public class Bogged extends AbstractSkeleton implements Shearable {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.boggedRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.boggedRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.boggedControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.boggedMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.boggedScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
diff --git a/net/minecraft/world/entity/monster/CaveSpider.java b/net/minecraft/world/entity/monster/CaveSpider.java
index 2e32567fca7a2a4cd87bc078a6eeb30e3ffabfce..7eca4b751d900c6d6ee34993c3e2368127d19e03 100644
--- a/net/minecraft/world/entity/monster/CaveSpider.java
+++ b/net/minecraft/world/entity/monster/CaveSpider.java
@@ -26,6 +26,45 @@ public class CaveSpider extends Spider {
return Spider.createAttributes().add(Attributes.MAX_HEALTH, 12.0);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.caveSpiderRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.caveSpiderRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.caveSpiderControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.caveSpiderMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.caveSpiderScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.caveSpiderTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.caveSpiderAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public boolean doHurtTarget(ServerLevel level, Entity source) {
if (super.doHurtTarget(level, source)) {
diff --git a/net/minecraft/world/entity/monster/Creeper.java b/net/minecraft/world/entity/monster/Creeper.java
index e832612a7ed3f25d087c4f8f85f5d737ade5d645..a85b64799faa860367cded67a264b436cd46f049 100644
--- a/net/minecraft/world/entity/monster/Creeper.java
+++ b/net/minecraft/world/entity/monster/Creeper.java
@@ -50,21 +50,107 @@ public class Creeper extends Monster {
public int explosionRadius = 3;
private int droppedSkulls;
public Entity entityIgniter; // CraftBukkit
+ private boolean exploding = false; // Purpur - Config to make Creepers explode on death
+ // Purpur start - Ridables
+ private int spacebarCharge = 0;
+ private int prevSpacebarCharge = 0;
+ private int powerToggleDelay = 0;
+ // Purpur end - Ridables
public Creeper(EntityType<? extends Creeper> entityType, Level level) {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.creeperRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creeperRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.creeperControllable;
+ }
+
+ @Override
+ protected void customServerAiStep(ServerLevel world) {
+ if (powerToggleDelay > 0) {
+ powerToggleDelay--;
+ }
+ if (getRider() != null && this.isControllable()) {
+ if (getRider().getForwardMot() != 0 || getRider().getStrafeMot() != 0) {
+ spacebarCharge = 0;
+ setIgnited(false);
+ setSwellDir(-1);
+ }
+ if (spacebarCharge == prevSpacebarCharge) {
+ spacebarCharge = 0;
+ }
+ prevSpacebarCharge = spacebarCharge;
+ }
+ super.customServerAiStep(world);
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setIgnited(false);
+ setSwellDir(-1);
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (powerToggleDelay > 0) {
+ return true; // just toggled power, do not jump or ignite
+ }
+ spacebarCharge++;
+ if (spacebarCharge > maxSwell - 2) {
+ spacebarCharge = 0;
+ if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.powered.creeper")) {
+ powerToggleDelay = 20;
+ setPowered(!isPowered());
+ setIgnited(false);
+ setSwellDir(-1);
+ return true;
+ }
+ }
+ if (!isIgnited()) {
+ if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0 &&
+ getRider().getBukkitEntity().hasPermission("allow.special.creeper")) {
+ setIgnited(true);
+ setSwellDir(1);
+ return true;
+ }
+ }
+ return getForwardMot() == 0 && getStrafeMot() == 0; // do not jump if standing still
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creeperMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.creeperScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
this.goalSelector.addGoal(2, new SwellGoal(this));
+ this.goalSelector.addGoal(3, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0, 1.2));
this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0, 1.2));
this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, false));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(2, new HurtByTargetGoal(this));
}
@@ -161,6 +247,40 @@ public class Creeper extends Monster {
}
}
+ // Purpur start - Special mobs naturally spawn
+ 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
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.creeperTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.creeperAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.CREEPER_HURT;
@@ -243,14 +363,16 @@ public class Creeper extends Monster {
}
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 +383,7 @@ public class Creeper extends Monster {
}
// CraftBukkit end
}
+ this.exploding = false; // Purpur - Config to make Creepers explode on death
}
private void spawnLingeringCloud() {
@@ -288,6 +411,7 @@ public class Creeper extends Monster {
com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited);
if (event.callEvent()) {
this.entityData.set(DATA_IS_IGNITED, event.isIgnited());
+ if (!event.isIgnited()) setSwellDir(-1); // Purpur - Ridables
}
}
}
diff --git a/net/minecraft/world/entity/monster/Drowned.java b/net/minecraft/world/entity/monster/Drowned.java
index c23c4e44ece85fb746a497cbb8a7cd14b2f9768a..6c73245b8d04f194e72165aa0000ca79a95db59d 100644
--- a/net/minecraft/world/entity/monster/Drowned.java
+++ b/net/minecraft/world/entity/monster/Drowned.java
@@ -75,6 +75,67 @@ public class Drowned extends Zombie implements RangedAttackMob {
return Zombie.createAttributes().add(Attributes.STEP_HEIGHT, 1.0);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.drownedRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.drownedRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.drownedControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.drownedMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.drownedScale);
+ }
+
+ @Override
+ protected void randomizeReinforcementsChance() {
+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.drownedSpawnReinforcements);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Configurable jockey options
+ @Override
+ public boolean jockeyOnlyBaby() {
+ return level().purpurConfig.drownedJockeyOnlyBaby;
+ }
+
+ @Override
+ public double jockeyChance() {
+ return level().purpurConfig.drownedJockeyChance;
+ }
+
+ @Override
+ public boolean jockeyTryExistingChickens() {
+ return level().purpurConfig.drownedJockeyTryExistingChickens;
+ }
+ // Purpur end - Configurable jockey options
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.drownedTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.drownedAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void addBehaviourGoals() {
this.goalSelector.addGoal(1, new Drowned.DrownedGoToWaterGoal(this, 1.0));
@@ -82,10 +143,23 @@ public class Drowned extends Zombie implements RangedAttackMob {
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));
@@ -395,7 +469,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
}
}
- static class DrownedMoveControl extends MoveControl {
+ static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
private final Drowned drowned;
public DrownedMoveControl(Drowned mob) {
@@ -404,7 +478,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
LivingEntity target = this.drowned.getTarget();
if (this.drowned.wantsToSwim() && this.drowned.isInWater()) {
if (target != null && target.getY() > this.drowned.getY() || this.drowned.searchingForLand) {
@@ -424,7 +498,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
float f = (float)(Mth.atan2(d2, d) * 180.0F / (float)Math.PI) - 90.0F;
this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), f, 90.0F));
this.drowned.yBodyRot = this.drowned.getYRot();
- float f1 = (float)(this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float f1 = (float)(this.getSpeedModifier() * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables
float f2 = Mth.lerp(0.125F, this.drowned.getSpeed(), f1);
this.drowned.setSpeed(f2);
this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(f2 * d * 0.005, f2 * d1 * 0.1, f2 * d2 * 0.005));
@@ -433,7 +507,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0, -0.008, 0.0));
}
- super.tick();
+ super.vanillaTick(); // Purpur - Ridables
}
}
}
diff --git a/net/minecraft/world/entity/monster/ElderGuardian.java b/net/minecraft/world/entity/monster/ElderGuardian.java
index 4585b7c867685f8419c4d2b5b90af5946d337f90..358021a6eb936ac43e29c7c2c92c71e9cae6ab93 100644
--- a/net/minecraft/world/entity/monster/ElderGuardian.java
+++ b/net/minecraft/world/entity/monster/ElderGuardian.java
@@ -31,6 +31,40 @@ public class ElderGuardian extends Guardian {
}
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.elderGuardianRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.elderGuardianControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.elderGuardianMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.elderGuardianScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.elderGuardianTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.elderGuardianAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static AttributeSupplier.Builder createAttributes() {
return Guardian.createAttributes().add(Attributes.MOVEMENT_SPEED, 0.3F).add(Attributes.ATTACK_DAMAGE, 8.0).add(Attributes.MAX_HEALTH, 80.0);
}
diff --git a/net/minecraft/world/entity/monster/EnderMan.java b/net/minecraft/world/entity/monster/EnderMan.java
index 00c0d2432b4a0e46f85f1f422772e6c43ef5222c..c7897532163d4fdf5a82982f7d24a47dd61e3dfa 100644
--- a/net/minecraft/world/entity/monster/EnderMan.java
+++ b/net/minecraft/world/entity/monster/EnderMan.java
@@ -87,12 +87,45 @@ public class EnderMan extends Monster implements NeutralMob {
public EnderMan(EntityType<? extends EnderMan> entityType, Level level) {
super(entityType, level);
- this.setPathfindingMalus(PathType.WATER, -1.0F);
+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.endermanRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermanRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.endermanControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermanMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.endermanScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.endermanAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new EnderMan.EndermanFreezeWhenLookedAt(this));
this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0, false));
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0, 0.0F));
@@ -100,9 +133,10 @@ public class EnderMan extends Monster implements NeutralMob {
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
this.goalSelector.addGoal(10, new EnderMan.EndermanLeaveBlockGoal(this));
this.goalSelector.addGoal(11, new EnderMan.EndermanTakeBlockGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
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));
}
@@ -230,7 +264,7 @@ public class EnderMan extends Monster implements NeutralMob {
boolean isBeingStaredBy(Player player) {
// Paper start - EndermanAttackPlayerEvent
- final boolean shouldAttack = isBeingStaredBy0(player);
+ final boolean shouldAttack = !this.level().purpurConfig.endermanDisableStareAggro && 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();
@@ -267,12 +301,12 @@ public class EnderMan extends Monster implements NeutralMob {
@Override
public boolean isSensitiveToWater() {
- return true;
+ return this.level().purpurConfig.endermanTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage
}
@Override
protected void customServerAiStep(ServerLevel level) {
- if (level.isDay() && this.tickCount >= this.targetChangeTime + 600) {
+ if ((getRider() == null || !this.isControllable()) && level.isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - Ridables - no random teleporting
float lightLevelDependentMagicValue = this.getLightLevelDependentMagicValue();
if (lightLevelDependentMagicValue > 0.5F
&& level.canSeeSky(this.blockPosition())
@@ -393,6 +427,8 @@ public class EnderMan extends Monster implements NeutralMob {
public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
if (this.isInvulnerableTo(level, damageSource)) {
return false;
+ } else if (getRider() != null && this.isControllable()) { return super.hurtServer(level, damageSource, amount); // Purpur - no teleporting on damage
+ } 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 {
boolean flag = damageSource.getDirectEntity() instanceof ThrownPotion;
if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && !flag) {
@@ -405,6 +441,7 @@ public class EnderMan extends Monster implements NeutralMob {
} else {
boolean flag1 = flag && this.hurtWithCleanWater(level, damageSource, (ThrownPotion)damageSource.getDirectEntity(), amount);
+ if (!flag1 && 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()) {
@@ -448,7 +485,7 @@ public class EnderMan extends Monster implements NeutralMob {
@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 {
@@ -492,8 +529,9 @@ public class EnderMan extends Monster implements NeutralMob {
@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().getBoolean(GameRules.RULE_MOBGRIEFING)
+ && getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing // Purpur - Add mobGriefing bypass to everything affected
&& this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0;
}
@@ -641,8 +679,9 @@ public class EnderMan extends Monster implements NeutralMob {
@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().getBoolean(GameRules.RULE_MOBGRIEFING)
+ && getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing // Purpur - Add mobGriefing bypass to everything affected
&& this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0;
}
diff --git a/net/minecraft/world/entity/monster/Endermite.java b/net/minecraft/world/entity/monster/Endermite.java
index 2a219c9ae39d7cbee8484b2a93bd278d913afe95..441f7d3cfa1e9896a065dae51d871901ac4330e3 100644
--- a/net/minecraft/world/entity/monster/Endermite.java
+++ b/net/minecraft/world/entity/monster/Endermite.java
@@ -28,20 +28,72 @@ import net.minecraft.world.level.block.state.BlockState;
public class Endermite extends Monster {
private static final int MAX_LIFE = 2400;
public int life;
+ private boolean isPlayerSpawned; // Purpur - Add back player spawned endermite API
public Endermite(EntityType<? extends Endermite> entityType, Level level) {
super(entityType, 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
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.endermiteRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermiteRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.endermiteControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermiteMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.endermiteScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.endermiteTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.endermiteAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0, false));
this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 1.0));
this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
@@ -79,12 +131,14 @@ public class Endermite extends Monster {
public void readAdditionalSaveData(CompoundTag compound) {
super.readAdditionalSaveData(compound);
this.life = compound.getInt("Lifetime");
+ this.isPlayerSpawned = compound.getBoolean("PlayerSpawned"); // Purpur - Add back player spawned endermite API
}
@Override
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putInt("Lifetime", this.life);
+ compound.putBoolean("PlayerSpawned", this.isPlayerSpawned); // Purpur - Add back player spawned endermite API
}
@Override
diff --git a/net/minecraft/world/entity/monster/Evoker.java b/net/minecraft/world/entity/monster/Evoker.java
index b70ea1af39cada6bb17001c6b65502510e34c4b2..5c32406d00ec20516649d7a219a5b3c27c8945aa 100644
--- a/net/minecraft/world/entity/monster/Evoker.java
+++ b/net/minecraft/world/entity/monster/Evoker.java
@@ -50,10 +50,50 @@ public class Evoker extends SpellcasterIllager {
this.xpReward = 10;
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.evokerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.evokerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.evokerControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.evokerMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.evokerScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.evokerTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.evokerAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new Evoker.EvokerCastingSpellGoal());
this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 0.6, 1.0));
this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 0.6, 1.0));
@@ -63,6 +103,7 @@ public class Evoker extends SpellcasterIllager {
this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6));
this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true).setUnseenMemoryTicks(300));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false).setUnseenMemoryTicks(300));
@@ -296,7 +337,7 @@ public class Evoker extends SpellcasterIllager {
return false;
} else {
ServerLevel serverLevel = getServerLevel(Evoker.this.level());
- if (!serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (!serverLevel.purpurConfig.evokerBypassMobGriefing == !serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
return false;
} else {
List<Sheep> nearbyEntities = serverLevel.getNearbyEntities(
diff --git a/net/minecraft/world/entity/monster/Ghast.java b/net/minecraft/world/entity/monster/Ghast.java
index b97bbfbbc8c1a4f38b4b858ef4915b637cc8a627..a9d92e857c5d05da6f2a5b6264590efe235061a7 100644
--- a/net/minecraft/world/entity/monster/Ghast.java
+++ b/net/minecraft/world/entity/monster/Ghast.java
@@ -42,11 +42,69 @@ public class Ghast extends FlyingMob implements Enemy {
this.moveControl = new Ghast.GhastMoveControl(this);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.ghastRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ghastRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.ghastControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.ghastMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ghastMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ghastScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.ghastTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.ghastAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(5, new Ghast.RandomFloatAroundGoal(this));
this.goalSelector.addGoal(7, new Ghast.GhastLookGoal(this));
this.goalSelector.addGoal(7, new Ghast.GhastShootFireballGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector
.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entity, level) -> Math.abs(entity.getY() - this.getY()) <= 4.0));
}
@@ -101,7 +159,7 @@ public class Ghast extends FlyingMob implements Enemy {
}
public static AttributeSupplier.Builder createAttributes() {
- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.FOLLOW_RANGE, 100.0);
+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.FOLLOW_RANGE, 100.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables
}
@Override
@@ -191,7 +249,7 @@ public class Ghast extends FlyingMob implements Enemy {
}
}
- static class GhastMoveControl extends MoveControl {
+ static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables
private final Ghast ghast;
private int floatDuration;
@@ -201,7 +259,7 @@ public class Ghast extends FlyingMob implements Enemy {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (this.operation == MoveControl.Operation.MOVE_TO) {
if (this.floatDuration-- <= 0) {
this.floatDuration = this.floatDuration + this.ghast.getRandom().nextInt(5) + 2;
diff --git a/net/minecraft/world/entity/monster/Giant.java b/net/minecraft/world/entity/monster/Giant.java
index 969eb604851d1cce50f0f99ed479189061d5de0c..3f575abee4c8933d1642400d134b0fc915215a1a 100644
--- a/net/minecraft/world/entity/monster/Giant.java
+++ b/net/minecraft/world/entity/monster/Giant.java
@@ -12,12 +12,104 @@ public class Giant extends Monster {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.giantRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.giantRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.giantControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ // Purpur start - Giants AI settings
+ if (level().purpurConfig.giantHaveAI) {
+ this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
+ this.goalSelector.addGoal(7, new net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal(this, 1.0D));
+ this.goalSelector.addGoal(8, new net.minecraft.world.entity.ai.goal.LookAtPlayerGoal(this, net.minecraft.world.entity.player.Player.class, 16.0F));
+ this.goalSelector.addGoal(8, new net.minecraft.world.entity.ai.goal.RandomLookAroundGoal(this));
+ this.goalSelector.addGoal(5, new net.minecraft.world.entity.ai.goal.MoveTowardsRestrictionGoal(this, 1.0D));
+ if (level().purpurConfig.giantHaveHostileAI) {
+ this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.0D, false));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
+ this.targetSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class));
+ this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.player.Player.class, true));
+ this.targetSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.npc.Villager.class, false));
+ this.targetSelector.addGoal(4, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.animal.IronGolem.class, true));
+ this.targetSelector.addGoal(5, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.animal.Turtle.class, true));
+ }
+ }
+ // Purpur end - Giants AI settings
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ protected void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.giantMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.giantScale);
+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.giantMovementSpeed);
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.giantAttackDamage);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.giantTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.giantAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 100.0).add(Attributes.MOVEMENT_SPEED, 0.5).add(Attributes.ATTACK_DAMAGE, 50.0);
}
+ // Purpur - Giants AI settings
+ @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, @org.jetbrains.annotations.Nullable net.minecraft.world.entity.SpawnGroupData entityData) {
+ net.minecraft.world.entity.SpawnGroupData groupData = super.finalizeSpawn(world, difficulty, spawnReason, entityData);
+ if (groupData == null) {
+ populateDefaultEquipmentSlots(this.random, difficulty);
+ populateDefaultEquipmentEnchantments(world, this.random, difficulty);
+ }
+ return groupData;
+ }
+
+ @Override
+ protected void populateDefaultEquipmentSlots(net.minecraft.util.RandomSource random, net.minecraft.world.DifficultyInstance difficulty) {
+ super.populateDefaultEquipmentSlots(this.random, difficulty);
+ // TODO make configurable
+ if (random.nextFloat() < (level().getDifficulty() == net.minecraft.world.Difficulty.HARD ? 0.1F : 0.05F)) {
+ this.setItemSlot(net.minecraft.world.entity.EquipmentSlot.MAINHAND, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.IRON_SWORD));
+ }
+ }
+
+ @Override
+ public float getJumpPower() {
+ // make giants jump as high as everything else relative to their size
+ // 1.0 makes bottom of feet about as high as their waist when they jump
+ return level().purpurConfig.giantJumpHeight;
+ }
+ // Purpur end - Giants AI settings
+
@Override
public float getWalkTargetValue(BlockPos pos, LevelReader level) {
- return level.getPathfindingCostFromLightLevels(pos);
+ return super.getWalkTargetValue(pos, level); // Purpur - Giants AI settings - fix light requirements for natural spawns
}
}
diff --git a/net/minecraft/world/entity/monster/Guardian.java b/net/minecraft/world/entity/monster/Guardian.java
index c8e249b8f7ee8e9c075169ec988f5a0d459a3767..60278b2491f644b70ef96b2e14e1f07bb341a0f1 100644
--- a/net/minecraft/world/entity/monster/Guardian.java
+++ b/net/minecraft/world/entity/monster/Guardian.java
@@ -66,14 +66,57 @@ public class Guardian extends Monster {
this.xpReward = 10;
this.setPathfindingMalus(PathType.WATER, 0.0F);
this.moveControl = new Guardian.GuardianMoveControl(this);
+ // Purpur start - Ridables
+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) {
+ @Override
+ public void setYawPitch(float yaw, float pitch) {
+ super.setYawPitch(yaw, pitch * 0.35F);
+ }
+ };
+ // Purpur end - Ridables
this.clientSideTailAnimation = this.random.nextFloat();
this.clientSideTailAnimationO = this.clientSideTailAnimation;
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.guardianRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.guardianControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.guardianMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.guardianScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.guardianTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.guardianAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
MoveTowardsRestrictionGoal moveTowardsRestrictionGoal = new MoveTowardsRestrictionGoal(this, 1.0);
this.randomStrollGoal = new RandomStrollGoal(this, 1.0, 80);
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(4, this.guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field
this.goalSelector.addGoal(5, moveTowardsRestrictionGoal);
this.goalSelector.addGoal(7, this.randomStrollGoal);
@@ -82,6 +125,7 @@ public class Guardian extends Monster {
this.goalSelector.addGoal(9, new RandomLookAroundGoal(this));
this.randomStrollGoal.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
moveTowardsRestrictionGoal.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, true, false, new Guardian.GuardianAttackSelector(this)));
}
@@ -344,7 +388,7 @@ public class Guardian extends Monster {
@Override
public void travel(Vec3 travelVector) {
if (this.isControlledByLocalInstance() && this.isInWater()) {
- this.moveRelative(0.1F, travelVector);
+ this.moveRelative(getRider() != null && this.isControllable() ? getSpeed() : 0.1F, travelVector); // Purpur - Ridables
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(0.9));
if (!this.isMoving() && this.getTarget() == null) {
@@ -452,7 +496,7 @@ public class Guardian extends Monster {
}
}
- static class GuardianMoveControl extends MoveControl {
+ static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - Ridables
private final Guardian guardian;
public GuardianMoveControl(Guardian mob) {
@@ -460,8 +504,17 @@ public class Guardian extends Monster {
this.guardian = mob;
}
+ // Purpur start - Ridables
@Override
- public void tick() {
+ public void purpurTick(Player rider) {
+ super.purpurTick(rider);
+ guardian.setDeltaMovement(guardian.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
+ guardian.setMoving(guardian.getForwardMot() > 0.0F); // control tail speed
+ }
+ // Purpur end - Ridables
+
+ @Override
+ public void vanillaTick() { // Purpur - Ridables
if (this.operation == MoveControl.Operation.MOVE_TO && !this.guardian.getNavigation().isDone()) {
Vec3 vec3 = new Vec3(this.wantedX - this.guardian.getX(), this.wantedY - this.guardian.getY(), this.wantedZ - this.guardian.getZ());
double len = vec3.length();
@@ -471,7 +524,7 @@ public class Guardian extends Monster {
float f = (float)(Mth.atan2(vec3.z, vec3.x) * 180.0F / (float)Math.PI) - 90.0F;
this.guardian.setYRot(this.rotlerp(this.guardian.getYRot(), f, 90.0F));
this.guardian.yBodyRot = this.guardian.getYRot();
- float f1 = (float)(this.speedModifier * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float f1 = (float)(this.getSpeedModifier() * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables
float f2 = Mth.lerp(0.125F, this.guardian.getSpeed(), f1);
this.guardian.setSpeed(f2);
double d3 = Math.sin((this.guardian.tickCount + this.guardian.getId()) * 0.5) * 0.05;
diff --git a/net/minecraft/world/entity/monster/Husk.java b/net/minecraft/world/entity/monster/Husk.java
index 6155c544ad2722a49c5e41dd7d7b02fedc56474e..a5bfc6f5caba1da8cfcb345524e05e8676672cb0 100644
--- a/net/minecraft/world/entity/monster/Husk.java
+++ b/net/minecraft/world/entity/monster/Husk.java
@@ -19,8 +19,69 @@ import net.minecraft.world.level.ServerLevelAccessor;
public class Husk extends Zombie {
public Husk(EntityType<? extends Husk> entityType, Level level) {
super(entityType, level);
+ this.setShouldBurnInDay(false); // Purpur - API for any mob to burn daylight
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.huskRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.huskRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.huskControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.huskMaxHealth);
+ }
+
+ @Override
+ protected void randomizeReinforcementsChance() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.huskSpawnReinforcements);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Configurable jockey options
+ @Override
+ public boolean jockeyOnlyBaby() {
+ return level().purpurConfig.huskJockeyOnlyBaby;
+ }
+
+ @Override
+ public double jockeyChance() {
+ return level().purpurConfig.huskJockeyChance;
+ }
+
+ @Override
+ public boolean jockeyTryExistingChickens() {
+ return level().purpurConfig.huskJockeyTryExistingChickens;
+ }
+ // Purpur end - Configurable jockey options
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.huskTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.huskAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static boolean checkHuskSpawnRules(
EntityType<Husk> entityType, ServerLevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random
) {
@@ -29,7 +90,7 @@ public class Husk extends Zombie {
@Override
public boolean isSunSensitive() {
- return false;
+ return this.shouldBurnInDay; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight
}
@Override
diff --git a/net/minecraft/world/entity/monster/Illusioner.java b/net/minecraft/world/entity/monster/Illusioner.java
index 40ca12e391b2adac6b132f1832b1427acb3748bc..9686658b90e886d6236f553d7406771814d18672 100644
--- a/net/minecraft/world/entity/monster/Illusioner.java
+++ b/net/minecraft/world/entity/monster/Illusioner.java
@@ -57,10 +57,52 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob {
}
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.illusionerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.illusionerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.illusionerControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ protected void initAttributes() {
+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.illusionerMovementSpeed);
+ this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.illusionerFollowRange);
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.illusionerMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.illusionerScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.illusionerTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.illusionerAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new SpellcasterIllager.SpellcasterCastingSpellGoal());
this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0, 1.2));
this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal());
@@ -69,6 +111,7 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob {
this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6));
this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true).setUnseenMemoryTicks(300));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false).setUnseenMemoryTicks(300));
diff --git a/net/minecraft/world/entity/monster/MagmaCube.java b/net/minecraft/world/entity/monster/MagmaCube.java
index 905ecbd8b22c785ee4ea18004ac50eb1b7005d3f..312d4a3d312b5c326d6ca13ccfc48171e18f4370 100644
--- a/net/minecraft/world/entity/monster/MagmaCube.java
+++ b/net/minecraft/world/entity/monster/MagmaCube.java
@@ -24,6 +24,64 @@ public class MagmaCube extends Slime {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.magmaCubeRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.magmaCubeRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.magmaCubeControllable;
+ }
+
+ @Override
+ public float getJumpPower() {
+ return 0.42F * this.getBlockJumpFactor(); // from EntityLiving
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ protected String getMaxHealthEquation() {
+ return level().purpurConfig.magmaCubeMaxHealth;
+ }
+
+ @Override
+ protected String getAttackDamageEquation() {
+ return level().purpurConfig.magmaCubeAttackDamage;
+ }
+
+ @Override
+ protected java.util.Map<Integer, Double> getMaxHealthCache() {
+ return level().purpurConfig.magmaCubeMaxHealthCache;
+ }
+
+ @Override
+ protected java.util.Map<Integer, Double> getAttackDamageCache() {
+ return level().purpurConfig.magmaCubeAttackDamageCache;
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.magmaCubeTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.magmaCubeAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, 0.2F);
}
@@ -71,6 +129,7 @@ public class MagmaCube extends Slime {
float f = this.getSize() * 0.1F;
this.setDeltaMovement(deltaMovement.x, this.getJumpPower() + f, deltaMovement.z);
this.hasImpulse = true;
+ this.actualJump = false; // Purpur - Ridables
}
@Override
diff --git a/net/minecraft/world/entity/monster/Monster.java b/net/minecraft/world/entity/monster/Monster.java
index d0d3c825cf8088df4794cf5bfde12a69f4d71754..c1ebb74b0d4a8e2eb8880ccaf20f0f9bc1940094 100644
--- a/net/minecraft/world/entity/monster/Monster.java
+++ b/net/minecraft/world/entity/monster/Monster.java
@@ -88,6 +88,14 @@ public abstract class Monster extends PathfinderMob implements Enemy {
}
public static boolean isDarkEnoughToSpawn(ServerLevelAccessor level, BlockPos pos, RandomSource random) {
+ // Purpur start - Config to disable hostile mob spawn on ice
+ if (!level.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce || !level.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce) {
+ net.minecraft.world.level.block.state.BlockState spawnBlock = level.getBlockState(pos.below());
+ if ((!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))) {
+ return false;
+ }
+ }
+ // Purpur end - Config to disable hostile mob spawn on ice
if (level.getBrightness(LightLayer.SKY, pos) > random.nextInt(32)) {
return false;
} else {
diff --git a/net/minecraft/world/entity/monster/Phantom.java b/net/minecraft/world/entity/monster/Phantom.java
index a91aba11ecda561d117c9d8db85c92cdcd81887e..3c105d164acd9e45de2335ef28ddecf3fa48d267 100644
--- a/net/minecraft/world/entity/monster/Phantom.java
+++ b/net/minecraft/world/entity/monster/Phantom.java
@@ -47,19 +47,123 @@ public class Phantom extends FlyingMob implements Enemy {
Vec3 moveTargetPoint = Vec3.ZERO;
public BlockPos anchorPoint = BlockPos.ZERO;
Phantom.AttackPhase attackPhase = Phantom.AttackPhase.CIRCLE;
+ Vec3 crystalPosition; // Purpur - Phantoms attracted to crystals and crystals shoot phantoms
// Paper start
@Nullable
public java.util.UUID spawningEntity;
public boolean shouldBurnInDay = true;
// Paper end
+ private static final net.minecraft.world.item.crafting.Ingredient TORCH = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.item.Items.TORCH, net.minecraft.world.item.Items.SOUL_TORCH); // Purpur - Phantoms burn in light
public Phantom(EntityType<? extends Phantom> entityType, Level level) {
super(entityType, level);
this.xpReward = 5;
this.moveControl = new Phantom.PhantomMoveControl(this);
this.lookControl = new Phantom.PhantomLookControl(this);
+ this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.phantomRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.phantomRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.phantomControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.phantomMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, speed, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+
+ public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() {
+ return Monster.createMonsterAttributes().add(Attributes.FLYING_SPEED, 3.0D);
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.special.phantom")) {
+ shoot();
+ }
+ return false;
+ }
+
+ public boolean shoot() {
+ org.bukkit.Location loc = ((org.bukkit.entity.LivingEntity) getBukkitEntity()).getEyeLocation();
+ loc.setPitch(-loc.getPitch());
+ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(100).add(loc.toVector());
+
+ org.purpurmc.purpur.entity.projectile.PhantomFlames flames = new org.purpurmc.purpur.entity.projectile.PhantomFlames(level(), this);
+ flames.canGrief = level().purpurConfig.phantomAllowGriefing;
+ flames.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), 1.0F, 5.0F);
+ level().addFreshEntity(flames);
+ return true;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms
+ @Override
+ protected void dropFromLootTable(ServerLevel world, DamageSource damageSource, boolean causedByPlayer) {
+ boolean dropped = false;
+ if (lastHurtByPlayer == null && damageSource.getEntity() instanceof net.minecraft.world.entity.boss.enderdragon.EndCrystal) {
+ if (random.nextInt(5) < 1) {
+ dropped = spawnAtLocation(world, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.PHANTOM_MEMBRANE)) != null;
+ }
+ }
+ if (!dropped) {
+ super.dropFromLootTable(world, damageSource, causedByPlayer);
+ }
+ }
+
+ public boolean isCirclingCrystal() {
+ return crystalPosition != null;
+ }
+ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.phantomTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ //private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight
+ // Purpur start - API for any mob to burn daylight
+ public boolean shouldBurnInDay() {
+ boolean burnFromDaylight = this.shouldBurnInDay && this.level().purpurConfig.phantomBurnInDaylight;
+ boolean burnFromLightSource = this.level().purpurConfig.phantomBurnInLight > 0 && this.level().getMaxLocalRawBrightness(blockPosition()) >= this.level().purpurConfig.phantomBurnInLight;
+ return burnFromDaylight || burnFromLightSource;
+ }
+ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
+ // Purpur end - API for any mob to burn daylight
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.phantomAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public boolean isFlapping() {
return (this.getUniqueFlapTickOffset() + this.tickCount) % TICKS_PER_FLAP == 0;
@@ -72,9 +176,17 @@ public class Phantom extends FlyingMob implements Enemy {
@Override
protected void registerGoals() {
- this.goalSelector.addGoal(1, new Phantom.PhantomAttackStrategyGoal());
- this.goalSelector.addGoal(2, new Phantom.PhantomSweepAttackGoal());
- this.goalSelector.addGoal(3, new Phantom.PhantomCircleAroundAnchorGoal());
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms
+ if (level().purpurConfig.phantomOrbitCrystalRadius > 0) {
+ this.goalSelector.addGoal(1, new PhantomFindCrystalGoal(this));
+ this.goalSelector.addGoal(2, new PhantomOrbitCrystalGoal(this));
+ }
+ this.goalSelector.addGoal(3, new Phantom.PhantomAttackStrategyGoal());
+ this.goalSelector.addGoal(4, new Phantom.PhantomSweepAttackGoal());
+ this.goalSelector.addGoal(5, new Phantom.PhantomCircleAroundAnchorGoal());
+ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new Phantom.PhantomAttackPlayerTargetGoal());
}
@@ -90,7 +202,11 @@ public class Phantom extends FlyingMob implements Enemy {
private void updatePhantomSizeInfo() {
this.refreshDimensions();
- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(6 + this.getPhantomSize());
+ if (level().purpurConfig.phantomFlamesOnSwoop && attackPhase == AttackPhase.SWOOP) shoot(); // Purpur - Ridables - Phantom flames on swoop
+ // Purpur start - Configurable entity base attributes
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomMaxHealth, () -> this.level().purpurConfig.phantomMaxHealthCache, () -> 20.0D));
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomAttackDamage, () -> this.level().purpurConfig.phantomAttackDamageCache, () -> (double) (6 + this.getPhantomSize())));
+ // Purpur end - Configurable entity base attributes
}
public int getPhantomSize() {
@@ -115,6 +231,23 @@ public class Phantom extends FlyingMob implements Enemy {
return true;
}
+ // Purpur start - Configurable entity base attributes
+ private double getFromCache(java.util.function.Supplier<String> equation, java.util.function.Supplier<java.util.Map<Integer, Double>> cache, java.util.function.Supplier<Double> defaultValue) {
+ int size = getPhantomSize();
+ Double value = cache.get().get(size);
+ if (value == null) {
+ try {
+ value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ value = defaultValue.get();
+ }
+ cache.get().put(size, value);
+ }
+ return value;
+ }
+ // Purpur end - Configurable entity base attributes
+
@Override
public void tick() {
super.tick();
@@ -146,10 +279,7 @@ public class Phantom extends FlyingMob implements Enemy {
@Override
public void aiStep() {
- if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API
- this.igniteForSeconds(8.0F);
- }
-
+ // Purpur - implemented in LivingEntity; moved down to shouldBurnInDay() - API for any mob to burn daylight
super.aiStep();
}
@@ -158,7 +288,11 @@ public class Phantom extends FlyingMob implements Enemy {
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);
}
@@ -175,7 +309,7 @@ public class Phantom extends FlyingMob implements Enemy {
if (compound.hasUUID("Paper.SpawningEntity")) {
this.spawningEntity = compound.getUUID("Paper.SpawningEntity");
}
- if (compound.contains("Paper.ShouldBurnInDay")) {
+ if (false && compound.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight
this.shouldBurnInDay = compound.getBoolean("Paper.ShouldBurnInDay");
}
// Paper end
@@ -192,7 +326,7 @@ public class Phantom extends FlyingMob implements Enemy {
if (this.spawningEntity != null) {
compound.putUUID("Paper.SpawningEntity", this.spawningEntity);
}
- compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay);
+ //compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - implemented in LivingEntity - API for any mob to burn daylight
// Paper end
}
@@ -262,6 +396,7 @@ public class Phantom extends FlyingMob implements Enemy {
List<Player> nearbyPlayers = serverLevel.getNearbyPlayers(
this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0, 64.0, 16.0)
);
+ if (level().purpurConfig.phantomIgnorePlayersWithTorch) nearbyPlayers.removeIf(human -> TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND))); // Purpur - Phantoms burn in light
if (!nearbyPlayers.isEmpty()) {
nearbyPlayers.sort(Comparator.<Player, Double>comparing(Entity::getY).reversed());
@@ -407,25 +542,160 @@ public class Phantom extends FlyingMob implements Enemy {
}
}
- static class PhantomLookControl extends LookControl {
+ static class PhantomLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
public PhantomLookControl(Mob mob) {
super(mob);
}
+ // Purpur start - Ridables
+ public void purpurTick(Player rider) {
+ setYawPitch(rider.getYRot(), -rider.xRotO * 0.75F);
+ }
+ // Purpur end - Ridables
+
+ @Override
+ public void vanillaTick() { // Purpur - Ridables
+ }
+ }
+
+ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms
+ class PhantomFindCrystalGoal extends Goal {
+ private final Phantom phantom;
+ private net.minecraft.world.entity.boss.enderdragon.EndCrystal crystal;
+ private Comparator<net.minecraft.world.entity.boss.enderdragon.EndCrystal> comparator;
+
+ PhantomFindCrystalGoal(Phantom phantom) {
+ this.phantom = phantom;
+ this.comparator = Comparator.comparingDouble(phantom::distanceToSqr);
+ this.setFlags(EnumSet.of(Flag.LOOK));
+ }
+
+ @Override
+ public boolean canUse() {
+ double range = maxTargetRange();
+ List<net.minecraft.world.entity.boss.enderdragon.EndCrystal> crystals = level().getEntitiesOfClass(net.minecraft.world.entity.boss.enderdragon.EndCrystal.class, phantom.getBoundingBox().inflate(range));
+ if (crystals.isEmpty()) {
+ return false;
+ }
+ crystals.sort(comparator);
+ crystal = crystals.get(0);
+ if (phantom.distanceToSqr(crystal) > range * range) {
+ crystal = null;
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ if (crystal == null || !crystal.isAlive()) {
+ return false;
+ }
+ double range = maxTargetRange();
+ return phantom.distanceToSqr(crystal) <= (range * range) * 2;
+ }
+
+ @Override
+ public void start() {
+ phantom.crystalPosition = new Vec3(crystal.getX(), crystal.getY() + (phantom.random.nextInt(10) + 10), crystal.getZ());
+ }
+
+ @Override
+ public void stop() {
+ crystal = null;
+ phantom.crystalPosition = null;
+ super.stop();
+ }
+
+ private double maxTargetRange() {
+ return phantom.level().purpurConfig.phantomOrbitCrystalRadius;
+ }
+ }
+
+ class PhantomOrbitCrystalGoal extends Goal {
+ private final Phantom phantom;
+ private float offset;
+ private float radius;
+ private float verticalChange;
+ private float direction;
+
+ PhantomOrbitCrystalGoal(Phantom phantom) {
+ this.phantom = phantom;
+ this.setFlags(EnumSet.of(Flag.MOVE));
+ }
+
+ @Override
+ public boolean canUse() {
+ return phantom.isCirclingCrystal();
+ }
+
+ @Override
+ public void start() {
+ this.radius = 5.0F + phantom.random.nextFloat() * 10.0F;
+ this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F;
+ this.direction = phantom.random.nextBoolean() ? 1.0F : -1.0F;
+ updateOffset();
+ }
+
@Override
public void tick() {
+ if (phantom.random.nextInt(350) == 0) {
+ this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F;
+ }
+ if (phantom.random.nextInt(250) == 0) {
+ ++this.radius;
+ if (this.radius > 15.0F) {
+ this.radius = 5.0F;
+ this.direction = -this.direction;
+ }
+ }
+ if (phantom.random.nextInt(450) == 0) {
+ this.offset = phantom.random.nextFloat() * 2.0F * 3.1415927F;
+ updateOffset();
+ }
+ if (phantom.moveTargetPoint.distanceToSqr(phantom.getX(), phantom.getY(), phantom.getZ()) < 4.0D) {
+ updateOffset();
+ }
+ if (phantom.moveTargetPoint.y < phantom.getY() && !phantom.level().isEmptyBlock(new BlockPos(phantom).below(1))) {
+ this.verticalChange = Math.max(1.0F, this.verticalChange);
+ updateOffset();
+ }
+ if (phantom.moveTargetPoint.y > phantom.getY() && !phantom.level().isEmptyBlock(new BlockPos(phantom).above(1))) {
+ this.verticalChange = Math.min(-1.0F, this.verticalChange);
+ updateOffset();
+ }
+ }
+
+ private void updateOffset() {
+ this.offset += this.direction * 15.0F * 0.017453292F;
+ phantom.moveTargetPoint = phantom.crystalPosition.add(
+ this.radius * Mth.cos(this.offset),
+ -4.0F + this.verticalChange,
+ this.radius * Mth.sin(this.offset));
}
}
+ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms
- class PhantomMoveControl extends MoveControl {
+ class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables
private float speed = 0.1F;
public PhantomMoveControl(final Mob mob) {
super(mob);
}
+ // Purpur start - Ridables
+ public void purpurTick(Player rider) {
+ if (!Phantom.this.onGround) {
+ // phantom is always in motion when flying
+ // TODO - FIX THIS
+ // rider.setForward(1.0F);
+ }
+ super.purpurTick(rider);
+ }
+ // Purpur end - Ridables
+
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (Phantom.this.horizontalCollision) {
Phantom.this.setYRot(Phantom.this.getYRot() + 180.0F);
this.speed = 0.1F;
@@ -492,6 +762,12 @@ public class Phantom extends FlyingMob implements Enemy {
return false;
} else if (!target.isAlive()) {
return false;
+ // Purpur start - Phantoms burn in light
+ } else if (level().purpurConfig.phantomBurnInLight > 0 && level().getLightEmission(new BlockPos(Phantom.this)) >= level().purpurConfig.phantomBurnInLight) {
+ return false;
+ } else if (level().purpurConfig.phantomIgnorePlayersWithTorch && (TORCH.test(target.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(target.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)))) {
+ return false;
+ // Purpur end - Phantoms burn in light
} else if (target instanceof Player player && (target.isSpectator() || player.isCreative())) {
return false;
} else if (!this.canUse()) {
diff --git a/net/minecraft/world/entity/monster/Pillager.java b/net/minecraft/world/entity/monster/Pillager.java
index e855ebc5be2cef3b96e2c01a8c1d388e433c0d52..a57d869cdc6a05124237933437aa2d26ff72cab3 100644
--- a/net/minecraft/world/entity/monster/Pillager.java
+++ b/net/minecraft/world/entity/monster/Pillager.java
@@ -63,16 +63,57 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.pillagerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pillagerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.pillagerControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pillagerMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pillagerScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.pillagerTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.pillagerAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0, 1.2));
this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F));
this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0, 8.0F));
this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6));
this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 15.0F, 1.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 15.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false));
diff --git a/net/minecraft/world/entity/monster/Ravager.java b/net/minecraft/world/entity/monster/Ravager.java
index 129479cedda20e77719f4f7237ec5b9acc5b00c8..ce5cd032203839887a29008c2a1420c6bb6f4fee 100644
--- a/net/minecraft/world/entity/monster/Ravager.java
+++ b/net/minecraft/world/entity/monster/Ravager.java
@@ -66,14 +66,62 @@ public class Ravager extends Raider {
this.setPathfindingMalus(PathType.LEAVES, 0.0F);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.ravagerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ravagerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.ravagerControllable;
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ getNavigation().stop();
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ravagerMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ravagerScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.ravagerTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.ravagerAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
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(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
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));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(2, new HurtByTargetGoal(this, Raider.class).setAlertOthers());
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entity, level) -> !entity.isBaby()));
@@ -130,7 +178,7 @@ public class Ravager extends Raider {
@Override
public void aiStep() {
super.aiStep();
- if (this.isAlive()) {
+ if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur - Ridables
if (this.isImmobile()) {
this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0);
} else {
@@ -141,7 +189,7 @@ public class Ravager extends Raider {
if (this.level() instanceof ServerLevel serverLevel
&& this.horizontalCollision
- && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ && serverLevel.purpurConfig.ravagerBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected
boolean flag = false;
AABB aabb = this.getBoundingBox().inflate(0.2);
@@ -150,7 +198,7 @@ public class Ravager extends Raider {
)) {
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;
diff --git a/net/minecraft/world/entity/monster/Shulker.java b/net/minecraft/world/entity/monster/Shulker.java
index 9df62e2ae02c680501cc9284e44c1672315a9cbf..a006300aea2cbb05400550f1c79e872d095384f8 100644
--- a/net/minecraft/world/entity/monster/Shulker.java
+++ b/net/minecraft/world/entity/monster/Shulker.java
@@ -50,6 +50,7 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@@ -88,12 +89,68 @@ public class Shulker extends AbstractGolem implements VariantHolder<Optional<Dye
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
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.shulkerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.shulkerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.shulkerControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.shulkerMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.shulkerScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.shulkerTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.shulkerAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new LookAtPlayerGoal(this, Player.class, 8.0F, 0.02F, true));
this.goalSelector.addGoal(4, new Shulker.ShulkerAttackGoal());
this.goalSelector.addGoal(7, new Shulker.ShulkerPeekGoal());
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, this.getClass()).setAlertOthers());
this.targetSelector.addGoal(2, new Shulker.ShulkerNearestAttackGoal(this));
this.targetSelector.addGoal(3, new Shulker.ShulkerDefenseAttackGoal(this));
@@ -459,11 +516,21 @@ public class Shulker extends AbstractGolem implements VariantHolder<Optional<Dye
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((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.moveTo(vec3);
@@ -563,7 +630,7 @@ public class Shulker extends AbstractGolem implements VariantHolder<Optional<Dye
@Override
protected float sanitizeScale(float scale) {
- return Math.min(scale, 3.0F);
+ return Math.min(scale, MAX_SCALE); // Purpur - Configurable entity base attributes
}
@Override
@@ -573,7 +640,7 @@ public class Shulker extends AbstractGolem implements VariantHolder<Optional<Dye
@Override
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
}
@Nullable
@@ -671,7 +738,7 @@ public class Shulker extends AbstractGolem implements VariantHolder<Optional<Dye
}
}
- class ShulkerLookControl extends LookControl {
+ class ShulkerLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
public ShulkerLookControl(final Mob mob) {
super(mob);
}
diff --git a/net/minecraft/world/entity/monster/Silverfish.java b/net/minecraft/world/entity/monster/Silverfish.java
index b1ea60d5f42d0407b7ca492dff5ca6dc820a4921..465b0578435ff3fb028f860e84ba934fa71f61f2 100644
--- a/net/minecraft/world/entity/monster/Silverfish.java
+++ b/net/minecraft/world/entity/monster/Silverfish.java
@@ -39,14 +39,57 @@ public class Silverfish extends Monster {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.silverfishRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.silverfishRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.silverfishControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.silverfishMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.silverfishScale);
+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.silverfishMovementSpeed);
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.silverfishAttackDamage);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.silverfishTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.silverfishAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.friendsGoal = new Silverfish.SilverfishWakeUpFriendsGoal(this);
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
this.goalSelector.addGoal(3, this.friendsGoal);
this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, false));
this.goalSelector.addGoal(5, new Silverfish.SilverfishMergeWithStoneGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
@@ -141,7 +184,7 @@ public class Silverfish extends Monster {
return false;
} else {
RandomSource random = this.mob.getRandom();
- if (getServerLevel(this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && random.nextInt(reducedTickDelay(10)) == 0) {
+ if (getServerLevel(this.mob).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && random.nextInt(reducedTickDelay(10)) == 0) { // Purpur - Add mobGriefing bypass to everything affected
this.selectedDirection = Direction.getRandom(random);
BlockPos blockPos = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5, this.mob.getZ()).relative(this.selectedDirection);
BlockState blockState = this.mob.level().getBlockState(blockPos);
@@ -218,7 +261,7 @@ public class Silverfish extends Monster {
Block block = blockState.getBlock();
if (block instanceof InfestedBlock) {
// CraftBukkit start
- BlockState afterState = getServerLevel(level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? blockState.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(level.getBlockState(blockPos1)); // Paper - fix wrong block state
+ BlockState afterState = getServerLevel(level).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? blockState.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(level.getBlockState(blockPos1)); // Paper - fix wrong block state // Purpur - Add mobGriefing bypass to everything affected
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockPos1, afterState)) { // Paper - fix wrong block state
continue;
}
diff --git a/net/minecraft/world/entity/monster/Skeleton.java b/net/minecraft/world/entity/monster/Skeleton.java
index 72468b903a9bbebca817d8a1c6796dc05342a29d..7018471a465724e4a1d67fe18bb602176cddd522 100644
--- a/net/minecraft/world/entity/monster/Skeleton.java
+++ b/net/minecraft/world/entity/monster/Skeleton.java
@@ -25,6 +25,44 @@ public class Skeleton extends AbstractSkeleton {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.skeletonRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.skeletonRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.skeletonControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.skeletonMaxHealth);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.skeletonTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.skeletonAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
@@ -135,4 +173,64 @@ public class Skeleton extends AbstractSkeleton {
this.spawnAtLocation(level, Items.SKELETON_SKULL);
}
}
+
+ // 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.moveTo(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) {
+ ((ServerLevel) level()).sendParticlesSource(((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
}
diff --git a/net/minecraft/world/entity/monster/Slime.java b/net/minecraft/world/entity/monster/Slime.java
index 8db4cba1be6d7a5538295ba8da1fdaf7a77a16d0..240a54b210e23d5b79e6bcaf3806aa454668135d 100644
--- a/net/minecraft/world/entity/monster/Slime.java
+++ b/net/minecraft/world/entity/monster/Slime.java
@@ -57,6 +57,7 @@ public class Slime extends Mob implements Enemy {
public float oSquish;
private boolean wasOnGround;
private boolean canWander = true; // Paper - Slime pathfinder events
+ protected boolean actualJump; // Purpur - Ridables
public Slime(EntityType<? extends Slime> entityType, Level level) {
super(entityType, level);
@@ -64,12 +65,95 @@ public class Slime extends Mob implements Enemy {
this.moveControl = new Slime.SlimeMoveControl(this);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.slimeRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.slimeRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.slimeControllable;
+ }
+
+ @Override
+ public float getJumpPower() {
+ float height = super.getJumpPower();
+ return getRider() != null && this.isControllable() && actualJump ? height * 1.5F : height;
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (onGround && getRider() != null && this.isControllable()) {
+ actualJump = true;
+ if (getRider().getForwardMot() == 0 || getRider().getStrafeMot() == 0) {
+ jumpFromGround(); // jump() here if not moving
+ }
+ }
+ return true; // do not jump() in wasd controller, let vanilla controller handle
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ protected String getMaxHealthEquation() {
+ return level().purpurConfig.slimeMaxHealth;
+ }
+
+ protected String getAttackDamageEquation() {
+ return level().purpurConfig.slimeAttackDamage;
+ }
+
+ protected java.util.Map<Integer, Double> getMaxHealthCache() {
+ return level().purpurConfig.slimeMaxHealthCache;
+ }
+
+ protected java.util.Map<Integer, Double> getAttackDamageCache() {
+ return level().purpurConfig.slimeAttackDamageCache;
+ }
+
+ protected double getFromCache(java.util.function.Supplier<String> equation, java.util.function.Supplier<java.util.Map<Integer, Double>> cache, java.util.function.Supplier<Double> defaultValue) {
+ int size = getSize();
+ Double value = cache.get().get(size);
+ if (value == null) {
+ try {
+ value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ value = defaultValue.get();
+ }
+ cache.get().put(size, value);
+ }
+ return value;
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.slimeTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.slimeAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new Slime.SlimeFloatGoal(this));
this.goalSelector.addGoal(2, new Slime.SlimeAttackGoal(this));
this.goalSelector.addGoal(3, new Slime.SlimeRandomDirectionGoal(this));
this.goalSelector.addGoal(5, new Slime.SlimeKeepOnJumpingGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector
.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entity, level) -> Math.abs(entity.getY() - this.getY()) <= 4.0));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
@@ -92,9 +176,9 @@ public class Slime extends Mob implements Enemy {
this.entityData.set(ID_SIZE, i);
this.reapplyPosition();
this.refreshDimensions();
- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(i * i);
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(this::getMaxHealthEquation, this::getMaxHealthCache, () -> (double) (size * size))); // Purpur - Configurable entity base attributes
this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.2F + 0.1F * i);
- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(i);
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(this::getAttackDamageEquation, this::getAttackDamageCache, () -> (double) i)); // Purpur - Configurable entity base attributes
if (resetHealth) {
this.setHealth(this.getMaxHealth());
}
@@ -371,6 +455,7 @@ public class Slime extends Mob implements Enemy {
Vec3 deltaMovement = this.getDeltaMovement();
this.setDeltaMovement(deltaMovement.x, this.getJumpPower(), deltaMovement.z);
this.hasImpulse = true;
+ this.actualJump = false; // Purpur - Ridables
}
@Nullable
@@ -535,7 +620,7 @@ public class Slime extends Mob implements Enemy {
}
}
- static class SlimeMoveControl extends MoveControl {
+ static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
private float yRot;
private int jumpDelay;
private final Slime slime;
@@ -553,21 +638,33 @@ public class Slime extends Mob implements Enemy {
}
public void setWantedMovement(double speed) {
- this.speedModifier = speed;
+ this.setSpeedModifier(speed); // Purpur - Ridables
this.operation = MoveControl.Operation.MOVE_TO;
}
@Override
public void tick() {
+ // Purpur start - Ridables
+ if (slime.getRider() != null && slime.isControllable()) {
+ purpurTick(slime.getRider());
+ if (slime.getForwardMot() != 0 || slime.getStrafeMot() != 0) {
+ if (jumpDelay > 10) {
+ jumpDelay = 6;
+ }
+ } else {
+ jumpDelay = 20;
+ }
+ } else {
+ // Purpur end - Ridables
this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0F));
this.mob.yHeadRot = this.mob.getYRot();
this.mob.yBodyRot = this.mob.getYRot();
- if (this.operation != MoveControl.Operation.MOVE_TO) {
+ } if ((slime.getRider() == null || !slime.isControllable()) && this.operation != MoveControl.Operation.MOVE_TO) { // Purpur - Ridables
this.mob.setZza(0.0F);
} else {
this.operation = MoveControl.Operation.WAIT;
if (this.mob.onGround()) {
- this.mob.setSpeed((float)(this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
+ this.mob.setSpeed((float)(this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - Ridables
if (this.jumpDelay-- <= 0) {
this.jumpDelay = this.slime.getJumpDelay();
if (this.isAggressive) {
@@ -584,7 +681,7 @@ public class Slime extends Mob implements Enemy {
this.mob.setSpeed(0.0F);
}
} else {
- this.mob.setSpeed((float)(this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
+ this.mob.setSpeed((float)(this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - Ridables
}
}
}
diff --git a/net/minecraft/world/entity/monster/Spider.java b/net/minecraft/world/entity/monster/Spider.java
index af0305079a367899708ee2bbac82aefaa9129d2f..ece8d81e29709a1c0cb5c7b55b1193b630ecb113 100644
--- a/net/minecraft/world/entity/monster/Spider.java
+++ b/net/minecraft/world/entity/monster/Spider.java
@@ -50,15 +50,56 @@ public class Spider extends Monster {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.spiderRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.spiderRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.spiderControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.spiderMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.spiderScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.spiderTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.spiderAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Armadillo.class, 6.0F, 1.0, 1.2, livingEntity -> !((Armadillo)livingEntity).isScared()));
this.goalSelector.addGoal(3, new LeapAtTargetGoal(this, 0.4F));
this.goalSelector.addGoal(4, new Spider.SpiderAttackGoal(this));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new Spider.SpiderTargetGoal<>(this, Player.class));
this.targetSelector.addGoal(3, new Spider.SpiderTargetGoal<>(this, IronGolem.class));
diff --git a/net/minecraft/world/entity/monster/Stray.java b/net/minecraft/world/entity/monster/Stray.java
index 5fa2b7920a233afb3659b02cbd7ab82307ea9aaf..fdaa01c8bbaf9e613462e41f508fdc96b3467e03 100644
--- a/net/minecraft/world/entity/monster/Stray.java
+++ b/net/minecraft/world/entity/monster/Stray.java
@@ -22,6 +22,44 @@ public class Stray extends AbstractSkeleton {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.strayRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.strayRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.strayControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.strayMaxHealth);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.strayTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.strayAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static boolean checkStraySpawnRules(
EntityType<Stray> entityType, ServerLevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random
) {
diff --git a/net/minecraft/world/entity/monster/Strider.java b/net/minecraft/world/entity/monster/Strider.java
index cbae85171a1bb64ee3be40ba211d88e68bf672e4..241526239bdbd5d9276f85e7fca46a7051f46a25 100644
--- a/net/minecraft/world/entity/monster/Strider.java
+++ b/net/minecraft/world/entity/monster/Strider.java
@@ -88,12 +88,51 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
public Strider(EntityType<? extends Strider> entityType, Level level) {
super(entityType, level);
this.blocksBuilding = true;
- this.setPathfindingMalus(PathType.WATER, -1.0F);
+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage
this.setPathfindingMalus(PathType.LAVA, 0.0F);
this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F);
this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.striderRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.striderRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.striderControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.striderMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.striderScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.striderBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.striderAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static boolean checkStriderSpawnRules(
EntityType<Strider> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random
) {
@@ -156,6 +195,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new PanicGoal(this, 1.65));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0));
this.temptGoal = new TemptGoal(this, 1.4, itemStack -> itemStack.is(ItemTags.STRIDER_TEMPT_ITEMS), false);
this.goalSelector.addGoal(3, this.temptGoal);
@@ -370,7 +410,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
@Override
public boolean isSensitiveToWater() {
- return true;
+ return this.level().purpurConfig.striderTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage
}
@Override
@@ -414,6 +454,19 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
@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.steering.setSaddle(false);
+ 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);
@@ -424,7 +477,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
InteractionResult interactionResult = super.mobInteract(player, hand);
if (!interactionResult.consumesAction()) {
ItemStack itemInHand = player.getItemInHand(hand);
- return (InteractionResult)(itemInHand.is(Items.SADDLE) ? itemInHand.interactLivingEntity(player, this, hand) : InteractionResult.PASS);
+ return (InteractionResult)(itemInHand.is(Items.SADDLE) ? itemInHand.interactLivingEntity(player, this, hand) : tryRide(player, hand)); // Purpur - Ridables
} else {
if (isFood && !this.isSilent()) {
this.level()
diff --git a/net/minecraft/world/entity/monster/Vex.java b/net/minecraft/world/entity/monster/Vex.java
index af3fef70998cff4e4832adfa2071832324ebd91c..0d9dd9919b2b9902137df861bcac8057e4b741de 100644
--- a/net/minecraft/world/entity/monster/Vex.java
+++ b/net/minecraft/world/entity/monster/Vex.java
@@ -58,6 +58,72 @@ public class Vex extends Monster implements TraceableEntity {
this.xpReward = 3;
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.vexRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vexRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.vexControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.vexMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable()) {
+ float speed;
+ if (onGround) {
+ speed = (float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F;
+ } else {
+ speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
+ }
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+
+ @Override
+ public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) {
+ return false; // no fall damage please
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vexMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vexScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.vexTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.vexAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public boolean isFlapping() {
return this.tickCount % TICKS_PER_FLAP == 0;
@@ -70,7 +136,7 @@ public class Vex extends Monster implements TraceableEntity {
@Override
public void tick() {
- this.noPhysics = true;
+ this.noPhysics = getRider() == null || !this.isControllable(); // Purpur - Ridables
super.tick();
this.noPhysics = false;
this.setNoGravity(true);
@@ -84,17 +150,19 @@ public class Vex extends Monster implements TraceableEntity {
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(4, new Vex.VexChargeAttackGoal());
this.goalSelector.addGoal(8, new Vex.VexRandomMoveGoal());
this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers());
this.targetSelector.addGoal(2, new Vex.VexCopyOwnerTargetGoal(this));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0).add(Attributes.ATTACK_DAMAGE, 4.0);
+ return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0).add(Attributes.ATTACK_DAMAGE, 4.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur;
}
@Override
@@ -301,13 +369,13 @@ public class Vex extends Monster implements TraceableEntity {
}
}
- class VexMoveControl extends MoveControl {
+ class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables
public VexMoveControl(final Vex mob) {
super(mob);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (this.operation == MoveControl.Operation.MOVE_TO) {
Vec3 vec3 = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ());
double len = vec3.length();
@@ -315,7 +383,7 @@ public class Vex extends Monster implements TraceableEntity {
this.operation = MoveControl.Operation.WAIT;
Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5));
} else {
- Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3.scale(this.speedModifier * 0.05 / len)));
+ Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3.scale(this.getSpeedModifier() * 0.05 / len))); // Purpur - Ridables
if (Vex.this.getTarget() == null) {
Vec3 deltaMovement = Vex.this.getDeltaMovement();
Vex.this.setYRot(-((float)Mth.atan2(deltaMovement.x, deltaMovement.z)) * (180.0F / (float)Math.PI));
diff --git a/net/minecraft/world/entity/monster/Vindicator.java b/net/minecraft/world/entity/monster/Vindicator.java
index 5f649d1e69d2be8d8e6963544e3aab6848616893..b584f71440a81ac09d24e59763a21e857f290e5a 100644
--- a/net/minecraft/world/entity/monster/Vindicator.java
+++ b/net/minecraft/world/entity/monster/Vindicator.java
@@ -55,15 +55,56 @@ public class Vindicator extends AbstractIllager {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.vindicatorRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vindicatorRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.vindicatorControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vindicatorMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vindicatorScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.vindicatorTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.vindicatorAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0, 1.2));
this.goalSelector.addGoal(2, new Vindicator.VindicatorBreakDoorGoal(this));
this.goalSelector.addGoal(3, new AbstractIllager.RaiderOpenDoorGoal(this));
this.goalSelector.addGoal(4, new Raider.HoldGroundAttackGoal(this, 10.0F));
this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, false));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true));
@@ -132,6 +173,11 @@ public class Vindicator extends AbstractIllager {
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;
}
diff --git a/net/minecraft/world/entity/monster/Witch.java b/net/minecraft/world/entity/monster/Witch.java
index 9f5676b5fa0f369adb8643391738c5ae33911df7..e4353c64732067198f082cdd266c1f1ee1fe4f4e 100644
--- a/net/minecraft/world/entity/monster/Witch.java
+++ b/net/minecraft/world/entity/monster/Witch.java
@@ -56,6 +56,45 @@ public class Witch extends Raider implements RangedAttackMob {
super(entityType, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.witchRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witchRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.witchControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witchMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witchScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.witchTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.witchAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
super.registerGoals();
@@ -64,10 +103,12 @@ public class Witch extends Raider implements RangedAttackMob {
);
this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, null);
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0, 60, 10.0F));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(3, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class));
this.targetSelector.addGoal(2, this.healRaidersGoal);
this.targetSelector.addGoal(3, this.attackPlayersGoal);
diff --git a/net/minecraft/world/entity/monster/WitherSkeleton.java b/net/minecraft/world/entity/monster/WitherSkeleton.java
index eed8dbefd4d04082dc4e091c858e50309ed5c49b..ff2596f69d00b36c65872ab2e27e5d44a6ffa3e1 100644
--- a/net/minecraft/world/entity/monster/WitherSkeleton.java
+++ b/net/minecraft/world/entity/monster/WitherSkeleton.java
@@ -34,6 +34,45 @@ public class WitherSkeleton extends AbstractSkeleton {
this.setPathfindingMalus(PathType.LAVA, 8.0F);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.witherSkeletonRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherSkeletonRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.witherSkeletonControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherSkeletonMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherSkeletonScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.witherSkeletonTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.witherSkeletonAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractPiglin.class, true));
diff --git a/net/minecraft/world/entity/monster/Zoglin.java b/net/minecraft/world/entity/monster/Zoglin.java
index 8a7418db237553719671f3cd51f42ebed1eb7804..ee59cf42db965296e6e8d4aa4ec7b33dc5142237 100644
--- a/net/minecraft/world/entity/monster/Zoglin.java
+++ b/net/minecraft/world/entity/monster/Zoglin.java
@@ -83,6 +83,45 @@ public class Zoglin extends Monster implements HoglinBase {
this.xpReward = 5;
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.zoglinRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zoglinRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.zoglinControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zoglinMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zoglinScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zoglinTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zoglinAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected Brain.Provider<Zoglin> brainProvider() {
return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
@@ -246,6 +285,7 @@ public class Zoglin extends Monster implements HoglinBase {
@Override
protected void customServerAiStep(ServerLevel level) {
+ if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider
this.getBrain().tick(level, this);
this.updateActivity();
}
diff --git a/net/minecraft/world/entity/monster/Zombie.java b/net/minecraft/world/entity/monster/Zombie.java
index cf231380febd6d316eb902d43c636135ee0d7fa4..7af71c777dca26cd94b1807a2a77ea0d30e92976 100644
--- a/net/minecraft/world/entity/monster/Zombie.java
+++ b/net/minecraft/world/entity/monster/Zombie.java
@@ -89,22 +89,78 @@ public class Zombie extends Monster {
private boolean canBreakDoors;
private int inWaterTime;
public int conversionTime;
- private boolean shouldBurnInDay = true; // Paper - Add more Zombie API
+ //private boolean shouldBurnInDay = true; // Paper - Add more Zombie API // Purpur - implemented in LivingEntity - API for any mob to burn daylight
public Zombie(EntityType<? extends Zombie> entityType, Level level) {
super(entityType, level);
this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(level.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(entityType, level.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty
+ this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight
}
public Zombie(Level level) {
this(EntityType.ZOMBIE, level);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.zombieRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.zombieControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombieScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Configurable jockey options
+ public boolean jockeyOnlyBaby() {
+ return level().purpurConfig.zombieJockeyOnlyBaby;
+ }
+
+ public double jockeyChance() {
+ return level().purpurConfig.zombieJockeyChance;
+ }
+
+ public boolean jockeyTryExistingChickens() {
+ return level().purpurConfig.zombieJockeyTryExistingChickens;
+ }
+ // Purpur end - Configurable jockey options
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zombieTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zombieAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0, 3)); // Paper - Add zombie targets turtle egg config
this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
this.addBehaviourGoals();
}
@@ -114,7 +170,19 @@ public class Zombie extends Monster {
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));
}
@@ -230,29 +298,7 @@ public class Zombie extends Monster {
@Override
public void aiStep() {
- if (this.isAlive()) {
- boolean flag = this.isSunSensitive() && this.isSunBurnTick();
- if (flag) {
- ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD);
- if (!itemBySlot.isEmpty()) {
- if (itemBySlot.isDamageableItem()) {
- Item item = itemBySlot.getItem();
- itemBySlot.setDamageValue(itemBySlot.getDamageValue() + this.random.nextInt(2));
- if (itemBySlot.getDamageValue() >= itemBySlot.getMaxDamage()) {
- this.onEquippedItemBroken(item, EquipmentSlot.HEAD);
- this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY);
- }
- }
-
- flag = false;
- }
-
- if (flag) {
- this.igniteForSeconds(8.0F);
- }
- }
- }
-
+ // Purpur - implemented in LivingEntity - API for any mob to burn daylight
super.aiStep();
}
@@ -311,6 +357,7 @@ public class Zombie extends Monster {
// CraftBukkit end
}
+ public boolean shouldBurnInDay() { return this.isSunSensitive(); } // Purpur - for ABI compatibility - API for any mob to burn daylight
public boolean isSunSensitive() {
return this.shouldBurnInDay; // Paper - Add more Zombie API
}
@@ -449,7 +496,7 @@ public class Zombie extends Monster {
compound.putBoolean("CanBreakDoors", this.canBreakDoors());
compound.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1);
compound.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1);
- compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API
+ //compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API // Purpur - implemented in LivingEntity - API for any mob to burn daylight
}
@Override
@@ -462,7 +509,7 @@ public class Zombie extends Monster {
this.startUnderWaterConversion(compound.getInt("DrownedConversionTime"));
}
// Paper start - Add more Zombie API
- if (compound.contains("Paper.ShouldBurnInDay")) {
+ if (false && compound.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight
this.shouldBurnInDay = compound.getBoolean("Paper.ShouldBurnInDay");
}
// Paper end - Add more Zombie API
@@ -517,19 +564,18 @@ public class Zombie extends Monster {
}
if (spawnGroupData instanceof Zombie.ZombieGroupData zombieGroupData) {
- if (zombieGroupData.isBaby) {
- this.setBaby(true);
+ if (!jockeyOnlyBaby() || zombieGroupData.isBaby) { // Purpur - Configurable jockey options
+ this.setBaby(zombieGroupData.isBaby); // Purpur - Configurable jockey options
if (zombieGroupData.canSpawnJockey) {
- if (random.nextFloat() < 0.05) {
- List<Chicken> entitiesOfClass = level.getEntitiesOfClass(
+ if (random.nextFloat() < jockeyChance()) { // Purpur - Configurable jockey options
+ List<Chicken> entitiesOfClass = jockeyTryExistingChickens() ? level.getEntitiesOfClass( // Purpur - Configurable jockey options
Chicken.class, this.getBoundingBox().inflate(5.0, 3.0, 5.0), EntitySelector.ENTITY_NOT_BEING_RIDDEN
- );
+ ) : java.util.Collections.emptyList(); // Purpur - Configurable jockey options
if (!entitiesOfClass.isEmpty()) {
Chicken chicken = entitiesOfClass.get(0);
chicken.setChickenJockey(true);
this.startRiding(chicken);
- }
- } else if (random.nextFloat() < 0.05) {
+ } else { // Purpur - Configurable jockey options
Chicken chicken1 = EntityType.CHICKEN.create(this.level(), EntitySpawnReason.JOCKEY);
if (chicken1 != null) {
chicken1.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F);
@@ -538,6 +584,7 @@ public class Zombie extends Monster {
this.startRiding(chicken1);
level.addFreshEntity(chicken1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit
}
+ } // Purpur - Configurable jockey options
}
}
}
@@ -550,10 +597,7 @@ public class Zombie extends Monster {
}
if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
- LocalDate localDate = LocalDate.now();
- int i = localDate.get(ChronoField.DAY_OF_MONTH);
- int i1 = localDate.get(ChronoField.MONTH_OF_YEAR);
- if (i1 == 10 && i == 31 && random.nextFloat() < 0.25F) {
+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(level.getMinecraftWorld()) && this.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.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F;
}
@@ -603,7 +647,7 @@ public class Zombie extends Monster {
}
protected void randomizeReinforcementsChance() {
- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.1F);
+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieSpawnReinforcements); // Purpur - Configurable entity base attributes
}
@Override
diff --git a/net/minecraft/world/entity/monster/ZombieVillager.java b/net/minecraft/world/entity/monster/ZombieVillager.java
index 8403257d81367c7371fa94d458a59a4589dc0bd7..aa5c02b5c949c80a96c1dd60fd3de8e2261fe797 100644
--- a/net/minecraft/world/entity/monster/ZombieVillager.java
+++ b/net/minecraft/world/entity/monster/ZombieVillager.java
@@ -78,6 +78,66 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder {
.ifPresent(profession -> this.setVillagerData(this.getVillagerData().setProfession(profession.value())));
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.zombieVillagerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieVillagerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.zombieVillagerControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieVillagerMaxHealth);
+ }
+
+ @Override
+ protected void randomizeReinforcementsChance() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieVillagerSpawnReinforcements);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Configurable jockey options
+ @Override
+ public boolean jockeyOnlyBaby() {
+ return level().purpurConfig.zombieVillagerJockeyOnlyBaby;
+ }
+
+ @Override
+ public double jockeyChance() {
+ return level().purpurConfig.zombieVillagerJockeyChance;
+ }
+
+ @Override
+ public boolean jockeyTryExistingChickens() {
+ return level().purpurConfig.zombieVillagerJockeyTryExistingChickens;
+ }
+ // Purpur end - Configurable jockey options
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zombieVillagerTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zombieVillagerAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
@@ -156,10 +216,10 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder {
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;
diff --git a/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/net/minecraft/world/entity/monster/ZombifiedPiglin.java
index c7eab22fe4a0541ebdba96961521271ee5619cd4..fddbbffafea275dad187b7908386cf4c05c86743 100644
--- a/net/minecraft/world/entity/monster/ZombifiedPiglin.java
+++ b/net/minecraft/world/entity/monster/ZombifiedPiglin.java
@@ -63,6 +63,62 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
this.setPathfindingMalus(PathType.LAVA, 8.0F);
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.zombifiedPiglinRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombifiedPiglinRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.zombifiedPiglinControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombifiedPiglinMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombifiedPiglinScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Configurable jockey options
+ @Override
+ public boolean jockeyOnlyBaby() {
+ return level().purpurConfig.zombifiedPiglinJockeyOnlyBaby;
+ }
+
+ @Override
+ public double jockeyChance() {
+ return level().purpurConfig.zombifiedPiglinJockeyChance;
+ }
+
+ @Override
+ public boolean jockeyTryExistingChickens() {
+ return level().purpurConfig.zombifiedPiglinJockeyTryExistingChickens;
+ }
+ // Purpur end - Configurable jockey options
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zombifiedPiglinTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zombifiedPiglinAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public void setPersistentAngerTarget(@Nullable UUID target) {
this.persistentAngerTarget = target;
@@ -112,7 +168,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
this.maybeAlertOthers();
}
- if (this.isAngry()) {
+ if (this.isAngry() && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur - Toggle for Zombified Piglin death always counting as player kill when angry
this.lastHurtByPlayerTime = this.tickCount;
}
@@ -163,7 +219,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
this.ticksUntilNextAlert = ALERT_INTERVAL.sample(this.random);
}
- if (livingEntity instanceof Player) {
+ if (livingEntity instanceof Player && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur - Toggle for Zombified Piglin death always counting as player kill when angry
this.setLastHurtByPlayer((Player)livingEntity);
}
@@ -245,7 +301,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
@Override
protected void randomizeReinforcementsChance() {
- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(0.0);
+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombifiedPiglinSpawnReinforcements); // Purpur - Configurable entity base attributes
}
@Nullable
diff --git a/net/minecraft/world/entity/monster/creaking/Creaking.java b/net/minecraft/world/entity/monster/creaking/Creaking.java
index 6cd7d0f82bd97c6adb521eda3bc84c60f87c0cda..2c6833753950f1bb0941b0cbe54bebddb84b137d 100644
--- a/net/minecraft/world/entity/monster/creaking/Creaking.java
+++ b/net/minecraft/world/entity/monster/creaking/Creaking.java
@@ -100,6 +100,37 @@ public class Creaking extends Monster {
return this.getHomePos() != null;
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.creakingRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creakingRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.creakingControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creakingMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.creakingScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
@Override
protected BodyRotationControl createBodyControl() {
return new Creaking.CreakingBodyRotationControl(this);
@@ -575,28 +606,28 @@ public class Creaking extends Monster {
}
}
- class CreakingLookControl extends LookControl {
+ class CreakingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables {
public CreakingLookControl(final Creaking mob) {
super(mob);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (Creaking.this.canMove()) {
- super.tick();
+ super.vanillaTick(); // Purpur - Ridables
}
}
}
- class CreakingMoveControl extends MoveControl {
+ class CreakingMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
public CreakingMoveControl(final Creaking mob) {
super(mob);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur - Ridables
if (Creaking.this.canMove()) {
- super.tick();
+ super.vanillaTick(); // Purpur - Ridables
}
}
}
diff --git a/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/net/minecraft/world/entity/monster/hoglin/Hoglin.java
index f93d6564c59ae9a144b56ea3355c4c7425b99eeb..7c119b089259add643cd112efac55f92024d0275 100644
--- a/net/minecraft/world/entity/monster/hoglin/Hoglin.java
+++ b/net/minecraft/world/entity/monster/hoglin/Hoglin.java
@@ -90,6 +90,52 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
this.xpReward = 5;
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.hoglinRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.hoglinRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.hoglinControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.hoglinMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.hoglinScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Make entity breeding times configurable
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.hoglinBreedingTicks;
+ }
+ // Purpur end - Make entity breeding times configurable
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.hoglinTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.hoglinAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@VisibleForTesting
public void setTimeInOverworld(int timeInOverworld) {
this.timeInOverworld = timeInOverworld;
@@ -157,6 +203,7 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
private int behaviorTick; // Pufferfish
@Override
protected void customServerAiStep(ServerLevel level) {
+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(level, this);
HoglinAi.updateActivity(this);
diff --git a/net/minecraft/world/entity/monster/piglin/Piglin.java b/net/minecraft/world/entity/monster/piglin/Piglin.java
index 4c30f967c12e11c2e7ae24977509762747dd36de..242b2545b6082f567d0bb7900ef06ded3c0fdcdd 100644
--- a/net/minecraft/world/entity/monster/piglin/Piglin.java
+++ b/net/minecraft/world/entity/monster/piglin/Piglin.java
@@ -149,6 +149,45 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
this.xpReward = 5;
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.piglinRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.piglinControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.piglinTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.piglinAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
@@ -343,6 +382,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
private int behaviorTick; // Pufferfish
@Override
protected void customServerAiStep(ServerLevel level) {
+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(level, this);
PiglinAi.updateActivity(this);
@@ -444,7 +484,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
@Override
public boolean wantsToPickUp(ServerLevel level, ItemStack stack) {
- return level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack);
+ return level.purpurConfig.piglinBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); // Purpur - Add mobGriefing bypass to everything affected
}
protected boolean canReplaceCurrentItem(ItemStack candidate) {
diff --git a/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/net/minecraft/world/entity/monster/piglin/PiglinAi.java
index d0af8fc156408db7172267d45636b3c9e66d638c..650783aaa4ab1a3198cdfb74fd02edff969d8921 100644
--- a/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+++ b/net/minecraft/world/entity/monster/piglin/PiglinAi.java
@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.mojang.datafixers.util.Pair;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import net.minecraft.server.level.ServerLevel;
@@ -666,7 +667,7 @@ public class PiglinAi {
public static boolean isWearingSafeArmor(LivingEntity entity) {
for (ItemStack itemStack : entity.getArmorAndBodyArmorSlots()) {
- if (itemStack.is(ItemTags.PIGLIN_SAFE_ARMOR)) {
+ if (itemStack.is(ItemTags.PIGLIN_SAFE_ARMOR) || (entity.level().purpurConfig.piglinIgnoresArmorWithGoldTrim && isWearingGoldTrim(itemStack.getItem()))) { // Purpur - piglins ignore gold-trimmed armor
return true;
}
}
@@ -674,6 +675,13 @@ public class PiglinAi {
return false;
}
+ // Purpur start - piglins ignore gold-trimmed armor
+ private static boolean isWearingGoldTrim(Item itemstack) {
+ net.minecraft.world.item.equipment.trim.ArmorTrim armorTrim = itemstack.components().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);
piglin.getNavigation().stop();
diff --git a/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
index e5f91e64f61bdb7b7f7e3f101083e9bd5dbe7551..460dace4818605ecd5409be887013713f06442a5 100644
--- a/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
+++ b/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
@@ -63,6 +63,45 @@ public class PiglinBrute extends AbstractPiglin {
this.xpReward = 20;
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.piglinBruteRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinBruteRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.piglinBruteControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinBruteMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinBruteScale);
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.piglinBruteTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.piglinBruteAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes()
.add(Attributes.MAX_HEALTH, 50.0)
@@ -113,6 +152,7 @@ public class PiglinBrute extends AbstractPiglin {
@Override
protected void customServerAiStep(ServerLevel level) {
+ if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider
this.getBrain().tick(level, this);
PiglinBruteAi.updateActivity(this);
PiglinBruteAi.maybePlayActivitySound(this);
diff --git a/net/minecraft/world/entity/monster/warden/Warden.java b/net/minecraft/world/entity/monster/warden/Warden.java
index f7b9824519fc22b35a7b5f4f0ef9f9891162a493..26f3fe1c80b0d87b96076432f35fe4f95f92ce13 100644
--- a/net/minecraft/world/entity/monster/warden/Warden.java
+++ b/net/minecraft/world/entity/monster/warden/Warden.java
@@ -127,8 +127,32 @@ public class Warden extends Monster implements VibrationSystem {
this.setPathfindingMalus(PathType.LAVA, 8.0F);
this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F);
this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F);
+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur - Ridables
}
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.wardenRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wardenRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.wardenControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
+ }
+ // Purpur end - Ridables
+
@Override
public Packet<ClientGamePacketListener> getAddEntityPacket(ServerEntity entity) {
return new ClientboundAddEntityPacket(this, entity, this.hasPose(Pose.EMERGING) ? 1 : 0);
@@ -391,6 +415,7 @@ public class Warden extends Monster implements VibrationSystem {
@Contract("null->false")
public boolean canTargetEntity(@Nullable Entity entity) {
+ if (getRider() != null && isControllable()) return false; // Purpur - Ridables
return entity instanceof LivingEntity livingEntity
&& this.level() == entity.level()
&& EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity)
diff --git a/net/minecraft/world/entity/npc/AbstractVillager.java b/net/minecraft/world/entity/npc/AbstractVillager.java
index a71d16d968bb90fd7aca6f01a3dd56df4f9a7ce6..b4e79cac5611942240ce85120f7bbee329ae2fb8 100644
--- a/net/minecraft/world/entity/npc/AbstractVillager.java
+++ b/net/minecraft/world/entity/npc/AbstractVillager.java
@@ -45,6 +45,8 @@ import org.bukkit.event.entity.VillagerAcquireTradeEvent;
// CraftBukkit end
public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant {
+ static final net.minecraft.world.item.crafting.Ingredient TEMPT_ITEMS = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.level.block.Blocks.EMERALD_BLOCK.asItem()); // Purpur - Villagers follow emerald blocks
+
// CraftBukkit start
@Override
public CraftMerchant getCraftMerchant() {
diff --git a/net/minecraft/world/entity/npc/CatSpawner.java b/net/minecraft/world/entity/npc/CatSpawner.java
index e6d368bc601357cfca694ce328c8e6e47491f3b5..489613b31fe47a0bd43fafddbeab6bb61c17505d 100644
--- a/net/minecraft/world/entity/npc/CatSpawner.java
+++ b/net/minecraft/world/entity/npc/CatSpawner.java
@@ -27,7 +27,7 @@ public class CatSpawner implements CustomSpawner {
if (this.nextTick > 0) {
return 0;
} else {
- this.nextTick = 1200;
+ this.nextTick = level.purpurConfig.catSpawnDelay; // Purpur - Cat spawning options
Player randomPlayer = level.getRandomPlayer();
if (randomPlayer == null) {
return 0;
@@ -61,8 +61,12 @@ public class CatSpawner implements CustomSpawner {
private int spawnInVillage(ServerLevel serverLevel, BlockPos pos) {
int i = 48;
- if (serverLevel.getPoiManager().getCountInRange(holder -> holder.is(PoiTypes.HOME), pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) {
- List<Cat> entitiesOfClass = serverLevel.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(48.0, 8.0, 48.0));
+ // Purpur start - Cat spawning options
+ int range = serverLevel.purpurConfig.catSpawnVillageScanRange;
+ if (range <= 0) return 0;
+ if (serverLevel.getPoiManager().getCountInRange(holder -> holder.is(PoiTypes.HOME), pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) {
+ List<Cat> entitiesOfClass = serverLevel.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range));
+ // Purpur end - Cat spawning options
if (entitiesOfClass.size() < 5) {
return this.spawnCat(pos, serverLevel);
}
@@ -73,7 +77,11 @@ public class CatSpawner implements CustomSpawner {
private int spawnInHut(ServerLevel serverLevel, BlockPos pos) {
int i = 16;
- List<Cat> entitiesOfClass = serverLevel.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(16.0, 8.0, 16.0));
+ // Purpur start - Cat spawning options
+ int range = serverLevel.purpurConfig.catSpawnSwampHutScanRange;
+ if (range <= 0) return 0;
+ List<Cat> entitiesOfClass = serverLevel.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range));
+ // Purpur end - Cat spawning options
return entitiesOfClass.size() < 1 ? this.spawnCat(pos, serverLevel) : 0;
}
diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java
index a02cd34bcd643c7abad3a355043cb88d035143a0..e8e2848c12e3cf2ad86fc3bd18f03182ee291775 100644
--- a/net/minecraft/world/entity/npc/Villager.java
+++ b/net/minecraft/world/entity/npc/Villager.java
@@ -179,6 +179,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
);
public long nextGolemPanic = -1; // Pufferfish
+ 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> entityType, Level level) {
this(entityType, level, VillagerType.PLAINS);
@@ -193,6 +195,103 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
this.setVillagerData(this.getVillagerData().setType(villagerType).setProfession(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
+
+ // Purpur start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.villagerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.villagerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.villagerControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
+ if (level().purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); // Purpur - Villagers follow emerald blocks
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.villagerMaxHealth);
+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.villagerScale);
+ this.getAttribute(Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.villagerTemptRange); // Purpur - Villagers follow emerald blocks
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.villagerTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.villagerAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
public Brain<Villager> getBrain() {
return (Brain<Villager>)super.getBrain();
@@ -226,7 +325,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
villagerBrain.setSchedule(Schedule.VILLAGER_DEFAULT);
villagerBrain.addActivityWithConditions(
Activity.WORK,
- VillagerGoalPackages.getWorkPackage(profession, 0.5F),
+ VillagerGoalPackages.getWorkPackage(profession, 0.5F, this.level().purpurConfig.villagerClericsFarmWarts), // Purpur - Option for Villager Clerics to farm Nether Wart
ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))
);
}
@@ -258,7 +357,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
public static AttributeSupplier.Builder createAttributes() {
- return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5);
+ return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5).add(Attributes.TEMPT_RANGE, 10.0D); // Purpur - Villagers follow emerald blocks
}
public boolean assignProfessionWhenSpawned() {
@@ -290,12 +389,21 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
// 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
+ // Purpur start - Lobotomize stuck villagers
+ if (this.level().purpurConfig.villagerLobotomizeEnabled) {
+ // treat as inactive if lobotomized
+ inactive = inactive || checkLobotomized();
+ } else {
+ this.isLobotomized = false;
+ }
+ // Purpur end - Lobotomize stuck villagers
// Pufferfish start
- if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) {
+ if (!inactive && (getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) { // Purpur - Ridables
this.getBrain().tick(level, this); // Paper - EAR 2
}
+ else if (this.isLobotomized && shouldRestock()) restock(); // Purpur - Lobotomize stuck villagers
// Pufferfish end
if (this.assignProfessionWhenSpawned) {
this.assignProfessionWhenSpawned = false;
@@ -351,7 +459,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
return super.mobInteract(player, hand);
} else if (this.isBaby()) {
this.setUnhappy();
- return InteractionResult.SUCCESS;
+ return tryRide(player, hand, InteractionResult.SUCCESS); // Purpur - Ridables
} else {
if (!this.level().isClientSide) {
boolean isEmpty = this.getOffers().isEmpty();
@@ -364,9 +472,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
if (isEmpty) {
- return InteractionResult.CONSUME;
+ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur - Ridables
}
+ if (level().purpurConfig.villagerRidable && itemInHand.isEmpty()) return tryRide(player, hand); // Purpur - Ridables
+
+ if (this.level().purpurConfig.villagerAllowTrading) // Purpur - Add config for villager trading
this.startTrading(player);
}
@@ -505,7 +616,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
private void updateDemand() {
for (MerchantOffer merchantOffer : this.getOffers()) {
- merchantOffer.updateDemand();
+ merchantOffer.updateDemand(this.level().purpurConfig.villagerMinimumDemand); // Purpur - Configurable minimum demand for trades
}
}
@@ -709,7 +820,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
@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() {
@@ -878,7 +989,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
public boolean hasFarmSeeds() {
- return this.getInventory().hasAnyMatching(itemStack -> itemStack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS));
+ return this.getInventory().hasAnyMatching(itemStack -> this.level().purpurConfig.villagerClericsFarmWarts && this.getVillagerData().getProfession() == VillagerProfession.CLERIC ? itemStack.is(Items.NETHER_WART) : itemStack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS)); // Purpur - Option for Villager Clerics to farm Nether Wart
}
@Override
@@ -930,6 +1041,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
public void spawnGolemIfNeeded(ServerLevel serverLevel, long gameTime, int minVillagerAmount) {
+ if (serverLevel.purpurConfig.villagerSpawnIronGolemRadius > 0 && serverLevel.getEntitiesOfClass(net.minecraft.world.entity.animal.IronGolem.class, getBoundingBox().inflate(serverLevel.purpurConfig.villagerSpawnIronGolemRadius)).size() > serverLevel.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 = serverLevel.getEntitiesOfClass(Villager.class, aabb);
@@ -1003,6 +1115,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
@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);
diff --git a/net/minecraft/world/entity/npc/VillagerProfession.java b/net/minecraft/world/entity/npc/VillagerProfession.java
index 1ec8ad124c76483d11057eb97fcfb9aebee0c301..f783d058a080408d572938d36ba031f33a08e9af 100644
--- a/net/minecraft/world/entity/npc/VillagerProfession.java
+++ b/net/minecraft/world/entity/npc/VillagerProfession.java
@@ -31,7 +31,7 @@ public record VillagerProfession(
public static final VillagerProfession ARMORER = register("armorer", PoiTypes.ARMORER, SoundEvents.VILLAGER_WORK_ARMORER);
public static final VillagerProfession BUTCHER = register("butcher", PoiTypes.BUTCHER, SoundEvents.VILLAGER_WORK_BUTCHER);
public static final VillagerProfession CARTOGRAPHER = register("cartographer", PoiTypes.CARTOGRAPHER, SoundEvents.VILLAGER_WORK_CARTOGRAPHER);
- public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, SoundEvents.VILLAGER_WORK_CLERIC);
+ public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, ImmutableSet.of(Items.NETHER_WART), ImmutableSet.of(Blocks.SOUL_SAND), SoundEvents.VILLAGER_WORK_CLERIC); // Purpur - Option for Villager Clerics to farm Nether Wart
public static final VillagerProfession FARMER = register(
"farmer",
PoiTypes.FARMER,
diff --git a/net/minecraft/world/entity/npc/WanderingTrader.java b/net/minecraft/world/entity/npc/WanderingTrader.java
index 6655d06e2011e20e7346dfe57527795269094d8a..c3fbcc7956a64d49466874776f257ba27f55f2a4 100644
--- a/net/minecraft/world/entity/npc/WanderingTrader.java
+++ b/net/minecraft/world/entity/npc/WanderingTrader.java
@@ -69,6 +69,58 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
super(entityType, level);
}
+ // Purpur start - Allow leashing villagers
+ @Override
+ public boolean canBeLeashed() {
+ return level().purpurConfig.wanderingTraderCanBeLeashed;
+ }
+ // Purpur end - Allow leashing villagers
+
+ // Purpur - start - Ridables
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.wanderingTraderRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wanderingTraderRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.wanderingTraderControllable;
+ }
+ // Purpur end - Ridables
+
+ // Purpur start - Configurable entity base attributes
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wanderingTraderMaxHealth);
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.wanderingTraderTemptRange); // Purpur - Villagers follow emerald blocks
+ }
+ // Purpur end - Configurable entity base attributes
+
+ // Purpur start - Villagers follow emerald blocks
+ public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() {
+ return Mob.createMobAttributes().add(net.minecraft.world.entity.ai.attributes.Attributes.TEMPT_RANGE, 10.0D);
+ }
+ // Purpur end - Villagers follow emerald blocks
+
+ // Purpur start - Toggle for water sensitive mob damage
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.wanderingTraderTakeDamageFromWater;
+ }
+ // Purpur end - Toggle for water sensitive mob damage
+
+ // Purpur start - Mobs always drop experience
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.wanderingTraderAlwaysDropExp;
+ }
+ // Purpur end - Mobs always drop experience
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
@@ -89,7 +141,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
this,
new ItemStack(Items.MILK_BUCKET),
SoundEvents.WANDERING_TRADER_REAPPEARED,
- wanderingTrader -> this.canDrinkMilk && this.level().isDay() && wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API
+ wanderingTrader -> level().purpurConfig.milkClearsBeneficialEffects && this.canDrinkMilk && this.level().isDay() && wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API // // Purpur - Milk Keeps Beneficial Effects
)
);
this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this));
@@ -103,6 +155,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
this.goalSelector.addGoal(1, new PanicGoal(this, 0.5));
this.goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this));
this.goalSelector.addGoal(2, new WanderingTrader.WanderToPositionGoal(this, 2.0, 0.35));
+ if (level().purpurConfig.wanderingTraderFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); // Purpur - Villagers follow emerald blocks
this.goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.35));
this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 0.35));
this.goalSelector.addGoal(9, new InteractGoal(this, Player.class, 3.0F, 1.0F));
@@ -130,11 +183,14 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
if (!this.level().isClientSide) {
if (this.getOffers().isEmpty()) {
- return InteractionResult.CONSUME;
+ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur - Ridables
}
+ if (level().purpurConfig.wanderingTraderRidable && itemInHand.isEmpty()) return tryRide(player, hand); // Purpur - Ridables
+ 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;
diff --git a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
index ef2afb17a22a703470e13d12c989a685e72f0ab8..80a01b8c6dfb0f3bcc6872cdf38b48f7a9ce4b8b 100644
--- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
+++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
@@ -147,7 +147,17 @@ public class WanderingTraderSpawner implements CustomSpawner {
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;
diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java
index e587f8125ab29b3f9c829cd225deb5b7a6b7affa..477455fdfcc591a89823e88983eb12dabb078d9b 100644
--- a/net/minecraft/world/entity/player/Player.java
+++ b/net/minecraft/world/entity/player/Player.java
@@ -201,11 +201,22 @@ public abstract class Player extends LivingEntity {
private int currentImpulseContextResetGraceTime;
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
+ public int sixRowEnderchestSlotCount = -1; // Purpur - Barrels and enderchests 6 rows
// 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();
@@ -214,6 +225,19 @@ public abstract class Player extends LivingEntity {
public final int sendAllPlayerInfoBucketIndex; // Gale - Purpur - spread out sending all player info
+ // Purpur start - Ridables
+ public abstract void resetLastActionTime();
+
+ @Override
+ public boolean processClick(InteractionHand hand) {
+ Entity vehicle = getRootVehicle();
+ if (vehicle != null && vehicle.getRider() == this) {
+ return vehicle.onClick(hand);
+ }
+ return false;
+ }
+ // Purpur end - Ridables
+
public Player(Level level, BlockPos pos, float yRot, GameProfile gameProfile) {
super(EntityType.PLAYER, level);
this.setUUID(gameProfile.getId());
@@ -266,6 +290,12 @@ public abstract class Player extends LivingEntity {
@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);
@@ -348,6 +378,17 @@ public abstract class Player extends LivingEntity {
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) {
@@ -618,7 +659,7 @@ public abstract class Player extends LivingEntity {
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);
@@ -1277,7 +1318,7 @@ public abstract class Player extends LivingEntity {
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;
@@ -1892,7 +1933,23 @@ public abstract class Player extends LivingEntity {
@Override
protected int getBaseExperienceReward(ServerLevel level) {
- return !level.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator() ? Math.min(this.experienceLevel * 7, 100) : 0;
+ // Purpur start - Add player death exp control options
+ if (!level.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !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
@@ -1976,6 +2033,13 @@ public abstract class Player extends LivingEntity {
return slot != EquipmentSlot.BODY;
}
+ // Purpur start - Player ridable in water option
+ @Override
+ public boolean dismountsUnderwater() {
+ return !level().purpurConfig.playerRidableInWater;
+ }
+ // Purpur end - Player ridable in water option
+
public boolean setEntityOnShoulder(CompoundTag entityCompound) {
if (this.isPassenger() || !this.onGround() || this.isInWater() || this.isInPowderSnow) {
return false;
diff --git a/net/minecraft/world/entity/projectile/AbstractArrow.java b/net/minecraft/world/entity/projectile/AbstractArrow.java
index aa301c849b020fac951f8afa7cd27401def16d3a..ecf25b60096f6a77a7bd6b6212d322adb3fe5e8a 100644
--- a/net/minecraft/world/entity/projectile/AbstractArrow.java
+++ b/net/minecraft/world/entity/projectile/AbstractArrow.java
@@ -74,6 +74,7 @@ public abstract class AbstractArrow extends Projectile {
public ItemStack pickupItemStack = this.getDefaultPickupItem(); // Paper - private -> public
@Nullable
public ItemStack firedFromWeapon = null; // Paper - private -> public
+ 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> entityType, Level level) {
super(entityType, level);
@@ -560,6 +561,12 @@ public abstract class AbstractArrow extends Projectile {
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;
}
diff --git a/net/minecraft/world/entity/projectile/LargeFireball.java b/net/minecraft/world/entity/projectile/LargeFireball.java
index 4a752ace041228f095af7b1b4878a03c5ed2381f..3e8b5d042eddb817dee2504ff9aa263f6195b1c7 100644
--- a/net/minecraft/world/entity/projectile/LargeFireball.java
+++ b/net/minecraft/world/entity/projectile/LargeFireball.java
@@ -18,20 +18,20 @@ public class LargeFireball extends Fireball {
public LargeFireball(EntityType<? extends LargeFireball> entityType, Level level) {
super(entityType, level);
- this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+ this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.purpurConfig.fireballsBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur - Add mobGriefing bypass to everything affected
}
public LargeFireball(Level level, LivingEntity owner, Vec3 movement, int explosionPower) {
super(EntityType.FIREBALL, owner, movement, level);
this.explosionPower = explosionPower;
- this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+ this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.purpurConfig.fireballsBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur - Add mobGriefing bypass to everything affected
}
@Override
protected void onHit(HitResult result) {
super.onHit(result);
if (this.level() instanceof ServerLevel serverLevel) {
- boolean _boolean = serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+ boolean _boolean = serverLevel.purpurConfig.fireballsBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected
// CraftBukkit start - fire ExplosionPrimeEvent
org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity());
this.level().getCraftServer().getPluginManager().callEvent(event);
diff --git a/net/minecraft/world/entity/projectile/LlamaSpit.java b/net/minecraft/world/entity/projectile/LlamaSpit.java
index 4880db97135d54fa72f64c108b2bd4ded096438b..bc102b049047d6e2a1d29e10f92cdf5ae2c140bd 100644
--- a/net/minecraft/world/entity/projectile/LlamaSpit.java
+++ b/net/minecraft/world/entity/projectile/LlamaSpit.java
@@ -33,6 +33,12 @@ public class LlamaSpit extends Projectile {
);
}
+ // Purpur start - Ridables
+ public void projectileTick() {
+ super.tick();
+ }
+ // Purpur end - Ridables
+
@Override
protected double getDefaultGravity() {
return 0.06;
diff --git a/net/minecraft/world/entity/projectile/Projectile.java b/net/minecraft/world/entity/projectile/Projectile.java
index 168f50523b00ab8d43cbcc5ea5675a840762be11..a33641dd6e0839fd1b557d8583fe8bb929fcc1cb 100644
--- a/net/minecraft/world/entity/projectile/Projectile.java
+++ b/net/minecraft/world/entity/projectile/Projectile.java
@@ -503,7 +503,7 @@ public abstract class Projectile extends Entity implements TraceableEntity {
@Override
public boolean mayInteract(ServerLevel level, BlockPos pos) {
Entity owner = this.getOwner();
- return owner instanceof Player ? owner.mayInteract(level, pos) : owner == null || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+ return owner instanceof Player ? owner.mayInteract(level, pos) : owner == null || level.purpurConfig.projectilesBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected
}
public boolean mayBreak(ServerLevel level) {
diff --git a/net/minecraft/world/entity/projectile/SmallFireball.java b/net/minecraft/world/entity/projectile/SmallFireball.java
index 8c84cea43fc0e42a576004663670977eac99f1a6..808aa5dcb27c87b6ba5c1eee639486067447e370 100644
--- a/net/minecraft/world/entity/projectile/SmallFireball.java
+++ b/net/minecraft/world/entity/projectile/SmallFireball.java
@@ -25,7 +25,7 @@ public class SmallFireball extends Fireball {
super(EntityType.SMALL_FIREBALL, owner, movement, level);
// CraftBukkit start
if (this.getOwner() != null && this.getOwner() instanceof Mob) {
- this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+ this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.purpurConfig.fireballsBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected
}
// CraftBukkit end
}
diff --git a/net/minecraft/world/entity/projectile/Snowball.java b/net/minecraft/world/entity/projectile/Snowball.java
index c57bbdc13221d2ce349f3f1d894193f80ff1e24b..1d399532c67c213c95c06837b0c7855384f1a25c 100644
--- a/net/minecraft/world/entity/projectile/Snowball.java
+++ b/net/minecraft/world/entity/projectile/Snowball.java
@@ -52,10 +52,40 @@ public class Snowball extends ThrowableItemProjectile {
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) {
super.onHit(result);
diff --git a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
index 1345097a2a417f95c44143fd7e0d4cec38990121..d212f57c8c0b2086f567fd30237b110203d9e8cb 100644
--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
+++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
@@ -133,9 +133,10 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
return;
}
// CraftBukkit end
- if (this.random.nextFloat() < 0.05F && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
+ if (this.random.nextFloat() < serverLevel.purpurConfig.enderPearlEndermiteChance && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // 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.moveTo(owner.getX(), owner.getY(), owner.getZ(), owner.getYRot(), owner.getXRot());
serverLevel.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL);
}
@@ -155,7 +156,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
if (serverPlayer1 != null) {
serverPlayer1.resetFallDistance();
serverPlayer1.resetCurrentImpulseContext();
- serverPlayer1.hurtServer(serverPlayer.serverLevel(), this.damageSources().enderPearl().eventEntityDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API
+ serverPlayer1.hurtServer(serverPlayer.serverLevel(), this.damageSources().enderPearl().eventEntityDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - Configurable Ender Pearl damage
}
this.playSound(serverLevel, vec3);
diff --git a/net/minecraft/world/entity/projectile/ThrownTrident.java b/net/minecraft/world/entity/projectile/ThrownTrident.java
index 2c66f788759330fba412ccb2187945b6ca0cacb6..251e9a368800afab1a47bb9162077b2e76f4397f 100644
--- a/net/minecraft/world/entity/projectile/ThrownTrident.java
+++ b/net/minecraft/world/entity/projectile/ThrownTrident.java
@@ -64,7 +64,7 @@ public class ThrownTrident extends AbstractArrow {
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);
diff --git a/net/minecraft/world/entity/projectile/WitherSkull.java b/net/minecraft/world/entity/projectile/WitherSkull.java
index c4cdc7655a8e4931717e9fa788c5811879526e9e..a0b909c745ea60cae73def06f9d947345911c5e4 100644
--- a/net/minecraft/world/entity/projectile/WitherSkull.java
+++ b/net/minecraft/world/entity/projectile/WitherSkull.java
@@ -92,7 +92,7 @@ public class WitherSkull extends AbstractHurtingProjectile {
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
this.level().getCraftServer().getPluginManager().callEvent(event);
if (!event.isCancelled()) {
@@ -103,6 +103,21 @@ public class WitherSkull extends AbstractHurtingProjectile {
}
}
+ // Purpur start - Add canSaveToDisk to Entity
+ @Override
+ public boolean canSaveToDisk() {
+ return false;
+ }
+ // Purpur end - Add canSaveToDisk to Entity
+
+ // Purpur start - Ridables
+ @Override
+ public boolean canHitEntity(Entity target) {
+ // do not hit rider
+ return target != this.getRider() && super.canHitEntity(target);
+ }
+ // Purpur end - Ridables
+
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
builder.define(DATA_DANGEROUS, false);
diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java
index 8270d76a753bfd26a4c8ef6610bee5c24ee59cfe..c06b589e669b055a26f662df60070d5908256220 100644
--- a/net/minecraft/world/entity/raid/Raider.java
+++ b/net/minecraft/world/entity/raid/Raider.java
@@ -399,7 +399,7 @@ public abstract class Raider extends PatrollingMonster {
}
private boolean cannotPickUpBanner() {
- if (!getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items
+ if (!this.mob.level().purpurConfig.pillagerBypassMobGriefing == !getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur - Add mobGriefing bypass to everything affected
if (!this.mob.hasActiveRaid()) {
return true;
} else if (this.mob.getCurrentRaid().isOver()) {
diff --git a/net/minecraft/world/entity/raid/Raids.java b/net/minecraft/world/entity/raid/Raids.java
index 34eb038725d1577f1a2d7c35c897b1270eac5749..497915338d406815d6b54c661fbbf4024bdca7ea 100644
--- a/net/minecraft/world/entity/raid/Raids.java
+++ b/net/minecraft/world/entity/raid/Raids.java
@@ -25,6 +25,7 @@ import net.minecraft.world.phys.Vec3;
public class Raids extends SavedData {
private static final String RAID_FILE_ID = "raids";
+ public final Map<java.util.UUID, Integer> playerCooldowns = Maps.newHashMap(); // Purpur - Raid cooldown setting
public final Map<Integer, Raid> raidMap = Maps.newHashMap();
private final ServerLevel level;
private int nextAvailableID;
@@ -46,6 +47,17 @@ public class Raids extends SavedData {
public void tick() {
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()) {
@@ -119,11 +131,13 @@ public class Raids extends SavedData {
*/
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
+ if (level.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(player.getUUID())) return null; // Purpur - Raid cooldown setting
// CraftBukkit start
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, player)) {
player.removeEffect(net.minecraft.world.effect.MobEffects.RAID_OMEN);
return null;
}
+ if (level.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), level.purpurConfig.raidCooldownSeconds); // Purpur - Raid cooldown setting
if (!raid.isStarted() && !this.raidMap.containsKey(raid.getId())) {
this.raidMap.put(raid.getId(), raid);
diff --git a/net/minecraft/world/entity/vehicle/AbstractBoat.java b/net/minecraft/world/entity/vehicle/AbstractBoat.java
index 467783b764eec361b190566cb3d9050bb0821864..b1b312e45ed4514eaa6fb3941af64b641220c5bd 100644
--- a/net/minecraft/world/entity/vehicle/AbstractBoat.java
+++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java
@@ -483,6 +483,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable {
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;
@@ -878,7 +879,13 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable {
@Override
public final ItemStack getPickResult() {
- return new ItemStack(this.dropItem.get());
+ // Purpur start - Apply display names from item forms of entities to entities and vice versa
+ final ItemStack boat = new ItemStack(this.dropItem.get());
+ if (!this.level().purpurConfig.persistentDroppableEntityDisplayNames) {
+ boat.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, null);
+ }
+ return boat;
+ // Purpur end - Apply display names from item forms of entities to entities and vice versa
}
public static enum Status {
diff --git a/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/net/minecraft/world/entity/vehicle/AbstractMinecart.java
index 9e15e7159cf98b3928110df9eae6de93793bf44e..6df4d736d94b9e49a3eb3d59a329e37127aa64cd 100644
--- a/net/minecraft/world/entity/vehicle/AbstractMinecart.java
+++ b/net/minecraft/world/entity/vehicle/AbstractMinecart.java
@@ -83,6 +83,10 @@ public abstract class AbstractMinecart extends VehicleEntity {
private double flyingY = 0.95;
private double flyingZ = 0.95;
public Double maxSpeed;
+ // Purpur start - Minecart settings and WASD controls
+ public double storedMaxSpeed;
+ public boolean isNewBehavior;
+ // Purpur end - Minecart settings and WASD controls
public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API
// CraftBukkit end
@@ -91,8 +95,13 @@ public abstract class AbstractMinecart extends VehicleEntity {
this.blocksBuilding = true;
if (useExperimentalMovement(level)) {
this.behavior = new NewMinecartBehavior(this);
+ this.isNewBehavior = true; // Purpur - Minecart settings and WASD controls
} else {
this.behavior = new OldMinecartBehavior(this);
+ // Purpur start - Minecart settings and WASD controls
+ this.isNewBehavior = false;
+ maxSpeed = storedMaxSpeed = level.purpurConfig.minecartMaxSpeed;
+ // Purpur end - Minecart settings and WASD controls
}
}
@@ -258,6 +267,14 @@ public abstract class AbstractMinecart extends VehicleEntity {
@Override
public void tick() {
+ // Purpur start - Minecart settings and WASD controls
+ if (!this.isNewBehavior) {
+ if (storedMaxSpeed != level().purpurConfig.minecartMaxSpeed) {
+ maxSpeed = storedMaxSpeed = level().purpurConfig.minecartMaxSpeed;
+ }
+ }
+ // Purpur end - Minecart settings and WASD controls
+
// CraftBukkit start
double prevX = this.getX();
double prevY = this.getY();
@@ -394,15 +411,61 @@ public abstract class AbstractMinecart extends VehicleEntity {
this.behavior.moveAlongTrack(level);
}
+ // Purpur start - Minecart settings and WASD controls
+ private Double lastSpeed;
+
+ public double getControllableSpeed() {
+ BlockState blockState = level().getBlockState(this.blockPosition());
+ if (!blockState.isSolid()) {
+ blockState = level().getBlockState(this.blockPosition().relative(Direction.DOWN));
+ }
+ Double speed = level().purpurConfig.minecartControllableBlockSpeeds.get(blockState.getBlock());
+ if (!blockState.isSolid()) {
+ speed = lastSpeed;
+ }
+ if (speed == null) {
+ speed = level().purpurConfig.minecartControllableBaseSpeed;
+ }
+ return lastSpeed = speed;
+ }
+ // Purpur end - Minecart settings and WASD controls
+
protected void comeOffTrack(ServerLevel level) {
double maxSpeed = this.getMaxSpeed(level);
Vec3 deltaMovement = this.getDeltaMovement();
this.setDeltaMovement(Mth.clamp(deltaMovement.x, -maxSpeed, maxSpeed), deltaMovement.y, Mth.clamp(deltaMovement.z, -maxSpeed, maxSpeed));
+
+ // Purpur start - Minecart settings and WASD controls
+ if (level().purpurConfig.minecartControllable && !isInWater() && !isInLava() && !passengers.isEmpty()) {
+ Entity passenger = passengers.get(0);
+ if (passenger instanceof net.minecraft.server.level.ServerPlayer player) {
+ net.minecraft.world.entity.player.Input lastClientInput = player.getLastClientInput();
+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F);
+ if (lastClientInput.jump() && this.onGround) {
+ setDeltaMovement(new Vec3(getDeltaMovement().x, level().purpurConfig.minecartControllableHopBoost, getDeltaMovement().z));
+ }
+ if (forward != 0.0F) {
+ org.bukkit.util.Vector velocity = player.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(getControllableSpeed());
+ if (forward < 0.0) {
+ velocity.multiply(-0.5);
+ }
+ setDeltaMovement(new Vec3(velocity.getX(), getDeltaMovement().y, velocity.getZ()));
+ }
+ this.setYRot(passenger.getYRot() - 90);
+ maxUpStep = level().purpurConfig.minecartControllableStepHeight;
+ } else {
+ maxUpStep = 0.0F;
+ }
+ } else {
+ maxUpStep = 0.0F;
+ }
+ // Purpur end - Minecart settings and WASD controls
if (this.onGround()) {
// CraftBukkit start - replace magic numbers with our variables
this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.derailedX, this.getDeltaMovement().y * this.derailedY, this.getDeltaMovement().z * this.derailedZ));
// CraftBukkit end
}
+ else if (level().purpurConfig.minecartControllable) setDeltaMovement(new Vec3(getDeltaMovement().x * derailedX, getDeltaMovement().y, getDeltaMovement().z * derailedZ)); // Purpur - Minecart settings and WASD controls
this.move(MoverType.SELF, this.getDeltaMovement());
if (!this.onGround()) {
diff --git a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java
index f386bfa64ac11150845666b26e938a8ae0efe574..724466d14c925704671e510cea1919ee95a2ae02 100644
--- a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java
+++ b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java
@@ -391,7 +391,7 @@ public class NewMinecartBehavior extends MinecartBehavior {
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);
diff --git a/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java b/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java
index d12d0c71479ea890ce41e5e43a135606db16fb21..8d1b70e526892860cc286314642fe51e5a44d7dc 100644
--- a/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java
+++ b/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java
@@ -279,8 +279,8 @@ public class OldMinecartBehavior extends MinecartBehavior {
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;
diff --git a/net/minecraft/world/food/FoodData.java b/net/minecraft/world/food/FoodData.java
index d46b27913797c5a2f433efe086463b91a9c31f63..f205401b24cdf0f43d531fb33e58d7183f98e510 100644
--- a/net/minecraft/world/food/FoodData.java
+++ b/net/minecraft/world/food/FoodData.java
@@ -36,6 +36,7 @@ public class FoodData {
int oldFoodLevel = this.foodLevel;
org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(serverPlayer, foodProperties.nutrition() + oldFoodLevel, stack);
if (!event.isCancelled()) {
+ if (serverPlayer.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) serverPlayer.burpDelay = serverPlayer.level().purpurConfig.playerBurpDelay; // Purpur - Burp after eating food fills hunger bar completely
this.add(event.getFoodLevel() - oldFoodLevel, foodProperties.saturation());
}
serverPlayer.getBukkitEntity().sendHealthUpdate();
@@ -84,7 +85,7 @@ public class FoodData {
this.tickTimer++;
if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation
if (player.getHealth() > 10.0F || difficulty == Difficulty.HARD || player.getHealth() > 1.0F && difficulty == Difficulty.NORMAL) {
- player.hurtServer(serverLevel, player.damageSources().starve(), 1.0F);
+ player.hurtServer(serverLevel, player.damageSources().starve(), player.level().purpurConfig.hungerStarvationDamage); // Purpur - Configurable hunger starvation damage
}
this.tickTimer = 0;
diff --git a/net/minecraft/world/food/FoodProperties.java b/net/minecraft/world/food/FoodProperties.java
index 5e0d447409dc2223bb56cb8bb932e241bf88c78d..6e1544121c556cd8761dc86d4246e7270c08e732 100644
--- a/net/minecraft/world/food/FoodProperties.java
+++ b/net/minecraft/world/food/FoodProperties.java
@@ -42,9 +42,11 @@ public record FoodProperties(int nutrition, float saturation, boolean canAlwaysE
level.playSound(null, entity.getX(), entity.getY(), entity.getZ(), consumable.sound().value(), SoundSource.NEUTRAL, 1.0F, random.triangle(1.0F, 0.4F));
if (entity instanceof Player player) {
player.getFoodData().eat(this, stack, (net.minecraft.server.level.ServerPlayer) player); // CraftBukkit
- level.playSound(
- null, player.getX(), player.getY(), player.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(random, 0.9F, 1.0F)
- );
+ // Purpur start - Burp delay - moved to Player#tick()
+ //level.playSound(
+ // null, player.getX(), player.getY(), player.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(random, 0.9F, 1.0F)
+ //);
+ // Purpur end - Burp delay - moved to Player#tick()
}
}
diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java
index acca8c51d2030c675c157b10d0bbc6af631afe61..e6419715fab462b12790ecb175ce1e1a1fceed8f 100644
--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -65,6 +65,7 @@ public abstract class AbstractContainerMenu {
@Nullable
private ContainerSynchronizer synchronizer;
private boolean suppressRemoteUpdates;
+ @Nullable protected ItemStack activeQuickItem = null; // Purpur - Anvil API
// CraftBukkit start
public boolean checkReachable = true;
public abstract org.bukkit.inventory.InventoryView getBukkitView();
diff --git a/net/minecraft/world/inventory/AbstractFurnaceMenu.java b/net/minecraft/world/inventory/AbstractFurnaceMenu.java
index d3814f351e3b0cd00b2b9ad0d122ca376c18e6a3..a23ff3defe4e49cd04008b7d793994bf2bf95159 100644
--- a/net/minecraft/world/inventory/AbstractFurnaceMenu.java
+++ b/net/minecraft/world/inventory/AbstractFurnaceMenu.java
@@ -121,7 +121,13 @@ public abstract class AbstractFurnaceMenu extends RecipeBookMenu {
} else if (index != 1 && index != 0) {
if (this.canSmelt(item)) {
if (!this.moveItemStackTo(item, 0, 1, false)) {
- return ItemStack.EMPTY;
+ // Purpur start - Added the ability to add combustible items
+ if (this.isFuel(item)) {
+ if (!this.moveItemStackTo(item, 1, 2, false)) {
+ return ItemStack.EMPTY;
+ }
+ }
+ // Purpur end - Added the ability to add combustible items
}
} else if (this.isFuel(item)) {
if (!this.moveItemStackTo(item, 1, 2, false)) {
diff --git a/net/minecraft/world/inventory/AnvilMenu.java b/net/minecraft/world/inventory/AnvilMenu.java
index aaa022ac3656d68bad8dbd4c80a90b62fb6f9a16..84c438f922321bb80906589ebee30bff83f4ebfa 100644
--- a/net/minecraft/world/inventory/AnvilMenu.java
+++ b/net/minecraft/world/inventory/AnvilMenu.java
@@ -20,6 +20,12 @@ import net.minecraft.world.level.block.AnvilBlock;
import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
+// Purpur start - Anvil API
+import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.server.level.ServerPlayer;
+// Purpur end - Anvil API
+
public class AnvilMenu extends ItemCombinerMenu {
public static final int INPUT_SLOT = 0;
public static final int ADDITIONAL_SLOT = 1;
@@ -49,6 +55,10 @@ public class AnvilMenu extends ItemCombinerMenu {
private org.bukkit.craftbukkit.inventory.view.CraftAnvilView bukkitEntity;
// CraftBukkit end
public boolean bypassEnchantmentLevelRestriction = false; // Paper - bypass anvil level restrictions
+ // Purpur start - Anvil API
+ public boolean bypassCost = false;
+ public boolean canDoUnsafeEnchants = false;
+ // Purpur end - Anvil API
public AnvilMenu(int containerId, Inventory playerInventory) {
this(containerId, playerInventory, ContainerLevelAccess.NULL);
@@ -74,12 +84,17 @@ public class AnvilMenu extends ItemCombinerMenu {
@Override
protected boolean mayPickup(Player player, boolean hasStack) {
- return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && hasStack; // CraftBukkit - allow cost 0 like a free item
+ return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && (this.bypassCost || this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST) && hasStack; // CraftBukkit - allow cost 0 like a free item // Purpur - Anvil API
}
@Override
protected void onTake(Player player, ItemStack stack) {
+ // Purpur start - Anvil API
+ ItemStack itemstack = this.activeQuickItem != null ? this.activeQuickItem : stack;
+ if (org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)).callEvent();
+ // Purpur end - Anvil API
if (!player.getAbilities().instabuild) {
+ if (this.bypassCost) ((ServerPlayer) player).lastSentExp = -1; else // Purpur - Anvil API
player.giveExperienceLevels(-this.cost.get());
}
@@ -126,13 +141,19 @@ public class AnvilMenu extends ItemCombinerMenu {
@Override
public void createResult() {
+ // Purpur start - Anvil API
+ this.bypassCost = false;
+ this.canDoUnsafeEnchants = false;
+ if (org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent();
+ // Purpur end - Anvil API
+
ItemStack item = this.inputSlots.getItem(0);
this.onlyRenaming = false;
this.cost.set(1);
int i = 0;
long l = 0L;
int i1 = 0;
- if (!item.isEmpty() && EnchantmentHelper.canStoreEnchantments(item)) {
+ if (!item.isEmpty() && this.canDoUnsafeEnchants || EnchantmentHelper.canStoreEnchantments(item)) { // Purpur - Anvil API
ItemStack itemStack = item.copy();
ItemStack item1 = this.inputSlots.getItem(1);
ItemEnchantments.Mutable mutable = new ItemEnchantments.Mutable(EnchantmentHelper.getEnchantmentsForCrafting(itemStack));
@@ -191,23 +212,34 @@ public class AnvilMenu extends ItemCombinerMenu {
int intValue = entry.getIntValue();
intValue = level == intValue ? intValue + 1 : Math.max(intValue, level);
Enchantment enchantment = holder.value();
- boolean canEnchant = enchantment.canEnchant(item);
+ // Purpur start - Config to allow unsafe enchants
+ boolean canEnchant = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || enchantment.canEnchant(item); // whether the enchantment can be applied on specific item type
+ boolean canEnchant1 = true; // whether two incompatible enchantments can be applied on a single item
+ // Purpur end - Config to allow unsafe enchants
if (this.player.getAbilities().instabuild || item.is(Items.ENCHANTED_BOOK)) {
canEnchant = true;
}
+ java.util.Set<Holder<Enchantment>> removedEnchantments = new java.util.HashSet<>(); // Purpur - Config to allow unsafe enchants
for (Holder<Enchantment> holder1 : mutable.keySet()) {
if (!holder1.equals(holder) && !Enchantment.areCompatible(holder, holder1)) {
- canEnchant = false;
+ canEnchant1 = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants; // Purpur - Anvil API // Purpur - canEnchant -> canEnchant1 - Config to allow unsafe enchants
+ // Purpur start - Config to allow unsafe enchants
+ if (!canEnchant1 && org.purpurmc.purpur.PurpurConfig.replaceIncompatibleEnchants) {
+ removedEnchantments.add(holder1);
+ canEnchant1 = true;
+ }
+ // Purpur end - Config to allow unsafe enchants
i++;
}
}
+ mutable.removeIf(removedEnchantments::contains); // Purpur - Config to allow unsafe enchants
- if (!canEnchant) {
+ if (!canEnchant || !canEnchant1) { // Purpur - Config to allow unsafe enchants
flag1 = true;
} else {
flag = true;
- if (intValue > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions
+ if (!org.purpurmc.purpur.PurpurConfig.allowHigherEnchantsLevels && intValue > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions // Purpur - Config to allow unsafe enchants
intValue = enchantment.getMaxLevel();
}
@@ -236,6 +268,54 @@ public class AnvilMenu extends ItemCombinerMenu {
if (!this.itemName.equals(item.getHoverName().getString())) {
i1 = 1;
i += i1;
+ // Purpur start - Allow anvil colors
+ if (this.player != null) {
+ org.bukkit.craftbukkit.entity.CraftHumanEntity player = this.player.getBukkitEntity();
+ String name = this.itemName;
+ boolean removeItalics = false;
+ if (player.hasPermission("purpur.anvil.remove_italics")) {
+ if (name.startsWith("&r")) {
+ name = name.substring(2);
+ removeItalics = true;
+ } else if (name.startsWith("<r>")) {
+ name = name.substring(3);
+ removeItalics = true;
+ } else if (name.startsWith("<reset>")) {
+ name = name.substring(7);
+ removeItalics = true;
+ }
+ }
+ if (this.player.level().purpurConfig.anvilAllowColors) {
+ if (player.hasPermission("purpur.anvil.color")) {
+ java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([0-9a-fr])").matcher(name);
+ while (matcher.find()) {
+ String match = matcher.group(1);
+ name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT));
+ }
+ //name = name.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1");
+ }
+ if (player.hasPermission("purpur.anvil.format")) {
+ java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([k-or])").matcher(name);
+ while (matcher.find()) {
+ String match = matcher.group(1);
+ name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT));
+ }
+ //name = name.replaceAll("(?i)&([l-or])", "\u00a7$1");
+ }
+ }
+ net.kyori.adventure.text.Component component;
+ if (this.player.level().purpurConfig.anvilColorsUseMiniMessage && player.hasPermission("purpur.anvil.minimessage")) {
+ component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.bukkit.ChatColor.stripColor(name));
+ } else {
+ component = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(name);
+ }
+ if (removeItalics) {
+ component = component.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false);
+ }
+ itemStack.set(DataComponents.CUSTOM_NAME, io.papermc.paper.adventure.PaperAdventure.asVanilla(component));
+ }
+ else
+ // Purpur end - Allow anvil colors
itemStack.set(DataComponents.CUSTOM_NAME, Component.literal(this.itemName));
}
} else if (item.has(DataComponents.CUSTOM_NAME)) {
@@ -260,6 +340,12 @@ public class AnvilMenu extends ItemCombinerMenu {
this.onlyRenaming = true;
}
+ // Purpur start - Anvil API
+ if (this.bypassCost && this.cost.get() >= this.maximumRepairCost) {
+ this.cost.set(this.maximumRepairCost - 1);
+ }
+ // Purpur end - Anvil API
+
if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit
itemStack = ItemStack.EMPTY;
}
@@ -280,6 +366,13 @@ public class AnvilMenu extends ItemCombinerMenu {
org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemStack); // CraftBukkit
this.broadcastChanges();
+
+ // Purpur start - Anvil API
+ if ((this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants) && itemStack != ItemStack.EMPTY) { // Purpur - Config to allow unsafe enchants
+ ((ServerPlayer) this.player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 2, itemStack));
+ ((ServerPlayer) this.player).connection.send(new ClientboundContainerSetDataPacket(this.containerId, 0, this.cost.get()));
+ }
+ // Purpur end - Anvil API
} else {
org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit
this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item
@@ -288,7 +381,7 @@ public class AnvilMenu extends ItemCombinerMenu {
}
public static int calculateIncreasedRepairCost(int oldRepairCost) {
- return (int)Math.min(oldRepairCost * 2L + 1L, 2147483647L);
+ return org.purpurmc.purpur.PurpurConfig.anvilCumulativeCost ? (int)Math.min(oldRepairCost * 2L + 1L, 2147483647L) : 0; // Purpur - Make anvil cumulative cost configurable
}
public boolean setItemName(String itemName) {
diff --git a/net/minecraft/world/inventory/ArmorSlot.java b/net/minecraft/world/inventory/ArmorSlot.java
index 6a1c0f2b11a289c419b9070feb08d9464570c5b7..f9d82d7f7e76032a13d196bc1d0a209015b461ee 100644
--- a/net/minecraft/world/inventory/ArmorSlot.java
+++ b/net/minecraft/world/inventory/ArmorSlot.java
@@ -42,7 +42,7 @@ class ArmorSlot extends Slot {
@Override
public boolean mayPickup(Player player) {
ItemStack item = this.getItem();
- return (item.isEmpty() || player.isCreative() || !EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE))
+ return (item.isEmpty() || player.isCreative() || (!EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE) || player.level().purpurConfig.playerRemoveBindingWithWeakness && player.hasEffect(net.minecraft.world.effect.MobEffects.WEAKNESS))) // Purpur - Config to remove curse of binding with weakness
&& super.mayPickup(player);
}
diff --git a/net/minecraft/world/inventory/ChestMenu.java b/net/minecraft/world/inventory/ChestMenu.java
index 280169afbd637eeb67ddf7eaeb4eecd464a128d5..ba7730a24831efa33de4c5ffce57bfa7177f89d6 100644
--- a/net/minecraft/world/inventory/ChestMenu.java
+++ b/net/minecraft/world/inventory/ChestMenu.java
@@ -66,10 +66,30 @@ public class ChestMenu extends AbstractContainerMenu {
return new ChestMenu(MenuType.GENERIC_9x6, containerId, playerInventory, 6);
}
+ // Purpur start - Barrels and enderchests 6 rows
+ public static ChestMenu oneRow(int syncId, Inventory playerInventory, Container inventory) {
+ return new ChestMenu(MenuType.GENERIC_9x1, syncId, playerInventory, inventory, 1);
+ }
+
+ public static ChestMenu twoRows(int syncId, Inventory playerInventory, Container inventory) {
+ return new ChestMenu(MenuType.GENERIC_9x2, syncId, playerInventory, inventory, 2);
+ }
+ // Purpur end - Barrels and enderchests 6 rows
+
public static ChestMenu threeRows(int containerId, Inventory playerInventory, Container container) {
return new ChestMenu(MenuType.GENERIC_9x3, containerId, playerInventory, container, 3);
}
+ // Purpur start - Barrels and enderchests 6 rows
+ public static ChestMenu fourRows(int syncId, Inventory playerInventory, Container inventory) {
+ return new ChestMenu(MenuType.GENERIC_9x4, syncId, playerInventory, inventory, 4);
+ }
+
+ public static ChestMenu fiveRows(int syncId, Inventory playerInventory, Container inventory) {
+ return new ChestMenu(MenuType.GENERIC_9x5, syncId, playerInventory, inventory, 5);
+ }
+ // Purpur end - Barrels and enderchests 6 rows
+
public static ChestMenu sixRows(int containerId, Inventory playerInventory, Container container) {
return new ChestMenu(MenuType.GENERIC_9x6, containerId, playerInventory, container, 6);
}
diff --git a/net/minecraft/world/inventory/EnchantmentMenu.java b/net/minecraft/world/inventory/EnchantmentMenu.java
index 1bba36afb00ad2a63bbfba60aab0f614b4aa8174..0936e08f5f465dc34a1b4b5370fcc3e0d57eab0a 100644
--- a/net/minecraft/world/inventory/EnchantmentMenu.java
+++ b/net/minecraft/world/inventory/EnchantmentMenu.java
@@ -63,6 +63,22 @@ public class EnchantmentMenu extends AbstractContainerMenu {
return access.getLocation();
}
// CraftBukkit end
+
+ // Purpur start - Enchantment Table Persists Lapis
+ @Override
+ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ super.onClose(who);
+
+ if (who.getHandle().level().purpurConfig.enchantmentTableLapisPersists) {
+ access.execute((level, pos) -> {
+ net.minecraft.world.level.block.entity.BlockEntity blockEntity = level.getBlockEntity(pos);
+ if (blockEntity instanceof net.minecraft.world.level.block.entity.EnchantingTableBlockEntity enchantmentTable) {
+ enchantmentTable.setLapis(this.getItem(1).getCount());
+ }
+ });
+ }
+ }
+ // Purpur end - Enchantment Table Persists Lapis
};
// Paper end - Add missing InventoryHolders
this.access = access;
@@ -83,6 +99,16 @@ public class EnchantmentMenu extends AbstractContainerMenu {
return EnchantmentMenu.EMPTY_SLOT_LAPIS_LAZULI;
}
});
+ // Purpur start - Enchantment Table Persists Lapis
+ access.execute((level, pos) -> {
+ if (level.purpurConfig.enchantmentTableLapisPersists) {
+ net.minecraft.world.level.block.entity.BlockEntity blockEntity = level.getBlockEntity(pos);
+ if (blockEntity instanceof net.minecraft.world.level.block.entity.EnchantingTableBlockEntity enchantmentTable) {
+ this.getSlot(1).set(new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis()));
+ }
+ }
+ });
+ // Purpur end - Enchantment Table Persists Lapis
this.addStandardInventorySlots(playerInventory, 8, 84);
this.addDataSlot(DataSlot.shared(this.costs, 0));
this.addDataSlot(DataSlot.shared(this.costs, 1));
@@ -294,7 +320,7 @@ public class EnchantmentMenu extends AbstractContainerMenu {
@Override
public void removed(Player player) {
super.removed(player);
- this.access.execute((level, blockPos) -> this.clearContainer(player, this.enchantSlots));
+ this.access.execute((level, blockPos) -> {if (level.purpurConfig.enchantmentTableLapisPersists) this.getSlot(1).set(ItemStack.EMPTY);this.clearContainer(player, this.enchantSlots);}); // Purpur - Enchantment Table Persists Lapis
}
@Override
diff --git a/net/minecraft/world/inventory/GrindstoneMenu.java b/net/minecraft/world/inventory/GrindstoneMenu.java
index f85bd2a90c2694d96f67cc3701a9bbf081fe8475..2da45bd93f12aadae4e28357b3225353dba89427 100644
--- a/net/minecraft/world/inventory/GrindstoneMenu.java
+++ b/net/minecraft/world/inventory/GrindstoneMenu.java
@@ -91,11 +91,13 @@ public class GrindstoneMenu extends AbstractContainerMenu {
@Override
public void onTake(Player player, ItemStack stack) {
access.execute((level, blockPos) -> {
+ ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur - Grindstone API
if (level instanceof ServerLevel) {
// Paper start - Fire BlockExpEvent on grindstone use
org.bukkit.event.block.BlockExpEvent event = new org.bukkit.event.block.BlockExpEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos), this.getExperienceAmount(level));
event.callEvent();
- ExperienceOrb.award((ServerLevel) level, Vec3.atCenterOf(blockPos), event.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player);
+ org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent grindstoneTakeResultEvent = new org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), event.getExpToDrop()); grindstoneTakeResultEvent.callEvent(); // Purpur - Grindstone API
+ ExperienceOrb.award((ServerLevel) level, Vec3.atCenterOf(blockPos), grindstoneTakeResultEvent.getExperienceAmount(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Purpur - Grindstone API
// Paper end - Fire BlockExpEvent on grindstone use
}
@@ -124,7 +126,7 @@ public class GrindstoneMenu extends AbstractContainerMenu {
for (Entry<Holder<Enchantment>> entry : enchantmentsForCrafting.entrySet()) {
Holder<Enchantment> holder = entry.getKey();
int intValue = entry.getIntValue();
- if (!holder.is(EnchantmentTags.CURSE)) {
+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value())) { // Purpur - Config for grindstones
i += holder.value().getMinCost(intValue);
}
}
@@ -202,15 +204,75 @@ public class GrindstoneMenu extends AbstractContainerMenu {
for (Entry<Holder<Enchantment>> entry : enchantmentsForCrafting.entrySet()) {
Holder<Enchantment> holder = entry.getKey();
- if (!holder.is(EnchantmentTags.CURSE) || mutable.getLevel(holder) == 0) {
+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()) || mutable.getLevel(holder) == 0) { // Purpur - Config for grindstones
mutable.upgrade(holder, entry.getIntValue());
}
}
});
}
+ // Purpur start - Config for grindstones
+ private java.util.List<net.minecraft.core.component.DataComponentType<?>> GRINDSTONE_REMOVE_ATTRIBUTES_REMOVAL_LIST = java.util.List.of(
+ // DataComponents.MAX_STACK_SIZE,
+ // DataComponents.DAMAGE,
+ // DataComponents.BLOCK_STATE,
+ DataComponents.CUSTOM_DATA,
+ // DataComponents.MAX_DAMAGE,
+ // DataComponents.UNBREAKABLE,
+ // DataComponents.CUSTOM_NAME,
+ // DataComponents.ITEM_NAME,
+ // DataComponents.LORE,
+ // DataComponents.RARITY,
+ // DataComponents.ENCHANTMENTS,
+ // DataComponents.CAN_PLACE_ON,
+ // DataComponents.CAN_BREAK,
+ DataComponents.ATTRIBUTE_MODIFIERS,
+ DataComponents.CUSTOM_MODEL_DATA,
+ // DataComponents.HIDE_ADDITIONAL_TOOLTIP,
+ // DataComponents.HIDE_TOOLTIP,
+ // DataComponents.REPAIR_COST,
+ // DataComponents.CREATIVE_SLOT_LOCK,
+ // DataComponents.ENCHANTMENT_GLINT_OVERRIDE,
+ // DataComponents.INTANGIBLE_PROJECTILE,
+ // DataComponents.FOOD,
+ // DataComponents.FIRE_RESISTANT,
+ // DataComponents.TOOL,
+ // DataComponents.STORED_ENCHANTMENTS,
+ DataComponents.DYED_COLOR,
+ // DataComponents.MAP_COLOR,
+ // DataComponents.MAP_ID,
+ // DataComponents.MAP_DECORATIONS,
+ // DataComponents.MAP_POST_PROCESSING,
+ // DataComponents.CHARGED_PROJECTILES,
+ // DataComponents.BUNDLE_CONTENTS,
+ // DataComponents.POTION_CONTENTS,
+ DataComponents.SUSPICIOUS_STEW_EFFECTS
+ // DataComponents.WRITABLE_BOOK_CONTENT,
+ // DataComponents.WRITTEN_BOOK_CONTENT,
+ // DataComponents.TRIM,
+ // DataComponents.DEBUG_STICK_STATE,
+ // DataComponents.ENTITY_DATA,
+ // DataComponents.BUCKET_ENTITY_DATA,
+ // DataComponents.BLOCK_ENTITY_DATA,
+ // DataComponents.INSTRUMENT,
+ // DataComponents.OMINOUS_BOTTLE_AMPLIFIER,
+ // DataComponents.RECIPES,
+ // DataComponents.LODESTONE_TRACKER,
+ // DataComponents.FIREWORK_EXPLOSION,
+ // DataComponents.FIREWORKS,
+ // DataComponents.PROFILE,
+ // DataComponents.NOTE_BLOCK_SOUND,
+ // DataComponents.BANNER_PATTERNS,
+ // DataComponents.BASE_COLOR,
+ // DataComponents.POT_DECORATIONS,
+ // DataComponents.CONTAINER,
+ // DataComponents.BEES,
+ // DataComponents.LOCK,
+ // DataComponents.CONTAINER_LOOT,
+ );
+ // Purpur end - Config for grindstones
private ItemStack removeNonCursesFrom(ItemStack item) {
- ItemEnchantments itemEnchantments = EnchantmentHelper.updateEnchantments(item, mutable -> mutable.removeIf(holder -> !holder.is(EnchantmentTags.CURSE)));
+ ItemEnchantments itemEnchantments = EnchantmentHelper.updateEnchantments(item, mutable -> mutable.removeIf(holder -> !org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()))); // Purpur - Config for grindstones
if (item.is(Items.ENCHANTED_BOOK) && itemEnchantments.isEmpty()) {
item = item.transmuteCopy(Items.BOOK);
}
@@ -222,6 +284,23 @@ public class GrindstoneMenu extends AbstractContainerMenu {
}
item.set(DataComponents.REPAIR_COST, i);
+
+ // Purpur start - Config for grindstones
+ net.minecraft.core.component.DataComponentPatch.Builder builder = net.minecraft.core.component.DataComponentPatch.builder();
+ if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveAttributes) {
+ item.getComponents().forEach(typedDataComponent -> {
+ if (GRINDSTONE_REMOVE_ATTRIBUTES_REMOVAL_LIST.contains(typedDataComponent.type())) {
+ builder.remove(typedDataComponent.type());
+ }
+ });
+ }
+ if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveDisplay) {
+ builder.remove(DataComponents.CUSTOM_NAME);
+ builder.remove(DataComponents.LORE);
+ }
+ item.applyComponents(builder.build());
+ // Purpur end - Config for grindstones
+
return item;
}
@@ -278,7 +357,9 @@ public class GrindstoneMenu extends AbstractContainerMenu {
return ItemStack.EMPTY;
}
+ this.activeQuickItem = itemStack; // Purpur - Grindstone API
slot.onTake(player, item);
+ this.activeQuickItem = null; // Purpur - Grindstone API
}
return itemStack;
diff --git a/net/minecraft/world/inventory/ItemCombinerMenu.java b/net/minecraft/world/inventory/ItemCombinerMenu.java
index 34d52c941395645e77de810855b14012c259cf02..c605bd700fd9f5a6596a2bf9648492786306b025 100644
--- a/net/minecraft/world/inventory/ItemCombinerMenu.java
+++ b/net/minecraft/world/inventory/ItemCombinerMenu.java
@@ -156,7 +156,9 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu {
return ItemStack.EMPTY;
}
+ this.activeQuickItem = itemStack; // Purpur - Anvil API
slot.onTake(player, item);
+ this.activeQuickItem = null; // Purpur - Anvil API
}
return itemStack;
diff --git a/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/net/minecraft/world/inventory/PlayerEnderChestContainer.java
index a6a359bab2a727f4631b633a8bb370dd40decc75..d2d75e5c34c97300ce5da8c7ea70958aba31fa4a 100644
--- a/net/minecraft/world/inventory/PlayerEnderChestContainer.java
+++ b/net/minecraft/world/inventory/PlayerEnderChestContainer.java
@@ -25,11 +25,18 @@ public class PlayerEnderChestContainer extends SimpleContainer {
}
public PlayerEnderChestContainer(Player owner) {
- super(27);
+ super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur - Barrels and enderchests 6 rows
this.owner = owner;
// CraftBukkit end
}
+ // Purpur start - Barrels and enderchests 6 rows
+ @Override
+ public int getContainerSize() {
+ return owner.sixRowEnderchestSlotCount < 0 ? super.getContainerSize() : owner.sixRowEnderchestSlotCount;
+ }
+ // Purpur end - Barrels and enderchests 6 rows
+
public void setActiveChest(EnderChestBlockEntity enderChestBlockEntity) {
this.activeChest = enderChestBlockEntity;
}
diff --git a/net/minecraft/world/item/ArmorStandItem.java b/net/minecraft/world/item/ArmorStandItem.java
index d82e6651999a2650ec8884c4c3d8de4133cb42a4..a26b9fe964c79da57aaa0f755a81934f51a79913 100644
--- a/net/minecraft/world/item/ArmorStandItem.java
+++ b/net/minecraft/world/item/ArmorStandItem.java
@@ -51,6 +51,10 @@ public class ArmorStandItem extends Item {
return InteractionResult.FAIL;
}
// CraftBukkit end
+ // Purpur start - Apply display names from item forms of entities to entities and vice versa
+ if (!serverLevel.purpurConfig.persistentDroppableEntityDisplayNames) armorStand.setCustomName(null);
+ if (serverLevel.purpurConfig.armorstandSetNameVisible && armorStand.getCustomName() != null) armorStand.setCustomNameVisible(true);
+ // Purpur end - Apply display names from item forms of entities to entities and vice versa
serverLevel.addFreshEntityWithPassengers(armorStand);
level.playSound(
null, armorStand.getX(), armorStand.getY(), armorStand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F
diff --git a/net/minecraft/world/item/AxeItem.java b/net/minecraft/world/item/AxeItem.java
index d4b79f897ee7950893d608dc343073dbcff6ab14..7cf309c1b119ef8432c04c2b3df248d7d96dc15c 100644
--- a/net/minecraft/world/item/AxeItem.java
+++ b/net/minecraft/world/item/AxeItem.java
@@ -62,13 +62,15 @@ public class AxeItem extends DiggerItem {
if (playerHasShieldUseIntent(context)) {
return InteractionResult.PASS;
} else {
- Optional<BlockState> optional = this.evaluateNewBlockState(level, clickedPos, player, level.getBlockState(clickedPos));
+ Optional<org.purpurmc.purpur.tool.Actionable> optional = this.evaluateActionable(level, clickedPos, player, level.getBlockState(clickedPos)); // Purpur - Tool actionable options
if (optional.isEmpty()) {
return InteractionResult.PASS;
} else {
+ org.purpurmc.purpur.tool.Actionable actionable = optional.get(); // Purpur - Tool actionable options
+ BlockState state = actionable.into().withPropertiesOf(level.getBlockState(clickedPos)); // Purpur - Tool actionable options
ItemStack itemInHand = context.getItemInHand();
// Paper start - EntityChangeBlockEvent
- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, clickedPos, optional.get())) {
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, clickedPos, state)) { // Purpur - Tool actionable options
return InteractionResult.PASS;
}
// Paper end
@@ -76,8 +78,15 @@ public class AxeItem extends DiggerItem {
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand);
}
- level.setBlock(clickedPos, optional.get(), 11);
- level.gameEvent(GameEvent.BLOCK_CHANGE, clickedPos, GameEvent.Context.of(player, optional.get()));
+ // Purpur start - Tool actionable options
+ level.setBlock(clickedPos, state, 11);
+ actionable.drops().forEach((drop, chance) -> {
+ if (level.random.nextDouble() < chance) {
+ Block.popResourceFromFace(level, clickedPos, context.getClickedFace(), new ItemStack(drop));
+ }
+ });
+ level.gameEvent(GameEvent.BLOCK_CHANGE, clickedPos, GameEvent.Context.of(player, state));
+ // Purpur end - Tool actionable options
if (player != null) {
itemInHand.hurtAndBreak(1, player, LivingEntity.getSlotForHand(context.getHand()));
}
@@ -92,22 +101,24 @@ public class AxeItem extends DiggerItem {
return context.getHand().equals(InteractionHand.MAIN_HAND) && player.getOffhandItem().is(Items.SHIELD) && !player.isSecondaryUseActive();
}
- private Optional<BlockState> evaluateNewBlockState(Level level, BlockPos pos, @Nullable Player player, BlockState state) {
- Optional<BlockState> stripped = this.getStripped(state);
+ private Optional<org.purpurmc.purpur.tool.Actionable> evaluateActionable(Level level, BlockPos pos, @Nullable Player player, BlockState state) { // Purpur - Tool actionable options
+ Optional<org.purpurmc.purpur.tool.Actionable> stripped = Optional.ofNullable(level.purpurConfig.axeStrippables.get(state.getBlock())); // Purpur - Tool actionable options
if (stripped.isPresent()) {
- level.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F);
+ level.playSound(STRIPPABLES.containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound
return stripped;
} else {
- Optional<BlockState> previous = WeatheringCopper.getPrevious(state);
+ Optional<org.purpurmc.purpur.tool.Actionable> previous = Optional.ofNullable(level.purpurConfig.axeWeatherables.get(state.getBlock())); // Purpur - Tool actionable options
if (previous.isPresent()) {
- level.playSound(player, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F);
+ level.playSound(WeatheringCopper.getPrevious(state).isPresent() ? player : null, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - Tool actionable options - force sound
level.levelEvent(player, 3005, pos, 0);
return previous;
} else {
- Optional<BlockState> optional = Optional.ofNullable(HoneycombItem.WAX_OFF_BY_BLOCK.get().get(state.getBlock()))
- .map(block -> block.withPropertiesOf(state));
+ // Purpur start - Tool actionable options
+ Optional<org.purpurmc.purpur.tool.Actionable> optional = Optional.ofNullable(level.purpurConfig.axeWaxables.get(state.getBlock()));
+ // .map(block -> block.withPropertiesOf(state));
+ // Purpur end - Tool actionable options
if (optional.isPresent()) {
- level.playSound(player, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F);
+ level.playSound(HoneycombItem.WAX_OFF_BY_BLOCK.get().containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - Tool actionable options - force sound
level.levelEvent(player, 3004, pos, 0);
return optional;
} else {
diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java
index 68e50c6ade879d263424f244070677cb81c34c33..1df587402862d67a76ed090df60bfa20c3a9209d 100644
--- a/net/minecraft/world/item/BlockItem.java
+++ b/net/minecraft/world/item/BlockItem.java
@@ -152,7 +152,16 @@ public class BlockItem extends Item {
}
protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, @Nullable Player player, ItemStack stack, BlockState state) {
- return updateCustomBlockEntityTag(level, player, pos, stack);
+ // Purpur start - Persistent BlockEntity Lore and DisplayName
+ boolean handled = updateCustomBlockEntityTag(level, player, pos, stack);
+ if (level.purpurConfig.persistentTileEntityLore) {
+ BlockEntity blockEntity1 = level.getBlockEntity(pos);
+ if (blockEntity1 != null) {
+ blockEntity1.setPersistentLore(stack.getOrDefault(DataComponents.LORE, net.minecraft.world.item.component.ItemLore.EMPTY));
+ }
+ }
+ return handled;
+ // Purpur end - Persistent BlockEntity Lore and DisplayName
}
@Nullable
@@ -217,6 +226,7 @@ public class BlockItem extends Item {
}
if (!type.onlyOpCanSetNbt() || player != null && (player.canUseGameMasterBlocks() || (player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.nbt.place")))) { // Spigot - add permission
+ if (!(level.purpurConfig.silkTouchEnabled && blockEntity instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity && player.getBukkitEntity().hasPermission("purpur.drop.spawners"))) // Purpur - Silk touch spawners
return customData.loadInto(blockEntity, level.registryAccess());
}
@@ -264,6 +274,7 @@ public class BlockItem extends Item {
public void onDestroyed(ItemEntity itemEntity) {
ItemContainerContents itemContainerContents = itemEntity.getItem().set(DataComponents.CONTAINER, ItemContainerContents.EMPTY);
if (itemContainerContents != null) {
+ if (itemEntity.level().purpurConfig.shulkerBoxItemDropContentsWhenDestroyed && this.getBlock() instanceof ShulkerBoxBlock) // Purpur - option to disable shulker box items from dropping contents when destroyed
ItemUtils.onContainerDestroyed(itemEntity, itemContainerContents.nonEmptyItemsCopy());
}
}
diff --git a/net/minecraft/world/item/BoatItem.java b/net/minecraft/world/item/BoatItem.java
index 13ce174e4f7e406f57a68ea0d3ef0ee3367f3f3b..ca86122e38688b29340cd8413ccf1746315e292a 100644
--- a/net/minecraft/world/item/BoatItem.java
+++ b/net/minecraft/world/item/BoatItem.java
@@ -63,6 +63,7 @@ public class BoatItem extends Item {
return InteractionResult.FAIL;
} else {
boat.setYRot(player.getYRot());
+ if (!level.purpurConfig.persistentDroppableEntityDisplayNames) boat.setCustomName(null); // Purpur - Apply display names from item forms of entities to entities and vice versa
if (!level.noCollision(boat, boat.getBoundingBox())) {
return InteractionResult.FAIL;
} else {
diff --git a/net/minecraft/world/item/BowItem.java b/net/minecraft/world/item/BowItem.java
index 57c933af200551162774f1d473437521e5a85833..b3e003694ce0da357e91ab3ce2b1380f9ab0a32a 100644
--- a/net/minecraft/world/item/BowItem.java
+++ b/net/minecraft/world/item/BowItem.java
@@ -28,6 +28,11 @@ public class BowItem extends ProjectileWeaponItem {
return false;
} else {
ItemStack projectile = player.getProjectile(stack);
+ // Purpur start - Infinity bow settings
+ if (level.purpurConfig.infinityWorksWithoutArrows && projectile.isEmpty() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, stack) > 0) {
+ projectile = new ItemStack(Items.ARROW);
+ }
+ // Purpur end - Infinity bow settings
if (projectile.isEmpty()) {
return false;
} else {
@@ -38,7 +43,7 @@ public class BowItem extends ProjectileWeaponItem {
} else {
List<ItemStack> list = draw(stack, projectile, player);
if (level instanceof ServerLevel serverLevel && !list.isEmpty()) {
- this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, powerForTime * 3.0F, 1.0F, powerForTime == 1.0F, null);
+ this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, powerForTime * 3.0F, (float) serverLevel.purpurConfig.bowProjectileOffset, powerForTime == 1.0F, null); // Purpur - Projectile offset config
}
level.playSound(
@@ -89,7 +94,7 @@ public class BowItem extends ProjectileWeaponItem {
public InteractionResult use(Level level, Player player, InteractionHand hand) {
ItemStack itemInHand = player.getItemInHand(hand);
boolean flag = !player.getProjectile(itemInHand).isEmpty();
- if (!player.hasInfiniteMaterials() && !flag) {
+ if (!player.hasInfiniteMaterials() && !flag && !(level.purpurConfig.infinityWorksWithoutArrows && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, itemInHand) > 0)) { // Purpur - Infinity bow settings
return InteractionResult.FAIL;
} else {
player.startUsingItem(hand);
diff --git a/net/minecraft/world/item/BucketItem.java b/net/minecraft/world/item/BucketItem.java
index 62d79cb25879e6e48a1541f864d0b3782d926313..b820ecfbe71dd57a0f04b1c3381ffb76b0c16f16 100644
--- a/net/minecraft/world/item/BucketItem.java
+++ b/net/minecraft/world/item/BucketItem.java
@@ -147,7 +147,7 @@ public class BucketItem extends Item implements DispensibleContainerItem {
// CraftBukkit end
if (!flag) {
return result != null && this.emptyContents(player, level, result.getBlockPos().relative(result.getDirection()), null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit
- } else if (level.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) {
+ } else if ((level.dimensionType().ultraWarm() || (level.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) && this.content.is(FluidTags.WATER)) { // Purpur - Add allow water in end world option
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
@@ -156,7 +156,7 @@ public class BucketItem extends Item implements DispensibleContainerItem {
);
for (int i = 0; i < 8; i++) {
- level.addParticle(ParticleTypes.LARGE_SMOKE, x + Math.random(), y + Math.random(), z + Math.random(), 0.0, 0.0, 0.0);
+ ((net.minecraft.server.level.ServerLevel) level).sendParticlesSource(null, ParticleTypes.LARGE_SMOKE, true, false, x + Math.random(), y + Math.random(), z + Math.random(), 1, 0.0D, 0.0D, 0.0D, 0.0D); // Purpur - Add allow water in end world option
}
return true;
diff --git a/net/minecraft/world/item/CrossbowItem.java b/net/minecraft/world/item/CrossbowItem.java
index 944b0c455d5f2183a04ea1a10bc5d5debd563dcb..318316d3aa14f1e9e863d435515c182619a81b3e 100644
--- a/net/minecraft/world/item/CrossbowItem.java
+++ b/net/minecraft/world/item/CrossbowItem.java
@@ -70,7 +70,7 @@ public class CrossbowItem extends ProjectileWeaponItem {
ItemStack itemInHand = player.getItemInHand(hand);
ChargedProjectiles chargedProjectiles = itemInHand.get(DataComponents.CHARGED_PROJECTILES);
if (chargedProjectiles != null && !chargedProjectiles.isEmpty()) {
- this.performShooting(level, player, hand, itemInHand, getShootingPower(chargedProjectiles), 1.0F, null);
+ this.performShooting(level, player, hand, itemInHand, getShootingPower(chargedProjectiles), (float) level.purpurConfig.crossbowProjectileOffset, null); // Purpur - Projectile offset config
return InteractionResult.CONSUME;
} else if (!player.getProjectile(itemInHand).isEmpty()) {
this.startSoundPlayed = false;
diff --git a/net/minecraft/world/item/DyeColor.java b/net/minecraft/world/item/DyeColor.java
index 028bb51c6753d44cbae76890412aa55b070f8054..597e2357040af91fc8cfb81ff7e5d8815d61898d 100644
--- a/net/minecraft/world/item/DyeColor.java
+++ b/net/minecraft/world/item/DyeColor.java
@@ -213,4 +213,10 @@ public enum DyeColor implements StringRepresentable {
private static CraftingInput makeCraftColorInput(DyeColor first, DyeColor second) {
return CraftingInput.of(2, 1, List.of(new ItemStack(DyeItem.byColor(first)), new ItemStack(DyeItem.byColor(second))));
}
+
+ // Purpur start - Shulker spawn from bullet options
+ public static DyeColor random(net.minecraft.util.RandomSource random) {
+ return values()[random.nextInt(values().length)];
+ }
+ // Purpur end - Shulker spawn from bullet options
}
diff --git a/net/minecraft/world/item/EggItem.java b/net/minecraft/world/item/EggItem.java
index f22a3ad6228326f837e6c83576e14d9e4fa9c882..3560091fb5840d8170d6ea80f6db734591fe85fc 100644
--- a/net/minecraft/world/item/EggItem.java
+++ b/net/minecraft/world/item/EggItem.java
@@ -26,7 +26,7 @@ public class EggItem extends Item implements ProjectileItem {
if (level instanceof ServerLevel serverLevel) {
// CraftBukkit start
// Paper start - PlayerLaunchProjectileEvent
- final Projectile.Delayed<ThrownEgg> thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, serverLevel, itemInHand, player, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, 1.0F);
+ final Projectile.Delayed<ThrownEgg> thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, serverLevel, itemInHand, player, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, (float) serverLevel.purpurConfig.eggProjectileOffset); // Purpur - Projectile offset config
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity());
if (event.callEvent() && thrownEgg.attemptSpawn()) {
if (event.shouldConsume()) {
diff --git a/net/minecraft/world/item/EndCrystalItem.java b/net/minecraft/world/item/EndCrystalItem.java
index a1570503d3e4dcc9f1cd0b119ab2e60f7c63b6d8..3c84f7ce77e58ba49567aff5bc718e6928000bc2 100644
--- a/net/minecraft/world/item/EndCrystalItem.java
+++ b/net/minecraft/world/item/EndCrystalItem.java
@@ -24,7 +24,7 @@ public class EndCrystalItem extends Item {
Level level = context.getLevel();
BlockPos clickedPos = context.getClickedPos();
BlockState blockState = level.getBlockState(clickedPos);
- if (!blockState.is(Blocks.OBSIDIAN) && !blockState.is(Blocks.BEDROCK)) {
+ if (!level.purpurConfig.endCrystalPlaceAnywhere && !blockState.is(Blocks.OBSIDIAN) && !blockState.is(Blocks.BEDROCK)) { // Purpur - place end crystal on any block
return InteractionResult.FAIL;
} else {
BlockPos blockPos = clickedPos.above(); final BlockPos aboveBlockPosition = blockPos; // Paper - OBFHELPER
diff --git a/net/minecraft/world/item/EnderpearlItem.java b/net/minecraft/world/item/EnderpearlItem.java
index 3becd19d3264fa631497e967656cc7ca39252586..f824f155549857d5205ba67eb5916e0b0b65efb4 100644
--- a/net/minecraft/world/item/EnderpearlItem.java
+++ b/net/minecraft/world/item/EnderpearlItem.java
@@ -24,7 +24,7 @@ public class EnderpearlItem extends Item {
if (level instanceof ServerLevel serverLevel) {
// CraftBukkit start
// Paper start - PlayerLaunchProjectileEvent
- final Projectile.Delayed<ThrownEnderpearl> thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, serverLevel, itemInHand, player, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, 1.0F);
+ final Projectile.Delayed<ThrownEnderpearl> thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, serverLevel, itemInHand, player, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, (float) serverLevel.purpurConfig.enderPearlProjectileOffset); // Purpur - Projectile offset config
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownEnderpearl.projectile().getBukkitEntity());
if (event.callEvent() && thrownEnderpearl.attemptSpawn()) {
if (event.shouldConsume()) {
@@ -44,6 +44,7 @@ public class EnderpearlItem extends Item {
0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
);
player.awardStat(Stats.ITEM_USED.get(this));
+ player.getCooldowns().addCooldown(itemInHand, player.getAbilities().instabuild ? level.purpurConfig.enderPearlCooldownCreative : level.purpurConfig.enderPearlCooldown); // Purpur - Configurable Ender Pearl cooldown
} else {
if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
serverPlayer.deregisterEnderPearl(thrownEnderpearl.projectile());
diff --git a/net/minecraft/world/item/FireworkRocketItem.java b/net/minecraft/world/item/FireworkRocketItem.java
index 75a9bd205f32b77c5d242cb9fac0f571ce36045a..b03f182c62c699cc222e67c1ae6eadf99c45d48d 100644
--- a/net/minecraft/world/item/FireworkRocketItem.java
+++ b/net/minecraft/world/item/FireworkRocketItem.java
@@ -66,6 +66,19 @@ public class FireworkRocketItem extends Item implements ProjectileItem {
com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Firework) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand));
if (event.callEvent() && delayed.attemptSpawn()) {
player.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below
+
+ // Purpur start - Implement elytra settings
+ if (level.purpurConfig.elytraDamagePerFireworkBoost > 0) {
+ List<net.minecraft.world.entity.EquipmentSlot> list = net.minecraft.world.entity.EquipmentSlot.VALUES.stream().filter((enumitemslot) -> net.minecraft.world.entity.LivingEntity.canGlideUsing(player.getItemBySlot(enumitemslot), enumitemslot)).toList();
+ net.minecraft.world.entity.EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, player.random);
+
+ ItemStack glideItem = player.getItemBySlot(enumitemslot);
+ if (player.canGlide()) {
+ glideItem.hurtAndBreak(level.purpurConfig.elytraDamagePerFireworkBoost, player, enumitemslot);
+ }
+ }
+ // Purpur end - Implement elytra settings
+
if (event.shouldConsume() && !player.hasInfiniteMaterials()) {
itemInHand.shrink(1); // Moved up from below
} else {
diff --git a/net/minecraft/world/item/HangingEntityItem.java b/net/minecraft/world/item/HangingEntityItem.java
index fb301e4ba8faecf9c3b14e48fe9a8008a43f8cea..a11ef537a220fc16a4cea2fda221b649e74cea30 100644
--- a/net/minecraft/world/item/HangingEntityItem.java
+++ b/net/minecraft/world/item/HangingEntityItem.java
@@ -62,6 +62,7 @@ public class HangingEntityItem extends Item {
CustomData customData = itemInHand.getOrDefault(DataComponents.ENTITY_DATA, CustomData.EMPTY);
if (!customData.isEmpty()) {
EntityType.updateCustomEntityTag(level, player, hangingEntity, customData);
+ if (!level.purpurConfig.persistentDroppableEntityDisplayNames) hangingEntity.setCustomName(null); // Purpur - Apply display names from item forms of entities to entities and vice versa
}
if (hangingEntity.survives()) {
diff --git a/net/minecraft/world/item/HoeItem.java b/net/minecraft/world/item/HoeItem.java
index 4966b6cfb46c01691e087c466e636f58a65f45a7..acef797b884f6072ada4b9d5af53daf13273edca 100644
--- a/net/minecraft/world/item/HoeItem.java
+++ b/net/minecraft/world/item/HoeItem.java
@@ -46,15 +46,25 @@ public class HoeItem extends DiggerItem {
public InteractionResult useOn(UseOnContext context) {
Level level = context.getLevel();
BlockPos clickedPos = context.getClickedPos();
- Pair<Predicate<UseOnContext>, Consumer<UseOnContext>> pair = TILLABLES.get(level.getBlockState(clickedPos).getBlock());
- if (pair == null) {
+ // Purpur start - Tool actionable options
+ Block clickedBlock = level.getBlockState(clickedPos).getBlock();
+ org.purpurmc.purpur.tool.Tillable tillable = level.purpurConfig.hoeTillables.get(clickedBlock);
+ if (tillable == null) {
return InteractionResult.PASS;
} else {
- Predicate<UseOnContext> predicate = pair.getFirst();
- Consumer<UseOnContext> consumer = pair.getSecond();
+ Predicate<UseOnContext> predicate = tillable.condition().predicate();
+ Consumer<UseOnContext> consumer = (ctx) -> {
+ level.setBlock(clickedPos, tillable.into().defaultBlockState(), 11);
+ tillable.drops().forEach((drop, chance) -> {
+ if (level.random.nextDouble() < chance) {
+ Block.popResourceFromFace(level, clickedPos, ctx.getClickedFace(), new ItemStack(drop));
+ }
+ });
+ };
+ // Purpur end - Tool actionable options
if (predicate.test(context)) {
Player player = context.getPlayer();
- level.playSound(player, clickedPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F);
+ if (!TILLABLES.containsKey(clickedBlock)) level.playSound(null, clickedPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - Tool actionable options - force sound
if (!level.isClientSide) {
consumer.accept(context);
if (player != null) {
diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java
index 7fbfbe7777cc66170cc616565a8b94f2081da50f..aa2c00be86f42a6674694a20545399e441b75199 100644
--- a/net/minecraft/world/item/ItemStack.java
+++ b/net/minecraft/world/item/ItemStack.java
@@ -461,6 +461,7 @@ public final class ItemStack implements DataComponentHolder {
serverLevel.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
for (org.bukkit.block.BlockState blockstate : blocks) {
blockstate.update(true, false);
+ ((org.bukkit.craftbukkit.block.CraftBlock) blockstate.getBlock()).getNMS().getBlock().forgetPlacer(); // Purpur - Store placer on Block when placed
}
serverLevel.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
serverLevel.preventPoiUpdated = false;
@@ -486,6 +487,7 @@ public final class ItemStack implements DataComponentHolder {
if (!(block.getBlock() instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // Containers get placed automatically
block.onPlace(serverLevel, newPos, oldBlock, true, context);
}
+ block.getBlock().forgetPlacer(); // Purpur - Store placer on Block when placed
serverLevel.notifyAndUpdatePhysics(newPos, null, oldBlock, block, serverLevel.getBlockState(newPos), updateFlag, net.minecraft.world.level.block.Block.UPDATE_LIMIT); // send null chunk as chunk.k() returns false by this point
}
@@ -627,6 +629,26 @@ public final class ItemStack implements DataComponentHolder {
return this.isDamageableItem() && this.getDamageValue() > 0;
}
+ // Purpur start - Add option to mend the most damaged equipment first
+ public float getDamagePercent() {
+ if (this.has(DataComponents.UNBREAKABLE)) {
+ return 0.0F;
+ }
+
+ final int maxDamage = this.getOrDefault(DataComponents.MAX_DAMAGE, 0);
+ if (maxDamage == 0) {
+ return 0.0F;
+ }
+
+ final int damage = this.getOrDefault(DataComponents.DAMAGE, 0);
+ if (damage == 0) {
+ return 0.0F;
+ }
+
+ return (float) damage / maxDamage;
+ }
+ // Purpur end - Add option to mend the most damaged equipment first
+
public int getDamageValue() {
return Mth.clamp(this.getOrDefault(DataComponents.DAMAGE, Integer.valueOf(0)), 0, this.getMaxDamage());
}
@@ -711,6 +733,14 @@ public final class ItemStack implements DataComponentHolder {
org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent
}
// CraftBukkit end
+
+ // Purpur start - Implement elytra settings
+ if (this.has(DataComponents.GLIDER)) {
+ setDamageValue(this.getMaxDamage() - 1);
+ return;
+ }
+ // Purpur end - Implement elytra settings
+
this.shrink(1);
onBreak.accept(item);
}
@@ -1252,6 +1282,12 @@ public final class ItemStack implements DataComponentHolder {
return !this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).isEmpty();
}
+ // Purpur start - Config to allow unsafe enchants
+ public boolean hasEnchantment(Holder<Enchantment> enchantment) {
+ return this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).getLevel(enchantment) > 0;
+ }
+ // Purpur end - Config to allow unsafe enchants
+
public ItemEnchantments getEnchantments() {
return this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY);
}
diff --git a/net/minecraft/world/item/Items.java b/net/minecraft/world/item/Items.java
index 932565a2a3bf48b30ef53031d92afeb18623d549..d55e5e6deca43c7fddb15657fa5ceaeed8f95c67 100644
--- a/net/minecraft/world/item/Items.java
+++ b/net/minecraft/world/item/Items.java
@@ -367,7 +367,7 @@ public class Items {
public static final Item PURPUR_BLOCK = registerBlock(Blocks.PURPUR_BLOCK);
public static final Item PURPUR_PILLAR = registerBlock(Blocks.PURPUR_PILLAR);
public static final Item PURPUR_STAIRS = registerBlock(Blocks.PURPUR_STAIRS);
- public static final Item SPAWNER = registerBlock(Blocks.SPAWNER);
+ public static final Item SPAWNER = registerBlock(Blocks.SPAWNER, org.purpurmc.purpur.item.SpawnerItem::new, new Item.Properties().rarity(Rarity.EPIC)); // Purpur - Silk touch spawners
public static final Item CREAKING_HEART = registerBlock(Blocks.CREAKING_HEART);
public static final Item CHEST = registerBlock(Blocks.CHEST, properties -> properties.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY));
public static final Item CRAFTING_TABLE = registerBlock(Blocks.CRAFTING_TABLE);
@@ -2010,7 +2010,7 @@ public class Items {
"sweet_berries", createBlockItemWithCustomItemName(Blocks.SWEET_BERRY_BUSH), new Item.Properties().food(Foods.SWEET_BERRIES)
);
public static final Item GLOW_BERRIES = registerItem(
- "glow_berries", createBlockItemWithCustomItemName(Blocks.CAVE_VINES), new Item.Properties().food(Foods.GLOW_BERRIES)
+ "glow_berries", settings -> new org.purpurmc.purpur.item.GlowBerryItem(Blocks.CAVE_VINES, settings.useItemDescriptionPrefix()), new Item.Properties().food(Foods.GLOW_BERRIES) // Purpur - Eating glow berries adds glow effect
);
public static final Item CAMPFIRE = registerBlock(
Blocks.CAMPFIRE, properties -> properties.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY)
diff --git a/net/minecraft/world/item/MapItem.java b/net/minecraft/world/item/MapItem.java
index 8795d54cff569c911e0a535f38a0ec4130f7b4d5..309392d414ecbe60474abd0af534184740951707 100644
--- a/net/minecraft/world/item/MapItem.java
+++ b/net/minecraft/world/item/MapItem.java
@@ -196,6 +196,7 @@ public class MapItem extends Item {
public static void renderBiomePreviewMap(ServerLevel serverLevel, ItemStack stack) {
MapItemSavedData savedData = getSavedData(stack, serverLevel);
if (savedData != null) {
+ savedData.isExplorerMap = true; // Purpur - Explorer Map API
if (serverLevel.dimension() == savedData.dimension) {
int i = 1 << savedData.scale;
int i1 = savedData.centerX;
diff --git a/net/minecraft/world/item/MinecartItem.java b/net/minecraft/world/item/MinecartItem.java
index 620069daba04d48b57fc933328eda77f6ca9333e..0403b9b01994269d394820e8c8710ba1b9808bf0 100644
--- a/net/minecraft/world/item/MinecartItem.java
+++ b/net/minecraft/world/item/MinecartItem.java
@@ -30,8 +30,9 @@ public class MinecartItem extends Item {
BlockPos clickedPos = context.getClickedPos();
BlockState blockState = level.getBlockState(clickedPos);
if (!blockState.is(BlockTags.RAILS)) {
- return InteractionResult.FAIL;
- } else {
+ if (!level.purpurConfig.minecartPlaceAnywhere) return InteractionResult.FAIL; // Purpur - Minecart settings and WASD controls
+ if (blockState.isSolid()) clickedPos = clickedPos.relative(context.getClickedFace());
+ } // else { // Purpur - Minecart settings and WASD controls
ItemStack itemInHand = context.getItemInHand();
RailShape railShape = blockState.getBlock() instanceof BaseRailBlock
? blockState.getValue(((BaseRailBlock)blockState.getBlock()).getShapeProperty())
@@ -72,6 +73,6 @@ public class MinecartItem extends Item {
itemInHand.shrink(1);
return InteractionResult.SUCCESS;
}
- }
+ // } // Purpur - Minecart settings and WASD controls
}
}
diff --git a/net/minecraft/world/item/NameTagItem.java b/net/minecraft/world/item/NameTagItem.java
index 438d98b11275d792f18301c643254dfb733c0dd6..98fa29283994ec50c15591b13331bf9ae87683c6 100644
--- a/net/minecraft/world/item/NameTagItem.java
+++ b/net/minecraft/world/item/NameTagItem.java
@@ -24,6 +24,7 @@ public class NameTagItem extends Item {
LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle();
newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null);
+ if (player.level().purpurConfig.armorstandFixNametags && target instanceof net.minecraft.world.entity.decoration.ArmorStand) target.setCustomNameVisible(true); // Purpur - Set name visible when using a Name Tag on an Armor Stand
if (event.isPersistent() && newEntity instanceof Mob mob) {
// Paper end - Add PlayerNameEntityEvent
mob.setPersistenceRequired();
diff --git a/net/minecraft/world/item/ProjectileWeaponItem.java b/net/minecraft/world/item/ProjectileWeaponItem.java
index d3e45d6ad45b8f0b681d422edd0edd51ca07f252..f20c38c1ff978d00dc0c9810c050506deed44ebd 100644
--- a/net/minecraft/world/item/ProjectileWeaponItem.java
+++ b/net/minecraft/world/item/ProjectileWeaponItem.java
@@ -108,6 +108,8 @@ public abstract class ProjectileWeaponItem extends Item {
abstractArrow.setCritArrow(true);
}
+ abstractArrow.setActualEnchantments(weapon.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting
+
return abstractArrow;
}
diff --git a/net/minecraft/world/item/ShovelItem.java b/net/minecraft/world/item/ShovelItem.java
index 75bbe901e79d9ba3250ed2426a36c1c3363c19c1..e93f35f3bf82994c9e901341c4d6194ef574e3c6 100644
--- a/net/minecraft/world/item/ShovelItem.java
+++ b/net/minecraft/world/item/ShovelItem.java
@@ -47,9 +47,12 @@ public class ShovelItem extends DiggerItem {
BlockState blockState1 = FLATTENABLES.get(blockState.getBlock());
BlockState blockState2 = null;
Runnable afterAction = null; // Paper
+ org.purpurmc.purpur.tool.Flattenable flattenable = level.purpurConfig.shovelFlattenables.get(blockState.getBlock()); // Purpur - Tool actionable options
if (blockState1 != null && level.getBlockState(clickedPos.above()).isAir()) {
- afterAction = () -> level.playSound(player, clickedPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper
- blockState2 = blockState1;
+ // Purpur start - Tool actionable options
+ afterAction = () -> {if (!FLATTENABLES.containsKey(blockState.getBlock())) level.playSound(player, clickedPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F);}; // Paper
+ blockState2 = flattenable.into().defaultBlockState();
+ // Purpur end - Tool actionable options
} else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) {
afterAction = () -> { // Paper
if (!level.isClientSide()) {
diff --git a/net/minecraft/world/item/SnowballItem.java b/net/minecraft/world/item/SnowballItem.java
index 8eec16040fb9ae6bcccbd71bbe93521cdce5ccce..38b82537209449407922491506a7ca6224229ca9 100644
--- a/net/minecraft/world/item/SnowballItem.java
+++ b/net/minecraft/world/item/SnowballItem.java
@@ -26,7 +26,7 @@ public class SnowballItem extends Item implements ProjectileItem {
// CraftBukkit start - moved down
if (level instanceof ServerLevel serverLevel) {
// Paper start - PlayerLaunchProjectileEvent
- final Projectile.Delayed<Snowball> snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, serverLevel, itemInHand, player, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, 1.0F);
+ final Projectile.Delayed<Snowball> snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, serverLevel, itemInHand, player, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, (float) serverLevel.purpurConfig.snowballProjectileOffset); // Purpur - Projectile offset config
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) snowball.projectile().getBukkitEntity());
if (event.callEvent() && snowball.attemptSpawn()) {
player.awardStat(Stats.ITEM_USED.get(this));
diff --git a/net/minecraft/world/item/SpawnEggItem.java b/net/minecraft/world/item/SpawnEggItem.java
index bdb7bfd5212f066517587877a331485afc898cbf..f4728999e41ce01eefe6ce2f359a7c32a268105f 100644
--- a/net/minecraft/world/item/SpawnEggItem.java
+++ b/net/minecraft/world/item/SpawnEggItem.java
@@ -57,6 +57,23 @@ public class SpawnEggItem extends Item {
if (level.getBlockEntity(clickedPos) instanceof Spawner spawner) {
if (level.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation) return InteractionResult.FAIL; // Paper - Allow disabling mob spawner spawn egg transformation
EntityType<?> type = this.getType(level.registryAccess(), itemInHand);
+ // Purpur start - PlayerSetSpawnerTypeWithEggEvent
+ if (spawner instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity) {
+ org.bukkit.block.Block bukkitBlock = level.getWorld().getBlockAt(clickedPos.getX(), clickedPos.getY(), clickedPos.getZ());
+ org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.CreatureSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(type.getName()));
+ if (!event.callEvent()) {
+ return InteractionResult.FAIL;
+ }
+ type = EntityType.getFromBukkitType(event.getEntityType());
+ } else if (spawner instanceof net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity) {
+ org.bukkit.block.Block bukkitBlock = level.getWorld().getBlockAt(clickedPos.getX(), clickedPos.getY(), clickedPos.getZ());
+ org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.TrialSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(type.getName()));
+ if (!event.callEvent()) {
+ return InteractionResult.FAIL;
+ }
+ type = EntityType.getFromBukkitType(event.getEntityType());
+ }
+ // Purpur end - PlayerSetSpawnerTypeWithEggEvent
spawner.setEntityId(type, level.getRandom());
level.sendBlockUpdated(clickedPos, blockState, blockState, 3);
level.gameEvent(context.getPlayer(), GameEvent.BLOCK_CHANGE, clickedPos);
diff --git a/net/minecraft/world/item/ThrowablePotionItem.java b/net/minecraft/world/item/ThrowablePotionItem.java
index 2a72ad6686f28127a85faf02024cc6119fa76c58..6a8d6c9835754d142841a6798b7692e64b237fe5 100644
--- a/net/minecraft/world/item/ThrowablePotionItem.java
+++ b/net/minecraft/world/item/ThrowablePotionItem.java
@@ -23,7 +23,7 @@ public class ThrowablePotionItem extends PotionItem implements ProjectileItem {
ItemStack itemInHand = player.getItemInHand(hand);
if (level instanceof ServerLevel serverLevel) {
// Paper start - PlayerLaunchProjectileEvent
- final Projectile.Delayed<ThrownPotion> thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F);
+ final Projectile.Delayed<ThrownPotion> thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, (float) serverLevel.purpurConfig.throwablePotionProjectileOffset); // Purpur - Projectile offset config
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownPotion.projectile().getBukkitEntity());
if (event.callEvent() && thrownPotion.attemptSpawn()) {
if (event.shouldConsume()) {
diff --git a/net/minecraft/world/item/TridentItem.java b/net/minecraft/world/item/TridentItem.java
index 23284dbeff327d1b8dc89f3a0dc0ee549cec2daa..7ea7db834e7b627a1d7d37ca87cd43eb61408565 100644
--- a/net/minecraft/world/item/TridentItem.java
+++ b/net/minecraft/world/item/TridentItem.java
@@ -90,7 +90,7 @@ public class TridentItem extends Item implements ProjectileItem {
// stack.hurtWithoutBreaking(1, player); // CraftBukkit - moved down
if (tridentSpinAttackStrength == 0.0F) {
Projectile.Delayed<ThrownTrident> tridentDelayed = Projectile.spawnProjectileFromRotationDelayed( // Paper - PlayerLaunchProjectileEvent
- ThrownTrident::new, serverLevel, stack, player, 0.0F, 2.5F, 1.0F
+ ThrownTrident::new, serverLevel, stack, player, 0.0F, 2.5F, (float) serverLevel.purpurConfig.tridentProjectileOffset // Purpur - Projectile offset config
);
// Paper start - PlayerLaunchProjectileEvent
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) tridentDelayed.projectile().getBukkitEntity());
@@ -101,6 +101,9 @@ public class TridentItem extends Item implements ProjectileItem {
return false;
}
ThrownTrident thrownTrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent
+
+ thrownTrident.setActualEnchantments(stack.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting
+
if (event.shouldConsume()) stack.hurtWithoutBreaking(1, player); // Paper - PlayerLaunchProjectileEvent
thrownTrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved
// CraftBukkit end
@@ -130,6 +133,18 @@ public class TridentItem extends Item implements ProjectileItem {
f1 *= tridentSpinAttackStrength / squareRoot;
f2 *= tridentSpinAttackStrength / squareRoot;
org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerRiptideEvent(player, stack, f, f1, f2); // CraftBukkit
+
+ // Purpur start - Implement elytra settings
+ List<EquipmentSlot> list = EquipmentSlot.VALUES.stream().filter((enumitemslot) -> LivingEntity.canGlideUsing(entity.getItemBySlot(enumitemslot), enumitemslot)).toList();
+ if (!list.isEmpty()) {
+ EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, entity.random);
+ ItemStack glideItem = entity.getItemBySlot(enumitemslot);
+ if (glideItem.has(net.minecraft.core.component.DataComponents.GLIDER) && level.purpurConfig.elytraDamagePerTridentBoost > 0) {
+ glideItem.hurtAndBreak(level.purpurConfig.elytraDamagePerTridentBoost, entity, enumitemslot);
+ }
+ }
+ // Purpur end - Implement elytra settings
+
player.push(f, f1, f2);
player.startAutoSpinAttack(20, 8.0F, stack);
if (player.onGround()) {
diff --git a/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java b/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java
index 41e1e076a4567d3d3202cf8e426a1ebb391d85e8..2d710dd3f20cbea06c16f14a558b575b369c6ca2 100644
--- a/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java
+++ b/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java
@@ -20,6 +20,12 @@ public record ClearAllStatusEffectsConsumeEffect() implements ConsumeEffect {
@Override
// CraftBukkit start
public boolean apply(Level level, ItemStack stack, LivingEntity entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) {
+ // Purpur start - Option to toggle milk curing bad omen
+ net.minecraft.world.effect.MobEffectInstance badOmen = entity.getEffect(net.minecraft.world.effect.MobEffects.BAD_OMEN);
+ if (!level.purpurConfig.milkCuresBadOmen && stack.is(net.minecraft.world.item.Items.MILK_BUCKET) && badOmen != null) {
+ return entity.removeAllEffects(cause) && entity.addEffect(badOmen);
+ }
+ // Purpur end - Option to toggle milk curing bad omen
return entity.removeAllEffects(cause);
// CraftBukkit end
}
diff --git a/net/minecraft/world/item/crafting/Ingredient.java b/net/minecraft/world/item/crafting/Ingredient.java
index 879c8fe1f20decc793cfa39e686b61d521bd76ba..e9189347cc145bc6dcca2c66d1bd0f23ea71516d 100644
--- a/net/minecraft/world/item/crafting/Ingredient.java
+++ b/net/minecraft/world/item/crafting/Ingredient.java
@@ -36,6 +36,7 @@ public final class Ingredient implements StackedContents.IngredientInfo<io.paper
// CraftBukkit start
@javax.annotation.Nullable
private java.util.Set<ItemStack> itemStacks; // Paper - Improve exact choice recipe ingredients
+ public Predicate<org.bukkit.inventory.ItemStack> predicate; // Purpur - Add predicate to recipe's ExactChoice ingredient
public boolean isExact() {
return this.itemStacks != null;
@@ -87,6 +88,11 @@ public final class Ingredient implements StackedContents.IngredientInfo<io.paper
if (this.isExact()) {
return this.itemStacks.contains(stack); // Paper - Improve exact choice recipe ingredients (hashing FTW!)
}
+ // Purpur start - Add predicate to recipe's ExactChoice ingredient
+ if (predicate != null) {
+ return predicate.test(stack.asBukkitCopy());
+ }
+ // Purpur end - Add predicate to recipe's ExactChoice ingredient
// CraftBukkit end
return stack.is(this.values);
}
diff --git a/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/net/minecraft/world/item/enchantment/EnchantmentHelper.java
index 2847bf6c00366c6c2ffb1fc4d3030c1c3e4502eb..848d50f1bafdd885d58b50d9314944c372617db2 100644
--- a/net/minecraft/world/item/enchantment/EnchantmentHelper.java
+++ b/net/minecraft/world/item/enchantment/EnchantmentHelper.java
@@ -602,4 +602,58 @@ public class EnchantmentHelper {
interface EnchantmentVisitor {
void accept(Holder<Enchantment> enchantment, int level);
}
+
+ // Purpur start - Enchantment convenience methods
+ public static Holder.Reference<Enchantment> getEnchantmentHolder(ResourceKey<Enchantment> enchantment) {
+ return net.minecraft.server.MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(enchantment);
+ }
+
+ public static int getItemEnchantmentLevel(ResourceKey<Enchantment> enchantment, ItemStack stack) {
+ return getItemEnchantmentLevel(getEnchantmentHolder(enchantment), stack);
+ }
+ // Purpur end - Enchantment convenience methods
+
+ // Purpur start - Add option to mend the most damaged equipment first
+ public static Optional<EnchantedItemInUse> getMostDamagedItemWith(DataComponentType<?> componentType, LivingEntity entity) {
+ ItemStack maxStack = null;
+ EquipmentSlot maxSlot = null;
+ float maxPercent = 0.0F;
+
+ equipmentSlotLoop:
+ for (EquipmentSlot equipmentSlot : EquipmentSlot.values()) {
+ ItemStack stack = entity.getItemBySlot(equipmentSlot);
+
+ // do not even check enchantments for item with lower or equal damage percent
+ float percent = stack.getDamagePercent();
+ if (percent <= maxPercent) {
+ continue;
+ }
+
+ ItemEnchantments itemEnchantments = stack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY);
+
+ for (Entry<Holder<Enchantment>> entry : itemEnchantments.entrySet()) {
+ Enchantment enchantment = entry.getKey().value();
+
+ net.minecraft.core.component.DataComponentMap effects = enchantment.effects();
+ if (!effects.has(componentType)) {
+ // try with another enchantment
+ continue;
+ }
+
+ if (enchantment.matchingSlot(equipmentSlot)) {
+ maxStack = stack;
+ maxSlot = equipmentSlot;
+ maxPercent = percent;
+
+ // check another slot now
+ continue equipmentSlotLoop;
+ }
+ }
+ }
+
+ return maxStack != null
+ ? Optional.of(new EnchantedItemInUse(maxStack, maxSlot, entity))
+ : Optional.empty();
+ }
+ // Purpur end - Add option to mend the most damaged equipment first
}
diff --git a/net/minecraft/world/item/enchantment/ItemEnchantments.java b/net/minecraft/world/item/enchantment/ItemEnchantments.java
index cc87a5026a65192ae1f97b2bd03a918b75df0698..e10f1ffd7b3f17b5c0b6655c0b3edf21e938952a 100644
--- a/net/minecraft/world/item/enchantment/ItemEnchantments.java
+++ b/net/minecraft/world/item/enchantment/ItemEnchantments.java
@@ -32,7 +32,7 @@ public class ItemEnchantments implements TooltipProvider {
private static final java.util.Comparator<Holder<Enchantment>> ENCHANTMENT_ORDER = java.util.Comparator.comparing(Holder::getRegisteredName);
public static final ItemEnchantments EMPTY = new ItemEnchantments(new it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true);
// Paper end
- private static final Codec<Integer> LEVEL_CODEC = Codec.intRange(1, 255);
+ private static final Codec<Integer> LEVEL_CODEC = Codec.intRange(1, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)); // Purpur - Add toggle for enchant level clamping
// Paper start - sort enchantments
private static final Codec<it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<Holder<Enchantment>>> LEVELS_CODEC = Codec.unboundedMap(Enchantment.CODEC, LEVEL_CODEC)
.xmap(m -> {
@@ -65,7 +65,7 @@ public class ItemEnchantments implements TooltipProvider {
for (Entry<Holder<Enchantment>> entry : enchantments.object2IntEntrySet()) {
int intValue = entry.getIntValue();
- if (intValue < 0 || intValue > 255) {
+ if (intValue < 0 || intValue > (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)) { // Purpur - Add toggle for enchant level clamping
throw new IllegalArgumentException("Enchantment " + entry.getKey() + " has invalid level " + intValue);
}
}
@@ -160,13 +160,13 @@ public class ItemEnchantments implements TooltipProvider {
if (level <= 0) {
this.enchantments.removeInt(enchantment);
} else {
- this.enchantments.put(enchantment, Math.min(level, 255));
+ this.enchantments.put(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767))); // Purpur - Add toggle for enchant level clamping
}
}
public void upgrade(Holder<Enchantment> enchantment, int level) {
if (level > 0) {
- this.enchantments.merge(enchantment, Math.min(level, 255), Integer::max);
+ this.enchantments.merge(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)), Integer::max); // Purpur - Add toggle for enchant level clamping
}
}
diff --git a/net/minecraft/world/item/trading/MerchantOffer.java b/net/minecraft/world/item/trading/MerchantOffer.java
index 6c06350751db7543d5bde7723121d9d9dbb79071..455ceb46f1ad99f20bda2c31bc252426dc95719a 100644
--- a/net/minecraft/world/item/trading/MerchantOffer.java
+++ b/net/minecraft/world/item/trading/MerchantOffer.java
@@ -143,7 +143,12 @@ public class MerchantOffer {
}
public void updateDemand() {
- this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962
+ // Purpur start - Configurable minimum demand for trades
+ this.updateDemand(0);
+ }
+ public void updateDemand(int minimumDemand) {
+ this.demand = Math.max(minimumDemand, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962
+ // Purpur end - Configurable minimum demand for trades
}
public ItemStack assemble() {
diff --git a/net/minecraft/world/level/BaseSpawner.java b/net/minecraft/world/level/BaseSpawner.java
index 425fcc7872c6ebd2156be8bea1d516ce7aeeb280..8de482367f3d9d91048b7c85cbaefcda9f9fbcdc 100644
--- a/net/minecraft/world/level/BaseSpawner.java
+++ b/net/minecraft/world/level/BaseSpawner.java
@@ -52,6 +52,7 @@ public abstract class BaseSpawner {
}
public boolean isNearPlayer(Level level, BlockPos pos) {
+ if (level.purpurConfig.spawnerDeactivateByRedstone && level.hasNeighborSignal(pos)) return false; // Purpur - Redstone deactivates spawners
return level.hasNearbyAlivePlayerThatAffectsSpawning(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange); // Paper - Affects Spawning API
}
diff --git a/net/minecraft/world/level/EntityGetter.java b/net/minecraft/world/level/EntityGetter.java
index 892a7c1eb1b321ca6d5ca709142e7feae1220815..7719bc8ff1fbbc67cdf15e1fec28dc9233cea207 100644
--- a/net/minecraft/world/level/EntityGetter.java
+++ b/net/minecraft/world/level/EntityGetter.java
@@ -185,7 +185,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst
default boolean hasNearbyAlivePlayer(double x, double y, double z, double distance) {
for (Player player : this.players()) {
- if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) {
+ if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) { // Purpur - AFK API
double d = player.distanceToSqr(x, y, z);
if (distance < 0.0 || d < distance * distance) {
return true;
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index b831f189bcee62a41a16017c2bb958aeee728984..98cb62d4b79c0918abe139f198c5be118b9133c4 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -174,6 +174,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// Gale end - Gale configuration
public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
public static BlockPos lastPhysicsProblem; // Spigot
private org.spigotmc.TickLimiter entityLimiter;
private org.spigotmc.TickLimiter tileLimiter;
@@ -183,6 +184,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // Gale - Pufferfish - move random tick random
+ // Purpur start - Add adjustable breeding cooldown to config
+ private com.google.common.cache.Cache<BreedingCooldownPair, Object> playerBreedingCooldowns;
+
+ private com.google.common.cache.Cache<BreedingCooldownPair, Object> getNewBreedingCooldownCache() {
+ return com.google.common.cache.CacheBuilder.newBuilder().expireAfterWrite(this.purpurConfig.animalBreedingCooldownSeconds, java.util.concurrent.TimeUnit.SECONDS).build();
+ }
+
+ public void resetBreedingCooldowns() {
+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache();
+ }
+
+ public boolean hasBreedingCooldown(java.util.UUID player, Class<? extends net.minecraft.world.entity.animal.Animal> animalType) { // Purpur
+ return this.playerBreedingCooldowns.getIfPresent(new BreedingCooldownPair(player, animalType)) != null;
+ }
+
+ public void addBreedingCooldown(java.util.UUID player, Class<? extends net.minecraft.world.entity.animal.Animal> animalType) {
+ this.playerBreedingCooldowns.put(new BreedingCooldownPair(player, animalType), new Object());
+ }
+
+ private static final class BreedingCooldownPair {
+ private final java.util.UUID playerUUID;
+ private final Class<? extends net.minecraft.world.entity.animal.Animal> animalType;
+
+ public BreedingCooldownPair(java.util.UUID playerUUID, Class<? extends net.minecraft.world.entity.animal.Animal> animalType) {
+ this.playerUUID = playerUUID;
+ this.animalType = animalType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BreedingCooldownPair that = (BreedingCooldownPair) o;
+ return playerUUID.equals(that.playerUUID) && animalType.equals(that.animalType);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(playerUUID, animalType);
+ }
+ }
+ // Purpur end - Add adjustable breeding cooldown to config
+
public CraftWorld getWorld() {
return this.world;
}
@@ -860,6 +904,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName()); // Spigot
this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
this.galeConfig = galeWorldConfigCreator.apply(this.spigotConfig); // Gale - Gale configuration
+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), env); // Purpur - Purpur config files
+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - Add adjustable breeding cooldown to config
this.generator = gen;
this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
@@ -2131,4 +2177,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return this.id;
}
}
+
+ // Purpur start - Add allow water in end world option
+ public boolean isNether() {
+ return getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER;
+ }
+
+ public boolean isTheEnd() {
+ return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END;
+ }
+ // Purpur end - Add allow water in end world option
}
diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
index 4bee1ba137d078563cedfdd184a8b4603df17487..d3f5242fc66529bf3137da4d505a6cf55e749e43 100644
--- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/net/minecraft/world/level/NaturalSpawner.java
@@ -260,7 +260,7 @@ public final class NaturalSpawner {
mutableBlockPos.set(x, y, z);
double d = x + 0.5;
double d1 = z + 0.5;
- Player nearestPlayer = level.getNearestPlayer(d, y, d1, -1.0, false);
+ Player nearestPlayer = level.getNearestPlayer(d, y, d1, -1.0, level.purpurConfig.mobSpawningIgnoreCreativePlayers); // Purpur - mob spawning option to ignore creative players
if (nearestPlayer != null) {
double d2 = nearestPlayer.distanceToSqr(d, y, d1);
if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn
diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
index 4d183fb445c43621c5ce95edc1af27b6a41f0acb..6030c4eefd77969a1a9251de76d4291dcb0a2092 100644
--- a/net/minecraft/world/level/ServerExplosion.java
+++ b/net/minecraft/world/level/ServerExplosion.java
@@ -317,7 +317,7 @@ public class ServerExplosion implements Explosion {
) {
this.level = level;
this.source = source;
- this.radius = (float) Math.max(radius, 0.0); // CraftBukkit - clamp bad values
+ this.radius = (float) (level == null || level.purpurConfig.explosionClampRadius ? Math.max(radius, 0.0) : radius); // CraftBukkit - clamp bad values // Purpur - Config to remove explosion radius clamp
this.center = center;
this.fire = fire;
this.blockInteraction = blockInteraction;
@@ -647,10 +647,27 @@ public class ServerExplosion implements Explosion {
public void explode() {
// CraftBukkit start
- if (this.radius < 0.1F) {
+ if ((this.level == null || this.level.purpurConfig.explosionClampRadius) && this.radius < 0.1F) { // Purpur - Config to remove explosion radius clamp
return;
}
// CraftBukkit end
+ // Purpur start - add PreExplodeEvents
+ if (this.source != null) {
+ Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z);
+ if(!new org.purpurmc.purpur.event.entity.PreEntityExplodeEvent(this.source.getBukkitEntity(), location, this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, org.bukkit.craftbukkit.CraftExplosionResult.toBukkit(getBlockInteraction())).callEvent()) {
+ this.wasCanceled = true;
+ return;
+ }
+ } else {
+ Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z);
+ org.bukkit.block.Block block = location.getBlock();
+ org.bukkit.block.BlockState blockState = (this.damageSource.causingBlockSnapshot() != null) ? this.damageSource.causingBlockSnapshot() : block.getState();
+ if(!new org.purpurmc.purpur.event.PreBlockExplodeEvent(location.getBlock(), this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, blockState, org.bukkit.craftbukkit.CraftExplosionResult.toBukkit(getBlockInteraction())).callEvent()) {
+ this.wasCanceled = true;
+ return;
+ }
+ }
+ // Purpur end - Add PreExplodeEvents
// Paper start - collision optimisations
this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
diff --git a/net/minecraft/world/level/block/AnvilBlock.java b/net/minecraft/world/level/block/AnvilBlock.java
index a788ebfac915a87df49b31467844fcb087a9b89b..c2eaacb4657d7329cc16e4f3d36fa545c7e4c2b7 100644
--- a/net/minecraft/world/level/block/AnvilBlock.java
+++ b/net/minecraft/world/level/block/AnvilBlock.java
@@ -59,6 +59,53 @@ public class AnvilBlock extends FallingBlock {
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getClockWise());
}
+ // Purpur start - Anvil repair/damage options
+ @Override
+ protected net.minecraft.world.InteractionResult useItemOn(final net.minecraft.world.item.ItemStack stack, final BlockState state, final Level world, final BlockPos pos, final Player player, final net.minecraft.world.InteractionHand hand, final BlockHitResult hit) {
+ if (world.purpurConfig.anvilRepairIngotsAmount > 0 && stack.is(net.minecraft.world.item.Items.IRON_INGOT)) {
+ if (stack.getCount() < world.purpurConfig.anvilRepairIngotsAmount) {
+ // not enough iron ingots, play "error" sound and consume
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ if (state.is(Blocks.DAMAGED_ANVIL)) {
+ world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ } else if (state.is(Blocks.CHIPPED_ANVIL)) {
+ world.setBlock(pos, Blocks.ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ } else if (state.is(Blocks.ANVIL)) {
+ // anvil is already fully repaired, play "error" sound and consume
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(world.purpurConfig.anvilRepairIngotsAmount);
+ }
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_PLACE, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ if (world.purpurConfig.anvilDamageObsidianAmount > 0 && stack.is(net.minecraft.world.item.Items.OBSIDIAN)) {
+ if (stack.getCount() < world.purpurConfig.anvilDamageObsidianAmount) {
+ // not enough obsidian, play "error" sound and consume
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ if (state.is(Blocks.DAMAGED_ANVIL)) {
+ world.destroyBlock(pos, false);
+ } else if (state.is(Blocks.CHIPPED_ANVIL)) {
+ world.setBlock(pos, Blocks.DAMAGED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ } else if (state.is(Blocks.ANVIL)) {
+ world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ }
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(world.purpurConfig.anvilDamageObsidianAmount);
+ }
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_LAND, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+ return net.minecraft.world.InteractionResult.TRY_WITH_EMPTY_HAND;
+ }
+ // Purpur end - Anvil repair/damage options
+
@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
if (!level.isClientSide) {
diff --git a/net/minecraft/world/level/block/AzaleaBlock.java b/net/minecraft/world/level/block/AzaleaBlock.java
index 4555eb8e1be743f83b3f5b57b6e5f3301f9b4ee9..e9d92d29c5b403dd33463e968822e3414e7bf278 100644
--- a/net/minecraft/world/level/block/AzaleaBlock.java
+++ b/net/minecraft/world/level/block/AzaleaBlock.java
@@ -50,6 +50,20 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock {
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+ // Purpur start - Chance for azalea blocks to grow into trees naturally
+ growTree(level, random, pos, state);
+ }
+
+ @Override
+ public void randomTick(net.minecraft.world.level.block.state.BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+ double chance = state.getBlock() == Blocks.FLOWERING_AZALEA ? world.purpurConfig.floweringAzaleaGrowthChance : world.purpurConfig.azaleaGrowthChance;
+ if (chance > 0.0D && world.getMaxLocalRawBrightness(pos.above()) > 9 && random.nextDouble() < chance) {
+ growTree(world, random, pos, state);
+ }
+ }
+
+ private void growTree(ServerLevel level, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) {
+ // Purpur end - Chance for azalea blocks to grow into trees naturally
TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
}
diff --git a/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java b/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java
index 045d85b1267e20ca66ef08eb981f167c64d8c780..e804ca8d6c9c9b8d9f982a970cc3edddf5c03aa1 100644
--- a/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java
+++ b/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java
@@ -39,6 +39,7 @@ public abstract class BaseCoralPlantTypeBlock extends Block implements SimpleWat
}
protected static boolean scanForWater(BlockState state, BlockGetter level, BlockPos pos) {
+ if (!((net.minecraft.world.level.LevelAccessor) level).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - Config to not let coral die
if (state.getValue(WATERLOGGED)) {
return true;
} else {
diff --git a/net/minecraft/world/level/block/BedBlock.java b/net/minecraft/world/level/block/BedBlock.java
index 8c21e8aa4922691fa66cd22d631646c554251bdd..999abf6a27682eed16273c9a73dc9479fc3008c0 100644
--- a/net/minecraft/world/level/block/BedBlock.java
+++ b/net/minecraft/world/level/block/BedBlock.java
@@ -100,7 +100,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock
}
Vec3 center = pos.getCenter();
- level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK);
+ if (level.purpurConfig.bedExplode) level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, (float) level.purpurConfig.bedExplosionPower, level.purpurConfig.bedExplosionFire, level.purpurConfig.bedExplosionEffect); // Purpur - Implement bed explosion options
return InteractionResult.SUCCESS_SERVER;
} else if (state.getValue(OCCUPIED)) {
if (!BedBlock.canSetSpawn(level)) return this.explodeBed(state, level, pos); // Paper - check explode first
@@ -148,7 +148,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock
}
Vec3 center = pos.getCenter();
- level.explode(null, level.damageSources().badRespawnPointExplosion(center).causingBlockSnapshot(blockState), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state
+ if (level.purpurConfig.bedExplode) level.explode(null, level.damageSources().badRespawnPointExplosion(center).causingBlockSnapshot(blockState), null, center, (float) level.purpurConfig.bedExplosionPower, level.purpurConfig.bedExplosionFire, level.purpurConfig.bedExplosionEffect); // CraftBukkit - add state // Purpur - Implement bed explosion options
return InteractionResult.SUCCESS_SERVER;
}
// CraftBukkit end
@@ -169,7 +169,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock
@Override
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
- super.fallOn(level, state, pos, entity, fallDistance * 0.5F);
+ super.fallOn(level, state, pos, entity, fallDistance); // Purpur - Configurable block fall damage modifiers
}
@Override
diff --git a/net/minecraft/world/level/block/BigDripleafBlock.java b/net/minecraft/world/level/block/BigDripleafBlock.java
index 1cc8fd0a273c3a7a75bf1ea522d109bd94718f43..3c7544569edb83e03fc0b9e01fbff9a368110427 100644
--- a/net/minecraft/world/level/block/BigDripleafBlock.java
+++ b/net/minecraft/world/level/block/BigDripleafBlock.java
@@ -261,7 +261,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone
playTiltSound(level, pos, sound);
}
- int _int = DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt);
+ int _int = level.purpurConfig.bigDripleafTiltDelay.getOrDefault(tilt, -1); // Purpur - Big dripleaf tilt delay
if (_int != -1) {
level.scheduleTick(pos, this, _int);
}
diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java
index 79de4c558f7cbeff7e55b6d9ad2644be46d72cd9..706a25c42580c0b7317bc85be5c8f2e66a1ef1e0 100644
--- a/net/minecraft/world/level/block/Block.java
+++ b/net/minecraft/world/level/block/Block.java
@@ -90,6 +90,10 @@ public class Block extends BlockBehaviour implements ItemLike {
public static final int UPDATE_LIMIT = 512;
protected final StateDefinition<Block, BlockState> stateDefinition;
private BlockState defaultBlockState;
+ // Purpur start - Configurable block fall damage modifiers
+ public float fallDamageMultiplier = 1.0F;
+ public float fallDistanceMultiplier = 1.0F;
+ // Purpur end - Configurable block fall damage modifiers
// Paper start - Protect Bedrock and End Portal/Frames from being destroyed
public final boolean isDestroyable() {
return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits ||
@@ -299,7 +303,7 @@ public class Block extends BlockBehaviour implements ItemLike {
event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping
event.callEvent();
for (org.bukkit.inventory.ItemStack drop : event.getDrops()) {
- popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop));
+ popResource(serverLevel, pos, applyLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur - Persistent BlockEntity Lore and DisplayName
}
state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping
block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping
@@ -317,7 +321,7 @@ public class Block extends BlockBehaviour implements ItemLike {
public static void dropResources(BlockState state, LevelAccessor level, BlockPos pos, @Nullable BlockEntity blockEntity) {
if (level instanceof ServerLevel) {
- getDrops(state, (ServerLevel)level, pos, blockEntity).forEach(itemStack -> popResource((ServerLevel)level, pos, itemStack));
+ getDrops(state, (ServerLevel)level, pos, blockEntity).forEach(itemStack -> popResource((ServerLevel)level, pos, applyLoreFromTile(itemStack, blockEntity))); // Purpur - Persistent BlockEntity Lore and DisplayName
state.spawnAfterBreak((ServerLevel)level, pos, ItemStack.EMPTY, true);
}
}
@@ -329,11 +333,30 @@ public class Block extends BlockBehaviour implements ItemLike {
public static void dropResources(BlockState state, Level level, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) {
// Paper end - Properly handle xp dropping
if (level instanceof ServerLevel) {
- getDrops(state, (ServerLevel)level, pos, blockEntity, entity, tool).forEach(itemStack -> popResource(level, pos, itemStack));
+ getDrops(state, (ServerLevel)level, pos, blockEntity, entity, tool).forEach(itemStack -> popResource(level, pos, applyLoreFromTile(itemStack, blockEntity))); // Purpur - Persistent BlockEntity Lore and DisplayName
state.spawnAfterBreak((ServerLevel) level, pos, tool, dropExperience); // Paper - Properly handle xp dropping
}
}
+ // Purpur start - Persistent BlockEntity Lore and DisplayName
+ private static ItemStack applyLoreFromTile(ItemStack stack, @Nullable BlockEntity blockEntity) {
+ if (stack.getItem() instanceof BlockItem) {
+ if (blockEntity != null && blockEntity.getLevel() instanceof ServerLevel) {
+ net.minecraft.world.item.component.ItemLore lore = blockEntity.getPersistentLore();
+ net.minecraft.core.component.DataComponentPatch.Builder builder = net.minecraft.core.component.DataComponentPatch.builder();
+ if (blockEntity.getLevel().purpurConfig.persistentTileEntityLore && lore != null) {
+ builder.set(net.minecraft.core.component.DataComponents.LORE, lore);
+ }
+ if (!blockEntity.getLevel().purpurConfig.persistentTileEntityDisplayName) {
+ builder.remove(net.minecraft.core.component.DataComponents.CUSTOM_NAME);
+ }
+ stack.applyComponents(builder.build());
+ }
+ }
+ return stack;
+ }
+ // Purpur end - Persistent BlockEntity Lore and DisplayName
+
public static void popResource(Level level, BlockPos pos, ItemStack stack) {
double d = EntityType.ITEM.getHeight() / 2.0;
double d1 = pos.getX() + 0.5 + Mth.nextDouble(level.random, -0.25, 0.25);
@@ -412,7 +435,15 @@ public class Block extends BlockBehaviour implements ItemLike {
}
public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
+ this.placer = placer; // Purpur - Store placer on Block when placed
+ }
+
+ // Purpur start - Store placer on Block when placed
+ @Nullable protected LivingEntity placer = null;
+ public void forgetPlacer() {
+ this.placer = null;
}
+ // Purpur end - Store placer on Block when placed
public boolean isPossibleToRespawnInThis(BlockState state) {
return !state.isSolid() && !state.liquid();
@@ -423,7 +454,7 @@ public class Block extends BlockBehaviour implements ItemLike {
}
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
- entity.causeFallDamage(fallDistance, 1.0F, entity.damageSources().fall());
+ entity.causeFallDamage(fallDistance * fallDistanceMultiplier, fallDamageMultiplier, entity.damageSources().fall()); // Purpur - Configurable block fall damage modifiers
}
public void updateEntityMovementAfterFallOn(BlockGetter level, Entity entity) {
diff --git a/net/minecraft/world/level/block/Blocks.java b/net/minecraft/world/level/block/Blocks.java
index 0401a5e88fe7840ae667748409411ab73888799c..bf047be5b577b0d1bf70458df14618bcfe2d1de2 100644
--- a/net/minecraft/world/level/block/Blocks.java
+++ b/net/minecraft/world/level/block/Blocks.java
@@ -6486,6 +6486,7 @@ public class Blocks {
BlockBehaviour.Properties.of()
.mapColor(MapColor.PLANT)
.forceSolidOff()
+ .randomTicks() // Purpur - Chance for azalea blocks to grow into trees naturally
.instabreak()
.sound(SoundType.AZALEA)
.noOcclusion()
@@ -6497,6 +6498,7 @@ public class Blocks {
BlockBehaviour.Properties.of()
.mapColor(MapColor.PLANT)
.forceSolidOff()
+ .randomTicks() // Purpur - Chance for azalea blocks to grow into trees naturally
.instabreak()
.sound(SoundType.FLOWERING_AZALEA)
.noOcclusion()
diff --git a/net/minecraft/world/level/block/BubbleColumnBlock.java b/net/minecraft/world/level/block/BubbleColumnBlock.java
index 400e213f4a2f8f09198257ce64d528932180edb5..1d907cfa53808464f6cfe631435a3b55030b6f29 100644
--- a/net/minecraft/world/level/block/BubbleColumnBlock.java
+++ b/net/minecraft/world/level/block/BubbleColumnBlock.java
@@ -124,10 +124,10 @@ public class BubbleColumnBlock extends Block implements BucketPickup {
if (blockState.is(Blocks.BUBBLE_COLUMN)) {
return blockState;
} else if (blockState.is(Blocks.SOUL_SAND)) {
- return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(false));
+ return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(org.purpurmc.purpur.PurpurConfig.soulSandBlockReverseBubbleColumnFlow)); // Purpur - Config to reverse bubble column flow
} else {
return blockState.is(Blocks.MAGMA_BLOCK)
- ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(true))
+ ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(!org.purpurmc.purpur.PurpurConfig.magmaBlockReverseBubbleColumnFlow)) // Purpur - Config to reverse bubble column flow
: Blocks.WATER.defaultBlockState();
}
}
diff --git a/net/minecraft/world/level/block/BushBlock.java b/net/minecraft/world/level/block/BushBlock.java
index bc52568bfa56635300266424488e524d77d95e09..8f2eebc60d655d5a2c233e2b931cdca2c6a5e768 100644
--- a/net/minecraft/world/level/block/BushBlock.java
+++ b/net/minecraft/world/level/block/BushBlock.java
@@ -61,4 +61,24 @@ public abstract class BushBlock extends Block {
protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) {
return pathComputationType == PathComputationType.AIR && !this.hasCollision || super.isPathfindable(state, pathComputationType);
}
+
+ // Purpur start - Ability for hoe to replant crops
+ public void playerDestroyAndReplant(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, net.minecraft.world.item.ItemStack itemInHand, net.minecraft.world.level.ItemLike itemToReplant) {
+ player.awardStat(net.minecraft.stats.Stats.BLOCK_MINED.get(this));
+ player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED);
+ java.util.List<net.minecraft.world.item.ItemStack> dropList = Block.getDrops(state, (net.minecraft.server.level.ServerLevel) world, pos, blockEntity, player, itemInHand);
+
+ boolean planted = false;
+ for (net.minecraft.world.item.ItemStack itemToDrop : dropList) {
+ if (!planted && itemToDrop.getItem() == itemToReplant) {
+ world.setBlock(pos, defaultBlockState(), 3);
+ itemToDrop.setCount(itemToDrop.getCount() - 1);
+ planted = true;
+ }
+ Block.popResource(world, pos, itemToDrop);
+ }
+
+ state.spawnAfterBreak((net.minecraft.server.level.ServerLevel) world, pos, itemInHand, true);
+ }
+ // Purpur end - Ability for hoe to replant crops
}
diff --git a/net/minecraft/world/level/block/CactusBlock.java b/net/minecraft/world/level/block/CactusBlock.java
index 8c3aca2430eac2a31d1e0b1137e3324ec5b29a20..079b4c95cf81119ca99daeb159aefca389afed74 100644
--- a/net/minecraft/world/level/block/CactusBlock.java
+++ b/net/minecraft/world/level/block/CactusBlock.java
@@ -21,7 +21,7 @@ import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
-public class CactusBlock extends Block {
+public class CactusBlock extends Block implements BonemealableBlock { // Purpur - bonemealable cactus
public static final MapCodec<CactusBlock> CODEC = simpleCodec(CactusBlock::new);
public static final IntegerProperty AGE = BlockStateProperties.AGE_15;
public static final int MAX_AGE = 15;
@@ -104,7 +104,7 @@ public class CactusBlock extends Block {
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
for (Direction direction : Direction.Plane.HORIZONTAL) {
BlockState blockState = level.getBlockState(pos.relative(direction));
- if (blockState.isSolid() || level.getFluidState(pos.relative(direction)).is(FluidTags.LAVA)) {
+ if ((level.getWorldBorder().world.purpurConfig.cactusBreaksFromSolidNeighbors && blockState.isSolid()) || level.getFluidState(pos.relative(direction)).is(FluidTags.LAVA)) { // Purpur - Cactus breaks from solid neighbors config
return false;
}
}
@@ -128,4 +128,34 @@ public class CactusBlock extends Block {
protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) {
return false;
}
+
+ // Purpur start - bonemealable cactus
+ @Override
+ public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) {
+ if (!((Level) world).purpurConfig.cactusAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false;
+
+ int cactusHeight = 0;
+ while (world.getBlockState(pos.below(cactusHeight)).is(this)) {
+ cactusHeight++;
+ }
+
+ return cactusHeight < ((Level) world).paperConfig().maxGrowthHeight.cactus;
+ }
+
+ @Override
+ public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, BlockState state) {
+ return true;
+ }
+
+ @Override
+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ int cactusHeight = 0;
+ while (world.getBlockState(pos.below(cactusHeight)).is(this)) {
+ cactusHeight++;
+ }
+ for (int i = 0; i <= world.paperConfig().maxGrowthHeight.cactus - cactusHeight; i++) {
+ world.setBlockAndUpdate(pos.above(i), state.setValue(CactusBlock.AGE, 0));
+ }
+ }
+ // Purpur end - bonemealable cactus
}
diff --git a/net/minecraft/world/level/block/CakeBlock.java b/net/minecraft/world/level/block/CakeBlock.java
index f15b56cbfb6540ea26c6a0abdd701b7d7f1a974e..cf6cf43fc3b234cecccf5c7c0cd4571d8cf43099 100644
--- a/net/minecraft/world/level/block/CakeBlock.java
+++ b/net/minecraft/world/level/block/CakeBlock.java
@@ -119,6 +119,7 @@ public class CakeBlock extends Block {
org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, 2 + oldFoodLevel);
if (!event.isCancelled()) {
+ if (player.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) player.burpDelay = player.level().purpurConfig.playerBurpDelay; // Purpur - Burp after eating food fills hunger bar completely
player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 0.1F);
}
diff --git a/net/minecraft/world/level/block/CampfireBlock.java b/net/minecraft/world/level/block/CampfireBlock.java
index 0bf42a5fdf2c606062b9be4443a9a22346159ecb..9dc0c206e325d2818923e56c97d83b24e1161c26 100644
--- a/net/minecraft/world/level/block/CampfireBlock.java
+++ b/net/minecraft/world/level/block/CampfireBlock.java
@@ -141,7 +141,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB
return this.defaultBlockState()
.setValue(WATERLOGGED, Boolean.valueOf(flag))
.setValue(SIGNAL_FIRE, Boolean.valueOf(this.isSmokeSource(level.getBlockState(clickedPos.below()))))
- .setValue(LIT, Boolean.valueOf(!flag))
+ .setValue(LIT, Boolean.valueOf(level.getMinecraftWorld().purpurConfig.campFireLitWhenPlaced && !flag)) // Purpur - Campfire option for lit when placed
.setValue(FACING, context.getHorizontalDirection());
}
diff --git a/net/minecraft/world/level/block/CarvedPumpkinBlock.java b/net/minecraft/world/level/block/CarvedPumpkinBlock.java
index 291107e21a858215821b82d184fcfb54abbc0d97..b05a96f66724cdfb2daf625f943d7e81377cb93f 100644
--- a/net/minecraft/world/level/block/CarvedPumpkinBlock.java
+++ b/net/minecraft/world/level/block/CarvedPumpkinBlock.java
@@ -64,7 +64,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock {
if (blockPatternMatch != null) {
SnowGolem snowGolem = EntityType.SNOW_GOLEM.create(level, EntitySpawnReason.TRIGGERED);
if (snowGolem != null) {
- spawnGolemInWorld(level, blockPatternMatch, snowGolem, blockPatternMatch.getBlock(0, 2, 0).getPos());
+ spawnGolemInWorld(level, blockPatternMatch, snowGolem, blockPatternMatch.getBlock(0, 2, 0).getPos(), this.placer); // Purpur - Summoner API
}
} else {
BlockPattern.BlockPatternMatch blockPatternMatch1 = this.getOrCreateIronGolemFull().find(level, pos);
@@ -72,13 +72,23 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock {
IronGolem ironGolem = EntityType.IRON_GOLEM.create(level, EntitySpawnReason.TRIGGERED);
if (ironGolem != null) {
ironGolem.setPlayerCreated(true);
- spawnGolemInWorld(level, blockPatternMatch1, ironGolem, blockPatternMatch1.getBlock(1, 2, 0).getPos());
+ spawnGolemInWorld(level, blockPatternMatch1, ironGolem, blockPatternMatch1.getBlock(1, 2, 0).getPos(), this.placer); // Purpur - Summoner API
}
}
}
}
private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch patternMatch, Entity golem, BlockPos pos) {
+ // Purpur start - Summoner API
+ spawnGolemInWorld(level, patternMatch, golem, pos, null);
+ }
+ private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch patternMatch, Entity golem, BlockPos pos, net.minecraft.world.entity.LivingEntity placer) {
+ if (golem instanceof SnowGolem snowGolem) {
+ snowGolem.setSummoner(placer == null ? null : placer.getUUID());
+ } else if (golem instanceof IronGolem ironGolem) {
+ ironGolem.setSummoner(placer == null ? null : placer.getUUID());
+ }
+ // Purpur end - Summoner API
// clearPatternBlocks(level, patternMatch); // CraftBukkit - moved down
golem.moveTo(pos.getX() + 0.5, pos.getY() + 0.05, pos.getZ() + 0.5, 0.0F, 0.0F);
if (!level.addFreshEntity(golem, (golem.getType() == EntityType.SNOW_GOLEM) ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_SNOWMAN : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_IRONGOLEM)) {
diff --git a/net/minecraft/world/level/block/CauldronBlock.java b/net/minecraft/world/level/block/CauldronBlock.java
index d58b49e550cfa683f753db2a913fddf307a1bba2..62ee64b97dfb2f1426d43cf1f8b0b0b6ec63b5b1 100644
--- a/net/minecraft/world/level/block/CauldronBlock.java
+++ b/net/minecraft/world/level/block/CauldronBlock.java
@@ -32,8 +32,8 @@ public class CauldronBlock extends AbstractCauldronBlock {
protected static boolean shouldHandlePrecipitation(Level level, Biome.Precipitation precipitation) {
return precipitation == Biome.Precipitation.RAIN
- ? level.getRandom().nextFloat() < 0.05F
- : precipitation == Biome.Precipitation.SNOW && level.getRandom().nextFloat() < 0.1F;
+ ? level.getRandom().nextFloat() < level.purpurConfig.cauldronRainChance // Purpur - Cauldron fill chances
+ : precipitation == Biome.Precipitation.SNOW && level.getRandom().nextFloat() < level.purpurConfig.cauldronPowderSnowChance; // Purpur - Cauldron fill chances
}
@Override
diff --git a/net/minecraft/world/level/block/CaveVinesBlock.java b/net/minecraft/world/level/block/CaveVinesBlock.java
index ff445bb33a97e3fd2ef0f8759e59ef762d0a578f..b7a7a8d6587cc9b32ee23797411b25d7eccd92d4 100644
--- a/net/minecraft/world/level/block/CaveVinesBlock.java
+++ b/net/minecraft/world/level/block/CaveVinesBlock.java
@@ -92,4 +92,11 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines {
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
level.setBlock(pos, state.setValue(BERRIES, Boolean.valueOf(true)), 2);
}
+
+ // Purpur start - cave vines configurable max growth age
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.caveVinesMaxGrowthAge;
+ }
+ // Purpur end - cave vines configurable max growth age
}
diff --git a/net/minecraft/world/level/block/ChangeOverTimeBlock.java b/net/minecraft/world/level/block/ChangeOverTimeBlock.java
index 4f98ae8bd4bfb681883132eddb57cbc5703d7d9e..c84e1c124b733fb918de17340f7e0f57162b8051 100644
--- a/net/minecraft/world/level/block/ChangeOverTimeBlock.java
+++ b/net/minecraft/world/level/block/ChangeOverTimeBlock.java
@@ -51,7 +51,7 @@ public interface ChangeOverTimeBlock<T extends Enum<T>> {
}
float f = (float)(i1 + 1) / (i1 + i + 1);
- float f1 = f * f * this.getChanceModifier();
+ float f1 = level.purpurConfig.disableOxidationProximityPenalty ? this.getChanceModifier() :f * f * this.getChanceModifier();// Purpur - option to disable the copper oxidation proximity penalty
return random.nextFloat() < f1 ? this.getNext(state) : Optional.empty();
}
}
diff --git a/net/minecraft/world/level/block/ChestBlock.java b/net/minecraft/world/level/block/ChestBlock.java
index 4d2a5888695b99fb150e23b7d8c4b4d4a455b8c6..db4b26262e3ee1064f2930377b56f13298c47203 100644
--- a/net/minecraft/world/level/block/ChestBlock.java
+++ b/net/minecraft/world/level/block/ChestBlock.java
@@ -357,6 +357,7 @@ public class ChestBlock extends AbstractChestBlock<ChestBlockEntity> implements
}
public static boolean isBlockedChestByBlock(BlockGetter level, BlockPos pos) {
+ if (level instanceof Level level1 && level1.purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur - Option for chests to open even with a solid block on top
BlockPos blockPos = pos.above();
return level.getBlockState(blockPos).isRedstoneConductor(level, blockPos);
}
diff --git a/net/minecraft/world/level/block/ComposterBlock.java b/net/minecraft/world/level/block/ComposterBlock.java
index c8ffb634df661a6a4520731be725b51480764976..9fb36207ce829d1a8b952017d3b60db015efdaaf 100644
--- a/net/minecraft/world/level/block/ComposterBlock.java
+++ b/net/minecraft/world/level/block/ComposterBlock.java
@@ -241,17 +241,27 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder {
) {
int levelValue = state.getValue(LEVEL);
if (levelValue < 8 && COMPOSTABLES.containsKey(stack.getItem())) {
- if (levelValue < 7 && !level.isClientSide) {
- BlockState blockState = addItem(player, state, level, pos, stack);
- // Paper start - handle cancelled events
- if (blockState == null) {
- return InteractionResult.PASS;
- }
- // Paper end
- level.levelEvent(1500, pos, state != blockState ? 1 : 0);
- player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
- stack.consume(1, player);
+ // Purpur start - sneak to bulk process composter
+ BlockState newState = process(levelValue, player, state, level, pos, stack);
+ if (newState == null) {
+ return InteractionResult.PASS;
+ }
+ if (level.purpurConfig.composterBulkProcess && player.isShiftKeyDown() && newState != state) {
+ BlockState oldState;
+ int oldCount, newCount, oldLevel, newLevel;
+ do {
+ oldState = newState;
+ oldCount = stack.getCount();
+ oldLevel = oldState.getValue(ComposterBlock.LEVEL);
+ newState = process(oldLevel, player, oldState, level, pos, stack);
+ if (newState == null) {
+ return InteractionResult.PASS;
+ }
+ newCount = stack.getCount();
+ newLevel = newState.getValue(ComposterBlock.LEVEL);
+ } while (newCount > 0 && (newCount != oldCount || newLevel != oldLevel || newState != oldState));
}
+ // Purpur end - Sneak to bulk process composter
return InteractionResult.SUCCESS;
} else {
@@ -259,6 +269,25 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder {
}
}
+ // Purpur start - sneak to bulk process composter
+ private static @Nullable BlockState process(int levelValue, Player player, BlockState state, Level level, BlockPos pos, ItemStack stack) {
+ if (levelValue < 7 && !level.isClientSide) {
+ BlockState iblockdata1 = ComposterBlock.addItem(player, state, level, pos, stack);
+ // Paper start - handle cancelled events
+ if (iblockdata1 == null) {
+ return null;
+ }
+ // Paper end
+
+ level.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0);
+ player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
+ stack.consume(1, player);
+ return iblockdata1;
+ }
+ return state;
+ }
+ // Purpur end - Sneak to bulk process composter
+
@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
int levelValue = state.getValue(LEVEL);
diff --git a/net/minecraft/world/level/block/CoralBlock.java b/net/minecraft/world/level/block/CoralBlock.java
index e0be02eaa07d40d0738931383426517d20fe3b0b..b747af5f3c65f4b79a304b0e903f7b824fb03d8d 100644
--- a/net/minecraft/world/level/block/CoralBlock.java
+++ b/net/minecraft/world/level/block/CoralBlock.java
@@ -65,6 +65,7 @@ public class CoralBlock extends Block {
}
protected boolean scanForWater(BlockGetter level, BlockPos pos) {
+ if (!((net.minecraft.world.level.LevelAccessor) level).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - Config to not let coral die
for (Direction direction : Direction.values()) {
FluidState fluidState = level.getFluidState(pos.relative(direction));
if (fluidState.is(FluidTags.WATER)) {
diff --git a/net/minecraft/world/level/block/CropBlock.java b/net/minecraft/world/level/block/CropBlock.java
index bc0969f40814094e42a860a72314fccd1a66fabe..27f0c5c886a3f8b14ef9a00e2aaaabf4bf09c7db 100644
--- a/net/minecraft/world/level/block/CropBlock.java
+++ b/net/minecraft/world/level/block/CropBlock.java
@@ -182,7 +182,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock {
@Override
protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
- if (level instanceof ServerLevel serverLevel && entity instanceof Ravager && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
+ if (level instanceof ServerLevel serverLevel && entity instanceof Ravager && serverLevel.purpurConfig.ravagerGriefableBlocks.contains(serverLevel.getBlockState(pos).getBlock()) && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !serverLevel.purpurConfig.ravagerBypassMobGriefing == !serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Purpur - Configurable ravager griefable blocks list // Purpur - Add mobGriefing bypass to everything affected
serverLevel.destroyBlock(pos, true, entity);
}
@@ -217,4 +217,15 @@ public class CropBlock extends BushBlock implements BonemealableBlock {
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(AGE);
}
+
+ // Purpur start - Ability for hoe to replant crops
+ @Override
+ public void playerDestroy(Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) {
+ if (world.purpurConfig.hoeReplantsCrops && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) {
+ super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, getBaseSeedId());
+ } else {
+ super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp);
+ }
+ }
+ // Purpur end - Ability for hoe to replant crops
}
diff --git a/net/minecraft/world/level/block/DoorBlock.java b/net/minecraft/world/level/block/DoorBlock.java
index eaa735478d808f7279b6ca62c546cc3d357c11d0..46948eefac791f267695f5e8abb45ad6d61beaac 100644
--- a/net/minecraft/world/level/block/DoorBlock.java
+++ b/net/minecraft/world/level/block/DoorBlock.java
@@ -206,6 +206,7 @@ public class DoorBlock extends Block {
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
if (!this.type.canOpenByHand()) {
return InteractionResult.PASS;
+ } else if (requiresRedstone(level, state, pos)) { return InteractionResult.CONSUME; // Purpur - Option to make doors require redstone
} else {
state = state.cycle(OPEN);
level.setBlock(pos, state, 10);
@@ -294,4 +295,18 @@ public class DoorBlock extends Block {
public static boolean isWoodenDoor(BlockState state) {
return state.getBlock() instanceof DoorBlock doorBlock && doorBlock.type().canOpenByHand();
}
+
+ // Purpur start - Option to make doors require redstone
+ public static boolean requiresRedstone(Level level, BlockState state, BlockPos pos) {
+ if (level.purpurConfig.doorRequiresRedstone.contains(state.getBlock())) {
+ // force update client
+ BlockPos otherPos = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN);
+ BlockState otherState = level.getBlockState(otherPos);
+ level.sendBlockUpdated(pos, state, state, 3);
+ level.sendBlockUpdated(otherPos, otherState, otherState, 3);
+ return true;
+ }
+ return false;
+ }
+ // Purpur end - Option to make doors require redstone
}
diff --git a/net/minecraft/world/level/block/DragonEggBlock.java b/net/minecraft/world/level/block/DragonEggBlock.java
index 38f9003f8260eb2f0606cbd53aa30604cdeb48c0..e72c0f252138858f44e423b28d6e26fcab53a17e 100644
--- a/net/minecraft/world/level/block/DragonEggBlock.java
+++ b/net/minecraft/world/level/block/DragonEggBlock.java
@@ -46,6 +46,7 @@ public class DragonEggBlock extends FallingBlock {
}
private void teleport(BlockState state, Level level, BlockPos pos) {
+ if (!level.purpurConfig.dragonEggTeleport) return; // Purpur - Option to disable dragon egg teleporting
WorldBorder worldBorder = level.getWorldBorder();
for (int i = 0; i < 1000; i++) {
diff --git a/net/minecraft/world/level/block/EnchantingTableBlock.java b/net/minecraft/world/level/block/EnchantingTableBlock.java
index b2d7a8f45dc7bb447b7c1af3d4411bf8214aca05..92c75217860f1fca706f4e7105589f0f67ba81f4 100644
--- a/net/minecraft/world/level/block/EnchantingTableBlock.java
+++ b/net/minecraft/world/level/block/EnchantingTableBlock.java
@@ -119,4 +119,18 @@ public class EnchantingTableBlock extends BaseEntityBlock {
protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) {
return false;
}
+
+ // Purpur start - Enchantment Table Persists Lapis
+ @Override
+ public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean moved) {
+ BlockEntity blockEntity = level.getBlockEntity(pos);
+
+ if (level.purpurConfig.enchantmentTableLapisPersists && blockEntity instanceof EnchantingTableBlockEntity enchantmentTable) {
+ net.minecraft.world.Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.LAPIS_LAZULI, enchantmentTable.getLapis()));
+ level.updateNeighbourForOutputSignal(pos, this);
+ }
+
+ super.onRemove(state, level, pos, newState, moved);
+ }
+ // Purpur end - Enchantment Table Persists Lapis
}
diff --git a/net/minecraft/world/level/block/EndGatewayBlock.java b/net/minecraft/world/level/block/EndGatewayBlock.java
index 84a1bd5e40e635962d795506861447851e443eee..54abeb142e119edd1c1d1c263821b95b1f05c388 100644
--- a/net/minecraft/world/level/block/EndGatewayBlock.java
+++ b/net/minecraft/world/level/block/EndGatewayBlock.java
@@ -98,6 +98,13 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal {
org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.END_GATEWAY); // Paper - add portal type
if (!event.callEvent()) return;
// Paper end - call EntityPortalEnterEvent
+ // Purpur start - Add EntityTeleportHinderedEvent
+ if (level.purpurConfig.imposeTeleportRestrictionsOnGateways && (entity.isVehicle() || entity.isPassenger())) {
+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY).callEvent()) {
+ return;
+ }
+ }
+ // Purpur end - Add EntityTeleportHinderedEvent
entity.setAsInsidePortal(this, pos);
TheEndGatewayBlockEntity.triggerCooldown(level, pos, state, theEndGatewayBlockEntity);
}
diff --git a/net/minecraft/world/level/block/EndPortalBlock.java b/net/minecraft/world/level/block/EndPortalBlock.java
index c11366dd69e1c51bdab45c625b07c15ce2e42cb6..0e8dd3a62e9fca4701ee12acebf6c34e1fb3e6a2 100644
--- a/net/minecraft/world/level/block/EndPortalBlock.java
+++ b/net/minecraft/world/level/block/EndPortalBlock.java
@@ -58,6 +58,13 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal {
protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
if (entity.canUsePortal(false)) {
+ // Purpur start - Add EntityTeleportHinderedEvent
+ if (level.purpurConfig.imposeTeleportRestrictionsOnEndPortals && (entity.isVehicle() || entity.isPassenger())) {
+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent()) {
+ return;
+ }
+ }
+ // Purpur end - Add EntityTeleportHinderedEvent
// CraftBukkit start - Entity in portal
org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.ENDER); // Paper - add portal type
level.getCraftServer().getPluginManager().callEvent(event);
diff --git a/net/minecraft/world/level/block/EnderChestBlock.java b/net/minecraft/world/level/block/EnderChestBlock.java
index f5533960708bdbaf2eacefbc7c7c3123b7d26502..17aa27885b4431bf7b98799e02d080b5a0ecbbf1 100644
--- a/net/minecraft/world/level/block/EnderChestBlock.java
+++ b/net/minecraft/world/level/block/EnderChestBlock.java
@@ -85,8 +85,8 @@ public class EnderChestBlock extends AbstractChestBlock<EnderChestBlockEntity> i
enderChestInventory.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations
if (level instanceof ServerLevel serverLevel && player.openMenu(
new SimpleMenuProvider(
- (containerId, playerInventory, player1) -> ChestMenu.threeRows(containerId, playerInventory, enderChestInventory), CONTAINER_TITLE
- )
+ (containerId, playerInventory, player1) -> org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? getEnderChestSixRows(containerId, playerInventory, player, enderChestInventory) : ChestMenu.threeRows(containerId, playerInventory, enderChestInventory), CONTAINER_TITLE
+ ) // Purpur - Barrels and enderchests 6 rows
).isPresent()) {
// Paper end - Fix InventoryOpenEvent cancellation - moved up;
player.awardStat(Stats.OPEN_ENDERCHEST);
@@ -100,6 +100,35 @@ public class EnderChestBlock extends AbstractChestBlock<EnderChestBlockEntity> i
}
}
+ // Purpur start - Barrels and enderchests 6 rows
+ private ChestMenu getEnderChestSixRows(int syncId, net.minecraft.world.entity.player.Inventory inventory, Player player, PlayerEnderChestContainer playerEnderChestContainer) {
+ if (org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) {
+ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkitPlayer = player.getBukkitEntity();
+ if (bukkitPlayer.hasPermission("purpur.enderchest.rows.six")) {
+ player.sixRowEnderchestSlotCount = 54;
+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.five")) {
+ player.sixRowEnderchestSlotCount = 45;
+ return ChestMenu.fiveRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.four")) {
+ player.sixRowEnderchestSlotCount = 36;
+ return ChestMenu.fourRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.three")) {
+ player.sixRowEnderchestSlotCount = 27;
+ return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.two")) {
+ player.sixRowEnderchestSlotCount = 18;
+ return ChestMenu.twoRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.one")) {
+ player.sixRowEnderchestSlotCount = 9;
+ return ChestMenu.oneRow(syncId, inventory, playerEnderChestContainer);
+ }
+ }
+ player.sixRowEnderchestSlotCount = -1;
+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer);
+ }
+ // Purpur end - Barrels and enderchests 6 rows
+
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new EnderChestBlockEntity(pos, state);
diff --git a/net/minecraft/world/level/block/FarmBlock.java b/net/minecraft/world/level/block/FarmBlock.java
index 1fdede769b67cb5d2f9159c779f19e3639bb6ff5..2ff5457300d66378dbbea492deff0136629bc365 100644
--- a/net/minecraft/world/level/block/FarmBlock.java
+++ b/net/minecraft/world/level/block/FarmBlock.java
@@ -112,9 +112,9 @@ public class FarmBlock extends Block {
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
super.fallOn(level, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage.
if (level instanceof ServerLevel serverLevel
- && level.random.nextFloat() < fallDistance - 0.5F
+ && (serverLevel.purpurConfig.farmlandTrampleHeight >= 0D ? fallDistance >= serverLevel.purpurConfig.farmlandTrampleHeight : level.random.nextFloat() < fallDistance - 0.5F) // // Purpur - Configurable farmland trample height
&& entity instanceof LivingEntity
- && (entity instanceof Player || serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))
+ && (entity instanceof Player || serverLevel.purpurConfig.farmlandBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))
&& entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) {
// CraftBukkit start - Interact soil
org.bukkit.event.Cancellable cancellable;
@@ -129,6 +129,27 @@ public class FarmBlock extends Block {
return;
}
+ if (level.purpurConfig.farmlandTramplingDisabled) return; // Purpur - Farmland trampling changes
+ if (level.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof Player)) return; // Purpur - Farmland trampling changes
+
+ // Purpur start - Ability to re-add farmland mechanics from Alpha
+ if (level.purpurConfig.farmlandAlpha) {
+ Block block = level.getBlockState(pos.below()).getBlock();
+ if (block instanceof FenceBlock || block instanceof WallBlock) {
+ return;
+ }
+ }
+ // Purpur end - Ability to re-add farmland mechanics from Alpha
+
+ // Purpur start - Farmland trampling changes
+ if (level.purpurConfig.farmlandTramplingFeatherFalling) {
+ java.util.Iterator<net.minecraft.world.item.ItemStack> armor = ((LivingEntity) entity).getArmorSlots().iterator();
+ if (armor.hasNext() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, armor.next()) >= (int) entity.fallDistance) {
+ return;
+ }
+ }
+ // Purpur end - Farmland trampling changes
+
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) {
return;
}
@@ -177,7 +198,7 @@ public class FarmBlock extends Block {
}
}
- return false;
+ return ((ServerLevel) level).purpurConfig.farmlandGetsMoistFromBelow && level.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur - Allow soil to moisten from water directly under it
// Paper end - Perf: remove abstract block iteration
}
diff --git a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
index 0994f7265322d1f33365a1df0faaffd9df05fcc0..5dc1883ecd6e52666f553f30c150843c99fbef08 100644
--- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
+++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
@@ -34,12 +34,12 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
@Override
public BlockState getStateForPlacement(RandomSource random) {
- return this.defaultBlockState().setValue(AGE, Integer.valueOf(random.nextInt(25)));
+ return this.defaultBlockState().setValue(AGE, Integer.valueOf(random.nextInt(getMaxGrowthAge()))); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
@Override
protected boolean isRandomlyTicking(BlockState state) {
- return state.getValue(AGE) < 25;
+ return state.getValue(AGE) < getMaxGrowthAge(); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
@Override
@@ -55,7 +55,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
} else if (this == Blocks.CAVE_VINES) {
modifier = level.spigotConfig.caveVinesModifier;
}
- if (state.getValue(AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution
+ if (state.getValue(AGE) < getMaxGrowthAge() && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution // Purpur - kelp, cave, weeping, and twisting configurable max growth age
// Spigot end
BlockPos blockPos = pos.relative(this.growthDirection);
if (this.canGrowInto(level.getBlockState(blockPos))) {
@@ -75,11 +75,11 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
}
public BlockState getMaxAgeState(BlockState state) {
- return state.setValue(AGE, Integer.valueOf(25));
+ return state.setValue(AGE, Integer.valueOf(getMaxGrowthAge())); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
public boolean isMaxAge(BlockState state) {
- return state.getValue(AGE) == 25;
+ return state.getValue(AGE) >= getMaxGrowthAge(); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
protected BlockState updateBodyAfterConvertedFromHead(BlockState head, BlockState body) {
@@ -130,13 +130,13 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
BlockPos blockPos = pos.relative(this.growthDirection);
- int min = Math.min(state.getValue(AGE) + 1, 25);
+ int min = Math.min(state.getValue(AGE) + 1, getMaxGrowthAge()); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
int blocksToGrowWhenBonemealed = this.getBlocksToGrowWhenBonemealed(random);
for (int i = 0; i < blocksToGrowWhenBonemealed && this.canGrowInto(level.getBlockState(blockPos)); i++) {
level.setBlockAndUpdate(blockPos, state.setValue(AGE, Integer.valueOf(min)));
blockPos = blockPos.relative(this.growthDirection);
- min = Math.min(min + 1, 25);
+ min = Math.min(min + 1, getMaxGrowthAge()); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
}
@@ -148,4 +148,6 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
protected GrowingPlantHeadBlock getHeadBlock() {
return this;
}
+
+ public abstract int getMaxGrowthAge(); // Purpur - kelp, cave, weeping, and twisting configurable max growth age
}
diff --git a/net/minecraft/world/level/block/HayBlock.java b/net/minecraft/world/level/block/HayBlock.java
index 3d2ced08c1d40a558e82346672eaee9bf1315971..406e94e4a88921900f59a3a1b65e74a6482c3b8f 100644
--- a/net/minecraft/world/level/block/HayBlock.java
+++ b/net/minecraft/world/level/block/HayBlock.java
@@ -23,6 +23,6 @@ public class HayBlock extends RotatedPillarBlock {
@Override
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
- entity.causeFallDamage(fallDistance, 0.2F, level.damageSources().fall());
+ super.fallOn(level, state, pos, entity, fallDistance); // Purpur - Configurable block fall damage modifiers
}
}
diff --git a/net/minecraft/world/level/block/IceBlock.java b/net/minecraft/world/level/block/IceBlock.java
index be7141a4009036bcf3f92bba5d0ad74459e99bfa..a4d735a4365fdaf9e602315aa1176dfd5db77ff5 100644
--- a/net/minecraft/world/level/block/IceBlock.java
+++ b/net/minecraft/world/level/block/IceBlock.java
@@ -40,7 +40,7 @@ public class IceBlock extends HalfTransparentBlock {
public void afterDestroy(Level level, BlockPos pos, ItemStack stack) {
// Paper end - Improve Block#breakNaturally API
if (!EnchantmentHelper.hasTag(stack, EnchantmentTags.PREVENTS_ICE_MELTING)) {
- if (level.dimensionType().ultraWarm()) {
+ if (level.isNether() || (level.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - Add allow water in end world option
level.removeBlock(pos, false);
return;
}
@@ -65,7 +65,7 @@ public class IceBlock extends HalfTransparentBlock {
return;
}
// CraftBukkit end
- if (level.dimensionType().ultraWarm()) {
+ if (level.isNether() || (level.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - Add allow water in end world option
level.removeBlock(pos, false);
} else {
level.setBlockAndUpdate(pos, meltsInto());
diff --git a/net/minecraft/world/level/block/KelpBlock.java b/net/minecraft/world/level/block/KelpBlock.java
index 72fc688eba837246ae9eb89ce3fa8e621774c37c..af4532f50eacb32a3ae1fc62ea6d564c127d8691 100644
--- a/net/minecraft/world/level/block/KelpBlock.java
+++ b/net/minecraft/world/level/block/KelpBlock.java
@@ -72,4 +72,11 @@ public class KelpBlock extends GrowingPlantHeadBlock implements LiquidBlockConta
protected FluidState getFluidState(BlockState state) {
return Fluids.WATER.getSource(false);
}
+
+ // Purpur start - kelp vines configurable max growth age
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.kelpMaxGrowthAge;
+ }
+ // Purpur end - kelp vines configurable max growth age
}
diff --git a/net/minecraft/world/level/block/LiquidBlock.java b/net/minecraft/world/level/block/LiquidBlock.java
index 19d1906e9d4e92ff49a833bca03a7308ee8059e3..47a7ce88bf4d26408545dcc061aa763311af0dc9 100644
--- a/net/minecraft/world/level/block/LiquidBlock.java
+++ b/net/minecraft/world/level/block/LiquidBlock.java
@@ -134,7 +134,7 @@ public class LiquidBlock extends Block implements BucketPickup {
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
- if (this.shouldSpreadLiquid(level, pos, state)) {
+ if (level.purpurConfig.tickFluids && this.shouldSpreadLiquid(level, pos, state)) { // Purpur - Tick fluids config
level.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(level, pos)); // Paper - Configurable speed for water flowing over lava
}
}
@@ -169,7 +169,7 @@ public class LiquidBlock extends Block implements BucketPickup {
BlockState neighborState,
RandomSource random
) {
- if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) {
+ if (level.getWorldBorder().world.purpurConfig.tickFluids && state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { // Purpur - Tick fluids config
scheduledTickAccess.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(level));
}
@@ -178,7 +178,7 @@ public class LiquidBlock extends Block implements BucketPickup {
@Override
protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
- if (this.shouldSpreadLiquid(level, pos, state)) {
+ if (level.purpurConfig.tickFluids && this.shouldSpreadLiquid(level, pos, state)) { // Purpur - Tick fluids config
level.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(level, pos)); // Paper - Configurable speed for water flowing over lava
}
}
diff --git a/net/minecraft/world/level/block/MagmaBlock.java b/net/minecraft/world/level/block/MagmaBlock.java
index db83c3630064a6875b477021a1f78bdf59c4ddc3..bbf8447cf986015f8a2e55f39d7b4f0dd9abcb23 100644
--- a/net/minecraft/world/level/block/MagmaBlock.java
+++ b/net/minecraft/world/level/block/MagmaBlock.java
@@ -28,7 +28,7 @@ public class MagmaBlock extends Block {
@Override
public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
- if (!entity.isSteppingCarefully() && entity instanceof LivingEntity) {
+ if ((!entity.isSteppingCarefully() || level.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity) { // Purpur - Configurable damage settings for magma blocks
entity.hurt(level.damageSources().hotFloor().eventBlockDamager(level, pos), 1.0F); // CraftBukkit
}
diff --git a/net/minecraft/world/level/block/NetherPortalBlock.java b/net/minecraft/world/level/block/NetherPortalBlock.java
index e2eb693b0130513115392cb0cb5a829ede5be8c5..eb659209008209c0930770e5f9671a3d7a4abae6 100644
--- a/net/minecraft/world/level/block/NetherPortalBlock.java
+++ b/net/minecraft/world/level/block/NetherPortalBlock.java
@@ -72,7 +72,7 @@ public class NetherPortalBlock extends Block implements Portal {
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (level.spigotConfig.enableZombiePigmenPortalSpawns && level.dimensionType().natural() // Spigot
&& level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)
- && random.nextInt(2000) < level.getDifficulty().getId()) {
+ && random.nextInt(level.purpurConfig.piglinPortalSpawnModifier) < level.getDifficulty().getId()) { // Purpur - Piglin portal spawn modifier
while (level.getBlockState(pos).is(this)) {
pos = pos.below();
}
@@ -117,6 +117,13 @@ public class NetherPortalBlock extends Block implements Portal {
protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
if (entity.canUsePortal(false)) {
+ // Purpur start - Add EntityTeleportHinderedEvent
+ if (level.purpurConfig.imposeTeleportRestrictionsOnNetherPortals && (entity.isVehicle() || entity.isPassenger())) {
+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL).callEvent()) {
+ return;
+ }
+ }
+ // Purpur end - Add EntityTeleportHinderedEvent
// CraftBukkit start - Entity in portal
org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type
level.getCraftServer().getPluginManager().callEvent(event);
@@ -129,7 +136,7 @@ public class NetherPortalBlock extends Block implements Portal {
@Override
public int getPortalTransitionTime(ServerLevel level, Entity entity) {
return entity instanceof Player player
- ? Math.max(
+ ? player.canPortalInstant ? 1 : Math.max( // Purpur - Add portal permission bypass
0,
level.getGameRules()
.getInt(
diff --git a/net/minecraft/world/level/block/NetherWartBlock.java b/net/minecraft/world/level/block/NetherWartBlock.java
index 52891ddc14ad0e6f46a2f13d044723de9960b42d..4232fda95390be9faa9af6fa21e94deefe05ee69 100644
--- a/net/minecraft/world/level/block/NetherWartBlock.java
+++ b/net/minecraft/world/level/block/NetherWartBlock.java
@@ -16,7 +16,7 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
-public class NetherWartBlock extends BushBlock {
+public class NetherWartBlock extends BushBlock implements BonemealableBlock { // Purpur - bonemealable netherwart
public static final MapCodec<NetherWartBlock> CODEC = simpleCodec(NetherWartBlock::new);
public static final int MAX_AGE = 3;
public static final IntegerProperty AGE = BlockStateProperties.AGE_3;
@@ -70,4 +70,34 @@ public class NetherWartBlock extends BushBlock {
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(AGE);
}
+
+ // Purpur start - Ability for hoe to replant nether warts
+ @Override
+ public void playerDestroy(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) {
+ if (world.purpurConfig.hoeReplantsNetherWarts && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) {
+ super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, Items.NETHER_WART);
+ } else {
+ super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp);
+ }
+ }
+ // Purpur end - Ability for hoe to replant nether warts
+
+ // Purpur start - bonemealable netherwart
+ @Override
+ public boolean isValidBonemealTarget(final net.minecraft.world.level.LevelReader world, final BlockPos pos, final BlockState state) {
+ return ((net.minecraft.world.level.Level) world).purpurConfig.netherWartAffectedByBonemeal && state.getValue(NetherWartBlock.AGE) < 3;
+ }
+
+ @Override
+ public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) {
+ return true;
+ }
+
+ @Override
+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ int i = Math.min(3, state.getValue(NetherWartBlock.AGE) + 1);
+ state = state.setValue(NetherWartBlock.AGE, i);
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit
+ }
+ // Purpur end - bonemealable netherwart
}
diff --git a/net/minecraft/world/level/block/NoteBlock.java b/net/minecraft/world/level/block/NoteBlock.java
index 32d83e3ceceba37a8f76b1cd3591e8afed685141..62db23309d722868e406a52b8e67c23273e36c96 100644
--- a/net/minecraft/world/level/block/NoteBlock.java
+++ b/net/minecraft/world/level/block/NoteBlock.java
@@ -107,7 +107,7 @@ public class NoteBlock extends Block {
}
private void playNote(@Nullable Entity entity, BlockState state, Level level, BlockPos pos) {
- if (state.getValue(INSTRUMENT).worksAboveNoteBlock() || level.getBlockState(pos.above()).isAir()) {
+ if (level.purpurConfig.noteBlockIgnoreAbove || state.getValue(INSTRUMENT).worksAboveNoteBlock() || level.getBlockState(pos.above()).isAir()) { // Purpur - Config to allow Note Block sounds when blocked
level.blockEvent(pos, this, 0, 0);
level.gameEvent(entity, GameEvent.NOTE_BLOCK_PLAY, pos);
}
diff --git a/net/minecraft/world/level/block/ObserverBlock.java b/net/minecraft/world/level/block/ObserverBlock.java
index 9ca4ae8cdf9d88b8b6a6ed5d4c0b306bd0539ffe..42c696aa307516642029646305a4f71a93a56628 100644
--- a/net/minecraft/world/level/block/ObserverBlock.java
+++ b/net/minecraft/world/level/block/ObserverBlock.java
@@ -81,6 +81,7 @@ public class ObserverBlock extends DirectionalBlock {
RandomSource random
) {
if (state.getValue(FACING) == direction && !state.getValue(POWERED)) {
+ if (!level.getWorldBorder().world.purpurConfig.disableObserverClocks || !(neighborState.getBlock() instanceof ObserverBlock) || neighborState.getValue(ObserverBlock.FACING).getOpposite() != direction) // Purpur - Add Option for disable observer clocks
this.startSignal(level, scheduledTickAccess, pos);
}
diff --git a/net/minecraft/world/level/block/PointedDripstoneBlock.java b/net/minecraft/world/level/block/PointedDripstoneBlock.java
index 6a3548ee9f7d31e1fb02a47f4d8b9c1ed0bc2bc6..aa1fd38f26459944f0a08d88e68b989cb54728ed 100644
--- a/net/minecraft/world/level/block/PointedDripstoneBlock.java
+++ b/net/minecraft/world/level/block/PointedDripstoneBlock.java
@@ -197,20 +197,20 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate
@VisibleForTesting
public static void maybeTransferFluid(BlockState state, ServerLevel level, BlockPos pos, float randChance) {
- if (!(randChance > 0.17578125F) || !(randChance > 0.05859375F)) {
+ if (!(randChance > level.purpurConfig.cauldronDripstoneWaterFillChance) || !(randChance > level.purpurConfig.cauldronDripstoneLavaFillChance)) { // Purpur - Cauldron fill chances
if (isStalactiteStartPos(state, level, pos)) {
Optional<PointedDripstoneBlock.FluidInfo> fluidAboveStalactite = getFluidAboveStalactite(level, pos, state);
if (!fluidAboveStalactite.isEmpty()) {
Fluid fluid = fluidAboveStalactite.get().fluid;
float f;
if (fluid == Fluids.WATER) {
- f = 0.17578125F;
+ f = level.purpurConfig.cauldronDripstoneWaterFillChance; // Purpur - Cauldron fill chances
} else {
if (fluid != Fluids.LAVA) {
return;
}
- f = 0.05859375F;
+ f = level.purpurConfig.cauldronDripstoneLavaFillChance; // Purpur - Cauldron fill chances
}
if (!(randChance >= f)) {
diff --git a/net/minecraft/world/level/block/PowderSnowBlock.java b/net/minecraft/world/level/block/PowderSnowBlock.java
index 9c0ded7ae7e3a520704033a866f80743ae85d772..4f3646961beb877520e257e11224c3045467d351 100644
--- a/net/minecraft/world/level/block/PowderSnowBlock.java
+++ b/net/minecraft/world/level/block/PowderSnowBlock.java
@@ -84,7 +84,7 @@ public class PowderSnowBlock extends Block implements BucketPickup {
// CraftBukkit - move down
&& entity.mayInteract(serverLevel, pos)) {
// CraftBukkit start
- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) {
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(serverLevel.purpurConfig.powderSnowBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) {
return;
}
// CraftBukkit end
diff --git a/net/minecraft/world/level/block/PoweredRailBlock.java b/net/minecraft/world/level/block/PoweredRailBlock.java
index f33b53257a231dccf16740f1e78a3cdfa6e70726..4b3d00b085ea5d345b418815c9869f5f8c9fe558 100644
--- a/net/minecraft/world/level/block/PoweredRailBlock.java
+++ b/net/minecraft/world/level/block/PoweredRailBlock.java
@@ -34,7 +34,7 @@ public class PoweredRailBlock extends BaseRailBlock {
}
protected boolean findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean searchForward, int recursionCount) {
- if (recursionCount >= 8) {
+ if (recursionCount >= level.purpurConfig.railActivationRange) { // Purpur - Config for powered rail activation distance
return false;
} else {
int x = pos.getX();
diff --git a/net/minecraft/world/level/block/RespawnAnchorBlock.java b/net/minecraft/world/level/block/RespawnAnchorBlock.java
index f90d1b0a9e4b80a42f450ff49a46765da19ce562..0631e6f0c242a1fc1dfa606a4d60686c31c7eb13 100644
--- a/net/minecraft/world/level/block/RespawnAnchorBlock.java
+++ b/net/minecraft/world/level/block/RespawnAnchorBlock.java
@@ -159,7 +159,7 @@ public class RespawnAnchorBlock extends Block {
};
Vec3 center = pos2.getCenter();
level.explode(
- null, level.damageSources().badRespawnPointExplosion(center).causingBlockSnapshot(blockState), explosionDamageCalculator, center, 5.0F, true, Level.ExplosionInteraction.BLOCK // CraftBukkit - add state
+ null, level.damageSources().badRespawnPointExplosion(center).causingBlockSnapshot(blockState), explosionDamageCalculator, center, (float) level.purpurConfig.respawnAnchorExplosionPower, level.purpurConfig.respawnAnchorExplosionFire, level.purpurConfig.respawnAnchorExplosionEffect // CraftBukkit - add state // Purpur - Implement respawn anchor explosion options
);
}
diff --git a/net/minecraft/world/level/block/SculkShriekerBlock.java b/net/minecraft/world/level/block/SculkShriekerBlock.java
index 3e7c0a4df225f727d927c7a0ddb47e3d898858c3..63ccc1719a89ee43fb53c712bcbaf5947b136621 100644
--- a/net/minecraft/world/level/block/SculkShriekerBlock.java
+++ b/net/minecraft/world/level/block/SculkShriekerBlock.java
@@ -134,7 +134,7 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState()
- .setValue(WATERLOGGED, Boolean.valueOf(context.getLevel().getFluidState(context.getClickedPos()).getType() == Fluids.WATER));
+ .setValue(WATERLOGGED, Boolean.valueOf(context.getLevel().getFluidState(context.getClickedPos()).getType() == Fluids.WATER)).setValue(SculkShriekerBlock.CAN_SUMMON, context.getLevel().purpurConfig.sculkShriekerCanSummonDefault); // Purpur - Config for sculk shrieker can_summon state
}
@Override
diff --git a/net/minecraft/world/level/block/SlabBlock.java b/net/minecraft/world/level/block/SlabBlock.java
index f6e39b69c1cafd9a429386cfd374ee643d258e97..ad57109f2766f44e386cb77fdce53580505efe33 100644
--- a/net/minecraft/world/level/block/SlabBlock.java
+++ b/net/minecraft/world/level/block/SlabBlock.java
@@ -150,4 +150,25 @@ public class SlabBlock extends Block implements SimpleWaterloggedBlock {
return false;
}
}
+
+ // Purpur start - Break individual slabs when sneaking
+ public boolean halfBreak(BlockState state, BlockPos pos, net.minecraft.server.level.ServerPlayer player) {
+ if (state.getValue(SlabBlock.TYPE) != SlabType.DOUBLE) {
+ return false;
+ }
+ net.minecraft.world.phys.HitResult result = player.getRayTrace(16, net.minecraft.world.level.ClipContext.Fluid.NONE);
+ if (result.getType() != net.minecraft.world.phys.HitResult.Type.BLOCK) {
+ return false;
+ }
+ double hitY = result.getLocation().y();
+ int blockY = org.bukkit.util.NumberConversions.floor(hitY);
+ player.level().setBlock(pos, state.setValue(SlabBlock.TYPE, (hitY - blockY > 0.5 || blockY - pos.getY() == 1) ? SlabType.BOTTOM : SlabType.TOP), 3);
+ if (!player.getAbilities().instabuild) {
+ net.minecraft.world.entity.item.ItemEntity item = new net.minecraft.world.entity.item.ItemEntity(player.level(), pos.getX(), pos.getY(), pos.getZ(), new ItemStack(asItem()));
+ item.setDefaultPickUpDelay();
+ player.level().addFreshEntity(item);
+ }
+ return true;
+ }
+ // Purpur end - Break individual slabs when sneaking
}
diff --git a/net/minecraft/world/level/block/SnowLayerBlock.java b/net/minecraft/world/level/block/SnowLayerBlock.java
index dd2bd6afd5e97a9ff3d102546361fc2332525611..b53b210707e32509ee4b3bc8c026608f1beceb06 100644
--- a/net/minecraft/world/level/block/SnowLayerBlock.java
+++ b/net/minecraft/world/level/block/SnowLayerBlock.java
@@ -96,6 +96,7 @@ public class SnowLayerBlock extends Block {
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
BlockState blockState = level.getBlockState(pos.below());
+ if (blockState.is(Blocks.BLUE_ICE) && !level.getWorldBorder().world.purpurConfig.snowOnBlueIce) return false; // Purpur - Add config for snow on blue ice
return !blockState.is(BlockTags.SNOW_LAYER_CANNOT_SURVIVE_ON)
&& (
blockState.is(BlockTags.SNOW_LAYER_CAN_SURVIVE_ON)
diff --git a/net/minecraft/world/level/block/SpawnerBlock.java b/net/minecraft/world/level/block/SpawnerBlock.java
index 635e2608f9148c27d5d632c1618826226eb1d879..1121843e9db120ef02818369a1fe5f487342a49b 100644
--- a/net/minecraft/world/level/block/SpawnerBlock.java
+++ b/net/minecraft/world/level/block/SpawnerBlock.java
@@ -43,6 +43,57 @@ public class SpawnerBlock extends BaseEntityBlock {
);
}
+ // Purpur start - Silk touch spawners
+ @Override
+ public void playerDestroy(Level level, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack stack, boolean includeDrops, boolean dropExp) {
+ if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(level, stack)) {
+ ItemStack item = new ItemStack(Blocks.SPAWNER.asItem());
+
+ net.minecraft.world.level.SpawnData nextSpawnData = blockEntity instanceof SpawnerBlockEntity spawnerBlock ? spawnerBlock.getSpawner().nextSpawnData : null;
+ java.util.Optional<net.minecraft.world.entity.EntityType<?>> type = java.util.Optional.empty();
+ if (nextSpawnData != null) {
+ type = net.minecraft.world.entity.EntityType.by(nextSpawnData.getEntityToSpawn());
+ net.minecraft.world.level.SpawnData.CODEC.encodeStart(net.minecraft.nbt.NbtOps.INSTANCE, nextSpawnData).result().ifPresent(tag -> item.set(net.minecraft.core.component.DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY.update(compoundTag -> compoundTag.put("Purpur.SpawnData", tag))));
+ }
+
+ if (type.isPresent()) {
+ final net.kyori.adventure.text.Component mobName = io.papermc.paper.adventure.PaperAdventure.asAdventure(type.get().getDescription());
+
+ String name = level.purpurConfig.silkTouchSpawnerName;
+ if (name != null && !name.isEmpty() && !name.equals("Monster Spawner")) {
+ net.kyori.adventure.text.Component displayName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName));
+ if (name.startsWith("<reset>")) {
+ displayName = displayName.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false);
+ }
+ item.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName));
+ }
+
+ List<String> lore = level.purpurConfig.silkTouchSpawnerLore;
+ if (lore != null && !lore.isEmpty()) {
+
+ List<Component> loreComponentList = new java.util.ArrayList<>();
+ for (String line : lore) {
+ net.kyori.adventure.text.Component lineComponent = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(line, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName));
+ if (line.startsWith("<reset>")) {
+ lineComponent = lineComponent.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false);
+ }
+ loreComponentList.add(io.papermc.paper.adventure.PaperAdventure.asVanilla(lineComponent));
+ }
+
+ item.set(net.minecraft.core.component.DataComponents.LORE, new net.minecraft.world.item.component.ItemLore(loreComponentList, loreComponentList));
+ }
+ item.set(net.minecraft.core.component.DataComponents.HIDE_ADDITIONAL_TOOLTIP, net.minecraft.util.Unit.INSTANCE);
+ }
+ popResource(level, pos, item);
+ }
+ super.playerDestroy(level, player, pos, state, blockEntity, stack, includeDrops, dropExp);
+ }
+
+ private boolean isSilkTouch(Level level, ItemStack stack) {
+ return stack != null && level.purpurConfig.silkTouchTools.contains(stack.getItem()) && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) >= level.purpurConfig.minimumSilkTouchSpawnerRequire;
+ }
+ // Purpur end - Silk touch spawners
+
@Override
protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
super.spawnAfterBreak(state, level, pos, stack, dropExperience);
@@ -51,6 +102,7 @@ public class SpawnerBlock extends BaseEntityBlock {
@Override
public int getExpDrop(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+ if (level.purpurConfig.silkTouchEnabled && isSilkTouch(level, stack)) return 0; // Purpur - Silk touch spawners
if (dropExperience) {
int i = 15 + level.random.nextInt(15) + level.random.nextInt(15);
// this.popExperience(level, pos, i);
diff --git a/net/minecraft/world/level/block/SpongeBlock.java b/net/minecraft/world/level/block/SpongeBlock.java
index 88e7320a48b08f052ae1cac914ebbcb905baf693..a20bf795d56a8b8aa1536eed887b57957952c714 100644
--- a/net/minecraft/world/level/block/SpongeBlock.java
+++ b/net/minecraft/world/level/block/SpongeBlock.java
@@ -53,8 +53,8 @@ public class SpongeBlock extends Block {
org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(level); // CraftBukkit - Use BlockStateListPopulator
BlockPos.breadthFirstTraversal(
pos,
- 6,
- 65,
+ level.purpurConfig.spongeAbsorptionRadius, // Purpur - Configurable sponge absorption
+ level.purpurConfig.spongeAbsorptionArea, // Purpur - Configurable sponge absorption
(validPos, queueAdder) -> {
for (Direction direction : ALL_DIRECTIONS) {
queueAdder.accept(validPos.relative(direction));
@@ -68,7 +68,7 @@ public class SpongeBlock extends Block {
BlockState blockState = blockList.getBlockState(blockPos);
FluidState fluidState = blockList.getFluidState(blockPos);
// CraftBukkit end
- if (!fluidState.is(FluidTags.WATER)) {
+ if (!fluidState.is(FluidTags.WATER) && (!level.purpurConfig.spongeAbsorbsLava || !fluidState.is(FluidTags.LAVA)) && (!level.purpurConfig.spongeAbsorbsWaterFromMud || !blockState.is(Blocks.MUD))) { // Purpur - Option for sponges to work on lava and mud
return BlockPos.TraversalNodeStatus.SKIP;
} else if (blockState.getBlock() instanceof BucketPickup bucketPickup
&& !bucketPickup.pickupBlock(null, blockList, blockPos, blockState).isEmpty()) { // CraftBukkit
@@ -76,6 +76,10 @@ public class SpongeBlock extends Block {
} else {
if (blockState.getBlock() instanceof LiquidBlock) {
blockList.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit
+ // Purpur start - Option for sponges to work on lava and mud
+ } else if (blockState.is(Blocks.MUD)) {
+ blockList.setBlock(blockPos, Blocks.CLAY.defaultBlockState(), 3);
+ // Purpur end - Option for sponges to work on lava and mud
} else {
if (!blockState.is(Blocks.KELP)
&& !blockState.is(Blocks.KELP_PLANT)
diff --git a/net/minecraft/world/level/block/StonecutterBlock.java b/net/minecraft/world/level/block/StonecutterBlock.java
index 04706b35549d7d9c1684a106ab6bff6de8199bc9..9dc7c3d0d6f8bcbc670fe10e594fa9c5495cffab 100644
--- a/net/minecraft/world/level/block/StonecutterBlock.java
+++ b/net/minecraft/world/level/block/StonecutterBlock.java
@@ -93,4 +93,14 @@ public class StonecutterBlock extends Block {
protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) {
return false;
}
+
+ // Purpur start - Stonecutter damage
+ @Override
+ public void stepOn(Level level, BlockPos pos, BlockState state, net.minecraft.world.entity.Entity entity) {
+ if (level.purpurConfig.stonecutterDamage > 0.0F && entity instanceof net.minecraft.world.entity.LivingEntity) {
+ entity.hurtServer((net.minecraft.server.level.ServerLevel) level, entity.damageSources().stonecutter().eventBlockDamager(level, pos), level.purpurConfig.stonecutterDamage);
+ }
+ super.stepOn(level, pos, state, entity);
+ }
+ // Purpur end - Stonecutter damage
}
diff --git a/net/minecraft/world/level/block/SugarCaneBlock.java b/net/minecraft/world/level/block/SugarCaneBlock.java
index 63d53f9090caca304c7f8c3f9910c57a6bdbb4d5..5a61216cea641f75fd1937d485651f6b6e4b6384 100644
--- a/net/minecraft/world/level/block/SugarCaneBlock.java
+++ b/net/minecraft/world/level/block/SugarCaneBlock.java
@@ -19,7 +19,7 @@ import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
-public class SugarCaneBlock extends Block {
+public class SugarCaneBlock extends Block implements BonemealableBlock { // Purpur - bonemealable sugarcane
public static final MapCodec<SugarCaneBlock> CODEC = simpleCodec(SugarCaneBlock::new);
public static final IntegerProperty AGE = BlockStateProperties.AGE_15;
protected static final float AABB_OFFSET = 6.0F;
@@ -113,4 +113,34 @@ public class SugarCaneBlock extends Block {
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(AGE);
}
+
+ // Purpur start - bonemealable sugarcane
+ @Override
+ public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) {
+ if (!((net.minecraft.world.level.Level) world).purpurConfig.sugarCanAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false;
+
+ int reedHeight = 0;
+ while (world.getBlockState(pos.below(reedHeight)).is(this)) {
+ reedHeight++;
+ }
+
+ return reedHeight < ((net.minecraft.world.level.Level) world).paperConfig().maxGrowthHeight.reeds;
+ }
+
+ @Override
+ public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) {
+ return true;
+ }
+
+ @Override
+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ int reedHeight = 0;
+ while (world.getBlockState(pos.below(reedHeight)).is(this)) {
+ reedHeight++;
+ }
+ for (int i = 0; i <= world.paperConfig().maxGrowthHeight.reeds - reedHeight; i++) {
+ world.setBlockAndUpdate(pos.above(i), state.setValue(SugarCaneBlock.AGE, 0));
+ }
+ }
+ // Purpur end - bonemealable sugarcane
}
diff --git a/net/minecraft/world/level/block/TurtleEggBlock.java b/net/minecraft/world/level/block/TurtleEggBlock.java
index 8a09af26bbccdecd19607da84c11029c3cb9a11e..a3a093d95306baac22e5cf720f5b14f733b548d4 100644
--- a/net/minecraft/world/level/block/TurtleEggBlock.java
+++ b/net/minecraft/world/level/block/TurtleEggBlock.java
@@ -157,7 +157,7 @@ public class TurtleEggBlock extends Block {
private boolean shouldUpdateHatchLevel(Level level) {
float timeOfDay = level.getTimeOfDay(1.0F);
- return timeOfDay < 0.69 && timeOfDay > 0.65 || level.random.nextInt(500) == 0;
+ return timeOfDay < 0.69 && timeOfDay > 0.65 || level.random.nextInt(level.purpurConfig.turtleEggsRandomTickCrackChance) == 0; // Purpur - Turtle eggs random tick crack chance
}
@Override
@@ -192,9 +192,31 @@ public class TurtleEggBlock extends Block {
}
private boolean canDestroyEgg(ServerLevel level, Entity entity) {
- return !(entity instanceof Turtle)
- && !(entity instanceof Bat)
- && entity instanceof LivingEntity
- && (entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING));
+ // Purpur start - Add turtle egg block options
+ if (entity instanceof Turtle || entity instanceof Bat) {
+ return false;
+ }
+ if (level.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof net.minecraft.world.entity.ExperienceOrb) {
+ return true;
+ }
+ if (level.purpurConfig.turtleEggsBreakFromItems && entity instanceof net.minecraft.world.entity.item.ItemEntity) {
+ return true;
+ }
+ if (level.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) {
+ return true;
+ }
+ if (!(entity instanceof LivingEntity)) {
+ return false;
+ }
+ // Purpur start - Option to disable turtle egg trampling with feather falling
+ if (level.purpurConfig.turtleEggsTramplingFeatherFalling) {
+ java.util.Iterator<ItemStack> armor = ((LivingEntity) entity).getArmorSlots().iterator();
+ return !armor.hasNext() || net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, armor.next()) < (int) entity.fallDistance;
+ }
+ // Purpur end - Option to disable turtle egg trampling with feather falling
+ if (entity instanceof Player) return true;
+
+ return level.purpurConfig.turtleEggsBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected
+ // Purpur end - Add turtle egg block options
}
}
diff --git a/net/minecraft/world/level/block/TwistingVinesBlock.java b/net/minecraft/world/level/block/TwistingVinesBlock.java
index 138832bb247f263aa393e9c3d9b5e9c79743c24c..3f7b0e488fd259badced8a2b4b09b4930b3d9573 100644
--- a/net/minecraft/world/level/block/TwistingVinesBlock.java
+++ b/net/minecraft/world/level/block/TwistingVinesBlock.java
@@ -34,4 +34,11 @@ public class TwistingVinesBlock extends GrowingPlantHeadBlock {
protected boolean canGrowInto(BlockState state) {
return NetherVines.isValidGrowthState(state);
}
+
+ // Purpur start - twisting vines configurable max growth age
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.twistingVinesMaxGrowthAge;
+ }
+ // Purpur end - twisting vines configurable max growth age
}
diff --git a/net/minecraft/world/level/block/WeepingVinesBlock.java b/net/minecraft/world/level/block/WeepingVinesBlock.java
index 9d7fa63a699e7042118b29b154c62f406b51861a..92763699d12118ab0a304afbd564fd1c954ee749 100644
--- a/net/minecraft/world/level/block/WeepingVinesBlock.java
+++ b/net/minecraft/world/level/block/WeepingVinesBlock.java
@@ -34,4 +34,11 @@ public class WeepingVinesBlock extends GrowingPlantHeadBlock {
protected boolean canGrowInto(BlockState state) {
return NetherVines.isValidGrowthState(state);
}
+
+ // Purpur start - weeping vines configurable max growth age
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.weepingVinesMaxGrowthAge;
+ }
+ // Purpur end - weeping vines configurable max growth age
}
diff --git a/net/minecraft/world/level/block/WitherSkullBlock.java b/net/minecraft/world/level/block/WitherSkullBlock.java
index dc70aaa8d929c40c5f34c8facc1ad2bff4e98768..31d776ce04a8035977ad82527e90ab3b215940c1 100644
--- a/net/minecraft/world/level/block/WitherSkullBlock.java
+++ b/net/minecraft/world/level/block/WitherSkullBlock.java
@@ -71,6 +71,7 @@ public class WitherSkullBlock extends SkullBlock {
);
witherBoss.yBodyRot = blockPatternMatch.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F;
witherBoss.makeInvulnerable();
+ witherBoss.setSummoner(blockState.getBlock().placer == null ? null : blockState.getBlock().placer.getUUID()); // Purpur - Summoner API
// CraftBukkit start
if (!level.addFreshEntity(witherBoss, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_WITHER)) {
return;
diff --git a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
index ad9f009dd6c86e08c193839479b4b3285afd7dfb..901201c36c6dcd35e32e1da524a06d41e34da11f 100644
--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
@@ -191,6 +191,21 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
}
ItemStack itemStack = furnace.items.get(1);
+ // Purpur start - Furnace uses lava from underneath
+ boolean usedLavaFromUnderneath = false;
+ if (level.purpurConfig.furnaceUseLavaFromUnderneath && !furnace.isLit() && itemStack.isEmpty() && !furnace.items.get(0).isEmpty() && level.getGameTime() % 20 == 0) {
+ BlockPos below = furnace.getBlockPos().below();
+ BlockState belowState = level.getBlockStateIfLoaded(below);
+ if (belowState != null && belowState.is(Blocks.LAVA)) {
+ net.minecraft.world.level.material.FluidState fluidState = belowState.getFluidState();
+ if (fluidState != null && fluidState.isSource()) {
+ level.setBlock(below, Blocks.AIR.defaultBlockState(), 3);
+ itemStack = Items.LAVA_BUCKET.getDefaultInstance();
+ usedLavaFromUnderneath = true;
+ }
+ }
+ }
+ // Purpur end - Furnace uses lava from underneath
ItemStack itemStack1 = furnace.items.get(0);
boolean flag1 = !itemStack1.isEmpty();
boolean flag2 = !itemStack.isEmpty();
@@ -274,6 +289,8 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
if (flag) {
setChanged(level, pos, state);
}
+
+ if (usedLavaFromUnderneath) furnace.items.set(1, ItemStack.EMPTY); // Purpur - Furnace uses lava from underneath
}
private static boolean canBurn(
diff --git a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
index 0f808855f58281578c2758513787f0f7330c9291..9f6063089f0aa3a68d26ae7cfe39379123ab2f47 100644
--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
@@ -55,7 +55,17 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
this.maxStack = i;
}
// CraftBukkit end
- private NonNullList<ItemStack> items = NonNullList.withSize(27, ItemStack.EMPTY);
+ // Purpur start - Barrels and enderchests 6 rows
+ private NonNullList<ItemStack> items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
+ case 6 -> 54;
+ case 5 -> 45;
+ case 4 -> 36;
+ case 2 -> 18;
+ case 1 -> 9;
+ default -> 27;
+ }, ItemStack.EMPTY);
+ // Purpur end - Barrels and enderchests 6 rows
+
public final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() {
@Override
protected void onOpen(Level level, BlockPos pos, BlockState state) {
@@ -107,7 +117,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
@Override
public int getContainerSize() {
- return 27;
+ // Purpur start - Barrels and enderchests 6 rows
+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
+ case 6 -> 54;
+ case 5 -> 45;
+ case 4 -> 36;
+ case 2 -> 18;
+ case 1 -> 9;
+ default -> 27;
+ };
+ // Purpur end - Barrels and enderchests 6 rows
}
@Override
@@ -127,7 +146,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
@Override
protected AbstractContainerMenu createMenu(int id, Inventory player) {
- return ChestMenu.threeRows(id, player, this);
+ // Purpur start - Barrels and enderchests 6 rows
+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
+ case 6 -> ChestMenu.sixRows(id, player, this);
+ case 5 -> ChestMenu.fiveRows(id, player, this);
+ case 4 -> ChestMenu.fourRows(id, player, this);
+ case 2 -> ChestMenu.twoRows(id, player, this);
+ case 1 -> ChestMenu.oneRow(id, player, this);
+ default -> ChestMenu.threeRows(id, player, this);
+ };
+ // Purpur end - Barrels and enderchests 6 rows
}
@Override
diff --git a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
index deef33d96db188cb297f04b581ab29e77e3716a9..80b0feac68813f11dc5cadc5faf413a59ad73e5b 100644
--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
@@ -139,6 +139,16 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
public double getEffectRange() {
if (this.effectRange < 0) {
+ // Purpur start - Beacon Activation Range Configurable
+ if (this.level != null) {
+ switch (this.levels) {
+ case 1: return this.level.purpurConfig.beaconLevelOne;
+ case 2: return this.level.purpurConfig.beaconLevelTwo;
+ case 3: return this.level.purpurConfig.beaconLevelThree;
+ case 4: return this.level.purpurConfig.beaconLevelFour;
+ }
+ }
+ // Purpur end - Beacon Activation Range Configurable
return this.levels * 10 + 10;
} else {
return effectRange;
@@ -168,6 +178,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
int y = pos.getY();
int z = pos.getZ();
BlockPos blockPos;
+ boolean isTintedGlass = false; // Purpur - allow beacon effects when covered by tinted glass
if (blockEntity.lastCheckY < y) {
blockPos = pos;
blockEntity.checkingBeamSections = Lists.newArrayList();
@@ -197,6 +208,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
}
}
} else {
+ if (level.purpurConfig.beaconAllowEffectsWithTintedGlass && blockState.getBlock().equals(Blocks.TINTED_GLASS)) {isTintedGlass = true;} // Purpur - allow beacon effects when covered by tinted glass
if (beaconBeamSection == null || blockState.getLightBlock() >= 15 && !blockState.is(Blocks.BEDROCK)) {
blockEntity.checkingBeamSections.clear();
blockEntity.lastCheckY = height;
@@ -216,7 +228,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
blockEntity.levels = updateBase(level, x, y, z);
}
- if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) {
+ if (blockEntity.levels > 0 && (!blockEntity.beamSections.isEmpty() || (level.purpurConfig.beaconAllowEffectsWithTintedGlass && isTintedGlass))) { // Purpur - allow beacon effects when covered by tinted glass
applyEffects(level, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges
playSound(level, pos, SoundEvents.BEACON_AMBIENT);
}
diff --git a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
index fbde2680b405b9fa7ed2fe70046b77e971b53e48..91d135e6b8cd0bbe0ffa6242d36c2d95e8f3fbab 100644
--- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
@@ -76,7 +76,7 @@ public class BeehiveBlockEntity extends BlockEntity {
"leash",
"UUID"
);
- public static final int MAX_OCCUPANTS = 3;
+ public static final int MAX_OCCUPANTS = org.purpurmc.purpur.PurpurConfig.beeInsideBeeHive; // Purpur - Config to change max number of bees
private static final int MIN_TICKS_BEFORE_REENTERING_HIVE = 400;
private static final int MIN_OCCUPATION_TICKS_NECTAR = 2400;
public static final int MIN_OCCUPATION_TICKS_NECTARLESS = 600;
@@ -154,11 +154,33 @@ public class BeehiveBlockEntity extends BlockEntity {
return list;
}
+ // Purpur start - Stored Bee API
+ public List<Entity> releaseBee(BlockState iblockdata, BeehiveBlockEntity.BeeData data, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) {
+ List<Entity> list = Lists.newArrayList();
+
+ BeehiveBlockEntity.releaseOccupant(this.level, this.worldPosition, iblockdata, data.occupant, list, tileentitybeehive_releasestatus, this.savedFlowerPos, force);
+
+ if (!list.isEmpty()) {
+ stored.remove(data);
+
+ super.setChanged();
+ }
+
+ return list;
+ }
+ // Purpur end - Stored Bee API
+
@VisibleForDebug
public int getOccupantCount() {
return this.stored.size();
}
+ // Purpur start - Stored Bee API
+ public List<BeeData> getStored() {
+ return stored;
+ }
+ // Purpur end - Stored Bee API
+
// Paper start - Add EntityBlockStorage clearEntities
public void clearBees() {
this.stored.clear();
@@ -408,8 +430,8 @@ public class BeehiveBlockEntity extends BlockEntity {
return this.stored.stream().map(BeehiveBlockEntity.BeeData::toOccupant).toList();
}
- static class BeeData {
- private final BeehiveBlockEntity.Occupant occupant;
+ public static class BeeData { // Purpur - make public - Stored Bee API
+ public final BeehiveBlockEntity.Occupant occupant; // Purpur - make public - Stored Bee API
private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts
private int ticksInHive;
diff --git a/net/minecraft/world/level/block/entity/BlockEntity.java b/net/minecraft/world/level/block/entity/BlockEntity.java
index 77618757c0e678532dbab814aceed83f7f1cd892..3fd0f42618e5c2c683335d1d3e0bb74c6d32ef66 100644
--- a/net/minecraft/world/level/block/entity/BlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BlockEntity.java
@@ -84,6 +84,14 @@ public abstract class BlockEntity {
this.persistentDataContainer.putAll((CompoundTag) persistentDataTag);
}
// Paper end - read persistent data container
+
+ // Purpur start - Persistent BlockEntity Lore and DisplayName
+ if (tag.contains("Purpur.persistentLore")) {
+ net.minecraft.world.item.component.ItemLore.CODEC.decode(net.minecraft.nbt.NbtOps.INSTANCE, tag.getCompound("Purpur.persistentLore")).result()
+ .ifPresent(tag1 -> this.persistentLore = tag1.getFirst());
+ }
+ // Purpur end - Persistent BlockEntity Lore and DisplayName
+
}
public final void loadWithComponents(CompoundTag tag, HolderLookup.Provider registries) {
@@ -98,6 +106,15 @@ public abstract class BlockEntity {
this.loadAdditional(tag, registries);
}
+ // Purpur start - Persistent BlockEntity Lore and DisplayName
+ protected void saveAdditional(CompoundTag nbt) {
+ if (this.persistentLore != null) {
+ net.minecraft.world.item.component.ItemLore.CODEC.encodeStart(net.minecraft.nbt.NbtOps.INSTANCE, this.persistentLore).result()
+ .ifPresent(tag -> nbt.put("Purpur.persistentLore", tag));
+ }
+ }
+ // Purpur end - Persistent BlockEntity Lore and DisplayName
+
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
}
@@ -378,4 +395,16 @@ public abstract class BlockEntity {
<T> T getOrDefault(DataComponentType<? extends T> component, T defaultValue);
}
+ // Purpur start - Persistent BlockEntity Lore and DisplayName
+ @Nullable
+ private net.minecraft.world.item.component.ItemLore persistentLore = null;
+
+ public void setPersistentLore(net.minecraft.world.item.component.ItemLore lore) {
+ this.persistentLore = lore;
+ }
+
+ public @org.jetbrains.annotations.Nullable net.minecraft.world.item.component.ItemLore getPersistentLore() {
+ return this.persistentLore;
+ }
+ // Purpur end - Persistent BlockEntity Lore and DisplayName
}
diff --git a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
index 9d80625fc95e4968cf80492dc7ecf1fd27e585b8..00b2d069c6eb26742f0fd74ac1103e428873248a 100644
--- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
@@ -155,7 +155,7 @@ public class ConduitBlockEntity extends BlockEntity {
BlockPos blockPos1 = pos.offset(i, i1, i2x);
BlockState blockState = level.getBlockState(blockPos1);
- for (Block block : VALID_BLOCKS) {
+ for (Block block : level.purpurConfig.conduitBlocks) { // Purpur - Conduit behavior configuration
if (blockState.is(block)) {
positions.add(blockPos1);
}
@@ -170,13 +170,13 @@ public class ConduitBlockEntity extends BlockEntity {
private static void applyEffects(Level level, BlockPos pos, List<BlockPos> positions) {
// CraftBukkit start
- ConduitBlockEntity.applyEffects(level, pos, ConduitBlockEntity.getRange(positions));
+ ConduitBlockEntity.applyEffects(level, pos, ConduitBlockEntity.getRange(positions, level)); // Purpur - Conduit behavior configuration
}
- public static int getRange(List<BlockPos> positions) {
+ public static int getRange(List<BlockPos> positions, Level level) { // Purpur - Conduit behavior configuration
// CraftBukkit end
int size = positions.size();
- int i = size / 7 * 16;
+ int i = size / 7 * level.purpurConfig.conduitDistance; // Purpur - Conduit behavior configuration
// CraftBukkit start
return i;
}
@@ -213,17 +213,17 @@ public class ConduitBlockEntity extends BlockEntity {
blockEntity.destroyTargetUUID = null;
} else if (blockEntity.destroyTarget == null) {
List<LivingEntity> entitiesOfClass = level.getEntitiesOfClass(
- LivingEntity.class, getDestroyRangeAABB(pos), collidedEntity -> collidedEntity instanceof Enemy && collidedEntity.isInWaterOrRain()
+ LivingEntity.class, getDestroyRangeAABB(pos, level), collidedEntity -> collidedEntity instanceof Enemy && collidedEntity.isInWaterOrRain() // Purpur - Conduit behavior configuration
);
if (!entitiesOfClass.isEmpty()) {
blockEntity.destroyTarget = entitiesOfClass.get(level.random.nextInt(entitiesOfClass.size()));
}
- } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), 8.0)) {
+ } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), level.purpurConfig.conduitDamageDistance)) { // Purpur - Conduit behavior configuration
blockEntity.destroyTarget = null;
}
if (damageTarget && blockEntity.destroyTarget != null) { // CraftBukkit
- if (blockEntity.destroyTarget.hurtServer((net.minecraft.server.level.ServerLevel) level, level.damageSources().magic().eventBlockDamager(level, pos), 4.0F)) // CraftBukkit
+ if (blockEntity.destroyTarget.hurtServer((net.minecraft.server.level.ServerLevel) level, level.damageSources().magic().eventBlockDamager(level, pos), level.purpurConfig.conduitDamageAmount)) // CraftBukkit // Purpur - Conduit behavior configuration
level.playSound(
null,
blockEntity.destroyTarget.getX(),
@@ -253,16 +253,22 @@ public class ConduitBlockEntity extends BlockEntity {
}
public static AABB getDestroyRangeAABB(BlockPos pos) {
+ // Purpur start - Conduit behavior configuration
+ return getDestroyRangeAABB(pos, null);
+ }
+
+ private static AABB getDestroyRangeAABB(BlockPos pos, Level level) {
+ // Purpur end - Conduit behavior configuration
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
- return new AABB(x, y, z, x + 1, y + 1, z + 1).inflate(8.0);
+ return new AABB(x, y, z, x + 1, y + 1, z + 1).inflate(level == null ? 8.0 : level.purpurConfig.conduitDamageDistance); // Purpur - Conduit behavior configuration
}
@Nullable
private static LivingEntity findDestroyTarget(Level level, BlockPos pos, UUID targetId) {
List<LivingEntity> entitiesOfClass = level.getEntitiesOfClass(
- LivingEntity.class, getDestroyRangeAABB(pos), collidedEntity -> collidedEntity.getUUID().equals(targetId)
+ LivingEntity.class, getDestroyRangeAABB(pos, level), collidedEntity -> collidedEntity.getUUID().equals(targetId) // Purpur - Conduit behavior configuration
);
return entitiesOfClass.size() == 1 ? entitiesOfClass.get(0) : null;
}
diff --git a/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java b/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java
index 337bbef7e89a46bc4e7d6763db6d1e9ffe5a8c2e..5ced128ad3b9ce5e32a15fd996ef4bd07c72da97 100644
--- a/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java
@@ -28,6 +28,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable
private static final RandomSource RANDOM = RandomSource.create();
@Nullable
private Component name;
+ private int lapis = 0; // Purpur - Enchantment Table Persists Lapis
public EnchantingTableBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.ENCHANTING_TABLE, pos, state);
@@ -39,6 +40,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable
if (this.hasCustomName()) {
tag.putString("CustomName", Component.Serializer.toJson(this.name, registries));
}
+ tag.putInt("Purpur.Lapis", this.lapis); // Purpur - Enchantment Table Persists Lapis
}
@Override
@@ -47,6 +49,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable
if (tag.contains("CustomName", 8)) {
this.name = parseCustomNameSafe(tag.getString("CustomName"), registries);
}
+ this.lapis = tag.getInt("Purpur.Lapis"); // Purpur - Enchantment Table Persists Lapis
}
public static void bookAnimationTick(Level level, BlockPos pos, BlockState state, EnchantingTableBlockEntity enchantingTable) {
@@ -138,4 +141,14 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable
public void removeComponentsFromTag(CompoundTag tag) {
tag.remove("CustomName");
}
+
+ // Purpur start - Enchantment Table Persists Lapis
+ public int getLapis() {
+ return this.lapis;
+ }
+
+ public void setLapis(int lapis) {
+ this.lapis = lapis;
+ }
+ // Purpur end - Enchantment Table Persists Lapis
}
diff --git a/net/minecraft/world/level/block/entity/SignBlockEntity.java b/net/minecraft/world/level/block/entity/SignBlockEntity.java
index a1a537fc373b92b84f003844e3ea328bb91b9a4a..662f53ca5826fb5b68eb4d426f1d9c5d83906eaf 100644
--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java
@@ -163,16 +163,32 @@ public class SignBlockEntity extends BlockEntity {
return this.setText(updater.apply(text), isFrontText);
}
+ // Purpur start - Signs allow color codes
+ private Component translateColors(org.bukkit.entity.Player player, String line, Style style) {
+ if (level.purpurConfig.signAllowColors) {
+ if (player.hasPermission("purpur.sign.color")) line = line.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1");
+ if (player.hasPermission("purpur.sign.style")) line = line.replaceAll("(?i)&([l-or])", "\u00a7$1");
+ if (player.hasPermission("purpur.sign.magic")) line = line.replaceAll("(?i)&([kr])", "\u00a7$1");
+
+ return io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(line));
+ } else {
+ return Component.literal(line).setStyle(style);
+ }
+ }
+ // Purpur end - Signs allow color codes
+
private SignText setMessages(Player player, List<FilteredText> filteredText, SignText text, boolean front) { // CraftBukkit
SignText originalText = text; // CraftBukkit
for (int i = 0; i < filteredText.size(); i++) {
FilteredText filteredText1 = filteredText.get(i);
Style style = text.getMessage(i, player.isTextFilteringEnabled()).getStyle();
+
+ org.bukkit.entity.Player craftPlayer = (org.bukkit.craftbukkit.entity.CraftPlayer) player.getBukkitEntity(); // Purpur - Signs allow color codes
if (player.isTextFilteringEnabled()) {
- text = text.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style)); // Paper - filter sign text to chat only
+ text = text.setMessage(i, translateColors(craftPlayer, net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty()), style)); // Paper - filter sign text to chat only // Purpur - Signs allow color codes
} else {
text = text.setMessage(
- i, Component.literal(filteredText1.raw()).setStyle(style), Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style) // Paper - filter sign text to chat only
+ i, translateColors(craftPlayer, net.minecraft.util.StringUtil.filterText(filteredText1.raw()), style), translateColors(craftPlayer, net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty()), style) // Paper - filter sign text to chat only // Purpur - Signs allow color codes
);
}
}
@@ -311,6 +327,28 @@ public class SignBlockEntity extends BlockEntity {
return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel)level, 2, string, component, level.getServer(), player); // Paper - Fix commands from signs not firing command events
}
+ // Purpur start - Signs allow color codes
+ public ClientboundBlockEntityDataPacket getTranslatedUpdatePacket(boolean filtered, boolean front) {
+ final CompoundTag nbt = new CompoundTag();
+ this.saveAdditional(nbt, this.getLevel().registryAccess());
+ final Component[] lines = front ? frontText.getMessages(filtered) : backText.getMessages(filtered);
+ final String side = front ? "front_text" : "back_text";
+ for (int i = 0; i < 4; i++) {
+ final var component = io.papermc.paper.adventure.PaperAdventure.asAdventure(lines[i]);
+ final String line = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(component);
+ final var text = net.kyori.adventure.text.Component.text(line);
+ final String json = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(text);
+ if (!nbt.contains(side)) nbt.put(side, new CompoundTag());
+ final CompoundTag sideNbt = nbt.getCompound(side);
+ if (!sideNbt.contains("messages")) sideNbt.put("messages", new net.minecraft.nbt.ListTag());
+ final net.minecraft.nbt.ListTag messagesNbt = sideNbt.getList("messages", Tag.TAG_STRING);
+ messagesNbt.set(i, net.minecraft.nbt.StringTag.valueOf(json));
+ }
+ nbt.putString("PurpurEditor", "true");
+ return ClientboundBlockEntityDataPacket.create(this, (blockEntity, registryAccess) -> nbt);
+ }
+ // Purpur end - Signs allow color codes
+
@Override
public ClientboundBlockEntityDataPacket getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
diff --git a/net/minecraft/world/level/block/piston/PistonStructureResolver.java b/net/minecraft/world/level/block/piston/PistonStructureResolver.java
index ad143a92569f5b420ccaa2089758b2fb3b4ab7c5..5a3660e02bc805e9a35a81b8a61f07b3f20b5ba9 100644
--- a/net/minecraft/world/level/block/piston/PistonStructureResolver.java
+++ b/net/minecraft/world/level/block/piston/PistonStructureResolver.java
@@ -81,7 +81,7 @@ public class PistonStructureResolver {
return true;
} else {
int i = 1;
- if (i + this.toPush.size() > 12) {
+ if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit
return false;
} else {
while (isSticky(blockState)) {
@@ -95,7 +95,7 @@ public class PistonStructureResolver {
break;
}
- if (++i + this.toPush.size() > 12) {
+ if (++i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit
return false;
}
}
@@ -140,7 +140,7 @@ public class PistonStructureResolver {
return true;
}
- if (this.toPush.size() >= 12) {
+ if (this.toPush.size() >= this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit
return false;
}
diff --git a/net/minecraft/world/level/chunk/storage/EntityStorage.java b/net/minecraft/world/level/chunk/storage/EntityStorage.java
index 2856206eafddfcbcc1b65408deda40357f43a6f8..dee0c4ac773c47fb87258b6126854d802ab26db8 100644
--- a/net/minecraft/world/level/chunk/storage/EntityStorage.java
+++ b/net/minecraft/world/level/chunk/storage/EntityStorage.java
@@ -106,6 +106,7 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
}
// Paper end - Entity load/save limit per chunk
CompoundTag compoundTag1 = new CompoundTag();
+ if (!entity.canSaveToDisk()) return; // Purpur - Add canSaveToDisk to Entity
if (entity.save(compoundTag1)) {
listTag.add(compoundTag1);
}
diff --git a/net/minecraft/world/level/levelgen/PhantomSpawner.java b/net/minecraft/world/level/levelgen/PhantomSpawner.java
index c792483860d31ce663e7de34e9f79ff46de75b8c..b9fdd5518ee1fe743485abb3557a21101846041f 100644
--- a/net/minecraft/world/level/levelgen/PhantomSpawner.java
+++ b/net/minecraft/world/level/levelgen/PhantomSpawner.java
@@ -43,7 +43,7 @@ public class PhantomSpawner implements CustomSpawner {
int spawnAttemptMaxSeconds = level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds;
this.nextTick += (spawnAttemptMinSeconds + randomSource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20;
// Paper end - Ability to control player's insomnia and phantoms
- if (level.getSkyDarken() < 5 && level.dimensionType().hasSkyLight()) {
+ if (level.getSkyDarken() < level.purpurConfig.phantomSpawnMinSkyDarkness && level.dimensionType().hasSkyLight()) { // Purpur - Add phantom spawning options
return 0;
} else {
int i = 0;
@@ -51,9 +51,9 @@ public class PhantomSpawner implements CustomSpawner {
for (ServerPlayer serverPlayer : level.players()) {
if (!serverPlayer.isSpectator() && (!level.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !serverPlayer.isCreative())) { // Paper - Add phantom creative and insomniac controls
BlockPos blockPos = serverPlayer.blockPosition();
- if (!level.dimensionType().hasSkyLight() || blockPos.getY() >= level.getSeaLevel() && level.canSeeSky(blockPos)) {
+ if (!level.dimensionType().hasSkyLight() || (!level.purpurConfig.phantomSpawnOnlyAboveSeaLevel || blockPos.getY() >= level.getSeaLevel()) && (!level.purpurConfig.phantomSpawnOnlyWithVisibleSky || level.canSeeSky(blockPos))) { // Purpur - Add phantom spawning options
DifficultyInstance currentDifficultyAt = level.getCurrentDifficultyAt(blockPos);
- if (currentDifficultyAt.isHarderThan(randomSource.nextFloat() * 3.0F)) {
+ if (currentDifficultyAt.isHarderThan(randomSource.nextFloat() * (float) level.purpurConfig.phantomSpawnLocalDifficultyChance)) { // Purpur - Add phantom spawning options
ServerStatsCounter stats = serverPlayer.getStats();
int i1 = Mth.clamp(stats.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
int i2 = 24000;
@@ -73,7 +73,7 @@ public class PhantomSpawner implements CustomSpawner {
FluidState fluidState = level.getFluidState(blockPos1);
if (NaturalSpawner.isValidEmptySpawnBlock(level, blockPos1, blockState, fluidState, EntityType.PHANTOM)) {
SpawnGroupData spawnGroupData = null;
- int i3 = 1 + randomSource.nextInt(currentDifficultyAt.getDifficulty().getId() + 1);
+ int i3 = level.purpurConfig.phantomSpawnMinPerAttempt + level.random.nextInt((level.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? currentDifficultyAt.getDifficulty().getId() : level.purpurConfig.phantomSpawnMaxPerAttempt - level.purpurConfig.phantomSpawnMinPerAttempt) + 1); // Purpur - Add phantom spawning options
for (int i4 = 0; i4 < i3; i4++) {
// Paper start - PhantomPreSpawnEvent
diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java
index e9e93a6b0866c816609ff0620d242c31f064d18b..738defb8cbd9c63dc85c479911ebe2f795d0a815 100644
--- a/net/minecraft/world/level/material/FlowingFluid.java
+++ b/net/minecraft/world/level/material/FlowingFluid.java
@@ -232,7 +232,7 @@ public abstract class FlowingFluid extends Fluid {
}
}
- if (i1 >= 2 && this.canConvertToSource(level)) {
+ if (i1 >= this.getRequiredSources(level) && this.canConvertToSource(level)) { // Purpur - Implement infinite liquids
BlockState blockState1 = level.getBlockState(mutableBlockPos.setWithOffset(pos, Direction.DOWN));
FluidState fluidState1 = blockState1.getFluidState();
if (blockState1.isSolid() || this.isSourceBlockOfThisType(fluidState1)) {
@@ -320,6 +320,12 @@ public abstract class FlowingFluid extends Fluid {
protected abstract boolean canConvertToSource(ServerLevel level);
+ // Purpur start - Implement infinite liquids
+ protected int getRequiredSources(Level level) {
+ return 2;
+ }
+ // Purpur end - Implement infinite liquids
+
protected void spreadTo(LevelAccessor level, BlockPos pos, BlockState blockState, Direction direction, FluidState fluidState) {
if (blockState.getBlock() instanceof LiquidBlockContainer liquidBlockContainer) {
liquidBlockContainer.placeLiquid(level, pos, blockState, fluidState);
diff --git a/net/minecraft/world/level/material/LavaFluid.java b/net/minecraft/world/level/material/LavaFluid.java
index 6f135caffb7638c6156f00341aeac12b50cea99d..85629a43f5469a89dd6078d879f475e8212438ec 100644
--- a/net/minecraft/world/level/material/LavaFluid.java
+++ b/net/minecraft/world/level/material/LavaFluid.java
@@ -177,7 +177,7 @@ public abstract class LavaFluid extends FlowingFluid {
@Override
public int getTickDelay(LevelReader level) {
- return level.dimensionType().ultraWarm() ? 10 : 30;
+ return level.dimensionType().ultraWarm() ? level.getWorldBorder().world.purpurConfig.lavaSpeedNether : level.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur - Make lava flow speed configurable
}
@Override
@@ -199,6 +199,13 @@ public abstract class LavaFluid extends FlowingFluid {
level.levelEvent(1501, pos, 0);
}
+ // Purpur start - Implement infinite liquids
+ @Override
+ protected int getRequiredSources(Level level) {
+ return level.purpurConfig.lavaInfiniteRequiredSources;
+ }
+ // Purpur end - Implement infinite liquids
+
@Override
protected boolean canConvertToSource(ServerLevel level) {
return level.getGameRules().getBoolean(GameRules.RULE_LAVA_SOURCE_CONVERSION);
diff --git a/net/minecraft/world/level/material/WaterFluid.java b/net/minecraft/world/level/material/WaterFluid.java
index 56781b47aeddf0c84d64ddf8b1aad7b26730b68c..2e4fed7c27910b6c886f710f33b0841c2a175837 100644
--- a/net/minecraft/world/level/material/WaterFluid.java
+++ b/net/minecraft/world/level/material/WaterFluid.java
@@ -74,6 +74,12 @@ public abstract class WaterFluid extends FlowingFluid {
protected boolean canConvertToSource(ServerLevel level) {
return level.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION);
}
+ // Purpur start - Implement infinite liquids
+ @Override
+ protected int getRequiredSources(Level level) {
+ return level.purpurConfig.waterInfiniteRequiredSources;
+ }
+ // Purpur end - Implement infinite liquids
// Paper start - Add BlockBreakBlockEvent
@Override
protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state, BlockPos source) {
diff --git a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
index 9e6b2bbc1f83d32d0332f036be4f1a0e18b826bf..db6baaa698fe93aba3fbd595158b568badd6cb8a 100644
--- a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
+++ b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
@@ -240,7 +240,7 @@ public class WalkNodeEvaluator extends NodeEvaluator {
if ((node == null || node.costMalus < 0.0F)
&& verticalDeltaLimit > 0
&& (cachedPathType != PathType.FENCE || this.canWalkOverFences())
- && cachedPathType != PathType.UNPASSABLE_RAIL
+ && (this.mob.level().purpurConfig.mobsIgnoreRails || cachedPathType != PathType.UNPASSABLE_RAIL) // Purpur - Config to allow mobs to pathfind over rails
&& cachedPathType != PathType.TRAPDOOR
&& cachedPathType != PathType.POWDER_SNOW) {
node = this.tryJumpOn(x, y, z, verticalDeltaLimit, nodeFloorLevel, direction, pathType, mutableBlockPos);
@@ -493,7 +493,7 @@ public class WalkNodeEvaluator extends NodeEvaluator {
return PathType.TRAPDOOR;
} else if (blockState.is(Blocks.POWDER_SNOW)) {
return PathType.POWDER_SNOW;
- } else if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH)) {
+ } else if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH) || blockState.is(Blocks.STONECUTTER)) { // Purpur - Stonecutter damage
return PathType.DAMAGE_OTHER;
} else if (blockState.is(Blocks.HONEY_BLOCK)) {
return PathType.STICKY_HONEY;
diff --git a/net/minecraft/world/level/portal/PortalShape.java b/net/minecraft/world/level/portal/PortalShape.java
index 710f4570bb45a25f20cf914c640539cfa9c9d31b..19ee66cc966cbd124d8c59bc55586237c2ca5deb 100644
--- a/net/minecraft/world/level/portal/PortalShape.java
+++ b/net/minecraft/world/level/portal/PortalShape.java
@@ -28,7 +28,7 @@ public class PortalShape {
public static final int MAX_WIDTH = 21;
private static final int MIN_HEIGHT = 3;
public static final int MAX_HEIGHT = 21;
- private static final BlockBehaviour.StatePredicate FRAME = (state, level, pos) -> state.is(Blocks.OBSIDIAN);
+ private static final BlockBehaviour.StatePredicate FRAME = (state, level, pos) -> state.is(Blocks.OBSIDIAN) || (org.purpurmc.purpur.PurpurConfig.cryingObsidianValidForPortalFrame && state.is(Blocks.CRYING_OBSIDIAN)); // Purpur - Crying obsidian valid for portal frames
private static final float SAFE_TRAVEL_MAX_ENTITY_XY = 4.0F;
private static final double SAFE_TRAVEL_MAX_VERTICAL_DELTA = 1.0;
private final Direction.Axis axis;
diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
index 3c1c89aade5ff092b880ba1bf1de83f54d3d62cc..681dec447486138088fe5f705ef4fadab531139f 100644
--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
@@ -68,6 +68,7 @@ public class MapItemSavedData extends SavedData {
public final Map<String, MapDecoration> decorations = Maps.newLinkedHashMap();
private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
private int trackedDecorationCount;
+ public boolean isExplorerMap; // Purpur - Explorer Map API
// CraftBukkit start
public final org.bukkit.craftbukkit.map.CraftMapView mapView;
diff --git a/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java b/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java
index c3d3dd9d3be4b824ccfc114243812ef31b46f4dc..b7636b7dcd2cbec3f08029733bb03e4bd0282b9e 100644
--- a/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java
+++ b/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java
@@ -66,6 +66,11 @@ public class EnchantedCountIncreaseFunction extends LootItemConditionalFunction
Entity entity = context.getOptionalParameter(LootContextParams.ATTACKING_ENTITY);
if (entity instanceof LivingEntity livingEntity) {
int enchantmentLevel = EnchantmentHelper.getEnchantmentLevel(this.enchantment, livingEntity);
+ // Purpur start - Add an option to fix MC-3304 projectile looting
+ if (org.purpurmc.purpur.PurpurConfig.fixProjectileLootingTransfer && context.getOptionalParameter(LootContextParams.DIRECT_ATTACKING_ENTITY) instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) {
+ enchantmentLevel = arrow.actualEnchantments.getLevel(this.enchantment);
+ }
+ // Purpur end - Add an option to fix MC-3304 projectile looting
if (enchantmentLevel == 0) {
return stack;
}
diff --git a/net/minecraft/world/phys/AABB.java b/net/minecraft/world/phys/AABB.java
index 85148858db1fd5e9da8bbdde4b0d84110d80e373..c9c6e4e460ad8435f12761704bb9b0284d6aa708 100644
--- a/net/minecraft/world/phys/AABB.java
+++ b/net/minecraft/world/phys/AABB.java
@@ -442,4 +442,10 @@ public class AABB {
center.x - xSize / 2.0, center.y - ySize / 2.0, center.z - zSize / 2.0, center.x + xSize / 2.0, center.y + ySize / 2.0, center.z + zSize / 2.0
);
}
+
+ // Purpur start - Stop squids floating on top of water - tuinity added method
+ public final AABB offsetY(double dy) {
+ return new AABB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ);
+ }
+ // Purpur end - Stop squids floating on top of water
}
diff --git a/org/purpurmc/purpur/PurpurConfig.java b/org/purpurmc/purpur/PurpurConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbfd05509dfc2ee453f847d299b3d261324b6fa9
--- /dev/null
+++ b/org/purpurmc/purpur/PurpurConfig.java
@@ -0,0 +1,581 @@
+package org.purpurmc.purpur;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+import net.kyori.adventure.bossbar.BossBar;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.minecraft.core.Registry;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.entity.EntityDimensions;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.item.enchantment.Enchantment;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockBehaviour;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.purpurmc.purpur.command.PurpurCommand;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import org.purpurmc.purpur.task.TPSBarTask;
+
+@SuppressWarnings("unused")
+public class PurpurConfig {
+ private static final String HEADER = "This is the main configuration file for Purpur.\n"
+ + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n"
+ + "with caution, and make sure you know what each option does before configuring.\n"
+ + "\n"
+ + "If you need help with the configuration or have any questions related to Purpur,\n"
+ + "join us in our Discord guild.\n"
+ + "\n"
+ + "Website: https://purpurmc.org \n"
+ + "Docs: https://purpurmc.org/docs \n";
+ private static File CONFIG_FILE;
+ public static YamlConfiguration config;
+
+ private static Map<String, Command> commands;
+
+ public static int version;
+ static boolean verbose;
+
+ public static void init(File configFile) {
+ CONFIG_FILE = configFile;
+ config = new YamlConfiguration();
+ try {
+ config.load(CONFIG_FILE);
+ } catch (IOException ignore) {
+ } catch (InvalidConfigurationException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.yml, please correct your syntax errors", ex);
+ throw Throwables.propagate(ex);
+ }
+ config.options().header(HEADER);
+ config.options().copyDefaults(true);
+ verbose = getBoolean("verbose", false);
+
+ commands = new HashMap<>();
+ commands.put("purpur", new PurpurCommand("purpur"));
+
+ version = getInt("config-version", 41);
+ set("config-version", 41);
+
+ readConfig(PurpurConfig.class, null);
+
+ Block.BLOCK_STATE_REGISTRY.forEach(BlockBehaviour.BlockStateBase::initCache);
+ }
+
+ protected static void log(String s) {
+ if (verbose) {
+ log(Level.INFO, s);
+ }
+ }
+
+ protected static void log(Level level, String s) {
+ Bukkit.getLogger().log(level, s);
+ }
+
+ public static void registerCommands() {
+ for (Map.Entry<String, Command> entry : commands.entrySet()) {
+ MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Purpur", entry.getValue());
+ }
+ }
+
+ static void readConfig(Class<?> clazz, Object instance) {
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (Modifier.isPrivate(method.getModifiers())) {
+ if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) {
+ try {
+ method.setAccessible(true);
+ method.invoke(instance);
+ } catch (InvocationTargetException ex) {
+ throw Throwables.propagate(ex.getCause());
+ } catch (Exception ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex);
+ }
+ }
+ }
+ }
+
+ try {
+ config.save(CONFIG_FILE);
+ } catch (IOException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex);
+ }
+ }
+
+ private static void set(String path, Object val) {
+ config.addDefault(path, val);
+ config.set(path, val);
+ }
+
+ private static String getString(String path, String def) {
+ config.addDefault(path, def);
+ return config.getString(path, config.getString(path));
+ }
+
+ private static boolean getBoolean(String path, boolean def) {
+ config.addDefault(path, def);
+ return config.getBoolean(path, config.getBoolean(path));
+ }
+
+ private static double getDouble(String path, double def) {
+ config.addDefault(path, def);
+ return config.getDouble(path, config.getDouble(path));
+ }
+
+ private static int getInt(String path, int def) {
+ config.addDefault(path, def);
+ return config.getInt(path, config.getInt(path));
+ }
+
+ private static <T> List<?> getList(String path, T def) {
+ config.addDefault(path, def);
+ return config.getList(path, config.getList(path));
+ }
+
+ static Map<String, Object> getMap(String path, Map<String, Object> def) {
+ if (def != null && config.getConfigurationSection(path) == null) {
+ config.addDefault(path, def);
+ return def;
+ }
+ return toMap(config.getConfigurationSection(path));
+ }
+
+ private static Map<String, Object> toMap(ConfigurationSection section) {
+ ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
+ if (section != null) {
+ for (String key : section.getKeys(false)) {
+ Object obj = section.get(key);
+ if (obj != null) {
+ builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj);
+ }
+ }
+ }
+ return builder.build();
+ }
+
+ public static String cannotRideMob = "<red>You cannot mount that mob";
+ public static String afkBroadcastAway = "<yellow><italic>%s is now AFK";
+ public static String afkBroadcastBack = "<yellow><italic>%s is no longer AFK";
+ public static boolean afkBroadcastUseDisplayName = false;
+ public static String afkTabListPrefix = "[AFK] ";
+ public static String afkTabListSuffix = "";
+ public static String creditsCommandOutput = "<green>%s has been shown the end credits";
+ public static String demoCommandOutput = "<green>%s has been shown the demo screen";
+ public static String pingCommandOutput = "<green>%s's ping is %sms";
+ public static String ramCommandOutput = "<green>Ram Usage: <used>/<xmx> (<percent>)";
+ public static String rambarCommandOutput = "<green>Rambar toggled <onoff> for <target>";
+ public static String tpsbarCommandOutput = "<green>Tpsbar toggled <onoff> for <target>";
+ public static String dontRunWithScissors = "<red><italic>Don't run with scissors!";
+ public static String uptimeCommandOutput = "<green>Server uptime is <uptime>";
+ public static String unverifiedUsername = "default";
+ public static String sleepSkippingNight = "default";
+ public static String sleepingPlayersPercent = "default";
+ public static String sleepNotPossible = "default";
+ private static void messages() {
+ cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob);
+ afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway);
+ afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack);
+ afkBroadcastUseDisplayName = getBoolean("settings.messages.afk-broadcast-use-display-name", afkBroadcastUseDisplayName);
+ afkTabListPrefix = MiniMessage.miniMessage().serialize(MiniMessage.miniMessage().deserialize(getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix)));
+ afkTabListSuffix = MiniMessage.miniMessage().serialize(MiniMessage.miniMessage().deserialize(getString("settings.messages.afk-tab-list-suffix", afkTabListSuffix)));
+ creditsCommandOutput = getString("settings.messages.credits-command-output", creditsCommandOutput);
+ demoCommandOutput = getString("settings.messages.demo-command-output", demoCommandOutput);
+ pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput);
+ ramCommandOutput = getString("settings.messages.ram-command-output", ramCommandOutput);
+ rambarCommandOutput = getString("settings.messages.rambar-command-output", rambarCommandOutput);
+ tpsbarCommandOutput = getString("settings.messages.tpsbar-command-output", tpsbarCommandOutput);
+ dontRunWithScissors = getString("settings.messages.dont-run-with-scissors", dontRunWithScissors);
+ uptimeCommandOutput = getString("settings.messages.uptime-command-output", uptimeCommandOutput);
+ unverifiedUsername = getString("settings.messages.unverified-username", unverifiedUsername);
+ sleepSkippingNight = getString("settings.messages.sleep-skipping-night", sleepSkippingNight);
+ sleepingPlayersPercent = getString("settings.messages.sleeping-players-percent", sleepingPlayersPercent);
+ sleepNotPossible = getString("settings.messages.sleep-not-possible", sleepNotPossible);
+ }
+
+ public static String deathMsgRunWithScissors = "<player> slipped and fell on their shears";
+ public static String deathMsgStonecutter = "<player> has sawed themself in half";
+ private static void deathMessages() {
+ deathMsgRunWithScissors = getString("settings.messages.death-message.run-with-scissors", deathMsgRunWithScissors);
+ deathMsgStonecutter = getString("settings.messages.death-message.stonecutter", deathMsgStonecutter);
+ }
+
+ public static boolean advancementOnlyBroadcastToAffectedPlayer = false;
+ public static boolean deathMessageOnlyBroadcastToAffectedPlayer = false;
+ private static void broadcastSettings() {
+ if (version < 13) {
+ boolean oldValue = getBoolean("settings.advancement.only-broadcast-to-affected-player", false);
+ set("settings.broadcasts.advancement.only-broadcast-to-affected-player", oldValue);
+ set("settings.advancement.only-broadcast-to-affected-player", null);
+ }
+ advancementOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.advancement.only-broadcast-to-affected-player", advancementOnlyBroadcastToAffectedPlayer);
+ deathMessageOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.death.only-broadcast-to-affected-player", deathMessageOnlyBroadcastToAffectedPlayer);
+ }
+
+ public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName();
+ private static void serverModName() {
+ serverModName = getString("settings.server-mod-name", serverModName);
+ }
+
+ public static double laggingThreshold = 19.0D;
+ private static void tickLoopSettings() {
+ laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold);
+ }
+
+ public static boolean disableGiveCommandDrops = false;
+ private static void disableGiveCommandDrops() {
+ disableGiveCommandDrops = getBoolean("settings.disable-give-dropping", disableGiveCommandDrops);
+ }
+
+ public static String commandRamBarTitle = "<gray>Ram<yellow>:</yellow> <used>/<xmx> (<percent>)";
+ public static BossBar.Overlay commandRamBarProgressOverlay = BossBar.Overlay.NOTCHED_20;
+ public static BossBar.Color commandRamBarProgressColorGood = BossBar.Color.GREEN;
+ public static BossBar.Color commandRamBarProgressColorMedium = BossBar.Color.YELLOW;
+ public static BossBar.Color commandRamBarProgressColorLow = BossBar.Color.RED;
+ public static String commandRamBarTextColorGood = "<gradient:#55ff55:#00aa00><text></gradient>";
+ public static String commandRamBarTextColorMedium = "<gradient:#ffff55:#ffaa00><text></gradient>";
+ public static String commandRamBarTextColorLow = "<gradient:#ff5555:#aa0000><text></gradient>";
+ public static int commandRamBarTickInterval = 20;
+ public static String commandTPSBarTitle = "<gray>TPS<yellow>:</yellow> <tps> MSPT<yellow>:</yellow> <mspt> Ping<yellow>:</yellow> <ping>ms";
+ public static BossBar.Overlay commandTPSBarProgressOverlay = BossBar.Overlay.NOTCHED_20;
+ public static TPSBarTask.FillMode commandTPSBarProgressFillMode = TPSBarTask.FillMode.MSPT;
+ public static BossBar.Color commandTPSBarProgressColorGood = BossBar.Color.GREEN;
+ public static BossBar.Color commandTPSBarProgressColorMedium = BossBar.Color.YELLOW;
+ public static BossBar.Color commandTPSBarProgressColorLow = BossBar.Color.RED;
+ public static String commandTPSBarTextColorGood = "<gradient:#55ff55:#00aa00><text></gradient>";
+ public static String commandTPSBarTextColorMedium = "<gradient:#ffff55:#ffaa00><text></gradient>";
+ public static String commandTPSBarTextColorLow = "<gradient:#ff5555:#aa0000><text></gradient>";
+ public static int commandTPSBarTickInterval = 20;
+ public static String commandCompassBarTitle = "S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 ";
+ public static BossBar.Overlay commandCompassBarProgressOverlay = BossBar.Overlay.PROGRESS;
+ public static BossBar.Color commandCompassBarProgressColor = BossBar.Color.BLUE;
+ public static float commandCompassBarProgressPercent = 1.0F;
+ public static int commandCompassBarTickInterval = 5;
+ public static boolean commandGamemodeRequiresPermission = false;
+ public static boolean hideHiddenPlayersFromEntitySelector = false;
+ public static String uptimeFormat = "<days><hours><minutes><seconds>";
+ public static String uptimeDay = "%02d day, ";
+ public static String uptimeDays = "%02d days, ";
+ public static String uptimeHour = "%02d hour, ";
+ public static String uptimeHours = "%02d hours, ";
+ public static String uptimeMinute = "%02d minute, and ";
+ public static String uptimeMinutes = "%02d minutes, and ";
+ public static String uptimeSecond = "%02d second";
+ public static String uptimeSeconds = "%02d seconds";
+ private static void commandSettings() {
+ commandRamBarTitle = getString("settings.command.rambar.title", commandRamBarTitle);
+ commandRamBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.rambar.overlay", commandRamBarProgressOverlay.name()));
+ commandRamBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.good", commandRamBarProgressColorGood.name()));
+ commandRamBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.medium", commandRamBarProgressColorMedium.name()));
+ commandRamBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.low", commandRamBarProgressColorLow.name()));
+ commandRamBarTextColorGood = getString("settings.command.rambar.text-color.good", commandRamBarTextColorGood);
+ commandRamBarTextColorMedium = getString("settings.command.rambar.text-color.medium", commandRamBarTextColorMedium);
+ commandRamBarTextColorLow = getString("settings.command.rambar.text-color.low", commandRamBarTextColorLow);
+ commandRamBarTickInterval = getInt("settings.command.rambar.tick-interval", commandRamBarTickInterval);
+
+ commandTPSBarTitle = getString("settings.command.tpsbar.title", commandTPSBarTitle);
+ commandTPSBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.tpsbar.overlay", commandTPSBarProgressOverlay.name()));
+ commandTPSBarProgressFillMode = TPSBarTask.FillMode.valueOf(getString("settings.command.tpsbar.fill-mode", commandTPSBarProgressFillMode.name()));
+ commandTPSBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.good", commandTPSBarProgressColorGood.name()));
+ commandTPSBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.medium", commandTPSBarProgressColorMedium.name()));
+ commandTPSBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.low", commandTPSBarProgressColorLow.name()));
+ commandTPSBarTextColorGood = getString("settings.command.tpsbar.text-color.good", commandTPSBarTextColorGood);
+ commandTPSBarTextColorMedium = getString("settings.command.tpsbar.text-color.medium", commandTPSBarTextColorMedium);
+ commandTPSBarTextColorLow = getString("settings.command.tpsbar.text-color.low", commandTPSBarTextColorLow);
+ commandTPSBarTickInterval = getInt("settings.command.tpsbar.tick-interval", commandTPSBarTickInterval);
+
+ commandCompassBarTitle = getString("settings.command.compass.title", commandCompassBarTitle);
+ commandCompassBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.compass.overlay", commandCompassBarProgressOverlay.name()));
+ commandCompassBarProgressColor = BossBar.Color.valueOf(getString("settings.command.compass.progress-color", commandCompassBarProgressColor.name()));
+ commandCompassBarProgressPercent = (float) getDouble("settings.command.compass.percent", commandCompassBarProgressPercent);
+ commandCompassBarTickInterval = getInt("settings.command.compass.tick-interval", commandCompassBarTickInterval);
+
+ commandGamemodeRequiresPermission = getBoolean("settings.command.gamemode.requires-specific-permission", commandGamemodeRequiresPermission);
+ hideHiddenPlayersFromEntitySelector = getBoolean("settings.command.hide-hidden-players-from-entity-selector", hideHiddenPlayersFromEntitySelector);
+ uptimeFormat = getString("settings.command.uptime.format", uptimeFormat);
+ uptimeDay = getString("settings.command.uptime.day", uptimeDay);
+ uptimeDays = getString("settings.command.uptime.days", uptimeDays);
+ uptimeHour = getString("settings.command.uptime.hour", uptimeHour);
+ uptimeHours = getString("settings.command.uptime.hours", uptimeHours);
+ uptimeMinute = getString("settings.command.uptime.minute", uptimeMinute);
+ uptimeMinutes = getString("settings.command.uptime.minutes", uptimeMinutes);
+ uptimeSecond = getString("settings.command.uptime.second", uptimeSecond);
+ uptimeSeconds = getString("settings.command.uptime.seconds", uptimeSeconds);
+ }
+
+ public static int barrelRows = 3;
+ public static boolean enderChestSixRows = false;
+ public static boolean enderChestPermissionRows = false;
+ public static boolean cryingObsidianValidForPortalFrame = false;
+ public static int beeInsideBeeHive = 3;
+ public static boolean anvilCumulativeCost = true;
+ public static int lightningRodRange = 128;
+ public static Set<Enchantment> grindstoneIgnoredEnchants = new HashSet<>();
+ public static boolean grindstoneRemoveAttributes = false;
+ public static boolean grindstoneRemoveDisplay = false;
+ public static int caveVinesMaxGrowthAge = 25;
+ public static int kelpMaxGrowthAge = 25;
+ public static int twistingVinesMaxGrowthAge = 25;
+ public static int weepingVinesMaxGrowthAge = 25;
+ public static boolean magmaBlockReverseBubbleColumnFlow = false;
+ public static boolean soulSandBlockReverseBubbleColumnFlow = false;
+ private static void blockSettings() {
+ if (version < 3) {
+ boolean oldValue = getBoolean("settings.barrel.packed-barrels", true);
+ set("settings.blocks.barrel.six-rows", oldValue);
+ set("settings.packed-barrels", null);
+ oldValue = getBoolean("settings.large-ender-chests", true);
+ set("settings.blocks.ender_chest.six-rows", oldValue);
+ set("settings.large-ender-chests", null);
+ }
+ if (version < 20) {
+ boolean oldValue = getBoolean("settings.blocks.barrel.six-rows", false);
+ set("settings.blocks.barrel.rows", oldValue ? 6 : 3);
+ set("settings.blocks.barrel.six-rows", null);
+ }
+ barrelRows = getInt("settings.blocks.barrel.rows", barrelRows);
+ if (barrelRows < 1 || barrelRows > 6) {
+ Bukkit.getLogger().severe("settings.blocks.barrel.rows must be 1-6, resetting to default");
+ barrelRows = 3;
+ }
+ org.bukkit.event.inventory.InventoryType.BARREL.setDefaultSize(switch (barrelRows) {
+ case 6 -> 54;
+ case 5 -> 45;
+ case 4 -> 36;
+ case 2 -> 18;
+ case 1 -> 9;
+ default -> 27;
+ });
+ enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows);
+ org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27);
+ enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows);
+ cryingObsidianValidForPortalFrame = getBoolean("settings.blocks.crying_obsidian.valid-for-portal-frame", cryingObsidianValidForPortalFrame);
+ beeInsideBeeHive = getInt("settings.blocks.beehive.max-bees-inside", beeInsideBeeHive);
+ anvilCumulativeCost = getBoolean("settings.blocks.anvil.cumulative-cost", anvilCumulativeCost);
+ lightningRodRange = getInt("settings.blocks.lightning_rod.range", lightningRodRange);
+ ArrayList<String> defaultCurses = new ArrayList<>(){{
+ add("minecraft:binding_curse");
+ add("minecraft:vanishing_curse");
+ }};
+ if (version < 24 && !getBoolean("settings.blocks.grindstone.ignore-curses", true)) {
+ defaultCurses.clear();
+ }
+ getList("settings.blocks.grindstone.ignored-enchants", defaultCurses).forEach(key -> {
+ Registry<Enchantment> registry = MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT);
+ Enchantment enchantment = registry.getValue(ResourceLocation.parse(key.toString()));
+ if (enchantment == null) return;
+ grindstoneIgnoredEnchants.add(enchantment);
+ });
+ grindstoneRemoveAttributes = getBoolean("settings.blocks.grindstone.remove-attributes", grindstoneRemoveAttributes);
+ grindstoneRemoveDisplay = getBoolean("settings.blocks.grindstone.remove-name-and-lore", grindstoneRemoveDisplay);
+ caveVinesMaxGrowthAge = getInt("settings.blocks.cave_vines.max-growth-age", caveVinesMaxGrowthAge);
+ if (caveVinesMaxGrowthAge > 25) {
+ caveVinesMaxGrowthAge = 25;
+ log(Level.WARNING, "blocks.cave_vines.max-growth-age is set to above maximum allowed value of 25");
+ log(Level.WARNING, "Using value of 25 to prevent issues");
+ }
+ kelpMaxGrowthAge = getInt("settings.blocks.kelp.max-growth-age", kelpMaxGrowthAge);
+ if (kelpMaxGrowthAge > 25) {
+ kelpMaxGrowthAge = 25;
+ log(Level.WARNING, "blocks.kelp.max-growth-age is set to above maximum allowed value of 25");
+ log(Level.WARNING, "Using value of 25 to prevent issues");
+ }
+ twistingVinesMaxGrowthAge = getInt("settings.blocks.twisting_vines.max-growth-age", twistingVinesMaxGrowthAge);
+ if (twistingVinesMaxGrowthAge > 25) {
+ twistingVinesMaxGrowthAge = 25;
+ log(Level.WARNING, "blocks.twisting_vines.max-growth-age is set to above maximum allowed value of 25");
+ log(Level.WARNING, "Using value of 25 to prevent issues");
+ }
+ weepingVinesMaxGrowthAge = getInt("settings.blocks.weeping_vines.max-growth-age", weepingVinesMaxGrowthAge);
+ if (weepingVinesMaxGrowthAge > 25) {
+ weepingVinesMaxGrowthAge = 25;
+ log(Level.WARNING, "blocks.weeping_vines.max-growth-age is set to above maximum allowed value of 25");
+ log(Level.WARNING, "Using value of 25 to prevent issues");
+ }
+ magmaBlockReverseBubbleColumnFlow = getBoolean("settings.blocks.magma-block.reverse-bubble-column-flow", magmaBlockReverseBubbleColumnFlow);
+ soulSandBlockReverseBubbleColumnFlow = getBoolean("settings.blocks.soul-sand.reverse-bubble-column-flow", soulSandBlockReverseBubbleColumnFlow);
+ }
+
+ public static boolean allowInapplicableEnchants = false;
+ public static boolean allowIncompatibleEnchants = false;
+ public static boolean allowHigherEnchantsLevels = false;
+ public static boolean allowUnsafeEnchantCommand = false;
+ public static boolean replaceIncompatibleEnchants = false;
+ public static boolean clampEnchantLevels = true;
+ private static void enchantmentSettings() {
+ if (version < 30) {
+ boolean oldValue = getBoolean("settings.enchantment.allow-unsafe-enchants", false);
+ set("settings.enchantment.anvil.allow-unsafe-enchants", oldValue);
+ set("settings.enchantment.anvil.allow-inapplicable-enchants", true);
+ set("settings.enchantment.anvil.allow-incompatible-enchants", true);
+ set("settings.enchantment.anvil.allow-higher-enchants-levels", true);
+ set("settings.enchantment.allow-unsafe-enchants", null);
+ }
+ if (version < 37) {
+ boolean allowUnsafeEnchants = getBoolean("settings.enchantment.anvil.allow-unsafe-enchants", false);
+ if (!allowUnsafeEnchants) {
+ set("settings.enchantment.anvil.allow-inapplicable-enchants", false);
+ set("settings.enchantment.anvil.allow-incompatible-enchants", false);
+ set("settings.enchantment.anvil.allow-higher-enchants-levels", false);
+ }
+ set("settings.enchantment.anvil.allow-unsafe-enchants", null);
+ }
+ allowInapplicableEnchants = getBoolean("settings.enchantment.anvil.allow-inapplicable-enchants", allowInapplicableEnchants);
+ allowIncompatibleEnchants = getBoolean("settings.enchantment.anvil.allow-incompatible-enchants", allowIncompatibleEnchants);
+ allowHigherEnchantsLevels = getBoolean("settings.enchantment.anvil.allow-higher-enchants-levels", allowHigherEnchantsLevels);
+ allowUnsafeEnchantCommand = getBoolean("settings.enchantment.allow-unsafe-enchant-command", allowUnsafeEnchantCommand);
+ replaceIncompatibleEnchants = getBoolean("settings.enchantment.anvil.replace-incompatible-enchants", replaceIncompatibleEnchants);
+ clampEnchantLevels = getBoolean("settings.enchantment.clamp-levels", clampEnchantLevels);
+ }
+
+ public static boolean endermanShortHeight = false;
+ private static void entitySettings() {
+ endermanShortHeight = getBoolean("settings.entity.enderman.short-height", endermanShortHeight);
+ if (endermanShortHeight) EntityType.ENDERMAN.dimensions = EntityDimensions.scalable(0.6F, 1.9F);
+ }
+
+ public static boolean allowWaterPlacementInTheEnd = true;
+ private static void allowWaterPlacementInEnd() {
+ allowWaterPlacementInTheEnd = getBoolean("settings.allow-water-placement-in-the-end", allowWaterPlacementInTheEnd);
+ }
+
+ public static boolean beeCountPayload = false;
+ private static void beeCountPayload() {
+ beeCountPayload = getBoolean("settings.bee-count-payload", beeCountPayload);
+ }
+
+ public static boolean tpsCatchup = true;
+ private static void tpsCatchup() {
+ tpsCatchup = getBoolean("settings.tps-catchup", tpsCatchup);
+ }
+
+ public static boolean useUPnP = false;
+ public static boolean maxJoinsPerSecond = false;
+ private static void networkSettings() {
+ useUPnP = getBoolean("settings.network.upnp-port-forwarding", useUPnP);
+ maxJoinsPerSecond = getBoolean("settings.network.max-joins-per-second", maxJoinsPerSecond);
+ }
+
+ public static Pattern usernameValidCharactersPattern;
+ private static void usernameValidationSettings() {
+ String defaultPattern = "^[a-zA-Z0-9_.]*$";
+ String setPattern = getString("settings.username-valid-characters", defaultPattern);
+ usernameValidCharactersPattern = Pattern.compile(setPattern == null || setPattern.isBlank() ? defaultPattern : setPattern);
+ }
+
+ public static boolean fixProjectileLootingTransfer = false;
+ private static void fixProjectileLootingTransfer() {
+ fixProjectileLootingTransfer = getBoolean("settings.fix-projectile-looting-transfer", fixProjectileLootingTransfer);
+ }
+
+ public static boolean clampAttributes = true;
+ private static void clampAttributes() {
+ clampAttributes = getBoolean("settings.clamp-attributes", clampAttributes);
+ }
+
+ public static boolean limitArmor = true;
+ private static void limitArmor() {
+ limitArmor = getBoolean("settings.limit-armor", limitArmor);
+ }
+
+ private static void blastResistanceSettings() {
+ getMap("settings.blast-resistance-overrides", Collections.emptyMap()).forEach((blockId, value) -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId));
+ if (block == Blocks.AIR) {
+ log(Level.SEVERE, "Invalid block for `settings.blast-resistance-overrides`: " + blockId);
+ return;
+ }
+ if (!(value instanceof Number blastResistance)) {
+ log(Level.SEVERE, "Invalid blast resistance for `settings.blast-resistance-overrides." + blockId + "`: " + value);
+ return;
+ }
+ block.explosionResistance = blastResistance.floatValue();
+ });
+ }
+ private static void blockFallMultiplierSettings() {
+ getMap("settings.block-fall-multipliers", Map.ofEntries(
+ Map.entry("minecraft:hay_block", Map.of("damage", 0.2F)),
+ Map.entry("minecraft:white_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:light_gray_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:gray_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:black_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:brown_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:pink_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:red_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:orange_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:yellow_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:green_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:lime_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:cyan_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:light_blue_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:blue_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:purple_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:magenta_bed", Map.of("distance", 0.5F))
+ )).forEach((blockId, value) -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId));
+ if (block == Blocks.AIR) {
+ log(Level.SEVERE, "Invalid block for `settings.block-fall-multipliers`: " + blockId);
+ return;
+ }
+ if (!(value instanceof Map<?, ?> map)) {
+ log(Level.SEVERE, "Invalid fall multiplier for `settings.block-fall-multipliers." + blockId + "`: " + value
+ + ", expected a map with keys `damage` and `distance` to floats.");
+ return;
+ }
+ Object rawFallDamageMultiplier = map.get("damage");
+ if (rawFallDamageMultiplier == null) rawFallDamageMultiplier = 1F;
+ if (!(rawFallDamageMultiplier instanceof Number fallDamageMultiplier)) {
+ log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".damage`: " + map.get("damage"));
+ return;
+ }
+ Object rawFallDistanceMultiplier = map.get("distance");
+ if (rawFallDistanceMultiplier == null) rawFallDistanceMultiplier = 1F;
+ if (!(rawFallDistanceMultiplier instanceof Number fallDistanceMultiplier)) {
+ log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".distance`: " + map.get("distance"));
+ return;
+ }
+ block.fallDamageMultiplier = fallDamageMultiplier.floatValue();
+ block.fallDistanceMultiplier = fallDistanceMultiplier.floatValue();
+ });
+ }
+
+ public static boolean playerDeathsAlwaysShowItem = false;
+ private static void playerDeathsAlwaysShowItem() {
+ playerDeathsAlwaysShowItem = getBoolean("settings.player-deaths-always-show-item", playerDeathsAlwaysShowItem);
+ }
+
+ public static boolean registerMinecraftDebugCommands = false;
+ private static void registerMinecraftDebugCommands() {
+ registerMinecraftDebugCommands = getBoolean("settings.register-minecraft-debug-commands", registerMinecraftDebugCommands);
+ }
+
+ public static List<String> startupCommands = new ArrayList<>();
+ private static void startupCommands() {
+ startupCommands.clear();
+ getList("settings.startup-commands", new ArrayList<String>()).forEach(line -> {
+ String command = line.toString();
+ if (command.startsWith("/")) {
+ command = command.substring(1);
+ }
+ startupCommands.add(command);
+ });
+ }
+}
diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3fca75c66b16e35f6841c3b7df9103d68f1308e
--- /dev/null
+++ b/org/purpurmc/purpur/PurpurWorldConfig.java
@@ -0,0 +1,3468 @@
+package org.purpurmc.purpur;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.monster.Shulker;
+import net.minecraft.world.item.DyeColor;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.properties.Tilt;
+import org.apache.commons.lang.BooleanUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.World;
+import org.bukkit.configuration.ConfigurationSection;
+import java.util.List;
+import java.util.Map;
+import org.purpurmc.purpur.tool.Flattenable;
+import org.purpurmc.purpur.tool.Strippable;
+import org.purpurmc.purpur.tool.Tillable;
+import org.purpurmc.purpur.tool.Waxable;
+import org.purpurmc.purpur.tool.Weatherable;
+
+import static org.purpurmc.purpur.PurpurConfig.log;
+
+@SuppressWarnings("unused")
+public class PurpurWorldConfig {
+
+ private final String worldName;
+ private final World.Environment environment;
+
+ public PurpurWorldConfig(String worldName, World.Environment environment) {
+ this.worldName = worldName;
+ this.environment = environment;
+ init();
+ }
+
+ public void init() {
+ log("-------- World Settings For [" + worldName + "] --------");
+ PurpurConfig.readConfig(PurpurWorldConfig.class, this);
+ }
+
+ private void set(String path, Object val) {
+ PurpurConfig.config.addDefault("world-settings.default." + path, val);
+ PurpurConfig.config.set("world-settings.default." + path, val);
+ if (PurpurConfig.config.get("world-settings." + worldName + "." + path) != null) {
+ PurpurConfig.config.addDefault("world-settings." + worldName + "." + path, val);
+ PurpurConfig.config.set("world-settings." + worldName + "." + path, val);
+ }
+ }
+
+ private ConfigurationSection getConfigurationSection(String path) {
+ ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path);
+ return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path);
+ }
+
+ private String getString(String path, String def) {
+ PurpurConfig.config.addDefault("world-settings.default." + path, def);
+ return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path));
+ }
+
+ private boolean getBoolean(String path, boolean def) {
+ PurpurConfig.config.addDefault("world-settings.default." + path, def);
+ return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path));
+ }
+
+ private boolean getBoolean(String path, Predicate<Boolean> predicate) {
+ String val = getString(path, "default").toLowerCase();
+ Boolean bool = BooleanUtils.toBooleanObject(val, "true", "false", "default");
+ return predicate.test(bool);
+ }
+
+ private double getDouble(String path, double def) {
+ PurpurConfig.config.addDefault("world-settings.default." + path, def);
+ return PurpurConfig.config.getDouble("world-settings." + worldName + "." + path, PurpurConfig.config.getDouble("world-settings.default." + path));
+ }
+
+ private int getInt(String path, int def) {
+ PurpurConfig.config.addDefault("world-settings.default." + path, def);
+ return PurpurConfig.config.getInt("world-settings." + worldName + "." + path, PurpurConfig.config.getInt("world-settings.default." + path));
+ }
+
+ private <T> List<?> getList(String path, T def) {
+ PurpurConfig.config.addDefault("world-settings.default." + path, def);
+ return PurpurConfig.config.getList("world-settings." + worldName + "." + path, PurpurConfig.config.getList("world-settings.default." + path));
+ }
+
+ private Map<String, Object> getMap(String path, Map<String, Object> def) {
+ final Map<String, Object> fallback = PurpurConfig.getMap("world-settings.default." + path, def);
+ final Map<String, Object> value = PurpurConfig.getMap("world-settings." + worldName + "." + path, null);
+ return value.isEmpty() ? fallback : value;
+ }
+
+ public float armorstandStepHeight = 0.0F;
+ public boolean armorstandSetNameVisible = false;
+ public boolean armorstandFixNametags = false;
+ public boolean armorstandMovement = true;
+ public boolean armorstandWaterMovement = true;
+ public boolean armorstandWaterFence = true;
+ public boolean armorstandPlaceWithArms = false;
+ private void armorstandSettings() {
+ armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight);
+ armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible);
+ armorstandFixNametags = getBoolean("gameplay-mechanics.armorstand.fix-nametags", armorstandFixNametags);
+ armorstandMovement = getBoolean("gameplay-mechanics.armorstand.can-movement-tick", armorstandMovement);
+ armorstandWaterMovement = getBoolean("gameplay-mechanics.armorstand.can-move-in-water", armorstandWaterMovement);
+ armorstandWaterFence = getBoolean("gameplay-mechanics.armorstand.can-move-in-water-over-fence", armorstandWaterFence);
+ armorstandPlaceWithArms = getBoolean("gameplay-mechanics.armorstand.place-with-arms-visible", armorstandPlaceWithArms);
+ }
+
+ public boolean useBetterMending = false;
+ public boolean alwaysTameInCreative = false;
+ public boolean boatEjectPlayersOnLand = false;
+ public boolean boatsDoFallDamage = false;
+ public boolean disableDropsOnCrammingDeath = false;
+ public boolean milkCuresBadOmen = true;
+ public double tridentLoyaltyVoidReturnHeight = 0.0D;
+ public boolean entitiesCanUsePortals = true;
+ public int raidCooldownSeconds = 0;
+ public int animalBreedingCooldownSeconds = 0;
+ public boolean persistentDroppableEntityDisplayNames = true;
+ public boolean entitiesPickUpLootBypassMobGriefing = false;
+ public boolean fireballsBypassMobGriefing = false;
+ public boolean projectilesBypassMobGriefing = false;
+ public boolean noteBlockIgnoreAbove = false;
+ public boolean imposeTeleportRestrictionsOnGateways = false;
+ public boolean imposeTeleportRestrictionsOnNetherPortals = false;
+ public boolean imposeTeleportRestrictionsOnEndPortals = false;
+ public boolean tickFluids = true;
+ public double mobsBlindnessMultiplier = 1;
+ public boolean mobsIgnoreRails = false;
+ public boolean rainStopsAfterSleep = true;
+ public boolean thunderStopsAfterSleep = true;
+ public boolean persistentTileEntityLore = false;
+ public boolean persistentTileEntityDisplayName = true;
+ public int mobLastHurtByPlayerTime = 100;
+ public boolean milkClearsBeneficialEffects = true;
+ public boolean disableOxidationProximityPenalty = false;
+ private void miscGameplayMechanicsSettings() {
+ useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending);
+ alwaysTameInCreative = getBoolean("gameplay-mechanics.always-tame-in-creative", alwaysTameInCreative);
+ boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand);
+ boatsDoFallDamage = getBoolean("gameplay-mechanics.boat.do-fall-damage", boatsDoFallDamage);
+ disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath);
+ milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen);
+ tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight);
+ entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals);
+ raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds);
+ animalBreedingCooldownSeconds = getInt("gameplay-mechanics.animal-breeding-cooldown-seconds", animalBreedingCooldownSeconds);
+ persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames);
+ entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing);
+ fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing);
+ projectilesBypassMobGriefing = getBoolean("gameplay-mechanics.projectiles-bypass-mob-griefing", projectilesBypassMobGriefing);
+ noteBlockIgnoreAbove = getBoolean("gameplay-mechanics.note-block-ignore-above", noteBlockIgnoreAbove);
+ imposeTeleportRestrictionsOnGateways = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-gateways", imposeTeleportRestrictionsOnGateways);
+ imposeTeleportRestrictionsOnNetherPortals = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-nether-portals", imposeTeleportRestrictionsOnNetherPortals);
+ imposeTeleportRestrictionsOnEndPortals = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-end-portals", imposeTeleportRestrictionsOnEndPortals);
+ tickFluids = getBoolean("gameplay-mechanics.tick-fluids", tickFluids);
+ mobsBlindnessMultiplier = getDouble("gameplay-mechanics.entity-blindness-multiplier", mobsBlindnessMultiplier);
+ mobsIgnoreRails = getBoolean("gameplay-mechanics.mobs-ignore-rails", mobsIgnoreRails);
+ rainStopsAfterSleep = getBoolean("gameplay-mechanics.rain-stops-after-sleep", rainStopsAfterSleep);
+ thunderStopsAfterSleep = getBoolean("gameplay-mechanics.thunder-stops-after-sleep", thunderStopsAfterSleep);
+ if (PurpurConfig.version < 35) {
+ boolean oldVal = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityLore);
+ set("gameplay-mechanics.persistent-tileentity-display-names-and-lore", null);
+ set("gameplay-mechanics.persistent-tileentity-lore", oldVal);
+ set("gameplay-mechanics.persistent-tileentity-display-name", !oldVal);
+ }
+ persistentTileEntityLore = getBoolean("gameplay-mechanics.persistent-tileentity-lore", persistentTileEntityLore);
+ persistentTileEntityDisplayName = getBoolean("gameplay-mechanics.persistent-tileentity-display-name", persistentTileEntityDisplayName);
+ mobLastHurtByPlayerTime = getInt("gameplay-mechanics.mob-last-hurt-by-player-time", mobLastHurtByPlayerTime);
+ milkClearsBeneficialEffects = getBoolean("gameplay-mechanics.milk-clears-beneficial-effects", milkClearsBeneficialEffects);
+ disableOxidationProximityPenalty = getBoolean("gameplay-mechanics.disable-oxidation-proximity-penalty", disableOxidationProximityPenalty);
+ }
+
+ public int daytimeTicks = 12000;
+ public int nighttimeTicks = 12000;
+ private void daytimeCycleSettings() {
+ daytimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.daytime", daytimeTicks);
+ nighttimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.nighttime", nighttimeTicks);
+ }
+
+ public int drowningAirTicks = 300;
+ public int drowningDamageInterval = 20;
+ public double damageFromDrowning = 2.0F;
+ private void drowningSettings() {
+ drowningAirTicks = getInt("gameplay-mechanics.drowning.air-ticks", drowningAirTicks);
+ drowningDamageInterval = getInt("gameplay-mechanics.drowning.ticks-per-damage", drowningDamageInterval);
+ damageFromDrowning = getDouble("gameplay-mechanics.drowning.damage-from-drowning", damageFromDrowning);
+ }
+
+ public int elytraDamagePerSecond = 1;
+ public double elytraDamageMultiplyBySpeed = 0;
+ public int elytraDamagePerFireworkBoost = 0;
+ public int elytraDamagePerTridentBoost = 0;
+ public boolean elytraKineticDamage = true;
+ private void elytraSettings() {
+ elytraDamagePerSecond = getInt("gameplay-mechanics.elytra.damage-per-second", elytraDamagePerSecond);
+ elytraDamageMultiplyBySpeed = getDouble("gameplay-mechanics.elytra.damage-multiplied-by-speed", elytraDamageMultiplyBySpeed);
+ elytraDamagePerFireworkBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.firework", elytraDamagePerFireworkBoost);
+ elytraDamagePerTridentBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.trident", elytraDamagePerTridentBoost);
+ elytraKineticDamage = getBoolean("gameplay-mechanics.elytra.kinetic-damage", elytraKineticDamage);
+ }
+
+ public int entityLifeSpan = 0;
+ public float entityLeftHandedChance = 0.05f;
+ public boolean entitySharedRandom = true;
+ private void entitySettings() {
+ entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan);
+ entityLeftHandedChance = (float) getDouble("gameplay-mechanics.entity-left-handed-chance", entityLeftHandedChance);
+ entitySharedRandom = getBoolean("settings.entity.shared-random", entitySharedRandom);
+ }
+
+ public boolean infinityWorksWithoutArrows = false;
+ private void infinityArrowsSettings() {
+ infinityWorksWithoutArrows = getBoolean("gameplay-mechanics.infinity-bow.works-without-arrows", infinityWorksWithoutArrows);
+ }
+
+ public boolean explosionClampRadius = true;
+ private void explosionSettings() {
+ explosionClampRadius = getBoolean("gameplay-mechanics.clamp-explosion-radius", explosionClampRadius);
+ }
+
+ public List<Item> itemImmuneToCactus = new ArrayList<>();
+ public List<Item> itemImmuneToExplosion = new ArrayList<>();
+ public List<Item> itemImmuneToFire = new ArrayList<>();
+ public List<Item> itemImmuneToLightning = new ArrayList<>();
+ public boolean dontRunWithScissors = false;
+ public ResourceLocation dontRunWithScissorsItemModelReference = ResourceLocation.parse("purpurmc:scissors");
+ public boolean ignoreScissorsInWater = false;
+ public boolean ignoreScissorsInLava = false;
+ public double scissorsRunningDamage = 1D;
+ public float enderPearlDamage = 5.0F;
+ public int enderPearlCooldown = 20;
+ public int enderPearlCooldownCreative = 20;
+ public float enderPearlEndermiteChance = 0.05F;
+ public int glowBerriesEatGlowDuration = 0;
+ public boolean shulkerBoxItemDropContentsWhenDestroyed = true;
+ public boolean compassItemShowsBossBar = false;
+ public boolean snowballExtinguishesFire = false;
+ public boolean snowballExtinguishesCandles = false;
+ public boolean snowballExtinguishesCampfires = false;
+ private void itemSettings() {
+ itemImmuneToCactus.clear();
+ getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> {
+ if (key.toString().equals("*")) {
+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToCactus.add(item));
+ return;
+ }
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString()));
+ if (item != Items.AIR) itemImmuneToCactus.add(item);
+ });
+ itemImmuneToExplosion.clear();
+ getList("gameplay-mechanics.item.immune.explosion", new ArrayList<>()).forEach(key -> {
+ if (key.toString().equals("*")) {
+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToExplosion.add(item));
+ return;
+ }
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString()));
+ if (item != Items.AIR) itemImmuneToExplosion.add(item);
+ });
+ itemImmuneToFire.clear();
+ getList("gameplay-mechanics.item.immune.fire", new ArrayList<>()).forEach(key -> {
+ if (key.toString().equals("*")) {
+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToFire.add(item));
+ return;
+ }
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString()));
+ if (item != Items.AIR) itemImmuneToFire.add(item);
+ });
+ itemImmuneToLightning.clear();
+ getList("gameplay-mechanics.item.immune.lightning", new ArrayList<>()).forEach(key -> {
+ if (key.toString().equals("*")) {
+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToLightning.add(item));
+ return;
+ }
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString()));
+ if (item != Items.AIR) itemImmuneToLightning.add(item);
+ });
+ dontRunWithScissors = getBoolean("gameplay-mechanics.item.shears.damage-if-sprinting", dontRunWithScissors);
+ dontRunWithScissorsItemModelReference = ResourceLocation.parse(getString("gameplay-mechanics.item.shears.damage-if-sprinting-item-model", "purpurmc:scissors"));
+ ignoreScissorsInWater = getBoolean("gameplay-mechanics.item.shears.ignore-in-water", ignoreScissorsInWater);
+ ignoreScissorsInLava = getBoolean("gameplay-mechanics.item.shears.ignore-in-lava", ignoreScissorsInLava);
+ scissorsRunningDamage = getDouble("gameplay-mechanics.item.shears.sprinting-damage", scissorsRunningDamage);
+ enderPearlDamage = (float) getDouble("gameplay-mechanics.item.ender-pearl.damage", enderPearlDamage);
+ enderPearlCooldown = getInt("gameplay-mechanics.item.ender-pearl.cooldown", enderPearlCooldown);
+ enderPearlCooldownCreative = getInt("gameplay-mechanics.item.ender-pearl.creative-cooldown", enderPearlCooldownCreative);
+ enderPearlEndermiteChance = (float) getDouble("gameplay-mechanics.item.ender-pearl.endermite-spawn-chance", enderPearlEndermiteChance);
+ glowBerriesEatGlowDuration = getInt("gameplay-mechanics.item.glow_berries.eat-glow-duration", glowBerriesEatGlowDuration);
+ shulkerBoxItemDropContentsWhenDestroyed = getBoolean("gameplay-mechanics.item.shulker_box.drop-contents-when-destroyed", shulkerBoxItemDropContentsWhenDestroyed);
+ compassItemShowsBossBar = getBoolean("gameplay-mechanics.item.compass.holding-shows-bossbar", compassItemShowsBossBar);
+ snowballExtinguishesFire = getBoolean("gameplay-mechanics.item.snowball.extinguish.fire", snowballExtinguishesFire);
+ snowballExtinguishesCandles = getBoolean("gameplay-mechanics.item.snowball.extinguish.candles", snowballExtinguishesCandles);
+ snowballExtinguishesCampfires = getBoolean("gameplay-mechanics.item.snowball.extinguish.campfires", snowballExtinguishesCampfires);
+ }
+
+ public double minecartMaxSpeed = 0.4D;
+ public boolean minecartPlaceAnywhere = false;
+ public boolean minecartControllable = false;
+ public float minecartControllableStepHeight = 1.0F;
+ public double minecartControllableHopBoost = 0.5D;
+ public boolean minecartControllableFallDamage = true;
+ public double minecartControllableBaseSpeed = 0.1D;
+ public Map<Block, Double> minecartControllableBlockSpeeds = new HashMap<>();
+ public double poweredRailBoostModifier = 0.06;
+ private void minecartSettings() {
+ if (PurpurConfig.version < 12) {
+ boolean oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.place-anywhere", minecartPlaceAnywhere);
+ set("gameplay-mechanics.controllable-minecarts.place-anywhere", null);
+ set("gameplay-mechanics.minecart.place-anywhere", oldBool);
+ oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.enabled", minecartControllable);
+ set("gameplay-mechanics.controllable-minecarts.enabled", null);
+ set("gameplay-mechanics.minecart.controllable.enabled", oldBool);
+ double oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.step-height", minecartControllableStepHeight);
+ set("gameplay-mechanics.controllable-minecarts.step-height", null);
+ set("gameplay-mechanics.minecart.controllable.step-height", oldDouble);
+ oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.hop-boost", minecartControllableHopBoost);
+ set("gameplay-mechanics.controllable-minecarts.hop-boost", null);
+ set("gameplay-mechanics.minecart.controllable.hop-boost", oldDouble);
+ oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.fall-damage", minecartControllableFallDamage);
+ set("gameplay-mechanics.controllable-minecarts.fall-damage", null);
+ set("gameplay-mechanics.minecart.controllable.fall-damage", oldBool);
+ oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.base-speed", minecartControllableBaseSpeed);
+ set("gameplay-mechanics.controllable-minecarts.base-speed", null);
+ set("gameplay-mechanics.minecart.controllable.base-speed", oldDouble);
+ ConfigurationSection section = getConfigurationSection("gameplay-mechanics.controllable-minecarts.block-speed");
+ if (section != null) {
+ for (String key : section.getKeys(false)) {
+ if ("grass-block".equals(key)) key = "grass_block"; // oopsie
+ oldDouble = section.getDouble(key, minecartControllableBaseSpeed);
+ set("gameplay-mechanics.controllable-minecarts.block-speed." + key, null);
+ set("gameplay-mechanics.minecart.controllable.block-speed." + key, oldDouble);
+ }
+ set("gameplay-mechanics.controllable-minecarts.block-speed", null);
+ }
+ set("gameplay-mechanics.controllable-minecarts", null);
+ }
+
+ minecartMaxSpeed = getDouble("gameplay-mechanics.minecart.max-speed", minecartMaxSpeed);
+ minecartPlaceAnywhere = getBoolean("gameplay-mechanics.minecart.place-anywhere", minecartPlaceAnywhere);
+ minecartControllable = getBoolean("gameplay-mechanics.minecart.controllable.enabled", minecartControllable);
+ minecartControllableStepHeight = (float) getDouble("gameplay-mechanics.minecart.controllable.step-height", minecartControllableStepHeight);
+ minecartControllableHopBoost = getDouble("gameplay-mechanics.minecart.controllable.hop-boost", minecartControllableHopBoost);
+ minecartControllableFallDamage = getBoolean("gameplay-mechanics.minecart.controllable.fall-damage", minecartControllableFallDamage);
+ minecartControllableBaseSpeed = getDouble("gameplay-mechanics.minecart.controllable.base-speed", minecartControllableBaseSpeed);
+ ConfigurationSection section = getConfigurationSection("gameplay-mechanics.minecart.controllable.block-speed");
+ if (section != null) {
+ for (String key : section.getKeys(false)) {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(key));
+ if (block != Blocks.AIR) {
+ minecartControllableBlockSpeeds.put(block, section.getDouble(key, minecartControllableBaseSpeed));
+ }
+ }
+ } else {
+ set("gameplay-mechanics.minecart.controllable.block-speed.grass_block", 0.3D);
+ set("gameplay-mechanics.minecart.controllable.block-speed.stone", 0.5D);
+ }
+ poweredRailBoostModifier = getDouble("gameplay-mechanics.minecart.powered-rail.boost-modifier", poweredRailBoostModifier);
+ }
+
+ public float entityHealthRegenAmount = 1.0F;
+ public float entityMinimalHealthPoison = 1.0F;
+ public float entityPoisonDegenerationAmount = 1.0F;
+ public float entityWitherDegenerationAmount = 1.0F;
+ public float humanHungerExhaustionAmount = 0.005F;
+ public float humanSaturationRegenAmount = 1.0F;
+ private void mobEffectSettings() {
+ entityHealthRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.health-regen-amount", entityHealthRegenAmount);
+ entityMinimalHealthPoison = (float) getDouble("gameplay-mechanics.mob-effects.minimal-health-poison-amount", entityMinimalHealthPoison);
+ entityPoisonDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.poison-degeneration-amount", entityPoisonDegenerationAmount);
+ entityWitherDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.wither-degeneration-amount", entityWitherDegenerationAmount);
+ humanHungerExhaustionAmount = (float) getDouble("gameplay-mechanics.mob-effects.hunger-exhaustion-amount", humanHungerExhaustionAmount);
+ humanSaturationRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.saturation-regen-amount", humanSaturationRegenAmount);
+ }
+
+ public boolean catSpawning;
+ public boolean patrolSpawning;
+ public boolean phantomSpawning;
+ public boolean villagerTraderSpawning;
+ public boolean villageSiegeSpawning;
+ public boolean mobSpawningIgnoreCreativePlayers = false;
+ private void mobSpawnerSettings() {
+ // values of "default" or null will default to true only if the world environment is normal (aka overworld)
+ Predicate<Boolean> predicate = (bool) -> (bool != null && bool) || (bool == null && environment == World.Environment.NORMAL);
+ catSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-cats", predicate);
+ patrolSpawning = getBoolean("gameplay-mechanics.mob-spawning.raid-patrols", predicate);
+ phantomSpawning = getBoolean("gameplay-mechanics.mob-spawning.phantoms", predicate);
+ villagerTraderSpawning = getBoolean("gameplay-mechanics.mob-spawning.wandering-traders", predicate);
+ villageSiegeSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-sieges", predicate);
+ mobSpawningIgnoreCreativePlayers = getBoolean("gameplay-mechanics.mob-spawning.ignore-creative-players", mobSpawningIgnoreCreativePlayers);
+ }
+
+ public boolean disableObserverClocks = false;
+ private void observerSettings() {
+ disableObserverClocks = getBoolean("blocks.observer.disable-clock", disableObserverClocks);
+ }
+
+ public int playerNetheriteFireResistanceDuration = 0;
+ public int playerNetheriteFireResistanceAmplifier = 0;
+ public boolean playerNetheriteFireResistanceAmbient = false;
+ public boolean playerNetheriteFireResistanceShowParticles = false;
+ public boolean playerNetheriteFireResistanceShowIcon = true;
+ private void playerNetheriteFireResistance() {
+ playerNetheriteFireResistanceDuration = getInt("gameplay-mechanics.player.netherite-fire-resistance.duration", playerNetheriteFireResistanceDuration);
+ playerNetheriteFireResistanceAmplifier = getInt("gameplay-mechanics.player.netherite-fire-resistance.amplifier", playerNetheriteFireResistanceAmplifier);
+ playerNetheriteFireResistanceAmbient = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.ambient", playerNetheriteFireResistanceAmbient);
+ playerNetheriteFireResistanceShowParticles = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-particles", playerNetheriteFireResistanceShowParticles);
+ playerNetheriteFireResistanceShowIcon = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-icon", playerNetheriteFireResistanceShowIcon);
+ }
+
+ public boolean idleTimeoutKick = true;
+ public boolean idleTimeoutTickNearbyEntities = true;
+ public boolean idleTimeoutCountAsSleeping = false;
+ public boolean idleTimeoutUpdateTabList = false;
+ public boolean idleTimeoutTargetPlayer = true;
+ public String playerDeathExpDropEquation = "expLevel * 7";
+ public int playerDeathExpDropMax = 100;
+ public boolean teleportIfOutsideBorder = false;
+ public boolean teleportOnNetherCeilingDamage = false;
+ public boolean totemOfUndyingWorksInInventory = false;
+ public boolean playerFixStuckPortal = false;
+ public boolean creativeOnePunch = false;
+ public boolean playerSleepNearMonsters = false;
+ public boolean playersSkipNight = true;
+ public double playerCriticalDamageMultiplier = 1.5D;
+ public int playerBurpDelay = 10;
+ public boolean playerBurpWhenFull = false;
+ public boolean playerRidableInWater = false;
+ public boolean playerRemoveBindingWithWeakness = false;
+ public int shiftRightClickRepairsMendingPoints = 0;
+ public int playerExpPickupDelay = 2;
+ public boolean playerVoidTrading = false;
+ private void playerSettings() {
+ if (PurpurConfig.version < 19) {
+ boolean oldVal = getBoolean("gameplay-mechanics.player.idle-timeout.mods-target", idleTimeoutTargetPlayer);
+ set("gameplay-mechanics.player.idle-timeout.mods-target", null);
+ set("gameplay-mechanics.player.idle-timeout.mobs-target", oldVal);
+ }
+ idleTimeoutKick = System.getenv("PURPUR_FORCE_IDLE_KICK") == null ? getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick) : Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK"));
+ idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities);
+ idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping);
+ idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList);
+ idleTimeoutTargetPlayer = getBoolean("gameplay-mechanics.player.idle-timeout.mobs-target", idleTimeoutTargetPlayer);
+ playerDeathExpDropEquation = getString("gameplay-mechanics.player.exp-dropped-on-death.equation", playerDeathExpDropEquation);
+ playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax);
+ teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder);
+ teleportOnNetherCeilingDamage = getBoolean("gameplay-mechanics.player.teleport-on-nether-ceiling-damage", teleportOnNetherCeilingDamage);
+ totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory);
+ playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal);
+ creativeOnePunch = getBoolean("gameplay-mechanics.player.one-punch-in-creative", creativeOnePunch);
+ playerSleepNearMonsters = getBoolean("gameplay-mechanics.player.sleep-ignore-nearby-mobs", playerSleepNearMonsters);
+ playersSkipNight = getBoolean("gameplay-mechanics.player.can-skip-night", playersSkipNight);
+ playerCriticalDamageMultiplier = getDouble("gameplay-mechanics.player.critical-damage-multiplier", playerCriticalDamageMultiplier);
+ playerBurpDelay = getInt("gameplay-mechanics.player.burp-delay", playerBurpDelay);
+ playerBurpWhenFull = getBoolean("gameplay-mechanics.player.burp-when-full", playerBurpWhenFull);
+ playerRidableInWater = getBoolean("gameplay-mechanics.player.ridable-in-water", playerRidableInWater);
+ playerRemoveBindingWithWeakness = getBoolean("gameplay-mechanics.player.curse-of-binding.remove-with-weakness", playerRemoveBindingWithWeakness);
+ shiftRightClickRepairsMendingPoints = getInt("gameplay-mechanics.player.shift-right-click-repairs-mending-points", shiftRightClickRepairsMendingPoints);
+ playerExpPickupDelay = getInt("gameplay-mechanics.player.exp-pickup-delay-ticks", playerExpPickupDelay);
+ playerVoidTrading = getBoolean("gameplay-mechanics.player.allow-void-trading", playerVoidTrading);
+ }
+
+ public boolean silkTouchEnabled = false;
+ public String silkTouchSpawnerName = "<reset><white>Monster Spawner";
+ public List<String> silkTouchSpawnerLore = new ArrayList<>();
+ public List<Item> silkTouchTools = new ArrayList<>();
+ public int minimumSilkTouchSpawnerRequire = 1;
+ private void silkTouchSettings() {
+ if (PurpurConfig.version < 21) {
+ String oldName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName);
+ set("gameplay-mechanics.silk-touch.spawner-name", "<reset>" + ChatColor.toMM(oldName.replace("{mob}", "<mob>")));
+ List<String> list = new ArrayList<>();
+ getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a <mob>"))
+ .forEach(line -> list.add("<reset>" + ChatColor.toMM(line.toString().replace("{mob}", "<mob>"))));
+ set("gameplay-mechanics.silk-touch.spawner-lore", list);
+ }
+ silkTouchEnabled = getBoolean("gameplay-mechanics.silk-touch.enabled", silkTouchEnabled);
+ silkTouchSpawnerName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName);
+ minimumSilkTouchSpawnerRequire = getInt("gameplay-mechanics.silk-touch.minimal-level", minimumSilkTouchSpawnerRequire);
+ silkTouchSpawnerLore.clear();
+ getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a <mob>"))
+ .forEach(line -> silkTouchSpawnerLore.add(line.toString()));
+ silkTouchTools.clear();
+ getList("gameplay-mechanics.silk-touch.tools", List.of(
+ "minecraft:iron_pickaxe",
+ "minecraft:golden_pickaxe",
+ "minecraft:diamond_pickaxe",
+ "minecraft:netherite_pickaxe"
+ )).forEach(key -> {
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString()));
+ if (item != Items.AIR) silkTouchTools.add(item);
+ });
+ }
+
+ public double bowProjectileOffset = 1.0D;
+ public double crossbowProjectileOffset = 1.0D;
+ public double eggProjectileOffset = 1.0D;
+ public double enderPearlProjectileOffset = 1.0D;
+ public double throwablePotionProjectileOffset = 1.0D;
+ public double tridentProjectileOffset = 1.0D;
+ public double snowballProjectileOffset = 1.0D;
+ private void projectileOffsetSettings() {
+ bowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.bow", bowProjectileOffset);
+ crossbowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.crossbow", crossbowProjectileOffset);
+ eggProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.egg", eggProjectileOffset);
+ enderPearlProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.ender-pearl", enderPearlProjectileOffset);
+ throwablePotionProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.throwable-potion", throwablePotionProjectileOffset);
+ tridentProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.trident", tridentProjectileOffset);
+ snowballProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.snowball", snowballProjectileOffset);
+ }
+
+ public int snowballDamage = -1;
+ private void snowballSettings() {
+ snowballDamage = getInt("gameplay-mechanics.projectile-damage.snowball", snowballDamage);
+ }
+
+ public Map<Block, Strippable> axeStrippables = new HashMap<>();
+ public Map<Block, Waxable> axeWaxables = new HashMap<>();
+ public Map<Block, Weatherable> axeWeatherables = new HashMap<>();
+ public Map<Block, Tillable> hoeTillables = new HashMap<>();
+ public Map<Block, Flattenable> shovelFlattenables = new HashMap<>();
+ public boolean hoeReplantsCrops = false;
+ public boolean hoeReplantsNetherWarts = false;
+ private void toolSettings() {
+ axeStrippables.clear();
+ axeWaxables.clear();
+ axeWeatherables.clear();
+ hoeTillables.clear();
+ shovelFlattenables.clear();
+ if (PurpurConfig.version < 18) {
+ ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + ".tools.hoe.tilling");
+ if (section != null) {
+ PurpurConfig.config.set("world-settings." + worldName + ".tools.hoe.tillables", section);
+ PurpurConfig.config.set("world-settings." + worldName + ".tools.hoe.tilling", null);
+ }
+ section = PurpurConfig.config.getConfigurationSection("world-settings.default.tools.hoe.tilling");
+ if (section != null) {
+ PurpurConfig.config.set("world-settings.default.tools.hoe.tillables", section);
+ PurpurConfig.config.set("world-settings.default.tools.hoe.tilling", null);
+ }
+ }
+ if (PurpurConfig.version < 29) {
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap<String, Double>()));
+ }
+ if (PurpurConfig.version < 32) {
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap<String, Double>()));
+ }
+ if (PurpurConfig.version < 33) {
+ getList("gameplay-mechanics.shovel-turns-block-to-grass-path", new ArrayList<String>(){{
+ add("minecraft:coarse_dirt");
+ add("minecraft:dirt");
+ add("minecraft:grass_block");
+ add("minecraft:mycelium");
+ add("minecraft:podzol");
+ add("minecraft:rooted_dirt");
+ }}).forEach(key -> {
+ PurpurConfig.config.set("world-settings.default.tools.shovel.flattenables." + key.toString(), Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>()));
+ });
+ set("gameplay-mechanics.shovel-turns-block-to-grass-path", null);
+ }
+ if (PurpurConfig.version < 34) {
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_chiseled_copper", Map.of("into", "minecraft:oxidized_chiseled_copper", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_door", Map.of("into", "minecraft:oxidized_copper_door", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_trapdoor", Map.of("into", "minecraft:oxidized_copper_trapdoor", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_grate", Map.of("into", "minecraft:oxidized_copper_grate", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_bulb", Map.of("into", "minecraft:oxidized_copper_bulb", "drops", new HashMap<String, Double>()));
+
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap<String, Double>()));
+ }
+ if (PurpurConfig.version < 39) {
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:pale_oak_wood", Map.of("into", "minecraft:stripped_pale_oak_wood", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:pale_oak_log", Map.of("into", "minecraft:stripped_pale_oak_log", "drops", new HashMap<String, Double>()));
+ }
+ getMap("tools.axe.strippables", Map.ofEntries(
+ Map.entry("minecraft:oak_wood", Map.of("into", "minecraft:stripped_oak_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oak_log", Map.of("into", "minecraft:stripped_oak_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dark_oak_wood", Map.of("into", "minecraft:stripped_dark_oak_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dark_oak_log", Map.of("into", "minecraft:stripped_dark_oak_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:pale_oak_wood", Map.of("into", "minecraft:stripped_pale_oak_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:pale_oak_log", Map.of("into", "minecraft:stripped_pale_oak_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:acacia_wood", Map.of("into", "minecraft:stripped_acacia_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:acacia_log", Map.of("into", "minecraft:stripped_acacia_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:birch_wood", Map.of("into", "minecraft:stripped_birch_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:birch_log", Map.of("into", "minecraft:stripped_birch_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:jungle_wood", Map.of("into", "minecraft:stripped_jungle_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:jungle_log", Map.of("into", "minecraft:stripped_jungle_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:spruce_wood", Map.of("into", "minecraft:stripped_spruce_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:spruce_log", Map.of("into", "minecraft:stripped_spruce_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:warped_stem", Map.of("into", "minecraft:stripped_warped_stem", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:warped_hyphae", Map.of("into", "minecraft:stripped_warped_hyphae", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:crimson_stem", Map.of("into", "minecraft:stripped_crimson_stem", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:crimson_hyphae", Map.of("into", "minecraft:stripped_crimson_hyphae", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap<String, Double>()))
+ )
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + "`"); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.strippables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ axeStrippables.put(block, new Strippable(into, drops));
+ });
+ getMap("tools.axe.waxables", Map.ofEntries(
+ Map.entry("minecraft:waxed_copper_block", Map.of("into", "minecraft:copper_block", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_copper", Map.of("into", "minecraft:oxidized_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_cut_copper", Map.of("into", "minecraft:oxidized_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_cut_copper_slab", Map.of("into", "minecraft:oxidized_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_cut_copper_stairs", Map.of("into", "minecraft:oxidized_cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_chiseled_copper", Map.of("into", "minecraft:oxidized_chiseled_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_copper_door", Map.of("into", "minecraft:oxidized_copper_door", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_copper_trapdoor", Map.of("into", "minecraft:oxidized_copper_trapdoor", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_copper_grate", Map.of("into", "minecraft:oxidized_copper_grate", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_copper_bulb", Map.of("into", "minecraft:oxidized_copper_bulb", "drops", new HashMap<String, Double>())))
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + "`"); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.waxables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ axeWaxables.put(block, new Waxable(into, drops));
+ });
+ getMap("tools.axe.weatherables", Map.ofEntries(
+ Map.entry("minecraft:exposed_copper", Map.of("into", "minecraft:copper_block", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap<String, Double>())))
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + "`"); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.weatherables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ axeWeatherables.put(block, new Weatherable(into, drops));
+ });
+ getMap("tools.hoe.tillables", Map.ofEntries(
+ Map.entry("minecraft:grass_block", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dirt_path", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dirt", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:coarse_dirt", Map.of("condition", "air_above", "into", "minecraft:dirt", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:rooted_dirt", Map.of("condition", "always", "into", "minecraft:dirt", "drops", Map.of("minecraft:hanging_roots", 1.0D))))
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + "`"); return; }
+ String conditionId = (String) map.get("condition");
+ Tillable.Condition condition = Tillable.Condition.get(conditionId);
+ if (condition == null) { PurpurConfig.log(Level.SEVERE, "Invalid condition for `tools.hoe.tillables." + blockId + ".condition`: " + conditionId); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.hoe.tillables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ hoeTillables.put(block, new Tillable(condition, into, drops));
+ });
+ getMap("tools.shovel.flattenables", Map.ofEntries(
+ Map.entry("minecraft:grass_block", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:podzol", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:coarse_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:mycelium", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:rooted_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())))
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + "`"); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.shovel.flattenables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ shovelFlattenables.put(block, new Flattenable(into, drops));
+ });
+ hoeReplantsCrops = getBoolean("tools.hoe.replant-crops", hoeReplantsCrops);
+ hoeReplantsNetherWarts = getBoolean("tools.hoe.replant-nether-warts", hoeReplantsNetherWarts);
+ }
+
+ public boolean anvilAllowColors = false;
+ public boolean anvilColorsUseMiniMessage;
+ public int anvilRepairIngotsAmount = 0;
+ public int anvilDamageObsidianAmount = 0;
+ private void anvilSettings() {
+ anvilAllowColors = getBoolean("blocks.anvil.allow-colors", anvilAllowColors);
+ anvilColorsUseMiniMessage = getBoolean("blocks.anvil.use-mini-message", anvilColorsUseMiniMessage);
+ anvilRepairIngotsAmount = getInt("blocks.anvil.iron-ingots-used-for-repair", anvilRepairIngotsAmount);
+ anvilDamageObsidianAmount = getInt("blocks.anvil.obsidian-used-for-damage", anvilDamageObsidianAmount);
+ }
+
+ public double azaleaGrowthChance = 0.0D;
+ private void azaleaSettings() {
+ azaleaGrowthChance = getDouble("blocks.azalea.growth-chance", azaleaGrowthChance);
+ }
+
+ public int beaconLevelOne = 20;
+ public int beaconLevelTwo = 30;
+ public int beaconLevelThree = 40;
+ public int beaconLevelFour = 50;
+ public boolean beaconAllowEffectsWithTintedGlass = false;
+ private void beaconSettings() {
+ beaconLevelOne = getInt("blocks.beacon.effect-range.level-1", beaconLevelOne);
+ beaconLevelTwo = getInt("blocks.beacon.effect-range.level-2", beaconLevelTwo);
+ beaconLevelThree = getInt("blocks.beacon.effect-range.level-3", beaconLevelThree);
+ beaconLevelFour = getInt("blocks.beacon.effect-range.level-4", beaconLevelFour);
+ beaconAllowEffectsWithTintedGlass = getBoolean("blocks.beacon.allow-effects-with-tinted-glass", beaconAllowEffectsWithTintedGlass);
+ }
+
+ public boolean bedExplode = true;
+ public boolean bedExplodeOnVillagerSleep = false;
+ public double bedExplosionPower = 5.0D;
+ public boolean bedExplosionFire = true;
+ public net.minecraft.world.level.Level.ExplosionInteraction bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ private void bedSettings() {
+ if (PurpurConfig.version < 31) {
+ if ("DESTROY".equals(getString("blocks.bed.explosion-effect", bedExplosionEffect.name()))) {
+ set("blocks.bed.explosion-effect", "BLOCK");
+ }
+ }
+ bedExplode = getBoolean("blocks.bed.explode", bedExplode);
+ bedExplodeOnVillagerSleep = getBoolean("blocks.bed.explode-on-villager-sleep", bedExplodeOnVillagerSleep);
+ bedExplosionPower = getDouble("blocks.bed.explosion-power", bedExplosionPower);
+ bedExplosionFire = getBoolean("blocks.bed.explosion-fire", bedExplosionFire);
+ try {
+ bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.bed.explosion-effect", bedExplosionEffect.name()));
+ } catch (IllegalArgumentException e) {
+ log(Level.SEVERE, "Unknown value for `blocks.bed.explosion-effect`! Using default of `BLOCK`");
+ bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ }
+ }
+
+ public Map<Tilt, Integer> bigDripleafTiltDelay = new HashMap<>();
+ private void bigDripleafSettings() {
+ bigDripleafTiltDelay.clear();
+ getMap("blocks.big_dripleaf.tilt-delay", Map.ofEntries(
+ Map.entry("UNSTABLE", 10),
+ Map.entry("PARTIAL", 10),
+ Map.entry("FULL", 100))
+ ).forEach((tilt, delay) -> {
+ try {
+ bigDripleafTiltDelay.put(Tilt.valueOf(tilt), (int) delay);
+ } catch (IllegalArgumentException e) {
+ PurpurConfig.log(Level.SEVERE, "Invalid big_dripleaf tilt key: " + tilt);
+ }
+ });
+ }
+
+ public boolean cactusBreaksFromSolidNeighbors = true;
+ public boolean cactusAffectedByBonemeal = false;
+ private void cactusSettings() {
+ cactusBreaksFromSolidNeighbors = getBoolean("blocks.cactus.breaks-from-solid-neighbors", cactusBreaksFromSolidNeighbors);
+ cactusAffectedByBonemeal = getBoolean("blocks.cactus.affected-by-bonemeal", cactusAffectedByBonemeal);
+ }
+
+ public boolean sugarCanAffectedByBonemeal = false;
+ private void sugarCaneSettings() {
+ sugarCanAffectedByBonemeal = getBoolean("blocks.sugar_cane.affected-by-bonemeal", sugarCanAffectedByBonemeal);
+ }
+
+ public boolean netherWartAffectedByBonemeal = false;
+ private void netherWartSettings() {
+ netherWartAffectedByBonemeal = getBoolean("blocks.nether_wart.affected-by-bonemeal", netherWartAffectedByBonemeal);
+ }
+
+ public boolean campFireLitWhenPlaced = true;
+ private void campFireSettings() {
+ campFireLitWhenPlaced = getBoolean("blocks.campfire.lit-when-placed", campFireLitWhenPlaced);
+ }
+
+ public boolean chestOpenWithBlockOnTop = false;
+ private void chestSettings() {
+ chestOpenWithBlockOnTop = getBoolean("blocks.chest.open-with-solid-block-on-top", chestOpenWithBlockOnTop);
+ }
+
+ public boolean composterBulkProcess = false;
+ private void composterSettings() {
+ composterBulkProcess = getBoolean("blocks.composter.sneak-to-bulk-process", composterBulkProcess);
+ }
+
+ public boolean coralDieOutsideWater = true;
+ private void coralSettings() {
+ coralDieOutsideWater = getBoolean("blocks.coral.die-outside-water", coralDieOutsideWater);
+ }
+
+ public boolean dispenserApplyCursedArmor = true;
+ public boolean dispenserPlaceAnvils = false;
+ private void dispenserSettings() {
+ dispenserApplyCursedArmor = getBoolean("blocks.dispenser.apply-cursed-to-armor-slots", dispenserApplyCursedArmor);
+ dispenserPlaceAnvils = getBoolean("blocks.dispenser.place-anvils", dispenserPlaceAnvils);
+ }
+
+ public List<Block> doorRequiresRedstone = new ArrayList<>();
+ private void doorSettings() {
+ getList("blocks.door.requires-redstone", new ArrayList<String>()).forEach(key -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(key.toString()));
+ if (!block.defaultBlockState().isAir()) {
+ doorRequiresRedstone.add(block);
+ }
+ });
+ }
+
+ public boolean dragonEggTeleport = true;
+ private void dragonEggSettings() {
+ dragonEggTeleport = getBoolean("blocks.dragon_egg.teleport", dragonEggTeleport);
+ }
+
+ public boolean baselessEndCrystalExplode = true;
+ public double baselessEndCrystalExplosionPower = 6.0D;
+ public boolean baselessEndCrystalExplosionFire = false;
+ public net.minecraft.world.level.Level.ExplosionInteraction baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ public boolean basedEndCrystalExplode = true;
+ public double basedEndCrystalExplosionPower = 6.0D;
+ public boolean basedEndCrystalExplosionFire = false;
+ public net.minecraft.world.level.Level.ExplosionInteraction basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ public int endCrystalCramming = 0;
+ public boolean endCrystalPlaceAnywhere = false;
+ private void endCrystalSettings() {
+ if (PurpurConfig.version < 31) {
+ if ("DESTROY".equals(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name()))) {
+ set("blocks.end-crystal.baseless.explosion-effect", "BLOCK");
+ }
+ if ("DESTROY".equals(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name()))) {
+ set("blocks.end-crystal.base.explosion-effect", "BLOCK");
+ }
+ }
+ baselessEndCrystalExplode = getBoolean("blocks.end-crystal.baseless.explode", baselessEndCrystalExplode);
+ baselessEndCrystalExplosionPower = getDouble("blocks.end-crystal.baseless.explosion-power", baselessEndCrystalExplosionPower);
+ baselessEndCrystalExplosionFire = getBoolean("blocks.end-crystal.baseless.explosion-fire", baselessEndCrystalExplosionFire);
+ try {
+ baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name()));
+ } catch (IllegalArgumentException e) {
+ log(Level.SEVERE, "Unknown value for `blocks.end-crystal.baseless.explosion-effect`! Using default of `BLOCK`");
+ baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ }
+ basedEndCrystalExplode = getBoolean("blocks.end-crystal.base.explode", basedEndCrystalExplode);
+ basedEndCrystalExplosionPower = getDouble("blocks.end-crystal.base.explosion-power", basedEndCrystalExplosionPower);
+ basedEndCrystalExplosionFire = getBoolean("blocks.end-crystal.base.explosion-fire", basedEndCrystalExplosionFire);
+ try {
+ basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name()));
+ } catch (IllegalArgumentException e) {
+ log(Level.SEVERE, "Unknown value for `blocks.end-crystal.base.explosion-effect`! Using default of `BLOCK`");
+ basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ }
+ endCrystalCramming = getInt("blocks.end-crystal.cramming-amount", endCrystalCramming);
+ endCrystalPlaceAnywhere = getBoolean("gameplay-mechanics.item.end-crystal.place-anywhere", endCrystalPlaceAnywhere);
+ }
+
+ public boolean farmlandBypassMobGriefing = false;
+ public boolean farmlandGetsMoistFromBelow = false;
+ public boolean farmlandAlpha = false;
+ public boolean farmlandTramplingDisabled = false;
+ public boolean farmlandTramplingOnlyPlayers = false;
+ public boolean farmlandTramplingFeatherFalling = false;
+ public double farmlandTrampleHeight = -1D;
+ private void farmlandSettings() {
+ farmlandBypassMobGriefing = getBoolean("blocks.farmland.bypass-mob-griefing", farmlandBypassMobGriefing);
+ farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow);
+ farmlandAlpha = getBoolean("blocks.farmland.use-alpha-farmland", farmlandAlpha);
+ farmlandTramplingDisabled = getBoolean("blocks.farmland.disable-trampling", farmlandTramplingDisabled);
+ farmlandTramplingOnlyPlayers = getBoolean("blocks.farmland.only-players-trample", farmlandTramplingOnlyPlayers);
+ farmlandTramplingFeatherFalling = getBoolean("blocks.farmland.feather-fall-distance-affects-trampling", farmlandTramplingFeatherFalling);
+ farmlandTrampleHeight = getDouble("blocks.farmland.trample-height", farmlandTrampleHeight);
+ }
+
+ public double floweringAzaleaGrowthChance = 0.0D;
+ private void floweringAzaleaSettings() {
+ floweringAzaleaGrowthChance = getDouble("blocks.flowering_azalea.growth-chance", floweringAzaleaGrowthChance);
+ }
+
+ public boolean furnaceUseLavaFromUnderneath = false;
+ private void furnaceSettings() {
+ if (PurpurConfig.version < 17) {
+ furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath);
+ boolean oldValue = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath);
+ set("blocks.furnace.infinite-fuel", null);
+ set("blocks.furnace.use-lava-from-underneath", oldValue);
+ }
+ furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.use-lava-from-underneath", furnaceUseLavaFromUnderneath);
+ }
+
+ public boolean mobsSpawnOnPackedIce = true;
+ public boolean mobsSpawnOnBlueIce = true;
+ public boolean snowOnBlueIce = true;
+ private void iceSettings() {
+ mobsSpawnOnPackedIce = getBoolean("blocks.packed_ice.allow-mob-spawns", mobsSpawnOnPackedIce);
+ mobsSpawnOnBlueIce = getBoolean("blocks.blue_ice.allow-mob-spawns", mobsSpawnOnBlueIce);
+ snowOnBlueIce = getBoolean("blocks.blue_ice.allow-snow-formation", snowOnBlueIce);
+ }
+
+ public int lavaInfiniteRequiredSources = 2;
+ public int lavaSpeedNether = 10;
+ public int lavaSpeedNotNether = 30;
+ private void lavaSettings() {
+ lavaInfiniteRequiredSources = getInt("blocks.lava.infinite-required-sources", lavaInfiniteRequiredSources);
+ lavaSpeedNether = getInt("blocks.lava.speed.nether", lavaSpeedNether);
+ lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether);
+ }
+
+ public int pistonBlockPushLimit = 12;
+ private void pistonSettings() {
+ pistonBlockPushLimit = getInt("blocks.piston.block-push-limit", pistonBlockPushLimit);
+ }
+
+ public boolean magmaBlockDamageWhenSneaking = false;
+ private void magmaBlockSettings() {
+ magmaBlockDamageWhenSneaking = getBoolean("blocks.magma-block.damage-when-sneaking", magmaBlockDamageWhenSneaking);
+ }
+
+ public boolean powderSnowBypassMobGriefing = false;
+ private void powderSnowSettings() {
+ powderSnowBypassMobGriefing = getBoolean("blocks.powder_snow.bypass-mob-griefing", powderSnowBypassMobGriefing);
+ }
+
+ public int railActivationRange = 8;
+ private void railSettings() {
+ railActivationRange = getInt("blocks.powered-rail.activation-range", railActivationRange);
+ }
+
+ public boolean respawnAnchorExplode = true;
+ public double respawnAnchorExplosionPower = 5.0D;
+ public boolean respawnAnchorExplosionFire = true;
+ public net.minecraft.world.level.Level.ExplosionInteraction respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ private void respawnAnchorSettings() {
+ if (PurpurConfig.version < 31) {
+ if ("DESTROY".equals(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name()))) {
+ set("blocks.respawn_anchor.explosion-effect", "BLOCK");
+ }
+ }
+ respawnAnchorExplode = getBoolean("blocks.respawn_anchor.explode", respawnAnchorExplode);
+ respawnAnchorExplosionPower = getDouble("blocks.respawn_anchor.explosion-power", respawnAnchorExplosionPower);
+ respawnAnchorExplosionFire = getBoolean("blocks.respawn_anchor.explosion-fire", respawnAnchorExplosionFire);
+ try {
+ respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name()));
+ } catch (IllegalArgumentException e) {
+ log(Level.SEVERE, "Unknown value for `blocks.respawn_anchor.explosion-effect`! Using default of `BLOCK`");
+ respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ }
+ }
+
+ public boolean sculkShriekerCanSummonDefault = false;
+ private void sculkShriekerSettings() {
+ sculkShriekerCanSummonDefault = getBoolean("blocks.sculk_shrieker.can-summon-default", sculkShriekerCanSummonDefault);
+ }
+
+ public boolean signAllowColors = false;
+ private void signSettings() {
+ signAllowColors = getBoolean("blocks.sign.allow-colors", signAllowColors);
+ }
+
+ public boolean slabHalfBreak = false;
+ private void slabSettings() {
+ slabHalfBreak = getBoolean("blocks.slab.break-individual-slabs-when-sneaking", slabHalfBreak);
+ }
+
+ public boolean spawnerDeactivateByRedstone = false;
+ private void spawnerSettings() {
+ spawnerDeactivateByRedstone = getBoolean("blocks.spawner.deactivate-by-redstone", spawnerDeactivateByRedstone);
+ }
+
+ public int spongeAbsorptionArea = 65;
+ public int spongeAbsorptionRadius = 6;
+ public boolean spongeAbsorbsLava = false;
+ public boolean spongeAbsorbsWaterFromMud = false;
+ private void spongeSettings() {
+ spongeAbsorptionArea = getInt("blocks.sponge.absorption.area", spongeAbsorptionArea);
+ spongeAbsorptionRadius = getInt("blocks.sponge.absorption.radius", spongeAbsorptionRadius);
+ spongeAbsorbsLava = getBoolean("blocks.sponge.absorbs-lava", spongeAbsorbsLava);
+ spongeAbsorbsWaterFromMud = getBoolean("blocks.sponge.absorbs-water-from-mud", spongeAbsorbsWaterFromMud);
+ }
+
+ public float stonecutterDamage = 0.0F;
+ private void stonecutterSettings() {
+ stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage);
+ }
+
+ public boolean turtleEggsBreakFromExpOrbs = false;
+ public boolean turtleEggsBreakFromItems = false;
+ public boolean turtleEggsBreakFromMinecarts = false;
+ public boolean turtleEggsBypassMobGriefing = false;
+ public int turtleEggsRandomTickCrackChance = 500;
+ public boolean turtleEggsTramplingFeatherFalling = false;
+ private void turtleEggSettings() {
+ turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs);
+ turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems);
+ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts);
+ turtleEggsBypassMobGriefing = getBoolean("blocks.turtle_egg.bypass-mob-griefing", turtleEggsBypassMobGriefing);
+ turtleEggsRandomTickCrackChance = getInt("blocks.turtle_egg.random-tick-crack-chance", turtleEggsRandomTickCrackChance);
+ turtleEggsTramplingFeatherFalling = getBoolean("blocks.turtle_egg.feather-fall-distance-affects-trampling", turtleEggsTramplingFeatherFalling);
+ }
+
+ public int waterInfiniteRequiredSources = 2;
+ private void waterSources() {
+ waterInfiniteRequiredSources = getInt("blocks.water.infinite-required-sources", waterInfiniteRequiredSources);
+ }
+
+ public boolean babiesAreRidable = true;
+ public boolean untamedTamablesAreRidable = true;
+ public boolean useNightVisionWhenRiding = false;
+ public boolean useDismountsUnderwaterTag = true;
+ private void ridableSettings() {
+ babiesAreRidable = getBoolean("ridable-settings.babies-are-ridable", babiesAreRidable);
+ untamedTamablesAreRidable = getBoolean("ridable-settings.untamed-tamables-are-ridable", untamedTamablesAreRidable);
+ useNightVisionWhenRiding = getBoolean("ridable-settings.use-night-vision", useNightVisionWhenRiding);
+ useDismountsUnderwaterTag = getBoolean("ridable-settings.use-dismounts-underwater-tag", useDismountsUnderwaterTag);
+ }
+
+ public boolean allayRidable = false;
+ public boolean allayRidableInWater = true;
+ public boolean allayControllable = true;
+ public double allayMaxHealth = 20.0D;
+ public double allayScale = 1.0D;
+ private void allaySettings() {
+ allayRidable = getBoolean("mobs.allay.ridable", allayRidable);
+ allayRidableInWater = getBoolean("mobs.allay.ridable-in-water", allayRidableInWater);
+ allayControllable = getBoolean("mobs.allay.controllable", allayControllable);
+ allayMaxHealth = getDouble("mobs.allay.attributes.max_health", allayMaxHealth);
+ allayScale = Mth.clamp(getDouble("mobs.allay.attributes.scale", allayScale), 0.0625D, 16.0D);
+ }
+
+ public boolean armadilloRidable = false;
+ public boolean armadilloRidableInWater = true;
+ public boolean armadilloControllable = true;
+ public double armadilloMaxHealth = 12.0D;
+ public double armadilloScale = 1.0D;
+ public int armadilloBreedingTicks = 6000;
+ private void armadilloSettings() {
+ armadilloRidable = getBoolean("mobs.armadillo.ridable", armadilloRidable);
+ armadilloRidableInWater = getBoolean("mobs.armadillo.ridable-in-water", armadilloRidableInWater);
+ armadilloControllable = getBoolean("mobs.armadillo.controllable", armadilloControllable);
+ armadilloMaxHealth = getDouble("mobs.armadillo.attributes.max_health", armadilloMaxHealth);
+ armadilloScale = Mth.clamp(getDouble("mobs.armadillo.attributes.scale", armadilloScale), 0.0625D, 16.0D);
+ armadilloBreedingTicks = getInt("mobs.armadillo.breeding-delay-ticks", armadilloBreedingTicks);
+ }
+
+ public boolean axolotlRidable = false;
+ public boolean axolotlControllable = true;
+ public double axolotlMaxHealth = 14.0D;
+ public double axolotlScale = 1.0D;
+ public int axolotlBreedingTicks = 6000;
+ public boolean axolotlTakeDamageFromWater = false;
+ public boolean axolotlAlwaysDropExp = false;
+ private void axolotlSettings() {
+ axolotlRidable = getBoolean("mobs.axolotl.ridable", axolotlRidable);
+ axolotlControllable = getBoolean("mobs.axolotl.controllable", axolotlControllable);
+ axolotlMaxHealth = getDouble("mobs.axolotl.attributes.max_health", axolotlMaxHealth);
+ axolotlScale = Mth.clamp(getDouble("mobs.axolotl.attributes.scale", axolotlScale), 0.0625D, 16.0D);
+ axolotlBreedingTicks = getInt("mobs.axolotl.breeding-delay-ticks", axolotlBreedingTicks);
+ axolotlTakeDamageFromWater = getBoolean("mobs.axolotl.takes-damage-from-water", axolotlTakeDamageFromWater);
+ axolotlAlwaysDropExp = getBoolean("mobs.axolotl.always-drop-exp", axolotlAlwaysDropExp);
+ }
+
+ public boolean batRidable = false;
+ public boolean batRidableInWater = true;
+ public boolean batControllable = true;
+ public double batMaxY = 320D;
+ public double batMaxHealth = 6.0D;
+ public double batScale = 1.0D;
+ public double batFollowRange = 16.0D;
+ public double batKnockbackResistance = 0.0D;
+ public double batMovementSpeed = 0.6D;
+ public double batFlyingSpeed = 0.6D;
+ public double batArmor = 0.0D;
+ public double batArmorToughness = 0.0D;
+ public double batAttackKnockback = 0.0D;
+ public boolean batTakeDamageFromWater = false;
+ public boolean batAlwaysDropExp = false;
+ private void batSettings() {
+ batRidable = getBoolean("mobs.bat.ridable", batRidable);
+ batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater);
+ batControllable = getBoolean("mobs.bat.controllable", batControllable);
+ batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.bat.attributes.max-health", batMaxHealth);
+ set("mobs.bat.attributes.max-health", null);
+ set("mobs.bat.attributes.max_health", oldValue);
+ }
+ batMaxHealth = getDouble("mobs.bat.attributes.max_health", batMaxHealth);
+ batScale = Mth.clamp(getDouble("mobs.bat.attributes.scale", batScale), 0.0625D, 16.0D);
+ batFollowRange = getDouble("mobs.bat.attributes.follow_range", batFollowRange);
+ batKnockbackResistance = getDouble("mobs.bat.attributes.knockback_resistance", batKnockbackResistance);
+ batMovementSpeed = getDouble("mobs.bat.attributes.movement_speed", batMovementSpeed);
+ batFlyingSpeed = getDouble("mobs.bat.attributes.flying_speed", batFlyingSpeed);
+ batArmor = getDouble("mobs.bat.attributes.armor", batArmor);
+ batArmorToughness = getDouble("mobs.bat.attributes.armor_toughness", batArmorToughness);
+ batAttackKnockback = getDouble("mobs.bat.attributes.attack_knockback", batAttackKnockback);
+ batTakeDamageFromWater = getBoolean("mobs.bat.takes-damage-from-water", batTakeDamageFromWater);
+ batAlwaysDropExp = getBoolean("mobs.bat.always-drop-exp", batAlwaysDropExp);
+ }
+
+ public boolean beeRidable = false;
+ public boolean beeRidableInWater = true;
+ public boolean beeControllable = true;
+ public double beeMaxY = 320D;
+ public double beeMaxHealth = 10.0D;
+ public double beeScale = 1.0D;
+ public int beeBreedingTicks = 6000;
+ public boolean beeTakeDamageFromWater = false;
+ public boolean beeCanInstantlyStartDrowning = true;
+ public boolean beeCanWorkAtNight = false;
+ public boolean beeCanWorkInRain = false;
+ public boolean beeAlwaysDropExp = false;
+ public boolean beeDiesAfterSting = true;
+ private void beeSettings() {
+ beeRidable = getBoolean("mobs.bee.ridable", beeRidable);
+ beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater);
+ beeControllable = getBoolean("mobs.bee.controllable", beeControllable);
+ beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.bee.attributes.max-health", beeMaxHealth);
+ set("mobs.bee.attributes.max-health", null);
+ set("mobs.bee.attributes.max_health", oldValue);
+ }
+ beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth);
+ beeScale = Mth.clamp(getDouble("mobs.bee.attributes.scale", beeScale), 0.0625D, 16.0D);
+ beeBreedingTicks = getInt("mobs.bee.breeding-delay-ticks", beeBreedingTicks);
+ if (PurpurConfig.version < 40) {
+ set("mobs.bee.takes-damage-from-water", false);
+ }
+ beeTakeDamageFromWater = getBoolean("mobs.bee.takes-damage-from-water", beeTakeDamageFromWater);
+ beeCanWorkAtNight = getBoolean("mobs.bee.can-work-at-night", beeCanWorkAtNight);
+ beeCanWorkInRain = getBoolean("mobs.bee.can-work-in-rain", beeCanWorkInRain);
+ beeCanInstantlyStartDrowning = getBoolean("mobs.bee.can-instantly-start-drowning", beeCanInstantlyStartDrowning);
+ beeAlwaysDropExp = getBoolean("mobs.bee.always-drop-exp", beeAlwaysDropExp);
+ beeDiesAfterSting = getBoolean("mobs.bee.dies-after-sting", beeDiesAfterSting);
+ }
+
+ public boolean blazeRidable = false;
+ public boolean blazeRidableInWater = true;
+ public boolean blazeControllable = true;
+ public double blazeMaxY = 320D;
+ public double blazeMaxHealth = 20.0D;
+ public double blazeScale = 1.0D;
+ public boolean blazeTakeDamageFromWater = true;
+ public boolean blazeAlwaysDropExp = false;
+ private void blazeSettings() {
+ blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable);
+ blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater);
+ blazeControllable = getBoolean("mobs.blaze.controllable", blazeControllable);
+ blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.blaze.attributes.max-health", blazeMaxHealth);
+ set("mobs.blaze.attributes.max-health", null);
+ set("mobs.blaze.attributes.max_health", oldValue);
+ }
+ blazeMaxHealth = getDouble("mobs.blaze.attributes.max_health", blazeMaxHealth);
+ blazeScale = Mth.clamp(getDouble("mobs.blaze.attributes.scale", blazeScale), 0.0625D, 16.0D);
+ blazeTakeDamageFromWater = getBoolean("mobs.blaze.takes-damage-from-water", blazeTakeDamageFromWater);
+ blazeAlwaysDropExp = getBoolean("mobs.blaze.always-drop-exp", blazeAlwaysDropExp);
+ }
+
+ public boolean boggedRidable = false;
+ public boolean boggedRidableInWater = true;
+ public boolean boggedControllable = true;
+ public double boggedMaxHealth = 16.0D;
+ public double boggedScale = 1.0D;
+ private void boggedSettings() {
+ boggedRidable = getBoolean("mobs.bogged.ridable", boggedRidable);
+ boggedRidableInWater = getBoolean("mobs.bogged.ridable-in-water", boggedRidableInWater);
+ boggedControllable = getBoolean("mobs.bogged.controllable", boggedControllable);
+ boggedMaxHealth = getDouble("mobs.bogged.attributes.max_health", boggedMaxHealth);
+ boggedScale = Mth.clamp(getDouble("mobs.bogged.attributes.scale", boggedScale), 0.0625D, 16.0D);
+ }
+
+ public boolean camelRidableInWater = false;
+ public double camelMaxHealthMin = 32.0D;
+ public double camelMaxHealthMax = 32.0D;
+ public double camelJumpStrengthMin = 0.42D;
+ public double camelJumpStrengthMax = 0.42D;
+ public double camelMovementSpeedMin = 0.09D;
+ public double camelMovementSpeedMax = 0.09D;
+ public int camelBreedingTicks = 6000;
+ private void camelSettings() {
+ camelRidableInWater = getBoolean("mobs.camel.ridable-in-water", camelRidableInWater);
+ camelMaxHealthMin = getDouble("mobs.camel.attributes.max_health.min", camelMaxHealthMin);
+ camelMaxHealthMax = getDouble("mobs.camel.attributes.max_health.max", camelMaxHealthMax);
+ camelJumpStrengthMin = getDouble("mobs.camel.attributes.jump_strength.min", camelJumpStrengthMin);
+ camelJumpStrengthMax = getDouble("mobs.camel.attributes.jump_strength.max", camelJumpStrengthMax);
+ camelMovementSpeedMin = getDouble("mobs.camel.attributes.movement_speed.min", camelMovementSpeedMin);
+ camelMovementSpeedMax = getDouble("mobs.camel.attributes.movement_speed.max", camelMovementSpeedMax);
+ camelBreedingTicks = getInt("mobs.camel.breeding-delay-ticks", camelBreedingTicks);
+ }
+
+ public boolean catRidable = false;
+ public boolean catRidableInWater = true;
+ public boolean catControllable = true;
+ public double catMaxHealth = 10.0D;
+ public double catScale = 1.0D;
+ public int catSpawnDelay = 1200;
+ public int catSpawnSwampHutScanRange = 16;
+ public int catSpawnVillageScanRange = 48;
+ public int catBreedingTicks = 6000;
+ public DyeColor catDefaultCollarColor = DyeColor.RED;
+ public boolean catTakeDamageFromWater = false;
+ public boolean catAlwaysDropExp = false;
+ private void catSettings() {
+ catRidable = getBoolean("mobs.cat.ridable", catRidable);
+ catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater);
+ catControllable = getBoolean("mobs.cat.controllable", catControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.cat.attributes.max-health", catMaxHealth);
+ set("mobs.cat.attributes.max-health", null);
+ set("mobs.cat.attributes.max_health", oldValue);
+ }
+ catMaxHealth = getDouble("mobs.cat.attributes.max_health", catMaxHealth);
+ catScale = Mth.clamp(getDouble("mobs.cat.attributes.scale", catScale), 0.0625D, 16.0D);
+ catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay);
+ catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange);
+ catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange);
+ catBreedingTicks = getInt("mobs.cat.breeding-delay-ticks", catBreedingTicks);
+ try {
+ catDefaultCollarColor = DyeColor.valueOf(getString("mobs.cat.default-collar-color", catDefaultCollarColor.name()));
+ } catch (IllegalArgumentException ignore) {
+ catDefaultCollarColor = DyeColor.RED;
+ }
+ catTakeDamageFromWater = getBoolean("mobs.cat.takes-damage-from-water", catTakeDamageFromWater);
+ catAlwaysDropExp = getBoolean("mobs.cat.always-drop-exp", catAlwaysDropExp);
+ }
+
+ public boolean caveSpiderRidable = false;
+ public boolean caveSpiderRidableInWater = true;
+ public boolean caveSpiderControllable = true;
+ public double caveSpiderMaxHealth = 12.0D;
+ public double caveSpiderScale = 1.0D;
+ public boolean caveSpiderTakeDamageFromWater = false;
+ public boolean caveSpiderAlwaysDropExp = false;
+ private void caveSpiderSettings() {
+ caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable);
+ caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater);
+ caveSpiderControllable = getBoolean("mobs.cave_spider.controllable", caveSpiderControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.cave_spider.attributes.max-health", caveSpiderMaxHealth);
+ set("mobs.cave_spider.attributes.max-health", null);
+ set("mobs.cave_spider.attributes.max_health", oldValue);
+ }
+ caveSpiderMaxHealth = getDouble("mobs.cave_spider.attributes.max_health", caveSpiderMaxHealth);
+ caveSpiderScale = Mth.clamp(getDouble("mobs.cave_spider.attributes.scale", caveSpiderScale), 0.0625D, 16.0D);
+ caveSpiderTakeDamageFromWater = getBoolean("mobs.cave_spider.takes-damage-from-water", caveSpiderTakeDamageFromWater);
+ caveSpiderAlwaysDropExp = getBoolean("mobs.cave_spider.always-drop-exp", caveSpiderAlwaysDropExp);
+ }
+
+ public boolean chickenRidable = false;
+ public boolean chickenRidableInWater = false;
+ public boolean chickenControllable = true;
+ public double chickenMaxHealth = 4.0D;
+ public double chickenScale = 1.0D;
+ public boolean chickenRetaliate = false;
+ public int chickenBreedingTicks = 6000;
+ public boolean chickenTakeDamageFromWater = false;
+ public boolean chickenAlwaysDropExp = false;
+ private void chickenSettings() {
+ chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable);
+ chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater);
+ chickenControllable = getBoolean("mobs.chicken.controllable", chickenControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.chicken.attributes.max-health", chickenMaxHealth);
+ set("mobs.chicken.attributes.max-health", null);
+ set("mobs.chicken.attributes.max_health", oldValue);
+ }
+ chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth);
+ chickenScale = Mth.clamp(getDouble("mobs.chicken.attributes.scale", chickenScale), 0.0625D, 16.0D);
+ chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate);
+ chickenBreedingTicks = getInt("mobs.chicken.breeding-delay-ticks", chickenBreedingTicks);
+ chickenTakeDamageFromWater = getBoolean("mobs.chicken.takes-damage-from-water", chickenTakeDamageFromWater);
+ chickenAlwaysDropExp = getBoolean("mobs.chicken.always-drop-exp", chickenAlwaysDropExp);
+ }
+
+ public boolean codRidable = false;
+ public boolean codControllable = true;
+ public double codMaxHealth = 3.0D;
+ public double codScale = 1.0D;
+ public boolean codTakeDamageFromWater = false;
+ public boolean codAlwaysDropExp = false;
+ private void codSettings() {
+ codRidable = getBoolean("mobs.cod.ridable", codRidable);
+ codControllable = getBoolean("mobs.cod.controllable", codControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.cod.attributes.max-health", codMaxHealth);
+ set("mobs.cod.attributes.max-health", null);
+ set("mobs.cod.attributes.max_health", oldValue);
+ }
+ codMaxHealth = getDouble("mobs.cod.attributes.max_health", codMaxHealth);
+ codScale = Mth.clamp(getDouble("mobs.cod.attributes.scale", codScale), 0.0625D, 16.0D);
+ codTakeDamageFromWater = getBoolean("mobs.cod.takes-damage-from-water", codTakeDamageFromWater);
+ codAlwaysDropExp = getBoolean("mobs.cod.always-drop-exp", codAlwaysDropExp);
+ }
+
+ public boolean cowRidable = false;
+ public boolean cowRidableInWater = true;
+ public boolean cowControllable = true;
+ public double cowMaxHealth = 10.0D;
+ public double cowScale = 1.0D;
+ public int cowFeedMushrooms = 0;
+ public int cowBreedingTicks = 6000;
+ public boolean cowTakeDamageFromWater = false;
+ public double cowNaturallyAggressiveToPlayersChance = 0.0D;
+ public double cowNaturallyAggressiveToPlayersDamage = 2.0D;
+ public boolean cowAlwaysDropExp = false;
+ private void cowSettings() {
+ if (PurpurConfig.version < 22) {
+ double oldValue = getDouble("mobs.cow.naturally-aggressive-to-players-chance", cowNaturallyAggressiveToPlayersChance);
+ set("mobs.cow.naturally-aggressive-to-players-chance", null);
+ set("mobs.cow.naturally-aggressive-to-players.chance", oldValue);
+ }
+ cowRidable = getBoolean("mobs.cow.ridable", cowRidable);
+ cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater);
+ cowControllable = getBoolean("mobs.cow.controllable", cowControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.cow.attributes.max-health", cowMaxHealth);
+ set("mobs.cow.attributes.max-health", null);
+ set("mobs.cow.attributes.max_health", oldValue);
+ }
+ cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth);
+ cowScale = Mth.clamp(getDouble("mobs.cow.attributes.scale", cowScale), 0.0625D, 16.0D);
+ cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms);
+ cowBreedingTicks = getInt("mobs.cow.breeding-delay-ticks", cowBreedingTicks);
+ cowTakeDamageFromWater = getBoolean("mobs.cow.takes-damage-from-water", cowTakeDamageFromWater);
+ cowNaturallyAggressiveToPlayersChance = getDouble("mobs.cow.naturally-aggressive-to-players.chance", cowNaturallyAggressiveToPlayersChance);
+ cowNaturallyAggressiveToPlayersDamage = getDouble("mobs.cow.naturally-aggressive-to-players.damage", cowNaturallyAggressiveToPlayersDamage);
+ cowAlwaysDropExp = getBoolean("mobs.cow.always-drop-exp", cowAlwaysDropExp);
+ }
+
+ public boolean creakingRidable = false;
+ public boolean creakingRidableInWater = true;
+ public boolean creakingControllable = true;
+ public double creakingMaxHealth = 1.0D;
+ public double creakingScale = 1.0D;
+ private void creakingSettings() {
+ creakingRidable = getBoolean("mobs.creaking.ridable", creakingRidable);
+ creakingRidableInWater = getBoolean("mobs.creaking.ridable-in-water", creakingRidableInWater);
+ creakingControllable = getBoolean("mobs.creaking.controllable", creakingControllable);
+ creakingMaxHealth = getDouble("mobs.creaking.attributes.max_health", creakingMaxHealth);
+ creakingScale = Mth.clamp(getDouble("mobs.creaking.attributes.scale", creakingScale), 0.0625D, 16.0D);
+ }
+
+ public boolean creeperRidable = false;
+ public boolean creeperRidableInWater = true;
+ public boolean creeperControllable = true;
+ public double creeperMaxHealth = 20.0D;
+ public double creeperScale = 1.0D;
+ public double creeperChargedChance = 0.0D;
+ public boolean creeperAllowGriefing = true;
+ public boolean creeperBypassMobGriefing = false;
+ public boolean creeperTakeDamageFromWater = false;
+ public boolean creeperExplodeWhenKilled = false;
+ public boolean creeperHealthRadius = false;
+ public boolean creeperAlwaysDropExp = false;
+ public double creeperHeadVisibilityPercent = 0.5D;
+ public boolean creeperEncircleTarget = false;
+ private void creeperSettings() {
+ creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable);
+ creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater);
+ creeperControllable = getBoolean("mobs.creeper.controllable", creeperControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.creeper.attributes.max-health", creeperMaxHealth);
+ set("mobs.creeper.attributes.max-health", null);
+ set("mobs.creeper.attributes.max_health", oldValue);
+ }
+ creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth);
+ creeperScale = Mth.clamp(getDouble("mobs.creeper.attributes.scale", creeperScale), 0.0625D, 16.0D);
+ creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance);
+ creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing);
+ creeperBypassMobGriefing = getBoolean("mobs.creeper.bypass-mob-griefing", creeperBypassMobGriefing);
+ creeperTakeDamageFromWater = getBoolean("mobs.creeper.takes-damage-from-water", creeperTakeDamageFromWater);
+ creeperExplodeWhenKilled = getBoolean("mobs.creeper.explode-when-killed", creeperExplodeWhenKilled);
+ creeperHealthRadius = getBoolean("mobs.creeper.health-impacts-explosion", creeperHealthRadius);
+ creeperAlwaysDropExp = getBoolean("mobs.creeper.always-drop-exp", creeperAlwaysDropExp);
+ creeperHeadVisibilityPercent = getDouble("mobs.creeper.head-visibility-percent", creeperHeadVisibilityPercent);
+ creeperEncircleTarget = getBoolean("mobs.creeper.encircle-target", creeperEncircleTarget);
+ }
+
+ public boolean dolphinRidable = false;
+ public boolean dolphinControllable = true;
+ public int dolphinSpitCooldown = 20;
+ public float dolphinSpitSpeed = 1.0F;
+ public float dolphinSpitDamage = 2.0F;
+ public double dolphinMaxHealth = 10.0D;
+ public double dolphinScale = 1.0D;
+ public boolean dolphinDisableTreasureSearching = false;
+ public boolean dolphinTakeDamageFromWater = false;
+ public double dolphinNaturallyAggressiveToPlayersChance = 0.0D;
+ public boolean dolphinAlwaysDropExp = false;
+ private void dolphinSettings() {
+ dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable);
+ dolphinControllable = getBoolean("mobs.dolphin.controllable", dolphinControllable);
+ dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown);
+ dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed);
+ dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.dolphin.attributes.max-health", dolphinMaxHealth);
+ set("mobs.dolphin.attributes.max-health", null);
+ set("mobs.dolphin.attributes.max_health", oldValue);
+ }
+ dolphinMaxHealth = getDouble("mobs.dolphin.attributes.max_health", dolphinMaxHealth);
+ dolphinScale = Mth.clamp(getDouble("mobs.dolphin.attributes.scale", dolphinScale), 0.0625D, 16.0D);
+ dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching);
+ dolphinTakeDamageFromWater = getBoolean("mobs.dolphin.takes-damage-from-water", dolphinTakeDamageFromWater);
+ dolphinNaturallyAggressiveToPlayersChance = getDouble("mobs.dolphin.naturally-aggressive-to-players-chance", dolphinNaturallyAggressiveToPlayersChance);
+ dolphinAlwaysDropExp = getBoolean("mobs.dolphin.always-drop-exp", dolphinAlwaysDropExp);
+ }
+
+ public boolean donkeyRidableInWater = false;
+ public double donkeyMaxHealthMin = 15.0D;
+ public double donkeyMaxHealthMax = 30.0D;
+ public double donkeyJumpStrengthMin = 0.5D;
+ public double donkeyJumpStrengthMax = 0.5D;
+ public double donkeyMovementSpeedMin = 0.175D;
+ public double donkeyMovementSpeedMax = 0.175D;
+ public int donkeyBreedingTicks = 6000;
+ public boolean donkeyTakeDamageFromWater = false;
+ public boolean donkeyAlwaysDropExp = false;
+ private void donkeySettings() {
+ donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.donkey.attributes.max-health.min", donkeyMaxHealthMin);
+ double oldMax = getDouble("mobs.donkey.attributes.max-health.max", donkeyMaxHealthMax);
+ set("mobs.donkey.attributes.max-health", null);
+ set("mobs.donkey.attributes.max_health.min", oldMin);
+ set("mobs.donkey.attributes.max_health.max", oldMax);
+ }
+ donkeyMaxHealthMin = getDouble("mobs.donkey.attributes.max_health.min", donkeyMaxHealthMin);
+ donkeyMaxHealthMax = getDouble("mobs.donkey.attributes.max_health.max", donkeyMaxHealthMax);
+ donkeyJumpStrengthMin = getDouble("mobs.donkey.attributes.jump_strength.min", donkeyJumpStrengthMin);
+ donkeyJumpStrengthMax = getDouble("mobs.donkey.attributes.jump_strength.max", donkeyJumpStrengthMax);
+ donkeyMovementSpeedMin = getDouble("mobs.donkey.attributes.movement_speed.min", donkeyMovementSpeedMin);
+ donkeyMovementSpeedMax = getDouble("mobs.donkey.attributes.movement_speed.max", donkeyMovementSpeedMax);
+ donkeyBreedingTicks = getInt("mobs.donkey.breeding-delay-ticks", donkeyBreedingTicks);
+ donkeyTakeDamageFromWater = getBoolean("mobs.donkey.takes-damage-from-water", donkeyTakeDamageFromWater);
+ donkeyAlwaysDropExp = getBoolean("mobs.donkey.always-drop-exp", donkeyAlwaysDropExp);
+ }
+
+ public boolean drownedRidable = false;
+ public boolean drownedRidableInWater = true;
+ public boolean drownedControllable = true;
+ public double drownedMaxHealth = 20.0D;
+ public double drownedScale = 1.0D;
+ public double drownedSpawnReinforcements = 0.1D;
+ public boolean drownedJockeyOnlyBaby = true;
+ public double drownedJockeyChance = 0.05D;
+ public boolean drownedJockeyTryExistingChickens = true;
+ public boolean drownedTakeDamageFromWater = false;
+ public boolean drownedBreakDoors = false;
+ public boolean drownedAlwaysDropExp = false;
+ private void drownedSettings() {
+ drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable);
+ drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater);
+ drownedControllable = getBoolean("mobs.drowned.controllable", drownedControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.drowned.attributes.max-health", drownedMaxHealth);
+ set("mobs.drowned.attributes.max-health", null);
+ set("mobs.drowned.attributes.max_health", oldValue);
+ }
+ drownedMaxHealth = getDouble("mobs.drowned.attributes.max_health", drownedMaxHealth);
+ drownedScale = Mth.clamp(getDouble("mobs.drowned.attributes.scale", drownedScale), 0.0625D, 16.0D);
+ drownedSpawnReinforcements = getDouble("mobs.drowned.attributes.spawn_reinforcements", drownedSpawnReinforcements);
+ drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby);
+ drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance);
+ drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens);
+ drownedTakeDamageFromWater = getBoolean("mobs.drowned.takes-damage-from-water", drownedTakeDamageFromWater);
+ drownedBreakDoors = getBoolean("mobs.drowned.can-break-doors", drownedBreakDoors);
+ drownedAlwaysDropExp = getBoolean("mobs.drowned.always-drop-exp", drownedAlwaysDropExp);
+ }
+
+ public boolean elderGuardianRidable = false;
+ public boolean elderGuardianControllable = true;
+ public double elderGuardianMaxHealth = 80.0D;
+ public double elderGuardianScale = 1.0D;
+ public boolean elderGuardianTakeDamageFromWater = false;
+ public boolean elderGuardianAlwaysDropExp = false;
+ private void elderGuardianSettings() {
+ elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable);
+ elderGuardianControllable = getBoolean("mobs.elder_guardian.controllable", elderGuardianControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.elder_guardian.attributes.max-health", elderGuardianMaxHealth);
+ set("mobs.elder_guardian.attributes.max-health", null);
+ set("mobs.elder_guardian.attributes.max_health", oldValue);
+ }
+ elderGuardianMaxHealth = getDouble("mobs.elder_guardian.attributes.max_health", elderGuardianMaxHealth);
+ elderGuardianScale = Mth.clamp(getDouble("mobs.elder_guardian.attributes.scale", elderGuardianScale), 0.0625D, 16.0D);
+ elderGuardianTakeDamageFromWater = getBoolean("mobs.elder_guardian.takes-damage-from-water", elderGuardianTakeDamageFromWater);
+ elderGuardianAlwaysDropExp = getBoolean("mobs.elder_guardian.always-drop-exp", elderGuardianAlwaysDropExp);
+ }
+
+ public boolean enchantmentTableLapisPersists = false;
+ private void enchantmentTableSettings() {
+ enchantmentTableLapisPersists = getBoolean("blocks.enchantment-table.lapis-persists", enchantmentTableLapisPersists);
+ }
+
+ public boolean enderDragonRidable = false;
+ public boolean enderDragonRidableInWater = true;
+ public boolean enderDragonControllable = true;
+ public double enderDragonMaxY = 320D;
+ public double enderDragonMaxHealth = 200.0D;
+ public boolean enderDragonAlwaysDropsFullExp = false;
+ public boolean enderDragonBypassMobGriefing = false;
+ public boolean enderDragonTakeDamageFromWater = false;
+ public boolean enderDragonCanRideVehicles = false;
+ private void enderDragonSettings() {
+ enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable);
+ enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater);
+ enderDragonControllable = getBoolean("mobs.ender_dragon.controllable", enderDragonControllable);
+ enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY);
+ if (PurpurConfig.version < 8) {
+ double oldValue = getDouble("mobs.ender_dragon.max-health", enderDragonMaxHealth);
+ set("mobs.ender_dragon.max-health", null);
+ set("mobs.ender_dragon.attributes.max_health", oldValue);
+ } else if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.ender_dragon.attributes.max-health", enderDragonMaxHealth);
+ set("mobs.ender_dragon.attributes.max-health", null);
+ set("mobs.ender_dragon.attributes.max_health", oldValue);
+ }
+ enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth);
+ enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp);
+ enderDragonBypassMobGriefing = getBoolean("mobs.ender_dragon.bypass-mob-griefing", enderDragonBypassMobGriefing);
+ enderDragonTakeDamageFromWater = getBoolean("mobs.ender_dragon.takes-damage-from-water", enderDragonTakeDamageFromWater);
+ enderDragonCanRideVehicles = getBoolean("mobs.ender_dragon.can-ride-vehicles", enderDragonCanRideVehicles);
+ }
+
+ public boolean endermanRidable = false;
+ public boolean endermanRidableInWater = true;
+ public boolean endermanControllable = true;
+ public double endermanMaxHealth = 40.0D;
+ public double endermanScale = 1.0D;
+ public boolean endermanAllowGriefing = true;
+ public boolean endermanDespawnEvenWithBlock = false;
+ public boolean endermanBypassMobGriefing = false;
+ public boolean endermanTakeDamageFromWater = true;
+ public boolean endermanAggroEndermites = true;
+ public boolean endermanAggroEndermitesOnlyIfPlayerSpawned = false;
+ public boolean endermanDisableStareAggro = false;
+ public boolean endermanIgnoreProjectiles = false;
+ public boolean endermanAlwaysDropExp = false;
+ private void endermanSettings() {
+ endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable);
+ endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater);
+ endermanControllable = getBoolean("mobs.enderman.controllable", endermanControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.enderman.attributes.max-health", endermanMaxHealth);
+ set("mobs.enderman.attributes.max-health", null);
+ set("mobs.enderman.attributes.max_health", oldValue);
+ }
+ if (PurpurConfig.version < 15) {
+ // remove old option
+ set("mobs.enderman.aggressive-towards-spawned-endermites", null);
+ }
+ endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth);
+ endermanScale = Mth.clamp(getDouble("mobs.enderman.attributes.scale", endermanScale), 0.0625D, 16.0D);
+ endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing);
+ endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock);
+ endermanBypassMobGriefing = getBoolean("mobs.enderman.bypass-mob-griefing", endermanBypassMobGriefing);
+ endermanTakeDamageFromWater = getBoolean("mobs.enderman.takes-damage-from-water", endermanTakeDamageFromWater);
+ endermanAggroEndermites = getBoolean("mobs.enderman.aggressive-towards-endermites", endermanAggroEndermites);
+ endermanAggroEndermitesOnlyIfPlayerSpawned = getBoolean("mobs.enderman.aggressive-towards-endermites-only-spawned-by-player-thrown-ender-pearls", endermanAggroEndermitesOnlyIfPlayerSpawned);
+ endermanDisableStareAggro = getBoolean("mobs.enderman.disable-player-stare-aggression", endermanDisableStareAggro);
+ endermanIgnoreProjectiles = getBoolean("mobs.enderman.ignore-projectiles", endermanIgnoreProjectiles);
+ endermanAlwaysDropExp = getBoolean("mobs.enderman.always-drop-exp", endermanAlwaysDropExp);
+ }
+
+ public boolean endermiteRidable = false;
+ public boolean endermiteRidableInWater = true;
+ public boolean endermiteControllable = true;
+ public double endermiteMaxHealth = 8.0D;
+ public double endermiteScale = 1.0D;
+ public boolean endermiteTakeDamageFromWater = false;
+ public boolean endermiteAlwaysDropExp = false;
+ private void endermiteSettings() {
+ endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable);
+ endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater);
+ endermiteControllable = getBoolean("mobs.endermite.controllable", endermiteControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.endermite.attributes.max-health", endermiteMaxHealth);
+ set("mobs.endermite.attributes.max-health", null);
+ set("mobs.endermite.attributes.max_health", oldValue);
+ }
+ endermiteMaxHealth = getDouble("mobs.endermite.attributes.max_health", endermiteMaxHealth);
+ endermiteScale = Mth.clamp(getDouble("mobs.endermite.attributes.scale", endermiteScale), 0.0625D, 16.0D);
+ endermiteTakeDamageFromWater = getBoolean("mobs.endermite.takes-damage-from-water", endermiteTakeDamageFromWater);
+ endermiteAlwaysDropExp = getBoolean("mobs.endermite.always-drop-exp", endermiteAlwaysDropExp);
+ }
+
+ public boolean evokerRidable = false;
+ public boolean evokerRidableInWater = true;
+ public boolean evokerControllable = true;
+ public double evokerMaxHealth = 24.0D;
+ public double evokerScale = 1.0D;
+ public boolean evokerBypassMobGriefing = false;
+ public boolean evokerTakeDamageFromWater = false;
+ public boolean evokerAlwaysDropExp = false;
+ private void evokerSettings() {
+ evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable);
+ evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater);
+ evokerControllable = getBoolean("mobs.evoker.controllable", evokerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.evoker.attributes.max-health", evokerMaxHealth);
+ set("mobs.evoker.attributes.max-health", null);
+ set("mobs.evoker.attributes.max_health", oldValue);
+ }
+ evokerMaxHealth = getDouble("mobs.evoker.attributes.max_health", evokerMaxHealth);
+ evokerScale = Mth.clamp(getDouble("mobs.evoker.attributes.scale", evokerScale), 0.0625D, 16.0D);
+ evokerBypassMobGriefing = getBoolean("mobs.evoker.bypass-mob-griefing", evokerBypassMobGriefing);
+ evokerTakeDamageFromWater = getBoolean("mobs.evoker.takes-damage-from-water", evokerTakeDamageFromWater);
+ evokerAlwaysDropExp = getBoolean("mobs.evoker.always-drop-exp", evokerAlwaysDropExp);
+ }
+
+ public boolean foxRidable = false;
+ public boolean foxRidableInWater = true;
+ public boolean foxControllable = true;
+ public double foxMaxHealth = 10.0D;
+ public double foxScale = 1.0D;
+ public boolean foxTypeChangesWithTulips = false;
+ public int foxBreedingTicks = 6000;
+ public boolean foxBypassMobGriefing = false;
+ public boolean foxTakeDamageFromWater = false;
+ public boolean foxAlwaysDropExp = false;
+ private void foxSettings() {
+ foxRidable = getBoolean("mobs.fox.ridable", foxRidable);
+ foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater);
+ foxControllable = getBoolean("mobs.fox.controllable", foxControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.fox.attributes.max-health", foxMaxHealth);
+ set("mobs.fox.attributes.max-health", null);
+ set("mobs.fox.attributes.max_health", oldValue);
+ }
+ foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth);
+ foxScale = Mth.clamp(getDouble("mobs.fox.attributes.scale", foxScale), 0.0625D, 16.0D);
+ foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips);
+ foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks);
+ foxBypassMobGriefing = getBoolean("mobs.fox.bypass-mob-griefing", foxBypassMobGriefing);
+ foxTakeDamageFromWater = getBoolean("mobs.fox.takes-damage-from-water", foxTakeDamageFromWater);
+ foxAlwaysDropExp = getBoolean("mobs.fox.always-drop-exp", foxAlwaysDropExp);
+ }
+
+ public boolean frogRidable = false;
+ public boolean frogRidableInWater = true;
+ public boolean frogControllable = true;
+ public float frogRidableJumpHeight = 0.65F;
+ public int frogBreedingTicks = 6000;
+ private void frogSettings() {
+ frogRidable = getBoolean("mobs.frog.ridable", frogRidable);
+ frogRidableInWater = getBoolean("mobs.frog.ridable-in-water", frogRidableInWater);
+ frogControllable = getBoolean("mobs.frog.controllable", frogControllable);
+ frogRidableJumpHeight = (float) getDouble("mobs.frog.ridable-jump-height", frogRidableJumpHeight);
+ frogBreedingTicks = getInt("mobs.frog.breeding-delay-ticks", frogBreedingTicks);
+ }
+
+ public boolean ghastRidable = false;
+ public boolean ghastRidableInWater = true;
+ public boolean ghastControllable = true;
+ public double ghastMaxY = 320D;
+ public double ghastMaxHealth = 10.0D;
+ public double ghastScale = 1.0D;
+ public boolean ghastTakeDamageFromWater = false;
+ public boolean ghastAlwaysDropExp = false;
+ private void ghastSettings() {
+ ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable);
+ ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater);
+ ghastControllable = getBoolean("mobs.ghast.controllable", ghastControllable);
+ ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.ghast.attributes.max-health", ghastMaxHealth);
+ set("mobs.ghast.attributes.max-health", null);
+ set("mobs.ghast.attributes.max_health", oldValue);
+ }
+ ghastMaxHealth = getDouble("mobs.ghast.attributes.max_health", ghastMaxHealth);
+ ghastScale = Mth.clamp(getDouble("mobs.ghast.attributes.scale", ghastScale), 0.0625D, 16.0D);
+ ghastTakeDamageFromWater = getBoolean("mobs.ghast.takes-damage-from-water", ghastTakeDamageFromWater);
+ ghastAlwaysDropExp = getBoolean("mobs.ghast.always-drop-exp", ghastAlwaysDropExp);
+ }
+
+ public boolean giantRidable = false;
+ public boolean giantRidableInWater = true;
+ public boolean giantControllable = true;
+ public double giantMovementSpeed = 0.5D;
+ public double giantAttackDamage = 50.0D;
+ public double giantMaxHealth = 100.0D;
+ public double giantScale = 1.0D;
+ public float giantStepHeight = 2.0F;
+ public float giantJumpHeight = 1.0F;
+ public boolean giantHaveAI = false;
+ public boolean giantHaveHostileAI = false;
+ public boolean giantTakeDamageFromWater = false;
+ public boolean giantAlwaysDropExp = false;
+ private void giantSettings() {
+ giantRidable = getBoolean("mobs.giant.ridable", giantRidable);
+ giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater);
+ giantControllable = getBoolean("mobs.giant.controllable", giantControllable);
+ giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed);
+ giantAttackDamage = getDouble("mobs.giant.attack-damage", giantAttackDamage);
+ if (PurpurConfig.version < 8) {
+ double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth);
+ set("mobs.giant.max-health", null);
+ set("mobs.giant.attributes.max_health", oldValue);
+ } else if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.giant.attributes.max-health", giantMaxHealth);
+ set("mobs.giant.attributes.max-health", null);
+ set("mobs.giant.attributes.max_health", oldValue);
+ }
+ giantMaxHealth = getDouble("mobs.giant.attributes.max_health", giantMaxHealth);
+ giantScale = Mth.clamp(getDouble("mobs.giant.attributes.scale", giantScale), 0.0625D, 16.0D);
+ giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight);
+ giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight);
+ giantHaveAI = getBoolean("mobs.giant.have-ai", giantHaveAI);
+ giantHaveHostileAI = getBoolean("mobs.giant.have-hostile-ai", giantHaveHostileAI);
+ giantTakeDamageFromWater = getBoolean("mobs.giant.takes-damage-from-water", giantTakeDamageFromWater);
+ giantAlwaysDropExp = getBoolean("mobs.giant.always-drop-exp", giantAlwaysDropExp);
+ }
+
+ public boolean glowSquidRidable = false;
+ public boolean glowSquidControllable = true;
+ public double glowSquidMaxHealth = 10.0D;
+ public double glowSquidScale = 1.0D;
+ public boolean glowSquidsCanFly = false;
+ public boolean glowSquidTakeDamageFromWater = false;
+ public boolean glowSquidAlwaysDropExp = false;
+ private void glowSquidSettings() {
+ glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable);
+ glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable);
+ glowSquidMaxHealth = getDouble("mobs.glow_squid.attributes.max_health", glowSquidMaxHealth);
+ glowSquidScale = Mth.clamp(getDouble("mobs.glow_squid.attributes.scale", glowSquidScale), 0.0625D, 16.0D);
+ glowSquidsCanFly = getBoolean("mobs.glow_squid.can-fly", glowSquidsCanFly);
+ glowSquidTakeDamageFromWater = getBoolean("mobs.glow_squid.takes-damage-from-water", glowSquidTakeDamageFromWater);
+ glowSquidAlwaysDropExp = getBoolean("mobs.glow_squid.always-drop-exp", glowSquidAlwaysDropExp);
+ }
+
+ public boolean goatRidable = false;
+ public boolean goatRidableInWater = true;
+ public boolean goatControllable = true;
+ public double goatMaxHealth = 10.0D;
+ public double goatScale = 1.0D;
+ public int goatBreedingTicks = 6000;
+ public boolean goatTakeDamageFromWater = false;
+ public boolean goatAlwaysDropExp = false;
+ private void goatSettings() {
+ goatRidable = getBoolean("mobs.goat.ridable", goatRidable);
+ goatRidableInWater = getBoolean("mobs.goat.ridable-in-water", goatRidableInWater);
+ goatControllable = getBoolean("mobs.goat.controllable", goatControllable);
+ goatMaxHealth = getDouble("mobs.goat.attributes.max_health", goatMaxHealth);
+ goatScale = Mth.clamp(getDouble("mobs.goat.attributes.scale", goatScale), 0.0625D, 16.0D);
+ goatBreedingTicks = getInt("mobs.goat.breeding-delay-ticks", goatBreedingTicks);
+ goatTakeDamageFromWater = getBoolean("mobs.goat.takes-damage-from-water", goatTakeDamageFromWater);
+ goatAlwaysDropExp = getBoolean("mobs.goat.always-drop-exp", goatAlwaysDropExp);
+ }
+
+ public boolean guardianRidable = false;
+ public boolean guardianControllable = true;
+ public double guardianMaxHealth = 30.0D;
+ public double guardianScale = 1.0D;
+ public boolean guardianTakeDamageFromWater = false;
+ public boolean guardianAlwaysDropExp = false;
+ private void guardianSettings() {
+ guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable);
+ guardianControllable = getBoolean("mobs.guardian.controllable", guardianControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.guardian.attributes.max-health", guardianMaxHealth);
+ set("mobs.guardian.attributes.max-health", null);
+ set("mobs.guardian.attributes.max_health", oldValue);
+ }
+ guardianMaxHealth = getDouble("mobs.guardian.attributes.max_health", guardianMaxHealth);
+ guardianScale = Mth.clamp(getDouble("mobs.guardian.attributes.scale", guardianScale), 0.0625D, 16.0D);
+ guardianTakeDamageFromWater = getBoolean("mobs.guardian.takes-damage-from-water", guardianTakeDamageFromWater);
+ guardianAlwaysDropExp = getBoolean("mobs.guardian.always-drop-exp", guardianAlwaysDropExp);
+ }
+
+ public boolean forceHalloweenSeason = false;
+ public float chanceHeadHalloweenOnEntity = 0.25F;
+ private void halloweenSetting() {
+ forceHalloweenSeason = getBoolean("gameplay-mechanics.halloween.force", forceHalloweenSeason);
+ chanceHeadHalloweenOnEntity = (float) getDouble("gameplay-mechanics.halloween.head-chance", chanceHeadHalloweenOnEntity);
+ }
+
+ public boolean hoglinRidable = false;
+ public boolean hoglinRidableInWater = true;
+ public boolean hoglinControllable = true;
+ public double hoglinMaxHealth = 40.0D;
+ public double hoglinScale = 1.0D;
+ public int hoglinBreedingTicks = 6000;
+ public boolean hoglinTakeDamageFromWater = false;
+ public boolean hoglinAlwaysDropExp = false;
+ private void hoglinSettings() {
+ hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable);
+ hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater);
+ hoglinControllable = getBoolean("mobs.hoglin.controllable", hoglinControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.hoglin.attributes.max-health", hoglinMaxHealth);
+ set("mobs.hoglin.attributes.max-health", null);
+ set("mobs.hoglin.attributes.max_health", oldValue);
+ }
+ hoglinMaxHealth = getDouble("mobs.hoglin.attributes.max_health", hoglinMaxHealth);
+ hoglinScale = Mth.clamp(getDouble("mobs.hoglin.attributes.scale", hoglinScale), 0.0625D, 16.0D);
+ hoglinBreedingTicks = getInt("mobs.hoglin.breeding-delay-ticks", hoglinBreedingTicks);
+ hoglinTakeDamageFromWater = getBoolean("mobs.hoglin.takes-damage-from-water", hoglinTakeDamageFromWater);
+ hoglinAlwaysDropExp = getBoolean("mobs.hoglin.always-drop-exp", hoglinAlwaysDropExp);
+ }
+
+ public boolean horseRidableInWater = false;
+ public double horseMaxHealthMin = 15.0D;
+ public double horseMaxHealthMax = 30.0D;
+ public double horseJumpStrengthMin = 0.4D;
+ public double horseJumpStrengthMax = 1.0D;
+ public double horseMovementSpeedMin = 0.1125D;
+ public double horseMovementSpeedMax = 0.3375D;
+ public int horseBreedingTicks = 6000;
+ public boolean horseTakeDamageFromWater = false;
+ public boolean horseAlwaysDropExp = false;
+ private void horseSettings() {
+ horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.horse.attributes.max-health.min", horseMaxHealthMin);
+ double oldMax = getDouble("mobs.horse.attributes.max-health.max", horseMaxHealthMax);
+ set("mobs.horse.attributes.max-health", null);
+ set("mobs.horse.attributes.max_health.min", oldMin);
+ set("mobs.horse.attributes.max_health.max", oldMax);
+ }
+ horseMaxHealthMin = getDouble("mobs.horse.attributes.max_health.min", horseMaxHealthMin);
+ horseMaxHealthMax = getDouble("mobs.horse.attributes.max_health.max", horseMaxHealthMax);
+ horseJumpStrengthMin = getDouble("mobs.horse.attributes.jump_strength.min", horseJumpStrengthMin);
+ horseJumpStrengthMax = getDouble("mobs.horse.attributes.jump_strength.max", horseJumpStrengthMax);
+ horseMovementSpeedMin = getDouble("mobs.horse.attributes.movement_speed.min", horseMovementSpeedMin);
+ horseMovementSpeedMax = getDouble("mobs.horse.attributes.movement_speed.max", horseMovementSpeedMax);
+ horseBreedingTicks = getInt("mobs.horse.breeding-delay-ticks", horseBreedingTicks);
+ horseTakeDamageFromWater = getBoolean("mobs.horse.takes-damage-from-water", horseTakeDamageFromWater);
+ horseAlwaysDropExp = getBoolean("mobs.horse.always-drop-exp", horseAlwaysDropExp);
+ }
+
+ public boolean huskRidable = false;
+ public boolean huskRidableInWater = true;
+ public boolean huskControllable = true;
+ public double huskMaxHealth = 20.0D;
+ public double huskScale = 1.0D;
+ public double huskSpawnReinforcements = 0.1D;
+ public boolean huskJockeyOnlyBaby = true;
+ public double huskJockeyChance = 0.05D;
+ public boolean huskJockeyTryExistingChickens = true;
+ public boolean huskTakeDamageFromWater = false;
+ public boolean huskAlwaysDropExp = false;
+ private void huskSettings() {
+ huskRidable = getBoolean("mobs.husk.ridable", huskRidable);
+ huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater);
+ huskControllable = getBoolean("mobs.husk.controllable", huskControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.husk.attributes.max-health", huskMaxHealth);
+ set("mobs.husk.attributes.max-health", null);
+ set("mobs.husk.attributes.max_health", oldValue);
+ }
+ huskMaxHealth = getDouble("mobs.husk.attributes.max_health", huskMaxHealth);
+ huskScale = Mth.clamp(getDouble("mobs.husk.attributes.scale", huskScale), 0.0625D, 16.0D);
+ huskSpawnReinforcements = getDouble("mobs.husk.attributes.spawn_reinforcements", huskSpawnReinforcements);
+ huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby);
+ huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance);
+ huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens);
+ huskTakeDamageFromWater = getBoolean("mobs.husk.takes-damage-from-water", huskTakeDamageFromWater);
+ huskAlwaysDropExp = getBoolean("mobs.husk.always-drop-exp", huskAlwaysDropExp);
+ }
+
+ public boolean illusionerRidable = false;
+ public boolean illusionerRidableInWater = true;
+ public boolean illusionerControllable = true;
+ public double illusionerMovementSpeed = 0.5D;
+ public double illusionerFollowRange = 18.0D;
+ public double illusionerMaxHealth = 32.0D;
+ public double illusionerScale = 1.0D;
+ public boolean illusionerTakeDamageFromWater = false;
+ public boolean illusionerAlwaysDropExp = false;
+ private void illusionerSettings() {
+ illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable);
+ illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater);
+ illusionerControllable = getBoolean("mobs.illusioner.controllable", illusionerControllable);
+ illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed);
+ illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange);
+ if (PurpurConfig.version < 8) {
+ double oldValue = getDouble("mobs.illusioner.max-health", illusionerMaxHealth);
+ set("mobs.illusioner.max-health", null);
+ set("mobs.illusioner.attributes.max_health", oldValue);
+ } else if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth);
+ set("mobs.illusioner.attributes.max-health", null);
+ set("mobs.illusioner.attributes.max_health", oldValue);
+ }
+ illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max_health", illusionerMaxHealth);
+ illusionerScale = Mth.clamp(getDouble("mobs.illusioner.attributes.scale", illusionerScale), 0.0625D, 16.0D);
+ illusionerTakeDamageFromWater = getBoolean("mobs.illusioner.takes-damage-from-water", illusionerTakeDamageFromWater);
+ illusionerAlwaysDropExp = getBoolean("mobs.illusioner.always-drop-exp", illusionerAlwaysDropExp);
+ }
+
+ public boolean ironGolemRidable = false;
+ public boolean ironGolemRidableInWater = true;
+ public boolean ironGolemControllable = true;
+ public boolean ironGolemCanSwim = false;
+ public double ironGolemMaxHealth = 100.0D;
+ public double ironGolemScale = 1.0D;
+ public boolean ironGolemTakeDamageFromWater = false;
+ public boolean ironGolemPoppyCalm = false;
+ public boolean ironGolemHealCalm = false;
+ public boolean ironGolemAlwaysDropExp = false;
+ private void ironGolemSettings() {
+ ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable);
+ ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater);
+ ironGolemControllable = getBoolean("mobs.iron_golem.controllable", ironGolemControllable);
+ ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.iron_golem.attributes.max-health", ironGolemMaxHealth);
+ set("mobs.iron_golem.attributes.max-health", null);
+ set("mobs.iron_golem.attributes.max_health", oldValue);
+ }
+ ironGolemMaxHealth = getDouble("mobs.iron_golem.attributes.max_health", ironGolemMaxHealth);
+ ironGolemScale = Mth.clamp(getDouble("mobs.iron_golem.attributes.scale", ironGolemScale), 0.0625D, 16.0D);
+ ironGolemTakeDamageFromWater = getBoolean("mobs.iron_golem.takes-damage-from-water", ironGolemTakeDamageFromWater);
+ ironGolemPoppyCalm = getBoolean("mobs.iron_golem.poppy-calms-anger", ironGolemPoppyCalm);
+ ironGolemHealCalm = getBoolean("mobs.iron_golem.healing-calms-anger", ironGolemHealCalm);
+ ironGolemAlwaysDropExp = getBoolean("mobs.iron_golem.always-drop-exp", ironGolemAlwaysDropExp);
+ }
+
+ public boolean llamaRidable = false;
+ public boolean llamaRidableInWater = false;
+ public boolean llamaControllable = true;
+ public double llamaMaxHealthMin = 15.0D;
+ public double llamaMaxHealthMax = 30.0D;
+ public double llamaJumpStrengthMin = 0.5D;
+ public double llamaJumpStrengthMax = 0.5D;
+ public double llamaMovementSpeedMin = 0.175D;
+ public double llamaMovementSpeedMax = 0.175D;
+ public int llamaBreedingTicks = 6000;
+ public boolean llamaTakeDamageFromWater = false;
+ public boolean llamaJoinCaravans = true;
+ public boolean llamaAlwaysDropExp = false;
+ private void llamaSettings() {
+ llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable);
+ llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater);
+ llamaControllable = getBoolean("mobs.llama.controllable", llamaControllable);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.llama.attributes.max-health.min", llamaMaxHealthMin);
+ double oldMax = getDouble("mobs.llama.attributes.max-health.max", llamaMaxHealthMax);
+ set("mobs.llama.attributes.max-health", null);
+ set("mobs.llama.attributes.max_health.min", oldMin);
+ set("mobs.llama.attributes.max_health.max", oldMax);
+ }
+ llamaMaxHealthMin = getDouble("mobs.llama.attributes.max_health.min", llamaMaxHealthMin);
+ llamaMaxHealthMax = getDouble("mobs.llama.attributes.max_health.max", llamaMaxHealthMax);
+ llamaJumpStrengthMin = getDouble("mobs.llama.attributes.jump_strength.min", llamaJumpStrengthMin);
+ llamaJumpStrengthMax = getDouble("mobs.llama.attributes.jump_strength.max", llamaJumpStrengthMax);
+ llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin);
+ llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax);
+ llamaBreedingTicks = getInt("mobs.llama.breeding-delay-ticks", llamaBreedingTicks);
+ llamaTakeDamageFromWater = getBoolean("mobs.llama.takes-damage-from-water", llamaTakeDamageFromWater);
+ llamaJoinCaravans = getBoolean("mobs.llama.join-caravans", llamaJoinCaravans);
+ llamaAlwaysDropExp = getBoolean("mobs.llama.always-drop-exp", llamaAlwaysDropExp);
+ }
+
+ public boolean magmaCubeRidable = false;
+ public boolean magmaCubeRidableInWater = true;
+ public boolean magmaCubeControllable = true;
+ public String magmaCubeMaxHealth = "size * size";
+ public String magmaCubeAttackDamage = "size";
+ public Map<Integer, Double> magmaCubeMaxHealthCache = new HashMap<>();
+ public Map<Integer, Double> magmaCubeAttackDamageCache = new HashMap<>();
+ public boolean magmaCubeTakeDamageFromWater = false;
+ public boolean magmaCubeAlwaysDropExp = false;
+ private void magmaCubeSettings() {
+ magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable);
+ magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater);
+ magmaCubeControllable = getBoolean("mobs.magma_cube.controllable", magmaCubeControllable);
+ if (PurpurConfig.version < 10) {
+ String oldValue = getString("mobs.magma_cube.attributes.max-health", magmaCubeMaxHealth);
+ set("mobs.magma_cube.attributes.max-health", null);
+ set("mobs.magma_cube.attributes.max_health", oldValue);
+ }
+ magmaCubeMaxHealth = getString("mobs.magma_cube.attributes.max_health", magmaCubeMaxHealth);
+ magmaCubeAttackDamage = getString("mobs.magma_cube.attributes.attack_damage", magmaCubeAttackDamage);
+ magmaCubeMaxHealthCache.clear();
+ magmaCubeAttackDamageCache.clear();
+ magmaCubeTakeDamageFromWater = getBoolean("mobs.magma_cube.takes-damage-from-water", magmaCubeTakeDamageFromWater);
+ magmaCubeAlwaysDropExp = getBoolean("mobs.magma_cube.always-drop-exp", magmaCubeAlwaysDropExp);
+ }
+
+ public boolean mooshroomRidable = false;
+ public boolean mooshroomRidableInWater = true;
+ public boolean mooshroomControllable = true;
+ public double mooshroomMaxHealth = 10.0D;
+ public double mooshroomScale = 1.0D;
+ public int mooshroomBreedingTicks = 6000;
+ public boolean mooshroomTakeDamageFromWater = false;
+ public boolean mooshroomAlwaysDropExp = false;
+ private void mooshroomSettings() {
+ mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable);
+ mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater);
+ mooshroomControllable = getBoolean("mobs.mooshroom.controllable", mooshroomControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.mooshroom.attributes.max-health", mooshroomMaxHealth);
+ set("mobs.mooshroom.attributes.max-health", null);
+ set("mobs.mooshroom.attributes.max_health", oldValue);
+ }
+ mooshroomMaxHealth = getDouble("mobs.mooshroom.attributes.max_health", mooshroomMaxHealth);
+ mooshroomScale = Mth.clamp(getDouble("mobs.mooshroom.attributes.scale", mooshroomScale), 0.0625D, 16.0D);
+ mooshroomBreedingTicks = getInt("mobs.mooshroom.breeding-delay-ticks", mooshroomBreedingTicks);
+ mooshroomTakeDamageFromWater = getBoolean("mobs.mooshroom.takes-damage-from-water", mooshroomTakeDamageFromWater);
+ mooshroomAlwaysDropExp = getBoolean("mobs.mooshroom.always-drop-exp", mooshroomAlwaysDropExp);
+ }
+
+ public boolean muleRidableInWater = false;
+ public double muleMaxHealthMin = 15.0D;
+ public double muleMaxHealthMax = 30.0D;
+ public double muleJumpStrengthMin = 0.5D;
+ public double muleJumpStrengthMax = 0.5D;
+ public double muleMovementSpeedMin = 0.175D;
+ public double muleMovementSpeedMax = 0.175D;
+ public int muleBreedingTicks = 6000;
+ public boolean muleTakeDamageFromWater = false;
+ public boolean muleAlwaysDropExp = false;
+ private void muleSettings() {
+ muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.mule.attributes.max-health.min", muleMaxHealthMin);
+ double oldMax = getDouble("mobs.mule.attributes.max-health.max", muleMaxHealthMax);
+ set("mobs.mule.attributes.max-health", null);
+ set("mobs.mule.attributes.max_health.min", oldMin);
+ set("mobs.mule.attributes.max_health.max", oldMax);
+ }
+ muleMaxHealthMin = getDouble("mobs.mule.attributes.max_health.min", muleMaxHealthMin);
+ muleMaxHealthMax = getDouble("mobs.mule.attributes.max_health.max", muleMaxHealthMax);
+ muleJumpStrengthMin = getDouble("mobs.mule.attributes.jump_strength.min", muleJumpStrengthMin);
+ muleJumpStrengthMax = getDouble("mobs.mule.attributes.jump_strength.max", muleJumpStrengthMax);
+ muleMovementSpeedMin = getDouble("mobs.mule.attributes.movement_speed.min", muleMovementSpeedMin);
+ muleMovementSpeedMax = getDouble("mobs.mule.attributes.movement_speed.max", muleMovementSpeedMax);
+ muleBreedingTicks = getInt("mobs.mule.breeding-delay-ticks", muleBreedingTicks);
+ muleTakeDamageFromWater = getBoolean("mobs.mule.takes-damage-from-water", muleTakeDamageFromWater);
+ muleAlwaysDropExp = getBoolean("mobs.mule.always-drop-exp", muleAlwaysDropExp);
+ }
+
+ public boolean ocelotRidable = false;
+ public boolean ocelotRidableInWater = true;
+ public boolean ocelotControllable = true;
+ public double ocelotMaxHealth = 10.0D;
+ public double ocelotScale = 1.0D;
+ public int ocelotBreedingTicks = 6000;
+ public boolean ocelotTakeDamageFromWater = false;
+ public boolean ocelotAlwaysDropExp = false;
+ public boolean ocelotSpawnUnderSeaLevel = false;
+ private void ocelotSettings() {
+ ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable);
+ ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater);
+ ocelotControllable = getBoolean("mobs.ocelot.controllable", ocelotControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.ocelot.attributes.max-health", ocelotMaxHealth);
+ set("mobs.ocelot.attributes.max-health", null);
+ set("mobs.ocelot.attributes.max_health", oldValue);
+ }
+ ocelotMaxHealth = getDouble("mobs.ocelot.attributes.max_health", ocelotMaxHealth);
+ ocelotScale = Mth.clamp(getDouble("mobs.ocelot.attributes.scale", ocelotScale), 0.0625D, 16.0D);
+ ocelotBreedingTicks = getInt("mobs.ocelot.breeding-delay-ticks", ocelotBreedingTicks);
+ ocelotTakeDamageFromWater = getBoolean("mobs.ocelot.takes-damage-from-water", ocelotTakeDamageFromWater);
+ ocelotAlwaysDropExp = getBoolean("mobs.ocelot.always-drop-exp", ocelotAlwaysDropExp);
+ ocelotSpawnUnderSeaLevel = getBoolean("mobs.ocelot.spawn-below-sea-level", ocelotSpawnUnderSeaLevel);
+ }
+
+ public boolean pandaRidable = false;
+ public boolean pandaRidableInWater = true;
+ public boolean pandaControllable = true;
+ public double pandaMaxHealth = 20.0D;
+ public double pandaScale = 1.0D;
+ public int pandaBreedingTicks = 6000;
+ public boolean pandaTakeDamageFromWater = false;
+ public boolean pandaAlwaysDropExp = false;
+ private void pandaSettings() {
+ pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable);
+ pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater);
+ pandaControllable = getBoolean("mobs.panda.controllable", pandaControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.panda.attributes.max-health", pandaMaxHealth);
+ set("mobs.panda.attributes.max-health", null);
+ set("mobs.panda.attributes.max_health", oldValue);
+ }
+ pandaMaxHealth = getDouble("mobs.panda.attributes.max_health", pandaMaxHealth);
+ pandaScale = Mth.clamp(getDouble("mobs.panda.attributes.scale", pandaScale), 0.0625D, 16.0D);
+ pandaBreedingTicks = getInt("mobs.panda.breeding-delay-ticks", pandaBreedingTicks);
+ pandaTakeDamageFromWater = getBoolean("mobs.panda.takes-damage-from-water", pandaTakeDamageFromWater);
+ pandaAlwaysDropExp = getBoolean("mobs.panda.always-drop-exp", pandaAlwaysDropExp);
+ }
+
+ public boolean parrotRidable = false;
+ public boolean parrotRidableInWater = true;
+ public boolean parrotControllable = true;
+ public double parrotMaxY = 320D;
+ public double parrotMaxHealth = 6.0D;
+ public double parrotScale = 1.0D;
+ public boolean parrotTakeDamageFromWater = false;
+ public boolean parrotBreedable = false;
+ public boolean parrotAlwaysDropExp = false;
+ private void parrotSettings() {
+ parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable);
+ parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater);
+ parrotControllable = getBoolean("mobs.parrot.controllable", parrotControllable);
+ parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.parrot.attributes.max-health", parrotMaxHealth);
+ set("mobs.parrot.attributes.max-health", null);
+ set("mobs.parrot.attributes.max_health", oldValue);
+ }
+ parrotMaxHealth = getDouble("mobs.parrot.attributes.max_health", parrotMaxHealth);
+ parrotScale = Mth.clamp(getDouble("mobs.parrot.attributes.scale", parrotScale), 0.0625D, 16.0D);
+ parrotTakeDamageFromWater = getBoolean("mobs.parrot.takes-damage-from-water", parrotTakeDamageFromWater);
+ parrotBreedable = getBoolean("mobs.parrot.can-breed", parrotBreedable);
+ parrotAlwaysDropExp = getBoolean("mobs.parrot.always-drop-exp", parrotAlwaysDropExp);
+ }
+
+ public boolean phantomRidable = false;
+ public boolean phantomRidableInWater = true;
+ public boolean phantomControllable = true;
+ public double phantomMaxY = 320D;
+ public float phantomFlameDamage = 1.0F;
+ public int phantomFlameFireTime = 8;
+ public boolean phantomAllowGriefing = false;
+ public String phantomMaxHealth = "20.0";
+ public String phantomAttackDamage = "6 + size";
+ public Map<Integer, Double> phantomMaxHealthCache = new HashMap<>();
+ public Map<Integer, Double> phantomAttackDamageCache = new HashMap<>();
+ public double phantomAttackedByCrystalRadius = 0.0D;
+ public float phantomAttackedByCrystalDamage = 1.0F;
+ public double phantomOrbitCrystalRadius = 0.0D;
+ public int phantomSpawnMinSkyDarkness = 5;
+ public boolean phantomSpawnOnlyAboveSeaLevel = true;
+ public boolean phantomSpawnOnlyWithVisibleSky = true;
+ public double phantomSpawnLocalDifficultyChance = 3.0D;
+ public int phantomSpawnMinPerAttempt = 1;
+ public int phantomSpawnMaxPerAttempt = -1;
+ public int phantomBurnInLight = 0;
+ public boolean phantomIgnorePlayersWithTorch = false;
+ public boolean phantomBurnInDaylight = true;
+ public boolean phantomFlamesOnSwoop = false;
+ public boolean phantomTakeDamageFromWater = false;
+ public boolean phantomAlwaysDropExp = false;
+ public int phantomMinSize = 0;
+ public int phantomMaxSize = 0;
+ private void phantomSettings() {
+ phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable);
+ phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater);
+ phantomControllable = getBoolean("mobs.phantom.controllable", phantomControllable);
+ phantomMaxY = getDouble("mobs.phantom.ridable-max-y", phantomMaxY);
+ phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage);
+ phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime);
+ phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.phantom.attributes.max-health", Double.parseDouble(phantomMaxHealth));
+ set("mobs.phantom.attributes.max-health", null);
+ set("mobs.phantom.attributes.max_health", String.valueOf(oldValue));
+ }
+ if (PurpurConfig.version < 25) {
+ double oldValue = getDouble("mobs.phantom.attributes.max_health", Double.parseDouble(phantomMaxHealth));
+ set("mobs.phantom.attributes.max_health", String.valueOf(oldValue));
+ }
+ phantomMaxHealth = getString("mobs.phantom.attributes.max_health", phantomMaxHealth);
+ phantomAttackDamage = getString("mobs.phantom.attributes.attack_damage", phantomAttackDamage);
+ phantomMaxHealthCache.clear();
+ phantomAttackDamageCache.clear();
+ phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius);
+ phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage);
+ phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius);
+ phantomSpawnMinSkyDarkness = getInt("mobs.phantom.spawn.min-sky-darkness", phantomSpawnMinSkyDarkness);
+ phantomSpawnOnlyAboveSeaLevel = getBoolean("mobs.phantom.spawn.only-above-sea-level", phantomSpawnOnlyAboveSeaLevel);
+ phantomSpawnOnlyWithVisibleSky = getBoolean("mobs.phantom.spawn.only-with-visible-sky", phantomSpawnOnlyWithVisibleSky);
+ phantomSpawnLocalDifficultyChance = getDouble("mobs.phantom.spawn.local-difficulty-chance", phantomSpawnLocalDifficultyChance);
+ phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt);
+ phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt);
+ phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight);
+ phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight);
+ phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch);
+ phantomFlamesOnSwoop = getBoolean("mobs.phantom.flames-on-swoop", phantomFlamesOnSwoop);
+ phantomTakeDamageFromWater = getBoolean("mobs.phantom.takes-damage-from-water", phantomTakeDamageFromWater);
+ phantomAlwaysDropExp = getBoolean("mobs.phantom.always-drop-exp", phantomAlwaysDropExp);
+ phantomMinSize = Mth.clamp(getInt("mobs.phantom.size.min", phantomMinSize), 0, 64);
+ phantomMaxSize = Mth.clamp(getInt("mobs.phantom.size.max", phantomMaxSize), 0, 64);
+ if (phantomMinSize > phantomMaxSize) {
+ phantomMinSize = phantomMinSize ^ phantomMaxSize;
+ phantomMaxSize = phantomMinSize ^ phantomMaxSize;
+ phantomMinSize = phantomMinSize ^ phantomMaxSize;
+ }
+ }
+
+ public boolean pigRidable = false;
+ public boolean pigRidableInWater = false;
+ public boolean pigControllable = true;
+ public double pigMaxHealth = 10.0D;
+ public double pigScale = 1.0D;
+ public boolean pigGiveSaddleBack = false;
+ public int pigBreedingTicks = 6000;
+ public boolean pigTakeDamageFromWater = false;
+ public boolean pigAlwaysDropExp = false;
+ private void pigSettings() {
+ pigRidable = getBoolean("mobs.pig.ridable", pigRidable);
+ pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater);
+ pigControllable = getBoolean("mobs.pig.controllable", pigControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.pig.attributes.max-health", pigMaxHealth);
+ set("mobs.pig.attributes.max-health", null);
+ set("mobs.pig.attributes.max_health", oldValue);
+ }
+ pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth);
+ pigScale = Mth.clamp(getDouble("mobs.pig.attributes.scale", pigScale), 0.0625D, 16.0D);
+ pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack);
+ pigBreedingTicks = getInt("mobs.pig.breeding-delay-ticks", pigBreedingTicks);
+ pigTakeDamageFromWater = getBoolean("mobs.pig.takes-damage-from-water", pigTakeDamageFromWater);
+ pigAlwaysDropExp = getBoolean("mobs.pig.always-drop-exp", pigAlwaysDropExp);
+ }
+
+ public boolean piglinRidable = false;
+ public boolean piglinRidableInWater = true;
+ public boolean piglinControllable = true;
+ public double piglinMaxHealth = 16.0D;
+ public double piglinScale = 1.0D;
+ public boolean piglinBypassMobGriefing = false;
+ public boolean piglinTakeDamageFromWater = false;
+ public int piglinPortalSpawnModifier = 2000;
+ public boolean piglinAlwaysDropExp = false;
+ public double piglinHeadVisibilityPercent = 0.5D;
+ public boolean piglinIgnoresArmorWithGoldTrim = false;
+ private void piglinSettings() {
+ piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable);
+ piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater);
+ piglinControllable = getBoolean("mobs.piglin.controllable", piglinControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.piglin.attributes.max-health", piglinMaxHealth);
+ set("mobs.piglin.attributes.max-health", null);
+ set("mobs.piglin.attributes.max_health", oldValue);
+ }
+ piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth);
+ piglinScale = Mth.clamp(getDouble("mobs.piglin.attributes.scale", piglinScale), 0.0625D, 16.0D);
+ piglinBypassMobGriefing = getBoolean("mobs.piglin.bypass-mob-griefing", piglinBypassMobGriefing);
+ piglinTakeDamageFromWater = getBoolean("mobs.piglin.takes-damage-from-water", piglinTakeDamageFromWater);
+ piglinPortalSpawnModifier = getInt("mobs.piglin.portal-spawn-modifier", piglinPortalSpawnModifier);
+ piglinAlwaysDropExp = getBoolean("mobs.piglin.always-drop-exp", piglinAlwaysDropExp);
+ piglinHeadVisibilityPercent = getDouble("mobs.piglin.head-visibility-percent", piglinHeadVisibilityPercent);
+ piglinIgnoresArmorWithGoldTrim = getBoolean("mobs.piglin.ignores-armor-with-gold-trim", piglinIgnoresArmorWithGoldTrim);
+ }
+
+ public boolean piglinBruteRidable = false;
+ public boolean piglinBruteRidableInWater = true;
+ public boolean piglinBruteControllable = true;
+ public double piglinBruteMaxHealth = 50.0D;
+ public double piglinBruteScale = 1.0D;
+ public boolean piglinBruteTakeDamageFromWater = false;
+ public boolean piglinBruteAlwaysDropExp = false;
+ private void piglinBruteSettings() {
+ piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable);
+ piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater);
+ piglinBruteControllable = getBoolean("mobs.piglin_brute.controllable", piglinBruteControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.piglin_brute.attributes.max-health", piglinBruteMaxHealth);
+ set("mobs.piglin_brute.attributes.max-health", null);
+ set("mobs.piglin_brute.attributes.max_health", oldValue);
+ }
+ piglinBruteMaxHealth = getDouble("mobs.piglin_brute.attributes.max_health", piglinBruteMaxHealth);
+ piglinBruteScale = Mth.clamp(getDouble("mobs.piglin_brute.attributes.scale", piglinBruteScale), 0.0625D, 16.0D);
+ piglinBruteTakeDamageFromWater = getBoolean("mobs.piglin_brute.takes-damage-from-water", piglinBruteTakeDamageFromWater);
+ piglinBruteAlwaysDropExp = getBoolean("mobs.piglin_brute.always-drop-exp", piglinBruteAlwaysDropExp);
+ }
+
+ public boolean pillagerRidable = false;
+ public boolean pillagerRidableInWater = true;
+ public boolean pillagerControllable = true;
+ public double pillagerMaxHealth = 24.0D;
+ public double pillagerScale = 1.0D;
+ public boolean pillagerBypassMobGriefing = false;
+ public boolean pillagerTakeDamageFromWater = false;
+ public boolean pillagerAlwaysDropExp = false;
+ private void pillagerSettings() {
+ pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable);
+ pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater);
+ pillagerControllable = getBoolean("mobs.pillager.controllable", pillagerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.pillager.attributes.max-health", pillagerMaxHealth);
+ set("mobs.pillager.attributes.max-health", null);
+ set("mobs.pillager.attributes.max_health", oldValue);
+ }
+ pillagerMaxHealth = getDouble("mobs.pillager.attributes.max_health", pillagerMaxHealth);
+ pillagerScale = Mth.clamp(getDouble("mobs.pillager.attributes.scale", pillagerScale), 0.0625D, 16.0D);
+ pillagerBypassMobGriefing = getBoolean("mobs.pillager.bypass-mob-griefing", pillagerBypassMobGriefing);
+ pillagerTakeDamageFromWater = getBoolean("mobs.pillager.takes-damage-from-water", pillagerTakeDamageFromWater);
+ pillagerAlwaysDropExp = getBoolean("mobs.pillager.always-drop-exp", pillagerAlwaysDropExp);
+ }
+
+ public boolean polarBearRidable = false;
+ public boolean polarBearRidableInWater = true;
+ public boolean polarBearControllable = true;
+ public double polarBearMaxHealth = 30.0D;
+ public double polarBearScale = 1.0D;
+ public String polarBearBreedableItemString = "";
+ public Item polarBearBreedableItem = null;
+ public int polarBearBreedingTicks = 6000;
+ public boolean polarBearTakeDamageFromWater = false;
+ public boolean polarBearAlwaysDropExp = false;
+ private void polarBearSettings() {
+ polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable);
+ polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater);
+ polarBearControllable = getBoolean("mobs.polar_bear.controllable", polarBearControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.polar_bear.attributes.max-health", polarBearMaxHealth);
+ set("mobs.polar_bear.attributes.max-health", null);
+ set("mobs.polar_bear.attributes.max_health", oldValue);
+ }
+ polarBearMaxHealth = getDouble("mobs.polar_bear.attributes.max_health", polarBearMaxHealth);
+ polarBearScale = Mth.clamp(getDouble("mobs.polar_bear.attributes.scale", polarBearScale), 0.0625D, 16.0D);
+ polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString);
+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(polarBearBreedableItemString));
+ if (item != Items.AIR) polarBearBreedableItem = item;
+ polarBearBreedingTicks = getInt("mobs.polar_bear.breeding-delay-ticks", polarBearBreedingTicks);
+ polarBearTakeDamageFromWater = getBoolean("mobs.polar_bear.takes-damage-from-water", polarBearTakeDamageFromWater);
+ polarBearAlwaysDropExp = getBoolean("mobs.polar_bear.always-drop-exp", polarBearAlwaysDropExp);
+ }
+
+ public boolean pufferfishRidable = false;
+ public boolean pufferfishControllable = true;
+ public double pufferfishMaxHealth = 3.0D;
+ public double pufferfishScale = 1.0D;
+ public boolean pufferfishTakeDamageFromWater = false;
+ public boolean pufferfishAlwaysDropExp = false;
+ private void pufferfishSettings() {
+ pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable);
+ pufferfishControllable = getBoolean("mobs.pufferfish.controllable", pufferfishControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.pufferfish.attributes.max-health", pufferfishMaxHealth);
+ set("mobs.pufferfish.attributes.max-health", null);
+ set("mobs.pufferfish.attributes.max_health", oldValue);
+ }
+ pufferfishMaxHealth = getDouble("mobs.pufferfish.attributes.max_health", pufferfishMaxHealth);
+ pufferfishScale = Mth.clamp(getDouble("mobs.pufferfish.attributes.scale", pufferfishScale), 0.0625D, 16.0D);
+ pufferfishTakeDamageFromWater = getBoolean("mobs.pufferfish.takes-damage-from-water", pufferfishTakeDamageFromWater);
+ pufferfishAlwaysDropExp = getBoolean("mobs.pufferfish.always-drop-exp", pufferfishAlwaysDropExp);
+ }
+
+ public boolean rabbitRidable = false;
+ public boolean rabbitRidableInWater = true;
+ public boolean rabbitControllable = true;
+ public double rabbitMaxHealth = 3.0D;
+ public double rabbitScale = 1.0D;
+ public double rabbitNaturalToast = 0.0D;
+ public double rabbitNaturalKiller = 0.0D;
+ public int rabbitBreedingTicks = 6000;
+ public boolean rabbitBypassMobGriefing = false;
+ public boolean rabbitTakeDamageFromWater = false;
+ public boolean rabbitAlwaysDropExp = false;
+ private void rabbitSettings() {
+ rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable);
+ rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater);
+ rabbitControllable = getBoolean("mobs.rabbit.controllable", rabbitControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.rabbit.attributes.max-health", rabbitMaxHealth);
+ set("mobs.rabbit.attributes.max-health", null);
+ set("mobs.rabbit.attributes.max_health", oldValue);
+ }
+ rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth);
+ rabbitScale = Mth.clamp(getDouble("mobs.rabbit.attributes.scale", rabbitScale), 0.0625D, 16.0D);
+ rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast);
+ rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller);
+ rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks);
+ rabbitBypassMobGriefing = getBoolean("mobs.rabbit.bypass-mob-griefing", rabbitBypassMobGriefing);
+ rabbitTakeDamageFromWater = getBoolean("mobs.rabbit.takes-damage-from-water", rabbitTakeDamageFromWater);
+ rabbitAlwaysDropExp = getBoolean("mobs.rabbit.always-drop-exp", rabbitAlwaysDropExp);
+ }
+
+ public boolean ravagerRidable = false;
+ public boolean ravagerRidableInWater = false;
+ public boolean ravagerControllable = true;
+ public double ravagerMaxHealth = 100.0D;
+ public double ravagerScale = 1.0D;
+ public boolean ravagerBypassMobGriefing = false;
+ public boolean ravagerTakeDamageFromWater = false;
+ public List<Block> ravagerGriefableBlocks = new ArrayList<>();
+ public boolean ravagerAlwaysDropExp = false;
+ public boolean ravagerAvoidRabbits = false;
+ private void ravagerSettings() {
+ ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable);
+ ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater);
+ ravagerControllable = getBoolean("mobs.ravager.controllable", ravagerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.ravager.attributes.max-health", ravagerMaxHealth);
+ set("mobs.ravager.attributes.max-health", null);
+ set("mobs.ravager.attributes.max_health", oldValue);
+ }
+ ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth);
+ ravagerScale = Mth.clamp(getDouble("mobs.ravager.attributes.scale", ravagerScale), 0.0625D, 16.0D);
+ ravagerBypassMobGriefing = getBoolean("mobs.ravager.bypass-mob-griefing", ravagerBypassMobGriefing);
+ ravagerTakeDamageFromWater = getBoolean("mobs.ravager.takes-damage-from-water", ravagerTakeDamageFromWater);
+ List<String> defaultRavagerGriefableBlocks = List.of(
+ "minecraft:oak_leaves",
+ "minecraft:spruce_leaves",
+ "minecraft:birch_leaves",
+ "minecraft:jungle_leaves",
+ "minecraft:acacia_leaves",
+ "minecraft:cherry_leaves",
+ "minecraft:dark_oak_leaves",
+ "minecraft:pale_oak_leaves",
+ "minecraft:mangrove_leaves",
+ "minecraft:azalea_leaves",
+ "minecraft:flowering_azalea_leaves",
+ "minecraft:wheat",
+ "minecraft:carrots",
+ "minecraft:potatoes",
+ "minecraft:torchflower_crop",
+ "minecraft:pitcher_crop",
+ "minecraft:beetroots"
+ );
+ if (PurpurConfig.version < 41) {
+ Set<String> set = new HashSet<>();
+ getList("mobs.ravager.griefable-blocks", defaultRavagerGriefableBlocks)
+ .forEach(key -> set.add(key.toString()));
+ set.add("minecraft:cherry_leaves");
+ set.add("minecraft:pale_oak_leaves");
+ set.add("minecraft:mangrove_leaves");
+ set.add("minecraft:azalea_leaves");
+ set.add("minecraft:flowering_azalea_leaves");
+ set.add("minecraft:torchflower_crop");
+ set.add("minecraft:pitcher_crop");
+ set("mobs.ravager.griefable-blocks", new ArrayList<>(set));
+ }
+ getList("mobs.ravager.griefable-blocks", defaultRavagerGriefableBlocks).forEach(key -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(key.toString()));
+ if (!block.defaultBlockState().isAir()) {
+ ravagerGriefableBlocks.add(block);
+ }
+ });
+ ravagerAlwaysDropExp = getBoolean("mobs.ravager.always-drop-exp", ravagerAlwaysDropExp);
+ ravagerAvoidRabbits = getBoolean("mobs.ravager.avoid-rabbits", ravagerAvoidRabbits);
+ }
+
+ public boolean salmonRidable = false;
+ public boolean salmonControllable = true;
+ public double salmonMaxHealth = 3.0D;
+ public double salmonScale = 1.0D;
+ public boolean salmonTakeDamageFromWater = false;
+ public boolean salmonAlwaysDropExp = false;
+ private void salmonSettings() {
+ salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable);
+ salmonControllable = getBoolean("mobs.salmon.controllable", salmonControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.salmon.attributes.max-health", salmonMaxHealth);
+ set("mobs.salmon.attributes.max-health", null);
+ set("mobs.salmon.attributes.max_health", oldValue);
+ }
+ salmonMaxHealth = getDouble("mobs.salmon.attributes.max_health", salmonMaxHealth);
+ salmonScale = Mth.clamp(getDouble("mobs.salmon.attributes.scale", salmonScale), 0.0625D, 16.0D);
+ salmonTakeDamageFromWater = getBoolean("mobs.salmon.takes-damage-from-water", salmonTakeDamageFromWater);
+ salmonAlwaysDropExp = getBoolean("mobs.salmon.always-drop-exp", salmonAlwaysDropExp);
+ }
+
+ public boolean sheepRidable = false;
+ public boolean sheepRidableInWater = true;
+ public boolean sheepControllable = true;
+ public double sheepMaxHealth = 8.0D;
+ public double sheepScale = 1.0D;
+ public int sheepBreedingTicks = 6000;
+ public boolean sheepBypassMobGriefing = false;
+ public boolean sheepTakeDamageFromWater = false;
+ public boolean sheepAlwaysDropExp = false;
+ private void sheepSettings() {
+ sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable);
+ sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater);
+ sheepControllable = getBoolean("mobs.sheep.controllable", sheepControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.sheep.attributes.max-health", sheepMaxHealth);
+ set("mobs.sheep.attributes.max-health", null);
+ set("mobs.sheep.attributes.max_health", oldValue);
+ }
+ sheepMaxHealth = getDouble("mobs.sheep.attributes.max_health", sheepMaxHealth);
+ sheepScale = Mth.clamp(getDouble("mobs.sheep.attributes.scale", sheepScale), 0.0625D, 16.0D);
+ sheepBreedingTicks = getInt("mobs.sheep.breeding-delay-ticks", sheepBreedingTicks);
+ sheepBypassMobGriefing = getBoolean("mobs.sheep.bypass-mob-griefing", sheepBypassMobGriefing);
+ sheepTakeDamageFromWater = getBoolean("mobs.sheep.takes-damage-from-water", sheepTakeDamageFromWater);
+ sheepAlwaysDropExp = getBoolean("mobs.sheep.always-drop-exp", sheepAlwaysDropExp);
+ }
+
+ public boolean shulkerRidable = false;
+ public boolean shulkerRidableInWater = true;
+ public boolean shulkerControllable = true;
+ public double shulkerMaxHealth = 30.0D;
+ public double shulkerScale = 1.0D;
+ public boolean shulkerTakeDamageFromWater = false;
+ public float shulkerSpawnFromBulletBaseChance = 1.0F;
+ public boolean shulkerSpawnFromBulletRequireOpenLid = true;
+ public double shulkerSpawnFromBulletNearbyRange = 8.0D;
+ public String shulkerSpawnFromBulletNearbyEquation = "(nearby - 1) / 5.0";
+ public boolean shulkerSpawnFromBulletRandomColor = false;
+ public boolean shulkerChangeColorWithDye = false;
+ public boolean shulkerAlwaysDropExp = false;
+ private void shulkerSettings() {
+ shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable);
+ shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater);
+ shulkerControllable = getBoolean("mobs.shulker.controllable", shulkerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.shulker.attributes.max-health", shulkerMaxHealth);
+ set("mobs.shulker.attributes.max-health", null);
+ set("mobs.shulker.attributes.max_health", oldValue);
+ }
+ shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth);
+ shulkerScale = Mth.clamp(getDouble("mobs.shulker.attributes.scale", shulkerScale), 0.0625D, Shulker.MAX_SCALE);
+ shulkerTakeDamageFromWater = getBoolean("mobs.shulker.takes-damage-from-water", shulkerTakeDamageFromWater);
+ shulkerSpawnFromBulletBaseChance = (float) getDouble("mobs.shulker.spawn-from-bullet.base-chance", shulkerSpawnFromBulletBaseChance);
+ shulkerSpawnFromBulletRequireOpenLid = getBoolean("mobs.shulker.spawn-from-bullet.require-open-lid", shulkerSpawnFromBulletRequireOpenLid);
+ shulkerSpawnFromBulletNearbyRange = getDouble("mobs.shulker.spawn-from-bullet.nearby-range", shulkerSpawnFromBulletNearbyRange);
+ shulkerSpawnFromBulletNearbyEquation = getString("mobs.shulker.spawn-from-bullet.nearby-equation", shulkerSpawnFromBulletNearbyEquation);
+ shulkerSpawnFromBulletRandomColor = getBoolean("mobs.shulker.spawn-from-bullet.random-color", shulkerSpawnFromBulletRandomColor);
+ shulkerChangeColorWithDye = getBoolean("mobs.shulker.change-color-with-dye", shulkerChangeColorWithDye);
+ shulkerAlwaysDropExp = getBoolean("mobs.shulker.always-drop-exp", shulkerAlwaysDropExp);
+ }
+
+ public boolean silverfishRidable = false;
+ public boolean silverfishRidableInWater = true;
+ public boolean silverfishControllable = true;
+ public double silverfishMaxHealth = 8.0D;
+ public double silverfishScale = 1.0D;
+ public double silverfishMovementSpeed = 0.25D;
+ public double silverfishAttackDamage = 1.0D;
+ public boolean silverfishBypassMobGriefing = false;
+ public boolean silverfishTakeDamageFromWater = false;
+ public boolean silverfishAlwaysDropExp = false;
+ private void silverfishSettings() {
+ silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable);
+ silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater);
+ silverfishControllable = getBoolean("mobs.silverfish.controllable", silverfishControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.silverfish.attributes.max-health", silverfishMaxHealth);
+ set("mobs.silverfish.attributes.max-health", null);
+ set("mobs.silverfish.attributes.max_health", oldValue);
+ }
+ silverfishMaxHealth = getDouble("mobs.silverfish.attributes.max_health", silverfishMaxHealth);
+ silverfishScale = Mth.clamp(getDouble("mobs.silverfish.attributes.scale", silverfishScale), 0.0625D, 16.0D);
+ silverfishMovementSpeed = getDouble("mobs.silverfish.attributes.movement_speed", silverfishMovementSpeed);
+ silverfishAttackDamage = getDouble("mobs.silverfish.attributes.attack_damage", silverfishAttackDamage);
+ silverfishBypassMobGriefing = getBoolean("mobs.silverfish.bypass-mob-griefing", silverfishBypassMobGriefing);
+ silverfishTakeDamageFromWater = getBoolean("mobs.silverfish.takes-damage-from-water", silverfishTakeDamageFromWater);
+ silverfishAlwaysDropExp = getBoolean("mobs.silverfish.always-drop-exp", silverfishAlwaysDropExp);
+ }
+
+ public boolean skeletonRidable = false;
+ public boolean skeletonRidableInWater = true;
+ public boolean skeletonControllable = true;
+ public double skeletonMaxHealth = 20.0D;
+ public double skeletonScale = 1.0D;
+ public boolean skeletonTakeDamageFromWater = false;
+ public boolean skeletonAlwaysDropExp = false;
+ public double skeletonHeadVisibilityPercent = 0.5D;
+ public int skeletonFeedWitherRoses = 0;
+ public String skeletonBowAccuracy = "14 - difficulty * 4";
+ public Map<Integer, Float> skeletonBowAccuracyMap = new HashMap<>();
+ private void skeletonSettings() {
+ skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable);
+ skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater);
+ skeletonControllable = getBoolean("mobs.skeleton.controllable", skeletonControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.skeleton.attributes.max-health", skeletonMaxHealth);
+ set("mobs.skeleton.attributes.max-health", null);
+ set("mobs.skeleton.attributes.max_health", oldValue);
+ }
+ skeletonMaxHealth = getDouble("mobs.skeleton.attributes.max_health", skeletonMaxHealth);
+ skeletonScale = Mth.clamp(getDouble("mobs.skeleton.attributes.scale", skeletonScale), 0.0625D, 16.0D);
+ skeletonTakeDamageFromWater = getBoolean("mobs.skeleton.takes-damage-from-water", skeletonTakeDamageFromWater);
+ skeletonAlwaysDropExp = getBoolean("mobs.skeleton.always-drop-exp", skeletonAlwaysDropExp);
+ skeletonHeadVisibilityPercent = getDouble("mobs.skeleton.head-visibility-percent", skeletonHeadVisibilityPercent);
+ skeletonFeedWitherRoses = getInt("mobs.skeleton.feed-wither-roses", skeletonFeedWitherRoses);
+ final String defaultSkeletonBowAccuracy = skeletonBowAccuracy;
+ skeletonBowAccuracy = getString("mobs.skeleton.bow-accuracy", skeletonBowAccuracy);
+ for (int i = 1; i < 4; i++) {
+ final float divergence;
+ try {
+ divergence = ((Number) Entity.scriptEngine.eval("let difficulty = " + i + "; " + skeletonBowAccuracy)).floatValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ break;
+ }
+ skeletonBowAccuracyMap.put(i, divergence);
+ }
+ }
+
+ public boolean skeletonHorseRidable = false;
+ public boolean skeletonHorseRidableInWater = true;
+ public boolean skeletonHorseCanSwim = false;
+ public double skeletonHorseMaxHealthMin = 15.0D;
+ public double skeletonHorseMaxHealthMax = 15.0D;
+ public double skeletonHorseJumpStrengthMin = 0.4D;
+ public double skeletonHorseJumpStrengthMax = 1.0D;
+ public double skeletonHorseMovementSpeedMin = 0.2D;
+ public double skeletonHorseMovementSpeedMax = 0.2D;
+ public boolean skeletonHorseTakeDamageFromWater = false;
+ public boolean skeletonHorseAlwaysDropExp = false;
+ private void skeletonHorseSettings() {
+ skeletonHorseRidable = getBoolean("mobs.skeleton_horse.ridable", skeletonHorseRidable);
+ skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater);
+ skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.skeleton_horse.attributes.max-health", skeletonHorseMaxHealthMin);
+ set("mobs.skeleton_horse.attributes.max-health", null);
+ set("mobs.skeleton_horse.attributes.max_health.min", oldValue);
+ set("mobs.skeleton_horse.attributes.max_health.max", oldValue);
+ }
+ skeletonHorseMaxHealthMin = getDouble("mobs.skeleton_horse.attributes.max_health.min", skeletonHorseMaxHealthMin);
+ skeletonHorseMaxHealthMax = getDouble("mobs.skeleton_horse.attributes.max_health.max", skeletonHorseMaxHealthMax);
+ skeletonHorseJumpStrengthMin = getDouble("mobs.skeleton_horse.attributes.jump_strength.min", skeletonHorseJumpStrengthMin);
+ skeletonHorseJumpStrengthMax = getDouble("mobs.skeleton_horse.attributes.jump_strength.max", skeletonHorseJumpStrengthMax);
+ skeletonHorseMovementSpeedMin = getDouble("mobs.skeleton_horse.attributes.movement_speed.min", skeletonHorseMovementSpeedMin);
+ skeletonHorseMovementSpeedMax = getDouble("mobs.skeleton_horse.attributes.movement_speed.max", skeletonHorseMovementSpeedMax);
+ skeletonHorseTakeDamageFromWater = getBoolean("mobs.skeleton_horse.takes-damage-from-water", skeletonHorseTakeDamageFromWater);
+ skeletonHorseAlwaysDropExp = getBoolean("mobs.skeleton_horse.always-drop-exp", skeletonHorseAlwaysDropExp);
+ }
+
+ public boolean slimeRidable = false;
+ public boolean slimeRidableInWater = true;
+ public boolean slimeControllable = true;
+ public String slimeMaxHealth = "size * size";
+ public String slimeAttackDamage = "size";
+ public Map<Integer, Double> slimeMaxHealthCache = new HashMap<>();
+ public Map<Integer, Double> slimeAttackDamageCache = new HashMap<>();
+ public boolean slimeTakeDamageFromWater = false;
+ public boolean slimeAlwaysDropExp = false;
+ private void slimeSettings() {
+ slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable);
+ slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater);
+ slimeControllable = getBoolean("mobs.slime.controllable", slimeControllable);
+ if (PurpurConfig.version < 10) {
+ String oldValue = getString("mobs.slime.attributes.max-health", slimeMaxHealth);
+ set("mobs.slime.attributes.max-health", null);
+ set("mobs.slime.attributes.max_health", oldValue);
+ }
+ slimeMaxHealth = getString("mobs.slime.attributes.max_health", slimeMaxHealth);
+ slimeAttackDamage = getString("mobs.slime.attributes.attack_damage", slimeAttackDamage);
+ slimeMaxHealthCache.clear();
+ slimeAttackDamageCache.clear();
+ slimeTakeDamageFromWater = getBoolean("mobs.slime.takes-damage-from-water", slimeTakeDamageFromWater);
+ slimeAlwaysDropExp = getBoolean("mobs.slime.always-drop-exp", slimeAlwaysDropExp);
+ }
+
+ public boolean snowGolemRidable = false;
+ public boolean snowGolemRidableInWater = true;
+ public boolean snowGolemControllable = true;
+ public boolean snowGolemLeaveTrailWhenRidden = false;
+ public double snowGolemMaxHealth = 4.0D;
+ public double snowGolemScale = 1.0D;
+ public boolean snowGolemPutPumpkinBack = false;
+ public int snowGolemSnowBallMin = 20;
+ public int snowGolemSnowBallMax = 20;
+ public float snowGolemSnowBallModifier = 10.0F;
+ public double snowGolemAttackDistance = 1.25D;
+ public boolean snowGolemBypassMobGriefing = false;
+ public boolean snowGolemTakeDamageFromWater = true;
+ public boolean snowGolemAlwaysDropExp = false;
+ private void snowGolemSettings() {
+ snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable);
+ snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater);
+ snowGolemControllable = getBoolean("mobs.snow_golem.controllable", snowGolemControllable);
+ snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.snow_golem.attributes.max-health", snowGolemMaxHealth);
+ set("mobs.snow_golem.attributes.max-health", null);
+ set("mobs.snow_golem.attributes.max_health", oldValue);
+ }
+ snowGolemMaxHealth = getDouble("mobs.snow_golem.attributes.max_health", snowGolemMaxHealth);
+ snowGolemScale = Mth.clamp(getDouble("mobs.snow_golem.attributes.scale", snowGolemScale), 0.0625D, 16.0D);
+ snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack);
+ snowGolemSnowBallMin = getInt("mobs.snow_golem.min-shoot-interval-ticks", snowGolemSnowBallMin);
+ snowGolemSnowBallMax = getInt("mobs.snow_golem.max-shoot-interval-ticks", snowGolemSnowBallMax);
+ snowGolemSnowBallModifier = (float) getDouble("mobs.snow_golem.snow-ball-modifier", snowGolemSnowBallModifier);
+ snowGolemAttackDistance = getDouble("mobs.snow_golem.attack-distance", snowGolemAttackDistance);
+ snowGolemBypassMobGriefing = getBoolean("mobs.snow_golem.bypass-mob-griefing", snowGolemBypassMobGriefing);
+ snowGolemTakeDamageFromWater = getBoolean("mobs.snow_golem.takes-damage-from-water", snowGolemTakeDamageFromWater);
+ snowGolemAlwaysDropExp = getBoolean("mobs.snow_golem.always-drop-exp", snowGolemAlwaysDropExp);
+ }
+
+ public boolean snifferRidable = false;
+ public boolean snifferRidableInWater = true;
+ public boolean snifferControllable = true;
+ public double snifferMaxHealth = 14.0D;
+ public double snifferScale = 1.0D;
+ public int snifferBreedingTicks = 6000;
+ private void snifferSettings() {
+ snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable);
+ snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater);
+ snifferControllable = getBoolean("mobs.sniffer.controllable", snifferControllable);
+ snifferMaxHealth = getDouble("mobs.sniffer.attributes.max_health", snifferMaxHealth);
+ snifferScale = Mth.clamp(getDouble("mobs.sniffer.attributes.scale", snifferScale), 0.0625D, 16.0D);
+ snifferBreedingTicks = getInt("mobs.sniffer.breeding-delay-ticks", snifferBreedingTicks);
+ }
+
+ public boolean squidRidable = false;
+ public boolean squidControllable = true;
+ public double squidMaxHealth = 10.0D;
+ public double squidScale = 1.0D;
+ public boolean squidImmuneToEAR = true;
+ public double squidOffsetWaterCheck = 0.0D;
+ public boolean squidsCanFly = false;
+ public boolean squidTakeDamageFromWater = false;
+ public boolean squidAlwaysDropExp = false;
+ private void squidSettings() {
+ squidRidable = getBoolean("mobs.squid.ridable", squidRidable);
+ squidControllable = getBoolean("mobs.squid.controllable", squidControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.squid.attributes.max-health", squidMaxHealth);
+ set("mobs.squid.attributes.max-health", null);
+ set("mobs.squid.attributes.max_health", oldValue);
+ }
+ squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth);
+ squidScale = Mth.clamp(getDouble("mobs.squid.attributes.scale", squidScale), 0.0625D, 16.0D);
+ squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR);
+ squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck);
+ squidsCanFly = getBoolean("mobs.squid.can-fly", squidsCanFly);
+ squidTakeDamageFromWater = getBoolean("mobs.squid.takes-damage-from-water", squidTakeDamageFromWater);
+ squidAlwaysDropExp = getBoolean("mobs.squid.always-drop-exp", squidAlwaysDropExp);
+ }
+
+ public boolean spiderRidable = false;
+ public boolean spiderRidableInWater = false;
+ public boolean spiderControllable = true;
+ public double spiderMaxHealth = 16.0D;
+ public double spiderScale = 1.0D;
+ public boolean spiderTakeDamageFromWater = false;
+ public boolean spiderAlwaysDropExp = false;
+ private void spiderSettings() {
+ spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable);
+ spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater);
+ spiderControllable = getBoolean("mobs.spider.controllable", spiderControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.spider.attributes.max-health", spiderMaxHealth);
+ set("mobs.spider.attributes.max-health", null);
+ set("mobs.spider.attributes.max_health", oldValue);
+ }
+ spiderMaxHealth = getDouble("mobs.spider.attributes.max_health", spiderMaxHealth);
+ spiderScale = Mth.clamp(getDouble("mobs.spider.attributes.scale", spiderScale), 0.0625D, 16.0D);
+ spiderTakeDamageFromWater = getBoolean("mobs.spider.takes-damage-from-water", spiderTakeDamageFromWater);
+ spiderAlwaysDropExp = getBoolean("mobs.spider.always-drop-exp", spiderAlwaysDropExp);
+ }
+
+ public boolean strayRidable = false;
+ public boolean strayRidableInWater = true;
+ public boolean strayControllable = true;
+ public double strayMaxHealth = 20.0D;
+ public double strayScale = 1.0D;
+ public boolean strayTakeDamageFromWater = false;
+ public boolean strayAlwaysDropExp = false;
+ private void straySettings() {
+ strayRidable = getBoolean("mobs.stray.ridable", strayRidable);
+ strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater);
+ strayControllable = getBoolean("mobs.stray.controllable", strayControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.stray.attributes.max-health", strayMaxHealth);
+ set("mobs.stray.attributes.max-health", null);
+ set("mobs.stray.attributes.max_health", oldValue);
+ }
+ strayMaxHealth = getDouble("mobs.stray.attributes.max_health", strayMaxHealth);
+ strayScale = Mth.clamp(getDouble("mobs.stray.attributes.scale", strayScale), 0.0625D, 16.0D);
+ strayTakeDamageFromWater = getBoolean("mobs.stray.takes-damage-from-water", strayTakeDamageFromWater);
+ strayAlwaysDropExp = getBoolean("mobs.stray.always-drop-exp", strayAlwaysDropExp);
+ }
+
+ public boolean striderRidable = false;
+ public boolean striderRidableInWater = false;
+ public boolean striderControllable = true;
+ public double striderMaxHealth = 20.0D;
+ public double striderScale = 1.0D;
+ public int striderBreedingTicks = 6000;
+ public boolean striderGiveSaddleBack = false;
+ public boolean striderTakeDamageFromWater = true;
+ public boolean striderAlwaysDropExp = false;
+ private void striderSettings() {
+ striderRidable = getBoolean("mobs.strider.ridable", striderRidable);
+ striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater);
+ striderControllable = getBoolean("mobs.strider.controllable", striderControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.strider.attributes.max-health", striderMaxHealth);
+ set("mobs.strider.attributes.max-health", null);
+ set("mobs.strider.attributes.max_health", oldValue);
+ }
+ striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth);
+ striderScale = Mth.clamp(getDouble("mobs.strider.attributes.scale", striderScale), 0.0625D, 16.0D);
+ striderBreedingTicks = getInt("mobs.strider.breeding-delay-ticks", striderBreedingTicks);
+ striderGiveSaddleBack = getBoolean("mobs.strider.give-saddle-back", striderGiveSaddleBack);
+ striderTakeDamageFromWater = getBoolean("mobs.strider.takes-damage-from-water", striderTakeDamageFromWater);
+ striderAlwaysDropExp = getBoolean("mobs.strider.always-drop-exp", striderAlwaysDropExp);
+ }
+
+ public boolean tadpoleRidable = false;
+ public boolean tadpoleRidableInWater = true;
+ public boolean tadpoleControllable = true;
+ private void tadpoleSettings() {
+ tadpoleRidable = getBoolean("mobs.tadpole.ridable", tadpoleRidable);
+ tadpoleRidableInWater = getBoolean("mobs.tadpole.ridable-in-water", tadpoleRidableInWater);
+ tadpoleControllable = getBoolean("mobs.tadpole.controllable", tadpoleControllable);
+ }
+
+ public boolean traderLlamaRidable = false;
+ public boolean traderLlamaRidableInWater = false;
+ public boolean traderLlamaControllable = true;
+ public double traderLlamaMaxHealthMin = 15.0D;
+ public double traderLlamaMaxHealthMax = 30.0D;
+ public double traderLlamaJumpStrengthMin = 0.5D;
+ public double traderLlamaJumpStrengthMax = 0.5D;
+ public double traderLlamaMovementSpeedMin = 0.175D;
+ public double traderLlamaMovementSpeedMax = 0.175D;
+ public int traderLlamaBreedingTicks = 6000;
+ public boolean traderLlamaTakeDamageFromWater = false;
+ public boolean traderLlamaAlwaysDropExp = false;
+ private void traderLlamaSettings() {
+ traderLlamaRidable = getBoolean("mobs.trader_llama.ridable", traderLlamaRidable);
+ traderLlamaRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", traderLlamaRidableInWater);
+ traderLlamaControllable = getBoolean("mobs.trader_llama.controllable", traderLlamaControllable);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.trader_llama.attributes.max-health.min", traderLlamaMaxHealthMin);
+ double oldMax = getDouble("mobs.trader_llama.attributes.max-health.max", traderLlamaMaxHealthMax);
+ set("mobs.trader_llama.attributes.max-health", null);
+ set("mobs.trader_llama.attributes.max_health.min", oldMin);
+ set("mobs.trader_llama.attributes.max_health.max", oldMax);
+ }
+ traderLlamaMaxHealthMin = getDouble("mobs.trader_llama.attributes.max_health.min", traderLlamaMaxHealthMin);
+ traderLlamaMaxHealthMax = getDouble("mobs.trader_llama.attributes.max_health.max", traderLlamaMaxHealthMax);
+ traderLlamaJumpStrengthMin = getDouble("mobs.trader_llama.attributes.jump_strength.min", traderLlamaJumpStrengthMin);
+ traderLlamaJumpStrengthMax = getDouble("mobs.trader_llama.attributes.jump_strength.max", traderLlamaJumpStrengthMax);
+ traderLlamaMovementSpeedMin = getDouble("mobs.trader_llama.attributes.movement_speed.min", traderLlamaMovementSpeedMin);
+ traderLlamaMovementSpeedMax = getDouble("mobs.trader_llama.attributes.movement_speed.max", traderLlamaMovementSpeedMax);
+ traderLlamaBreedingTicks = getInt("mobs.trader_llama.breeding-delay-ticks", traderLlamaBreedingTicks);
+ traderLlamaTakeDamageFromWater = getBoolean("mobs.trader_llama.takes-damage-from-water", traderLlamaTakeDamageFromWater);
+ traderLlamaAlwaysDropExp = getBoolean("mobs.trader_llama.always-drop-exp", traderLlamaAlwaysDropExp);
+ }
+
+ public boolean tropicalFishRidable = false;
+ public boolean tropicalFishControllable = true;
+ public double tropicalFishMaxHealth = 3.0D;
+ public double tropicalFishScale = 1.0D;
+ public boolean tropicalFishTakeDamageFromWater = false;
+ public boolean tropicalFishAlwaysDropExp = false;
+ private void tropicalFishSettings() {
+ tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable);
+ tropicalFishControllable = getBoolean("mobs.tropical_fish.controllable", tropicalFishControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.tropical_fish.attributes.max-health", tropicalFishMaxHealth);
+ set("mobs.tropical_fish.attributes.max-health", null);
+ set("mobs.tropical_fish.attributes.max_health", oldValue);
+ }
+ tropicalFishMaxHealth = getDouble("mobs.tropical_fish.attributes.max_health", tropicalFishMaxHealth);
+ tropicalFishScale = Mth.clamp(getDouble("mobs.tropical_fish.attributes.scale", tropicalFishScale), 0.0625D, 16.0D);
+ tropicalFishTakeDamageFromWater = getBoolean("mobs.tropical_fish.takes-damage-from-water", tropicalFishTakeDamageFromWater);
+ tropicalFishAlwaysDropExp = getBoolean("mobs.tropical_fish.always-drop-exp", tropicalFishAlwaysDropExp);
+ }
+
+ public boolean turtleRidable = false;
+ public boolean turtleRidableInWater = true;
+ public boolean turtleControllable = true;
+ public double turtleMaxHealth = 30.0D;
+ public double turtleScale = 1.0D;
+ public int turtleBreedingTicks = 6000;
+ public boolean turtleTakeDamageFromWater = false;
+ public boolean turtleAlwaysDropExp = false;
+ private void turtleSettings() {
+ turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable);
+ turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater);
+ turtleControllable = getBoolean("mobs.turtle.controllable", turtleControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.turtle.attributes.max-health", turtleMaxHealth);
+ set("mobs.turtle.attributes.max-health", null);
+ set("mobs.turtle.attributes.max_health", oldValue);
+ }
+ turtleMaxHealth = getDouble("mobs.turtle.attributes.max_health", turtleMaxHealth);
+ turtleScale = Mth.clamp(getDouble("mobs.turtle.attributes.scale", turtleScale), 0.0625D, 16.0D);
+ turtleBreedingTicks = getInt("mobs.turtle.breeding-delay-ticks", turtleBreedingTicks);
+ turtleTakeDamageFromWater = getBoolean("mobs.turtle.takes-damage-from-water", turtleTakeDamageFromWater);
+ turtleAlwaysDropExp = getBoolean("mobs.turtle.always-drop-exp", turtleAlwaysDropExp);
+ }
+
+ public boolean vexRidable = false;
+ public boolean vexRidableInWater = true;
+ public boolean vexControllable = true;
+ public double vexMaxY = 320D;
+ public double vexMaxHealth = 14.0D;
+ public double vexScale = 1.0D;
+ public boolean vexTakeDamageFromWater = false;
+ public boolean vexAlwaysDropExp = false;
+ private void vexSettings() {
+ vexRidable = getBoolean("mobs.vex.ridable", vexRidable);
+ vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater);
+ vexControllable = getBoolean("mobs.vex.controllable", vexControllable);
+ vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.vex.attributes.max-health", vexMaxHealth);
+ set("mobs.vex.attributes.max-health", null);
+ set("mobs.vex.attributes.max_health", oldValue);
+ }
+ vexMaxHealth = getDouble("mobs.vex.attributes.max_health", vexMaxHealth);
+ vexScale = Mth.clamp(getDouble("mobs.vex.attributes.scale", vexScale), 0.0625D, 16.0D);
+ vexTakeDamageFromWater = getBoolean("mobs.vex.takes-damage-from-water", vexTakeDamageFromWater);
+ vexAlwaysDropExp = getBoolean("mobs.vex.always-drop-exp", vexAlwaysDropExp);
+ }
+
+ public boolean villagerRidable = false;
+ public boolean villagerRidableInWater = true;
+ public boolean villagerControllable = true;
+ public double villagerMaxHealth = 20.0D;
+ public double villagerScale = 1.0D;
+ public boolean villagerFollowEmeraldBlock = false;
+ public double villagerTemptRange = 10.0D;
+ public boolean villagerCanBeLeashed = false;
+ public boolean villagerCanBreed = true;
+ public int villagerBreedingTicks = 6000;
+ public boolean villagerClericsFarmWarts = false;
+ public boolean villagerClericFarmersThrowWarts = true;
+ public boolean villagerBypassMobGriefing = false;
+ public boolean villagerTakeDamageFromWater = false;
+ public boolean villagerAllowTrading = true;
+ public boolean villagerAlwaysDropExp = false;
+ public int villagerMinimumDemand = 0;
+ public boolean villagerLobotomizeEnabled = false;
+ public int villagerLobotomizeCheckInterval = 100;
+ public boolean villagerLobotomizeWaitUntilTradeLocked = false;
+ public boolean villagerDisplayTradeItem = true;
+ public int villagerSpawnIronGolemRadius = 0;
+ public int villagerSpawnIronGolemLimit = 0;
+ public int villagerAcquirePoiSearchRadius = 48;
+ public int villagerNearestBedSensorSearchRadius = 48;
+ private void villagerSettings() {
+ villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable);
+ villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater);
+ villagerControllable = getBoolean("mobs.villager.controllable", villagerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.villager.attributes.max-health", villagerMaxHealth);
+ set("mobs.villager.attributes.max-health", null);
+ set("mobs.villager.attributes.max_health", oldValue);
+ }
+ villagerMaxHealth = getDouble("mobs.villager.attributes.max_health", villagerMaxHealth);
+ villagerScale = Mth.clamp(getDouble("mobs.villager.attributes.scale", villagerScale), 0.0625D, 16.0D);
+ villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock);
+ villagerTemptRange = getDouble("mobs.villager.attributes.tempt_range", villagerTemptRange);
+ villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed);
+ villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed);
+ villagerBreedingTicks = getInt("mobs.villager.breeding-delay-ticks", villagerBreedingTicks);
+ villagerClericsFarmWarts = getBoolean("mobs.villager.clerics-farm-warts", villagerClericsFarmWarts);
+ villagerClericFarmersThrowWarts = getBoolean("mobs.villager.cleric-wart-farmers-throw-warts-at-villagers", villagerClericFarmersThrowWarts);
+ villagerBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerBypassMobGriefing);
+ villagerTakeDamageFromWater = getBoolean("mobs.villager.takes-damage-from-water", villagerTakeDamageFromWater);
+ villagerAllowTrading = getBoolean("mobs.villager.allow-trading", villagerAllowTrading);
+ villagerAlwaysDropExp = getBoolean("mobs.villager.always-drop-exp", villagerAlwaysDropExp);
+ villagerMinimumDemand = getInt("mobs.villager.minimum-demand", villagerMinimumDemand);
+ if (PurpurConfig.version < 9) {
+ boolean oldValue = getBoolean("mobs.villager.lobotomize-1x1", villagerLobotomizeEnabled);
+ set("mobs.villager.lobotomize.enabled", oldValue);
+ set("mobs.villager.lobotomize-1x1", null);
+ }
+ if (PurpurConfig.version < 27) {
+ int oldValue = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval);
+ set("mobs.villager.lobotomize.check-interval", oldValue == 60 ? 100 : oldValue);
+ }
+ villagerLobotomizeEnabled = getBoolean("mobs.villager.lobotomize.enabled", villagerLobotomizeEnabled);
+ villagerLobotomizeCheckInterval = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval);
+ villagerLobotomizeWaitUntilTradeLocked = getBoolean("mobs.villager.lobotomize.wait-until-trade-locked", villagerLobotomizeWaitUntilTradeLocked);
+ villagerDisplayTradeItem = getBoolean("mobs.villager.display-trade-item", villagerDisplayTradeItem);
+ villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius);
+ villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit);
+ villagerAcquirePoiSearchRadius = getInt("mobs.villager.search-radius.acquire-poi", villagerAcquirePoiSearchRadius);
+ villagerNearestBedSensorSearchRadius = getInt("mobs.villager.search-radius.nearest-bed-sensor", villagerNearestBedSensorSearchRadius);
+ }
+
+ public boolean vindicatorRidable = false;
+ public boolean vindicatorRidableInWater = true;
+ public boolean vindicatorControllable = true;
+ public double vindicatorMaxHealth = 24.0D;
+ public double vindicatorScale = 1.0D;
+ public double vindicatorJohnnySpawnChance = 0D;
+ public boolean vindicatorTakeDamageFromWater = false;
+ public boolean vindicatorAlwaysDropExp = false;
+ private void vindicatorSettings() {
+ vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable);
+ vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater);
+ vindicatorControllable = getBoolean("mobs.vindicator.controllable", vindicatorControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.vindicator.attributes.max-health", vindicatorMaxHealth);
+ set("mobs.vindicator.attributes.max-health", null);
+ set("mobs.vindicator.attributes.max_health", oldValue);
+ }
+ vindicatorMaxHealth = getDouble("mobs.vindicator.attributes.max_health", vindicatorMaxHealth);
+ vindicatorScale = Mth.clamp(getDouble("mobs.vindicator.attributes.scale", vindicatorScale), 0.0625D, 16.0D);
+ vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance);
+ vindicatorTakeDamageFromWater = getBoolean("mobs.vindicator.takes-damage-from-water", vindicatorTakeDamageFromWater);
+ vindicatorAlwaysDropExp = getBoolean("mobs.vindicator.always-drop-exp", vindicatorAlwaysDropExp);
+ }
+
+ public boolean wanderingTraderRidable = false;
+ public boolean wanderingTraderRidableInWater = true;
+ public boolean wanderingTraderControllable = true;
+ public double wanderingTraderMaxHealth = 20.0D;
+ public double wanderingTraderScale = 1.0D;
+ public boolean wanderingTraderFollowEmeraldBlock = false;
+ public double wanderingTraderTemptRange = 10.0D;
+ public boolean wanderingTraderCanBeLeashed = false;
+ public boolean wanderingTraderTakeDamageFromWater = false;
+ public boolean wanderingTraderAllowTrading = true;
+ public boolean wanderingTraderAlwaysDropExp = false;
+ private void wanderingTraderSettings() {
+ wanderingTraderRidable = getBoolean("mobs.wandering_trader.ridable", wanderingTraderRidable);
+ wanderingTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", wanderingTraderRidableInWater);
+ wanderingTraderControllable = getBoolean("mobs.wandering_trader.controllable", wanderingTraderControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.wandering_trader.attributes.max-health", wanderingTraderMaxHealth);
+ set("mobs.wandering_trader.attributes.max-health", null);
+ set("mobs.wandering_trader.attributes.max_health", oldValue);
+ }
+ wanderingTraderMaxHealth = getDouble("mobs.wandering_trader.attributes.max_health", wanderingTraderMaxHealth);
+ wanderingTraderScale = Mth.clamp(getDouble("mobs.wandering_trader.attributes.scale", wanderingTraderScale), 0.0625D, 16.0D);
+ wanderingTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", wanderingTraderFollowEmeraldBlock);
+ wanderingTraderTemptRange = getDouble("mobs.wandering_trader.attributes.tempt_range", wanderingTraderTemptRange);
+ wanderingTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", wanderingTraderCanBeLeashed);
+ wanderingTraderTakeDamageFromWater = getBoolean("mobs.wandering_trader.takes-damage-from-water", wanderingTraderTakeDamageFromWater);
+ wanderingTraderAllowTrading = getBoolean("mobs.wandering_trader.allow-trading", wanderingTraderAllowTrading);
+ wanderingTraderAlwaysDropExp = getBoolean("mobs.wandering_trader.always-drop-exp", wanderingTraderAlwaysDropExp);
+ }
+
+ public boolean wardenRidable = false;
+ public boolean wardenRidableInWater = true;
+ public boolean wardenControllable = true;
+ private void wardenSettings() {
+ wardenRidable = getBoolean("mobs.warden.ridable", wardenRidable);
+ wardenRidableInWater = getBoolean("mobs.warden.ridable-in-water", wardenRidableInWater);
+ wardenControllable = getBoolean("mobs.warden.controllable", wardenControllable);
+ }
+
+ public boolean witchRidable = false;
+ public boolean witchRidableInWater = true;
+ public boolean witchControllable = true;
+ public double witchMaxHealth = 26.0D;
+ public double witchScale = 1.0D;
+ public boolean witchTakeDamageFromWater = false;
+ public boolean witchAlwaysDropExp = false;
+ private void witchSettings() {
+ witchRidable = getBoolean("mobs.witch.ridable", witchRidable);
+ witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater);
+ witchControllable = getBoolean("mobs.witch.controllable", witchControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.witch.attributes.max-health", witchMaxHealth);
+ set("mobs.witch.attributes.max-health", null);
+ set("mobs.witch.attributes.max_health", oldValue);
+ }
+ witchMaxHealth = getDouble("mobs.witch.attributes.max_health", witchMaxHealth);
+ witchScale = Mth.clamp(getDouble("mobs.witch.attributes.scale", witchScale), 0.0625D, 16.0D);
+ witchTakeDamageFromWater = getBoolean("mobs.witch.takes-damage-from-water", witchTakeDamageFromWater);
+ witchAlwaysDropExp = getBoolean("mobs.witch.always-drop-exp", witchAlwaysDropExp);
+ }
+
+ public boolean witherRidable = false;
+ public boolean witherRidableInWater = true;
+ public boolean witherControllable = true;
+ public double witherMaxY = 320D;
+ public double witherMaxHealth = 300.0D;
+ public double witherScale = 1.0D;
+ public float witherHealthRegenAmount = 1.0f;
+ public int witherHealthRegenDelay = 20;
+ public boolean witherBypassMobGriefing = false;
+ public boolean witherTakeDamageFromWater = false;
+ public boolean witherCanRideVehicles = false;
+ public float witherExplosionRadius = 1.0F;
+ public boolean witherPlaySpawnSound = true;
+ public boolean witherAlwaysDropExp = false;
+ private void witherSettings() {
+ witherRidable = getBoolean("mobs.wither.ridable", witherRidable);
+ witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater);
+ witherControllable = getBoolean("mobs.wither.controllable", witherControllable);
+ witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY);
+ if (PurpurConfig.version < 8) {
+ double oldValue = getDouble("mobs.wither.max-health", witherMaxHealth);
+ set("mobs.wither.max_health", null);
+ set("mobs.wither.attributes.max-health", oldValue);
+ } else if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.wither.attributes.max-health", witherMaxHealth);
+ set("mobs.wither.attributes.max-health", null);
+ set("mobs.wither.attributes.max_health", oldValue);
+ }
+ witherMaxHealth = getDouble("mobs.wither.attributes.max_health", witherMaxHealth);
+ witherScale = Mth.clamp(getDouble("mobs.wither.attributes.scale", witherScale), 0.0625D, 16.0D);
+ witherHealthRegenAmount = (float) getDouble("mobs.wither.health-regen-amount", witherHealthRegenAmount);
+ witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay);
+ witherBypassMobGriefing = getBoolean("mobs.wither.bypass-mob-griefing", witherBypassMobGriefing);
+ witherTakeDamageFromWater = getBoolean("mobs.wither.takes-damage-from-water", witherTakeDamageFromWater);
+ witherCanRideVehicles = getBoolean("mobs.wither.can-ride-vehicles", witherCanRideVehicles);
+ witherExplosionRadius = (float) getDouble("mobs.wither.explosion-radius", witherExplosionRadius);
+ witherPlaySpawnSound = getBoolean("mobs.wither.play-spawn-sound", witherPlaySpawnSound);
+ witherAlwaysDropExp = getBoolean("mobs.wither.always-drop-exp", witherAlwaysDropExp);
+ }
+
+ public boolean witherSkeletonRidable = false;
+ public boolean witherSkeletonRidableInWater = true;
+ public boolean witherSkeletonControllable = true;
+ public double witherSkeletonMaxHealth = 20.0D;
+ public double witherSkeletonScale = 1.0D;
+ public boolean witherSkeletonTakeDamageFromWater = false;
+ public boolean witherSkeletonAlwaysDropExp = false;
+ private void witherSkeletonSettings() {
+ witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable);
+ witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater);
+ witherSkeletonControllable = getBoolean("mobs.wither_skeleton.controllable", witherSkeletonControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.wither_skeleton.attributes.max-health", witherSkeletonMaxHealth);
+ set("mobs.wither_skeleton.attributes.max-health", null);
+ set("mobs.wither_skeleton.attributes.max_health", oldValue);
+ }
+ witherSkeletonMaxHealth = getDouble("mobs.wither_skeleton.attributes.max_health", witherSkeletonMaxHealth);
+ witherSkeletonScale = Mth.clamp(getDouble("mobs.wither_skeleton.attributes.scale", witherSkeletonScale), 0.0625D, 16.0D);
+ witherSkeletonTakeDamageFromWater = getBoolean("mobs.wither_skeleton.takes-damage-from-water", witherSkeletonTakeDamageFromWater);
+ witherSkeletonAlwaysDropExp = getBoolean("mobs.wither_skeleton.always-drop-exp", witherSkeletonAlwaysDropExp);
+ }
+
+ public boolean wolfRidable = false;
+ public boolean wolfRidableInWater = true;
+ public boolean wolfControllable = true;
+ public double wolfMaxHealth = 8.0D;
+ public double wolfScale = 1.0D;
+ public DyeColor wolfDefaultCollarColor = DyeColor.RED;
+ public boolean wolfMilkCuresRabies = true;
+ public double wolfNaturalRabid = 0.0D;
+ public int wolfBreedingTicks = 6000;
+ public boolean wolfTakeDamageFromWater = false;
+ public boolean wolfAlwaysDropExp = false;
+ private void wolfSettings() {
+ wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable);
+ wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater);
+ wolfControllable = getBoolean("mobs.wolf.controllable", wolfControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.wolf.attributes.max-health", wolfMaxHealth);
+ set("mobs.wolf.attributes.max-health", null);
+ set("mobs.wolf.attributes.max_health", oldValue);
+ }
+ wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth);
+ wolfScale = Mth.clamp(getDouble("mobs.wolf.attributes.scale", wolfScale), 0.0625D, 16.0D);
+ try {
+ wolfDefaultCollarColor = DyeColor.valueOf(getString("mobs.wolf.default-collar-color", wolfDefaultCollarColor.name()));
+ } catch (IllegalArgumentException ignore) {
+ wolfDefaultCollarColor = DyeColor.RED;
+ }
+ wolfMilkCuresRabies = getBoolean("mobs.wolf.milk-cures-rabid-wolves", wolfMilkCuresRabies);
+ wolfNaturalRabid = getDouble("mobs.wolf.spawn-rabid-chance", wolfNaturalRabid);
+ wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks);
+ wolfTakeDamageFromWater = getBoolean("mobs.wolf.takes-damage-from-water", wolfTakeDamageFromWater);
+ wolfAlwaysDropExp = getBoolean("mobs.wolf.always-drop-exp", wolfAlwaysDropExp);
+ }
+
+ public boolean zoglinRidable = false;
+ public boolean zoglinRidableInWater = true;
+ public boolean zoglinControllable = true;
+ public double zoglinMaxHealth = 40.0D;
+ public double zoglinScale = 1.0D;
+ public boolean zoglinTakeDamageFromWater = false;
+ public boolean zoglinAlwaysDropExp = false;
+ private void zoglinSettings() {
+ zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable);
+ zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater);
+ zoglinControllable = getBoolean("mobs.zoglin.controllable", zoglinControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zoglin.attributes.max-health", zoglinMaxHealth);
+ set("mobs.zoglin.attributes.max-health", null);
+ set("mobs.zoglin.attributes.max_health", oldValue);
+ }
+ zoglinMaxHealth = getDouble("mobs.zoglin.attributes.max_health", zoglinMaxHealth);
+ zoglinScale = Mth.clamp(getDouble("mobs.zoglin.attributes.scale", zoglinScale), 0.0625D, 16.0D);
+ zoglinTakeDamageFromWater = getBoolean("mobs.zoglin.takes-damage-from-water", zoglinTakeDamageFromWater);
+ zoglinAlwaysDropExp = getBoolean("mobs.zoglin.always-drop-exp", zoglinAlwaysDropExp);
+ }
+
+ public boolean zombieRidable = false;
+ public boolean zombieRidableInWater = true;
+ public boolean zombieControllable = true;
+ public double zombieMaxHealth = 20.0D;
+ public double zombieScale = 1.0D;
+ public double zombieSpawnReinforcements = 0.1D;
+ public boolean zombieJockeyOnlyBaby = true;
+ public double zombieJockeyChance = 0.05D;
+ public boolean zombieJockeyTryExistingChickens = true;
+ public boolean zombieAggressiveTowardsVillagerWhenLagging = true;
+ public boolean zombieBypassMobGriefing = false;
+ public boolean zombieTakeDamageFromWater = false;
+ public boolean zombieAlwaysDropExp = false;
+ public double zombieHeadVisibilityPercent = 0.5D;
+ private void zombieSettings() {
+ zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable);
+ zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater);
+ zombieControllable = getBoolean("mobs.zombie.controllable", zombieControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zombie.attributes.max-health", zombieMaxHealth);
+ set("mobs.zombie.attributes.max-health", null);
+ set("mobs.zombie.attributes.max_health", oldValue);
+ }
+ zombieMaxHealth = getDouble("mobs.zombie.attributes.max_health", zombieMaxHealth);
+ zombieScale = Mth.clamp(getDouble("mobs.zombie.attributes.scale", zombieScale), 0.0625D, 16.0D);
+ zombieSpawnReinforcements = getDouble("mobs.zombie.attributes.spawn_reinforcements", zombieSpawnReinforcements);
+ zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby);
+ zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance);
+ zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens);
+ zombieAggressiveTowardsVillagerWhenLagging = getBoolean("mobs.zombie.aggressive-towards-villager-when-lagging", zombieAggressiveTowardsVillagerWhenLagging);
+ zombieBypassMobGriefing = getBoolean("mobs.zombie.bypass-mob-griefing", zombieBypassMobGriefing);
+ zombieTakeDamageFromWater = getBoolean("mobs.zombie.takes-damage-from-water", zombieTakeDamageFromWater);
+ zombieAlwaysDropExp = getBoolean("mobs.zombie.always-drop-exp", zombieAlwaysDropExp);
+ zombieHeadVisibilityPercent = getDouble("mobs.zombie.head-visibility-percent", zombieHeadVisibilityPercent);
+ }
+
+ public boolean zombieHorseRidable = false;
+ public boolean zombieHorseRidableInWater = false;
+ public boolean zombieHorseCanSwim = false;
+ public double zombieHorseMaxHealthMin = 15.0D;
+ public double zombieHorseMaxHealthMax = 15.0D;
+ public double zombieHorseJumpStrengthMin = 0.4D;
+ public double zombieHorseJumpStrengthMax = 1.0D;
+ public double zombieHorseMovementSpeedMin = 0.2D;
+ public double zombieHorseMovementSpeedMax = 0.2D;
+ public double zombieHorseSpawnChance = 0.0D;
+ public boolean zombieHorseTakeDamageFromWater = false;
+ public boolean zombieHorseAlwaysDropExp = false;
+ private void zombieHorseSettings() {
+ zombieHorseRidable = getBoolean("mobs.zombie_horse.ridable", zombieHorseRidable);
+ zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater);
+ zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zombie_horse.attributes.max-health", zombieHorseMaxHealthMin);
+ set("mobs.zombie_horse.attributes.max-health", null);
+ set("mobs.zombie_horse.attributes.max_health.min", oldValue);
+ set("mobs.zombie_horse.attributes.max_health.max", oldValue);
+ }
+ zombieHorseMaxHealthMin = getDouble("mobs.zombie_horse.attributes.max_health.min", zombieHorseMaxHealthMin);
+ zombieHorseMaxHealthMax = getDouble("mobs.zombie_horse.attributes.max_health.max", zombieHorseMaxHealthMax);
+ zombieHorseJumpStrengthMin = getDouble("mobs.zombie_horse.attributes.jump_strength.min", zombieHorseJumpStrengthMin);
+ zombieHorseJumpStrengthMax = getDouble("mobs.zombie_horse.attributes.jump_strength.max", zombieHorseJumpStrengthMax);
+ zombieHorseMovementSpeedMin = getDouble("mobs.zombie_horse.attributes.movement_speed.min", zombieHorseMovementSpeedMin);
+ zombieHorseMovementSpeedMax = getDouble("mobs.zombie_horse.attributes.movement_speed.max", zombieHorseMovementSpeedMax);
+ zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance);
+ zombieHorseTakeDamageFromWater = getBoolean("mobs.zombie_horse.takes-damage-from-water", zombieHorseTakeDamageFromWater);
+ zombieHorseAlwaysDropExp = getBoolean("mobs.zombie_horse.always-drop-exp", zombieHorseAlwaysDropExp);
+ }
+
+ public boolean zombieVillagerRidable = false;
+ public boolean zombieVillagerRidableInWater = true;
+ public boolean zombieVillagerControllable = true;
+ public double zombieVillagerMaxHealth = 20.0D;
+ public double zombieVillagerScale = 1.0D;
+ public double zombieVillagerSpawnReinforcements = 0.1D;
+ public boolean zombieVillagerJockeyOnlyBaby = true;
+ public double zombieVillagerJockeyChance = 0.05D;
+ public boolean zombieVillagerJockeyTryExistingChickens = true;
+ public boolean zombieVillagerTakeDamageFromWater = false;
+ public int zombieVillagerCuringTimeMin = 3600;
+ public int zombieVillagerCuringTimeMax = 6000;
+ public boolean zombieVillagerCureEnabled = true;
+ public boolean zombieVillagerAlwaysDropExp = false;
+ private void zombieVillagerSettings() {
+ zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable);
+ zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater);
+ zombieVillagerControllable = getBoolean("mobs.zombie_villager.controllable", zombieVillagerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zombie_villager.attributes.max-health", zombieVillagerMaxHealth);
+ set("mobs.zombie_villager.attributes.max-health", null);
+ set("mobs.zombie_villager.attributes.max_health", oldValue);
+ }
+ zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth);
+ zombieVillagerScale = Mth.clamp(getDouble("mobs.zombie_villager.attributes.scale", zombieVillagerScale), 0.0625D, 16.0D);
+ zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements);
+ zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby);
+ zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance);
+ zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens);
+ zombieVillagerTakeDamageFromWater = getBoolean("mobs.zombie_villager.takes-damage-from-water", zombieVillagerTakeDamageFromWater);
+ zombieVillagerCuringTimeMin = getInt("mobs.zombie_villager.curing_time.min", zombieVillagerCuringTimeMin);
+ zombieVillagerCuringTimeMax = getInt("mobs.zombie_villager.curing_time.max", zombieVillagerCuringTimeMax);
+ zombieVillagerCureEnabled = getBoolean("mobs.zombie_villager.cure.enabled", zombieVillagerCureEnabled);
+ zombieVillagerAlwaysDropExp = getBoolean("mobs.zombie_villager.always-drop-exp", zombieVillagerAlwaysDropExp);
+ }
+
+ public boolean zombifiedPiglinRidable = false;
+ public boolean zombifiedPiglinRidableInWater = true;
+ public boolean zombifiedPiglinControllable = true;
+ public double zombifiedPiglinMaxHealth = 20.0D;
+ public double zombifiedPiglinScale = 1.0D;
+ public double zombifiedPiglinSpawnReinforcements = 0.0D;
+ public boolean zombifiedPiglinJockeyOnlyBaby = true;
+ public double zombifiedPiglinJockeyChance = 0.05D;
+ public boolean zombifiedPiglinJockeyTryExistingChickens = true;
+ public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true;
+ public boolean zombifiedPiglinTakeDamageFromWater = false;
+ public boolean zombifiedPiglinAlwaysDropExp = false;
+ private void zombifiedPiglinSettings() {
+ zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable);
+ zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater);
+ zombifiedPiglinControllable = getBoolean("mobs.zombified_piglin.controllable", zombifiedPiglinControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zombified_piglin.attributes.max-health", zombifiedPiglinMaxHealth);
+ set("mobs.zombified_piglin.attributes.max-health", null);
+ set("mobs.zombified_piglin.attributes.max_health", oldValue);
+ }
+ zombifiedPiglinMaxHealth = getDouble("mobs.zombified_piglin.attributes.max_health", zombifiedPiglinMaxHealth);
+ zombifiedPiglinScale = Mth.clamp(getDouble("mobs.zombified_piglin.attributes.scale", zombifiedPiglinScale), 0.0625D, 16.0D);
+ zombifiedPiglinSpawnReinforcements = getDouble("mobs.zombified_piglin.attributes.spawn_reinforcements", zombifiedPiglinSpawnReinforcements);
+ zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby);
+ zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance);
+ zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens);
+ zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry);
+ zombifiedPiglinTakeDamageFromWater = getBoolean("mobs.zombified_piglin.takes-damage-from-water", zombifiedPiglinTakeDamageFromWater);
+ zombifiedPiglinAlwaysDropExp = getBoolean("mobs.zombified_piglin.always-drop-exp", zombifiedPiglinAlwaysDropExp);
+ }
+
+ public float hungerStarvationDamage = 1.0F;
+ private void hungerSettings() {
+ hungerStarvationDamage = (float) getDouble("hunger.starvation-damage", hungerStarvationDamage);
+ }
+
+ public int conduitDistance = 16;
+ public double conduitDamageDistance = 8;
+ public float conduitDamageAmount = 4;
+ public Block[] conduitBlocks;
+ private void conduitSettings() {
+ conduitDistance = getInt("blocks.conduit.effect-distance", conduitDistance);
+ conduitDamageDistance = getDouble("blocks.conduit.mob-damage.distance", conduitDamageDistance);
+ conduitDamageAmount = (float) getDouble("blocks.conduit.mob-damage.damage-amount", conduitDamageAmount);
+ List<Block> conduitBlockList = new ArrayList<>();
+ getList("blocks.conduit.valid-ring-blocks", new ArrayList<String>(){{
+ add("minecraft:prismarine");
+ add("minecraft:prismarine_bricks");
+ add("minecraft:sea_lantern");
+ add("minecraft:dark_prismarine");
+ }}).forEach(key -> {
+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(key.toString()));
+ if (!block.defaultBlockState().isAir()) {
+ conduitBlockList.add(block);
+ }
+ });
+ conduitBlocks = conduitBlockList.toArray(Block[]::new);
+ }
+
+ public float cauldronRainChance = 0.05F;
+ public float cauldronPowderSnowChance = 0.1F;
+ public float cauldronDripstoneWaterFillChance = 0.17578125F;
+ public float cauldronDripstoneLavaFillChance = 0.05859375F;
+ private void cauldronSettings() {
+ cauldronRainChance = (float) getDouble("blocks.cauldron.fill-chances.rain", cauldronRainChance);
+ cauldronPowderSnowChance = (float) getDouble("blocks.cauldron.fill-chances.powder-snow", cauldronPowderSnowChance);
+ cauldronDripstoneWaterFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-water", cauldronDripstoneWaterFillChance);
+ cauldronDripstoneLavaFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-lava", cauldronDripstoneLavaFillChance);
+ }
+
+ public float shearsCanDefuseTntChance = 0.00F;
+ public boolean shearsCanDefuseTnt = false;
+ private void shearsCanDefuseTntSettings() {
+ shearsCanDefuseTntChance = (float) getDouble("gameplay-mechanics.item.shears.defuse-tnt-chance", 0.00D);
+ shearsCanDefuseTnt = shearsCanDefuseTntChance > 0.00F;
+ }
+}
diff --git a/org/purpurmc/purpur/command/CompassCommand.java b/org/purpurmc/purpur/command/CompassCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..79b8490832d2a0cc7846ddcb091cb6bcac74ea45
--- /dev/null
+++ b/org/purpurmc/purpur/command/CompassCommand.java
@@ -0,0 +1,27 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.server.level.ServerPlayer;
+import org.purpurmc.purpur.task.CompassTask;
+
+public class CompassCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+ dispatcher.register(Commands.literal("compass")
+ .requires(listener -> listener.hasPermission(2, "bukkit.command.compass"))
+ .executes(context -> {
+ ServerPlayer player = context.getSource().getPlayerOrException();
+ CompassTask task = CompassTask.instance();
+ if (player.compassBar()) {
+ task.removePlayer(player.getBukkitEntity());
+ player.compassBar(false);
+ } else {
+ task.addPlayer(player.getBukkitEntity());
+ player.compassBar(true);
+ }
+ return 1;
+ })
+ );
+ }
+}
diff --git a/org/purpurmc/purpur/command/CreditsCommand.java b/org/purpurmc/purpur/command/CreditsCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..40d2fab4a9728ac90c36e30c130f3116b7025d11
--- /dev/null
+++ b/org/purpurmc/purpur/command/CreditsCommand.java
@@ -0,0 +1,35 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.commands.arguments.EntityArgument;
+import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
+import net.minecraft.server.level.ServerPlayer;
+import org.purpurmc.purpur.PurpurConfig;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class CreditsCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+ dispatcher.register(Commands.literal("credits")
+ .requires((listener) -> listener.hasPermission(2, "bukkit.command.credits"))
+ .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException())))
+ .then(Commands.argument("targets", EntityArgument.players())
+ .requires(listener -> listener.hasPermission(2, "bukkit.command.credits.other"))
+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets")))
+ )
+ );
+ }
+
+ private static int execute(CommandSourceStack sender, Collection<ServerPlayer> targets) {
+ for (ServerPlayer player : targets) {
+ ClientboundGameEventPacket packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1F);
+ player.connection.send(packet);
+ String output = String.format(PurpurConfig.creditsCommandOutput, player.getGameProfile().getName());
+ sender.sendSuccess(output, false);
+ }
+ return targets.size();
+ }
+}
diff --git a/org/purpurmc/purpur/command/DemoCommand.java b/org/purpurmc/purpur/command/DemoCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..235f3cd89f675b70a6152a00534608c0902f19fd
--- /dev/null
+++ b/org/purpurmc/purpur/command/DemoCommand.java
@@ -0,0 +1,35 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.commands.arguments.EntityArgument;
+import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
+import net.minecraft.server.level.ServerPlayer;
+import org.purpurmc.purpur.PurpurConfig;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class DemoCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+ dispatcher.register(Commands.literal("demo")
+ .requires((listener) -> listener.hasPermission(2, "bukkit.command.demo"))
+ .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException())))
+ .then(Commands.argument("targets", EntityArgument.players())
+ .requires(listener -> listener.hasPermission(2, "bukkit.command.demo.other"))
+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets")))
+ )
+ );
+ }
+
+ private static int execute(CommandSourceStack sender, Collection<ServerPlayer> targets) {
+ for (ServerPlayer player : targets) {
+ ClientboundGameEventPacket packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.DEMO_EVENT, 0);
+ player.connection.send(packet);
+ String output = String.format(PurpurConfig.demoCommandOutput, player.getGameProfile().getName());
+ sender.sendSuccess(output, false);
+ }
+ return targets.size();
+ }
+}
diff --git a/org/purpurmc/purpur/command/PingCommand.java b/org/purpurmc/purpur/command/PingCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..74a602c331e9581d7425a09e4094f1d646099676
--- /dev/null
+++ b/org/purpurmc/purpur/command/PingCommand.java
@@ -0,0 +1,32 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.commands.arguments.EntityArgument;
+import net.minecraft.server.level.ServerPlayer;
+import org.purpurmc.purpur.PurpurConfig;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class PingCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+ dispatcher.register(Commands.literal("ping")
+ .requires((listener) -> listener.hasPermission(2, "bukkit.command.ping"))
+ .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException())))
+ .then(Commands.argument("targets", EntityArgument.players())
+ .requires(listener -> listener.hasPermission(2, "bukkit.command.ping.other"))
+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets")))
+ )
+ );
+ }
+
+ private static int execute(CommandSourceStack sender, Collection<ServerPlayer> targets) {
+ for (ServerPlayer player : targets) {
+ String output = String.format(PurpurConfig.pingCommandOutput, player.getGameProfile().getName(), player.connection.latency());
+ sender.sendSuccess(output, false);
+ }
+ return targets.size();
+ }
+}
diff --git a/org/purpurmc/purpur/command/PurpurCommand.java b/org/purpurmc/purpur/command/PurpurCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..7163c8247c5f564c723409e4dc645ebee0a7d4d1
--- /dev/null
+++ b/org/purpurmc/purpur/command/PurpurCommand.java
@@ -0,0 +1,66 @@
+package org.purpurmc.purpur.command;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import org.purpurmc.purpur.PurpurConfig;
+import org.bukkit.ChatColor;
+import org.bukkit.Location;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class PurpurCommand extends Command {
+ public PurpurCommand(String name) {
+ super(name);
+ this.description = "Purpur related commands";
+ this.usageMessage = "/purpur [reload | version]";
+ this.setPermission("bukkit.command.purpur");
+ }
+
+ @Override
+ public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
+ if (args.length == 1) {
+ return Stream.of("reload", "version")
+ .filter(arg -> arg.startsWith(args[0].toLowerCase()))
+ .collect(Collectors.toList());
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, String commandLabel, String[] args) {
+ if (!testPermission(sender)) return true;
+
+ if (args.length != 1) {
+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
+ return false;
+ }
+
+ if (args[0].equalsIgnoreCase("reload")) {
+ Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues.");
+ Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server.");
+
+ MinecraftServer console = MinecraftServer.getServer();
+ PurpurConfig.init((File) console.options.valueOf("purpur-settings"));
+ for (ServerLevel level : console.getAllLevels()) {
+ level.purpurConfig.init();
+ level.resetBreedingCooldowns(); // Purpur - Add adjustable breeding cooldown to config
+ }
+ console.server.reloadCount++;
+
+ Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Purpur config reload complete.");
+ } else if (args[0].equalsIgnoreCase("version")) {
+ Command verCmd = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version");
+ if (verCmd != null) {
+ return verCmd.execute(sender, commandLabel, new String[0]);
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/org/purpurmc/purpur/command/RamBarCommand.java b/org/purpurmc/purpur/command/RamBarCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..2852c07adb080c34905f5d1b19efed8ea47eecc6
--- /dev/null
+++ b/org/purpurmc/purpur/command/RamBarCommand.java
@@ -0,0 +1,44 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.commands.arguments.EntityArgument;
+import net.minecraft.server.level.ServerPlayer;
+import org.purpurmc.purpur.PurpurConfig;
+import org.purpurmc.purpur.task.RamBarTask;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class RamBarCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+ dispatcher.register(Commands.literal("rambar")
+ .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar"))
+ .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException())))
+ .then(Commands.argument("targets", EntityArgument.players())
+ .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar.other"))
+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets")))
+ )
+ );
+ }
+
+ private static int execute(CommandSourceStack sender, Collection<ServerPlayer> targets) {
+ for (ServerPlayer player : targets) {
+ boolean result = RamBarTask.instance().togglePlayer(player.getBukkitEntity());
+ player.ramBar(result);
+
+ Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.rambarCommandOutput,
+ Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off")
+ .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)),
+ Placeholder.parsed("target", player.getGameProfile().getName()));
+
+ sender.sendSuccess(output, false);
+ }
+ return targets.size();
+ }
+}
diff --git a/org/purpurmc/purpur/command/RamCommand.java b/org/purpurmc/purpur/command/RamCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..992f8dfc628c7485e335191e1308cdfd4eedfbe8
--- /dev/null
+++ b/org/purpurmc/purpur/command/RamCommand.java
@@ -0,0 +1,30 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import io.papermc.paper.adventure.PaperAdventure;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import org.purpurmc.purpur.PurpurConfig;
+import org.purpurmc.purpur.task.RamBarTask;
+
+public class RamCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+ dispatcher.register(Commands.literal("ram")
+ .requires(listener -> listener.hasPermission(2, "bukkit.command.ram"))
+ .executes(context -> {
+ CommandSourceStack sender = context.getSource();
+ RamBarTask ramBar = RamBarTask.instance();
+ sender.sendSuccess(() -> PaperAdventure.asVanilla(MiniMessage.miniMessage().deserialize(PurpurConfig.ramCommandOutput,
+ Placeholder.component("allocated", ramBar.format(ramBar.getAllocated())),
+ Placeholder.component("used", ramBar.format(ramBar.getUsed())),
+ Placeholder.component("xmx", ramBar.format(ramBar.getXmx())),
+ Placeholder.component("xms", ramBar.format(ramBar.getXms())),
+ Placeholder.unparsed("percent", ((int) (ramBar.getPercent() * 100)) + "%")
+ )), false);
+ return 1;
+ })
+ );
+ }
+}
diff --git a/org/purpurmc/purpur/command/TPSBarCommand.java b/org/purpurmc/purpur/command/TPSBarCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8f9b044107ff7c29a83eb5378aa9f5465ba1995
--- /dev/null
+++ b/org/purpurmc/purpur/command/TPSBarCommand.java
@@ -0,0 +1,44 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.commands.arguments.EntityArgument;
+import net.minecraft.server.level.ServerPlayer;
+import org.purpurmc.purpur.PurpurConfig;
+import org.purpurmc.purpur.task.TPSBarTask;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class TPSBarCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+ dispatcher.register(Commands.literal("tpsbar")
+ .requires(listener -> listener.hasPermission(2, "bukkit.command.tpsbar"))
+ .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException())))
+ .then(Commands.argument("targets", EntityArgument.players())
+ .requires(listener -> listener.hasPermission(2, "bukkit.command.tpsbar.other"))
+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets")))
+ )
+ );
+ }
+
+ private static int execute(CommandSourceStack sender, Collection<ServerPlayer> targets) {
+ for (ServerPlayer player : targets) {
+ boolean result = TPSBarTask.instance().togglePlayer(player.getBukkitEntity());
+ player.tpsBar(result);
+
+ Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.tpsbarCommandOutput,
+ Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off")
+ .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)),
+ Placeholder.parsed("target", player.getGameProfile().getName()));
+
+ sender.sendSuccess(output, false);
+ }
+ return targets.size();
+ }
+}
diff --git a/org/purpurmc/purpur/command/UptimeCommand.java b/org/purpurmc/purpur/command/UptimeCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..4bb475099bcf8f05d5f1474e7fbf29c57c2c40cd
--- /dev/null
+++ b/org/purpurmc/purpur/command/UptimeCommand.java
@@ -0,0 +1,55 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.server.MinecraftServer;
+import org.purpurmc.purpur.PurpurConfig;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+public class UptimeCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+ dispatcher.register(Commands.literal("uptime")
+ .requires((listener) -> listener.hasPermission(2, "bukkit.command.uptime"))
+ .executes((context) -> execute(context.getSource()))
+ );
+ }
+
+ private static int execute(CommandSourceStack sender) {
+ Data data = new Data();
+
+ data.format = PurpurConfig.uptimeFormat;
+ data.hide = true;
+ data.millis = System.currentTimeMillis() - MinecraftServer.startTimeMillis;
+
+ process(data, "<days>", PurpurConfig.uptimeDay, PurpurConfig.uptimeDays, TimeUnit.DAYS, TimeUnit.MILLISECONDS::toDays);
+ process(data, "<hours>", PurpurConfig.uptimeHour, PurpurConfig.uptimeHours, TimeUnit.HOURS, TimeUnit.MILLISECONDS::toHours);
+ process(data, "<minutes>", PurpurConfig.uptimeMinute, PurpurConfig.uptimeMinutes, TimeUnit.MINUTES, TimeUnit.MILLISECONDS::toMinutes);
+ data.hide = false; // never hide seconds
+ process(data, "<seconds>", PurpurConfig.uptimeSecond, PurpurConfig.uptimeSeconds, TimeUnit.SECONDS, TimeUnit.MILLISECONDS::toSeconds);
+
+ Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.uptimeCommandOutput, Placeholder.unparsed("uptime", data.format));
+ sender.sendSuccess(output, false);
+ return 1;
+ }
+
+ private static void process(Data data, String replace, String singular, String plural, TimeUnit unit, Function<Long, Long> func) {
+ if (data.format.contains(replace)) {
+ long val = func.apply(data.millis);
+ if (data.hide) data.hide = val == 0;
+ if (!data.hide) data.millis -= unit.toMillis(val);
+ data.format = data.format.replace(replace, data.hide ? "" : String.format(val == 1 ? singular : plural, val));
+ }
+ }
+
+ private static class Data {
+ String format;
+ boolean hide;
+ long millis;
+ }
+}
diff --git a/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java b/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..940bcc6f79b59cb3cce578912eb789efd394f456
--- /dev/null
+++ b/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java
@@ -0,0 +1,74 @@
+package org.purpurmc.purpur.controller;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.player.Input;
+import net.minecraft.world.entity.player.Player;
+
+public class FlyingMoveControllerWASD extends MoveControllerWASD {
+ protected final float groundSpeedModifier;
+ protected final float flyingSpeedModifier;
+ protected int tooHighCooldown = 0;
+ protected boolean setNoGravityFlag;
+
+ public FlyingMoveControllerWASD(Mob entity) {
+ this(entity, 1.0F);
+ }
+
+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier) {
+ this(entity, groundSpeedModifier, 1.0F, true);
+ }
+
+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier) {
+ this(entity, groundSpeedModifier, flyingSpeedModifier, true);
+ }
+
+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier, boolean setNoGravityFlag) {
+ super(entity);
+ this.groundSpeedModifier = groundSpeedModifier;
+ this.flyingSpeedModifier = flyingSpeedModifier;
+ this.setNoGravityFlag = setNoGravityFlag;
+ }
+
+ @Override
+ public void purpurTick(Player rider) {
+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput();
+ float forward = lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : 0.0F;
+ float vertical = forward == 0.0F ? 0.0F : -(rider.xRotO / 45.0F);
+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F);
+
+ if (lastClientInput.jump() && spacebarEvent(entity)) {
+ entity.onSpacebar();
+ }
+
+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) {
+ if (tooHighCooldown <= 0) {
+ tooHighCooldown = 20;
+ }
+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.05D, 0.0D));
+ vertical = 0.0F;
+ }
+
+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float speed = (float) getSpeedModifier();
+
+ if (entity.onGround) {
+ speed *= groundSpeedModifier; // TODO = fix this!
+ } else {
+ speed *= flyingSpeedModifier;
+ }
+
+ if (setNoGravityFlag) {
+ entity.setNoGravity(forward > 0);
+ }
+
+ entity.setSpeed(speed);
+ entity.setVerticalMot(vertical);
+ entity.setStrafeMot(strafe);
+ entity.setForwardMot(forward);
+
+ setForward(entity.getForwardMot());
+ setStrafe(entity.getStrafeMot());
+ }
+}
diff --git a/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java b/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0bbaec05afa0ae67ed486b14ea1fbadbbe90d9b
--- /dev/null
+++ b/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java
@@ -0,0 +1,66 @@
+package org.purpurmc.purpur.controller;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.player.Input;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.phys.Vec3;
+
+public class FlyingWithSpacebarMoveControllerWASD extends FlyingMoveControllerWASD {
+ public FlyingWithSpacebarMoveControllerWASD(Mob entity) {
+ super(entity);
+ }
+
+ public FlyingWithSpacebarMoveControllerWASD(Mob entity, float groundSpeedModifier) {
+ super(entity, groundSpeedModifier);
+ }
+
+ @Override
+ public void purpurTick(Player rider) {
+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput();
+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F);
+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.5F;
+ float vertical = 0;
+
+ if (forward < 0.0F) {
+ forward *= 0.5F;
+ strafe *= 0.5F;
+ }
+
+ float speed = (float) entity.getAttributeValue(Attributes.MOVEMENT_SPEED);
+
+ if (entity.onGround) {
+ speed *= groundSpeedModifier;
+ }
+
+ if (lastClientInput.jump() && spacebarEvent(entity) && !entity.onSpacebar()) {
+ entity.setNoGravity(true);
+ vertical = 1.0F;
+ } else {
+ entity.setNoGravity(false);
+ }
+
+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) {
+ if (tooHighCooldown <= 0) {
+ tooHighCooldown = 20;
+ }
+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.2D, 0.0D));
+ vertical = 0.0F;
+ }
+
+ setSpeedModifier(speed);
+ entity.setSpeed((float) getSpeedModifier());
+ entity.setVerticalMot(vertical);
+ entity.setStrafeMot(strafe);
+ entity.setForwardMot(forward);
+
+ setForward(entity.getForwardMot());
+ setStrafe(entity.getStrafeMot());
+
+ Vec3 mot = entity.getDeltaMovement();
+ if (mot.y > 0.2D) {
+ entity.setDeltaMovement(mot.x, 0.2D, mot.z);
+ }
+ }
+}
diff --git a/org/purpurmc/purpur/controller/LookControllerWASD.java b/org/purpurmc/purpur/controller/LookControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd219518150ca90f89ad238904fd4095efe032d8
--- /dev/null
+++ b/org/purpurmc/purpur/controller/LookControllerWASD.java
@@ -0,0 +1,79 @@
+package org.purpurmc.purpur.controller;
+
+
+import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.control.LookControl;
+import net.minecraft.world.entity.player.Player;
+
+public class LookControllerWASD extends LookControl {
+ protected final Mob entity;
+ private float yOffset = 0;
+ private float xOffset = 0;
+
+ public LookControllerWASD(Mob entity) {
+ super(entity);
+ this.entity = entity;
+ }
+
+ // tick
+ @Override
+ public void tick() {
+ if (entity.getRider() != null && entity.isControllable()) {
+ purpurTick(entity.getRider());
+ } else {
+ vanillaTick();
+ }
+ }
+
+ protected void purpurTick(Player rider) {
+ setYawPitch(rider.getYRot(), rider.getXRot());
+ }
+
+ public void vanillaTick() {
+ super.tick();
+ }
+
+ public void setYawPitch(float yRot, float xRot) {
+ entity.setXRot(normalizePitch(xRot + xOffset));
+ entity.setYRot(normalizeYaw(yRot + yOffset));
+ entity.setYHeadRot(entity.getYRot());
+ entity.xRotO = entity.getXRot();
+ entity.yRotO = entity.getYRot();
+
+ ClientboundMoveEntityPacket.PosRot entityPacket = new ClientboundMoveEntityPacket.PosRot(
+ entity.getId(),
+ (short) 0, (short) 0, (short) 0,
+ (byte) Mth.floor(entity.getYRot() * 256.0F / 360.0F),
+ (byte) Mth.floor(entity.getXRot() * 256.0F / 360.0F),
+ entity.onGround
+ );
+ ((ServerLevel) entity.level()).getChunkSource().broadcast(entity, entityPacket);
+ }
+
+ public void setOffsets(float yaw, float pitch) {
+ yOffset = yaw;
+ xOffset = pitch;
+ }
+
+ public float normalizeYaw(float yaw) {
+ yaw %= 360.0f;
+ if (yaw >= 180.0f) {
+ yaw -= 360.0f;
+ } else if (yaw < -180.0f) {
+ yaw += 360.0f;
+ }
+ return yaw;
+ }
+
+ public float normalizePitch(float pitch) {
+ if (pitch > 90.0f) {
+ pitch = 90.0f;
+ } else if (pitch < -90.0f) {
+ pitch = -90.0f;
+ }
+ return pitch;
+ }
+}
diff --git a/org/purpurmc/purpur/controller/MoveControllerWASD.java b/org/purpurmc/purpur/controller/MoveControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..34f3c43fa16e950326ac5e3d93faee0466ffedc6
--- /dev/null
+++ b/org/purpurmc/purpur/controller/MoveControllerWASD.java
@@ -0,0 +1,92 @@
+package org.purpurmc.purpur.controller;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.player.Input;
+import net.minecraft.world.entity.player.Player;
+import org.purpurmc.purpur.event.entity.RidableSpacebarEvent;
+
+public class MoveControllerWASD extends MoveControl {
+ protected final Mob entity;
+ private final double speedModifier;
+
+ public MoveControllerWASD(Mob entity) {
+ this(entity, 1.0D);
+ }
+
+ public MoveControllerWASD(Mob entity, double speedModifier) {
+ super(entity);
+ this.entity = entity;
+ this.speedModifier = speedModifier;
+ }
+
+ @Override
+ public boolean hasWanted() {
+ return entity.getRider() != null ? strafeForwards != 0 || strafeRight != 0 : super.hasWanted();
+ }
+
+ @Override
+ public void tick() {
+ if (entity.getRider() != null && entity.isControllable()) {
+ purpurTick(entity.getRider());
+ } else {
+ vanillaTick();
+ }
+ }
+
+ public void vanillaTick() {
+ super.tick();
+ }
+
+ public void purpurTick(Player rider) {
+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput();
+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F) * 0.5F;
+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.25F;
+
+ if (forward <= 0.0F) {
+ forward *= 0.5F;
+ }
+
+ float yawOffset = 0;
+ if (strafe != 0) {
+ if (forward == 0) {
+ yawOffset += strafe > 0 ? -90 : 90;
+ forward = Math.abs(strafe * 2);
+ } else {
+ yawOffset += strafe > 0 ? -30 : 30;
+ strafe /= 2;
+ if (forward < 0) {
+ yawOffset += strafe > 0 ? -110 : 110;
+ forward *= -1;
+ }
+ }
+ } else if (forward < 0) {
+ yawOffset -= 180;
+ forward *= -1;
+ }
+
+ ((LookControllerWASD) entity.getLookControl()).setOffsets(yawOffset, 0);
+
+ if (lastClientInput.jump() && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) {
+ entity.jumpFromGround();
+ }
+
+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier);
+
+ entity.setSpeed((float) getSpeedModifier());
+ entity.setForwardMot(forward);
+
+ setForward(entity.getForwardMot());
+ setStrafe(entity.getStrafeMot());
+ }
+
+ public static boolean spacebarEvent(Mob entity) {
+ if (RidableSpacebarEvent.getHandlerList().getRegisteredListeners().length > 0) {
+ return new RidableSpacebarEvent(entity.getBukkitEntity()).callEvent();
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java b/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..922e48799c43ca322a8f550c98a26e1e2959439c
--- /dev/null
+++ b/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java
@@ -0,0 +1,53 @@
+package org.purpurmc.purpur.controller;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.player.Input;
+import net.minecraft.world.entity.player.Player;
+
+public class WaterMoveControllerWASD extends MoveControllerWASD {
+ private final double speedModifier;
+
+ public WaterMoveControllerWASD(Mob entity) {
+ this(entity, 1.0D);
+ }
+
+ public WaterMoveControllerWASD(Mob entity, double speedModifier) {
+ super(entity);
+ this.speedModifier = speedModifier;
+ }
+
+ @Override
+ public void purpurTick(Player rider) {
+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput();
+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F);
+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.5F; // strafe slower by default
+ float vertical = -(rider.xRotO / 90);
+
+ if (forward == 0.0F) {
+ // strafe slower if not moving forward
+ strafe *= 0.5F;
+ // do not move vertically if not moving forward
+ vertical = 0.0F;
+ } else if (forward < 0.0F) {
+ // water animals can't swim backwards
+ forward = 0.0F;
+ vertical = 0.0F;
+ }
+
+ if (rider.jumping && spacebarEvent(entity)) {
+ entity.onSpacebar();
+ }
+
+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier);
+ entity.setSpeed((float) getSpeedModifier() * 0.1F);
+
+ entity.setForwardMot(forward * (float) speedModifier);
+ entity.setStrafeMot(strafe * (float) speedModifier);
+ entity.setVerticalMot(vertical * (float) speedModifier);
+
+ setForward(entity.getForwardMot());
+ setStrafe(entity.getStrafeMot());
+ }
+}
diff --git a/org/purpurmc/purpur/entity/PurpurStoredBee.java b/org/purpurmc/purpur/entity/PurpurStoredBee.java
new file mode 100644
index 0000000000000000000000000000000000000000..7608bf0981fa0d37031e51e57e4086cb5ec4c88b
--- /dev/null
+++ b/org/purpurmc/purpur/entity/PurpurStoredBee.java
@@ -0,0 +1,106 @@
+package org.purpurmc.purpur.entity;
+
+import io.papermc.paper.adventure.PaperAdventure;
+import net.kyori.adventure.text.Component;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.Tag;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.item.component.CustomData;
+import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
+import org.bukkit.block.EntityBlockStorage;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
+import org.bukkit.entity.Bee;
+import org.bukkit.entity.EntityType;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Locale;
+
+public class PurpurStoredBee implements StoredEntity<Bee> {
+ private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
+
+ private final EntityBlockStorage<Bee> blockStorage;
+ private final BeehiveBlockEntity.BeeData handle;
+ private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(PurpurStoredBee.DATA_TYPE_REGISTRY);
+
+ private Component customName;
+
+ public PurpurStoredBee(BeehiveBlockEntity.BeeData data, EntityBlockStorage<Bee> blockStorage) {
+ this.handle = data;
+ this.blockStorage = blockStorage;
+
+ CompoundTag customData = handle.occupant.entityData().copyTag();
+ this.customName = customData.contains("CustomName")
+ ? PaperAdventure.asAdventure(net.minecraft.network.chat.Component.Serializer.fromJson(customData.getString("CustomName"), MinecraftServer.getDefaultRegistryAccess()))
+ : null;
+
+ if(customData.contains("BukkitValues", Tag.TAG_COMPOUND)) {
+ this.persistentDataContainer.putAll(customData.getCompound("BukkitValues"));
+ }
+ }
+
+ public BeehiveBlockEntity.BeeData getHandle() {
+ return handle;
+ }
+
+ @Override
+ public @Nullable Component customName() {
+ return customName;
+ }
+
+ @Override
+ public void customName(@Nullable Component customName) {
+ this.customName = customName;
+ }
+
+ @Override
+ public @Nullable String getCustomName() {
+ return PaperAdventure.asPlain(customName, Locale.US);
+ }
+
+ @Override
+ public void setCustomName(@Nullable String name) {
+ customName(name != null ? Component.text(name) : null);
+ }
+
+ @Override
+ public @NotNull PersistentDataContainer getPersistentDataContainer() {
+ return persistentDataContainer;
+ }
+
+ @Override
+ public boolean hasBeenReleased() {
+ return !blockStorage.getEntities().contains(this);
+ }
+
+ @Override
+ public @Nullable Bee release() {
+ return blockStorage.releaseEntity(this);
+ }
+
+ @Override
+ public @Nullable EntityBlockStorage<Bee> getBlockStorage() {
+ if(hasBeenReleased()) {
+ return null;
+ }
+
+ return blockStorage;
+ }
+
+ @Override
+ public @NotNull EntityType getType() {
+ return EntityType.BEE;
+ }
+
+ @Override
+ public void update() {
+ handle.occupant.entityData().copyTag().put("BukkitValues", this.persistentDataContainer.toTagCompound());
+ if(customName == null) {
+ handle.occupant.entityData().copyTag().remove("CustomName");
+ } else {
+ handle.occupant.entityData().copyTag().putString("CustomName", net.minecraft.network.chat.Component.Serializer.toJson(PaperAdventure.asVanilla(customName), MinecraftServer.getDefaultRegistryAccess()));
+ }
+ }
+}
diff --git a/org/purpurmc/purpur/entity/ai/HasRider.java b/org/purpurmc/purpur/entity/ai/HasRider.java
new file mode 100644
index 0000000000000000000000000000000000000000..8babdaddd8b33278aea0369dbbeeb445abe45016
--- /dev/null
+++ b/org/purpurmc/purpur/entity/ai/HasRider.java
@@ -0,0 +1,20 @@
+package org.purpurmc.purpur.entity.ai;
+
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.goal.Goal;
+
+import java.util.EnumSet;
+
+public class HasRider extends Goal {
+ public final Mob entity;
+
+ public HasRider(Mob entity) {
+ this.entity = entity;
+ setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.TARGET, Flag.UNKNOWN_BEHAVIOR));
+ }
+
+ @Override
+ public boolean canUse() {
+ return entity.getRider() != null && entity.isControllable();
+ }
+}
diff --git a/org/purpurmc/purpur/entity/ai/HorseHasRider.java b/org/purpurmc/purpur/entity/ai/HorseHasRider.java
new file mode 100644
index 0000000000000000000000000000000000000000..432f4f3d82af2f19820890b68d33189a9f2c69f9
--- /dev/null
+++ b/org/purpurmc/purpur/entity/ai/HorseHasRider.java
@@ -0,0 +1,17 @@
+package org.purpurmc.purpur.entity.ai;
+
+import net.minecraft.world.entity.animal.horse.AbstractHorse;
+
+public class HorseHasRider extends HasRider {
+ public final AbstractHorse horse;
+
+ public HorseHasRider(AbstractHorse entity) {
+ super(entity);
+ this.horse = entity;
+ }
+
+ @Override
+ public boolean canUse() {
+ return super.canUse() && horse.isSaddled();
+ }
+}
diff --git a/org/purpurmc/purpur/entity/ai/LlamaHasRider.java b/org/purpurmc/purpur/entity/ai/LlamaHasRider.java
new file mode 100644
index 0000000000000000000000000000000000000000..18a95e043cbffa65eeaaf65ff7695e5dc939820c
--- /dev/null
+++ b/org/purpurmc/purpur/entity/ai/LlamaHasRider.java
@@ -0,0 +1,17 @@
+package org.purpurmc.purpur.entity.ai;
+
+import net.minecraft.world.entity.animal.horse.Llama;
+
+public class LlamaHasRider extends HasRider {
+ public final Llama llama;
+
+ public LlamaHasRider(Llama entity) {
+ super(entity);
+ this.llama = entity;
+ }
+
+ @Override
+ public boolean canUse() {
+ return super.canUse() && llama.isSaddled() && llama.isControllable();
+ }
+}
diff --git a/org/purpurmc/purpur/entity/ai/ReceiveFlower.java b/org/purpurmc/purpur/entity/ai/ReceiveFlower.java
new file mode 100644
index 0000000000000000000000000000000000000000..9660716f4162a4441c6e1b0baddef8f5086566c5
--- /dev/null
+++ b/org/purpurmc/purpur/entity/ai/ReceiveFlower.java
@@ -0,0 +1,91 @@
+package org.purpurmc.purpur.entity.ai;
+
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.ai.goal.Goal;
+import net.minecraft.world.entity.animal.IronGolem;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.block.Blocks;
+
+import java.util.EnumSet;
+import java.util.UUID;
+
+public class ReceiveFlower extends Goal {
+ private final IronGolem irongolem;
+ private ServerPlayer target;
+ private int cooldown;
+
+ public ReceiveFlower(IronGolem entity) {
+ this.irongolem = entity;
+ setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
+ }
+
+ @Override
+ public boolean canUse() {
+ if (this.irongolem.getOfferFlowerTick() > 0) {
+ return false;
+ }
+ if (!this.irongolem.isAngry()) {
+ return false;
+ }
+ UUID uuid = this.irongolem.getPersistentAngerTarget();
+ if (uuid == null) {
+ return false;
+ }
+ Entity target = ((ServerLevel) this.irongolem.level()).getEntity(uuid);
+ if (!(target instanceof ServerPlayer player)) {
+ return false;
+ }
+ InteractionHand hand = getPoppyHand(player);
+ if (hand == null) {
+ return false;
+ }
+ removeFlower(player, hand);
+ this.target = player;
+ return true;
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ return this.cooldown > 0;
+ }
+
+ @Override
+ public void start() {
+ this.cooldown = 100;
+ this.irongolem.stopBeingAngry();
+ this.irongolem.offerFlower(true);
+ }
+
+ @Override
+ public void stop() {
+ this.irongolem.offerFlower(false);
+ this.target = null;
+ }
+
+ @Override
+ public void tick() {
+ this.irongolem.getLookControl().setLookAt(this.target, 30.0F, 30.0F);
+ --this.cooldown;
+ }
+
+ private InteractionHand getPoppyHand(ServerPlayer player) {
+ if (isPoppy(player.getMainHandItem())) {
+ return InteractionHand.MAIN_HAND;
+ }
+ if (isPoppy(player.getOffhandItem())) {
+ return InteractionHand.OFF_HAND;
+ }
+ return null;
+ }
+
+ private void removeFlower(ServerPlayer player, InteractionHand hand) {
+ player.setItemInHand(hand, ItemStack.EMPTY);
+ }
+
+ private boolean isPoppy(ItemStack item) {
+ return item.getItem() == Blocks.POPPY.asItem();
+ }
+}
diff --git a/org/purpurmc/purpur/entity/projectile/DolphinSpit.java b/org/purpurmc/purpur/entity/projectile/DolphinSpit.java
new file mode 100644
index 0000000000000000000000000000000000000000..e489b16983b8eea75726baf6323b41af0929bdec
--- /dev/null
+++ b/org/purpurmc/purpur/entity/projectile/DolphinSpit.java
@@ -0,0 +1,105 @@
+package org.purpurmc.purpur.entity.projectile;
+
+import net.minecraft.core.particles.ParticleTypes;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.animal.Dolphin;
+import net.minecraft.world.entity.projectile.LlamaSpit;
+import net.minecraft.world.entity.projectile.ProjectileUtil;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.EntityHitResult;
+import net.minecraft.world.phys.HitResult;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.event.entity.EntityRemoveEvent;
+
+public class DolphinSpit extends LlamaSpit {
+ public LivingEntity dolphin;
+ public int ticksLived;
+
+ public DolphinSpit(EntityType<? extends LlamaSpit> type, Level world) {
+ super(type, world);
+ }
+
+ public DolphinSpit(Level world, Dolphin dolphin) {
+ this(EntityType.LLAMA_SPIT, world);
+ this.setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin);
+ this.dolphin = dolphin;
+ this.setPos(
+ dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5 * (double) Mth.sin(dolphin.yBodyRot * (float) (Math.PI / 180.0)),
+ dolphin.getEyeY() - 0.1F,
+ dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5 * (double) Mth.cos(dolphin.yBodyRot * (float) (Math.PI / 180.0)));
+ }
+
+ @Override
+ public boolean canSaveToDisk() {
+ return false;
+ }
+
+ public void tick() {
+ projectileTick();
+
+ Vec3 mot = this.getDeltaMovement();
+ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+
+ this.preHitTargetOrDeflectSelf(hitResult);
+
+ double x = this.getX() + mot.x;
+ double y = this.getY() + mot.y;
+ double z = this.getZ() + mot.z;
+
+ this.updateRotation();
+
+ Vec3 motDouble = mot.scale(2.0);
+ for (int i = 0; i < 5; i++) {
+ ((ServerLevel) level()).sendParticlesSource(null, ParticleTypes.BUBBLE,
+ true, false,
+ getX() + random.nextFloat() / 2 - 0.25F,
+ getY() + random.nextFloat() / 2 - 0.25F,
+ getZ() + random.nextFloat() / 2 - 0.25F,
+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D);
+ }
+
+ if (++ticksLived > 20) {
+ this.discard(EntityRemoveEvent.Cause.DISCARD);
+ } else {
+ this.setDeltaMovement(mot.scale(0.99D));
+ if (!this.isNoGravity()) {
+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D));
+ }
+
+ this.setPos(x, y, z);
+ }
+ }
+
+ @Override
+ public void shoot(double x, double y, double z, float speed, float inaccuracy) {
+ setDeltaMovement(new Vec3(x, y, z).normalize().add(
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy)
+ .scale(speed));
+ }
+
+ @Override
+ protected void onHitEntity(EntityHitResult entityHitResult) {
+ Entity shooter = this.getOwner();
+ if (shooter instanceof LivingEntity) {
+ entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.dolphinSpitDamage);
+ }
+ }
+
+ @Override
+ protected void onHitBlock(BlockHitResult blockHitResult) {
+ if (this.hitCancelled) {
+ return;
+ }
+ BlockState state = this.level().getBlockState(blockHitResult.getBlockPos());
+ state.onProjectileHit(this.level(), state, blockHitResult, this);
+ this.discard(EntityRemoveEvent.Cause.DISCARD);
+ }
+}
diff --git a/org/purpurmc/purpur/entity/projectile/PhantomFlames.java b/org/purpurmc/purpur/entity/projectile/PhantomFlames.java
new file mode 100644
index 0000000000000000000000000000000000000000..bac5eee2d101fa286285747170ba3365c47c87c3
--- /dev/null
+++ b/org/purpurmc/purpur/entity/projectile/PhantomFlames.java
@@ -0,0 +1,127 @@
+package org.purpurmc.purpur.entity.projectile;
+
+import net.minecraft.core.particles.ParticleTypes;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.decoration.ArmorStand;
+import net.minecraft.world.entity.monster.Phantom;
+import net.minecraft.world.entity.projectile.LlamaSpit;
+import net.minecraft.world.entity.projectile.ProjectileUtil;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockBehaviour;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.EntityHitResult;
+import net.minecraft.world.phys.HitResult;
+import net.minecraft.world.phys.Vec3;
+
+public class PhantomFlames extends LlamaSpit {
+ public Phantom phantom;
+ public int ticksLived;
+ public boolean canGrief = false;
+
+ public PhantomFlames(EntityType<? extends LlamaSpit> type, Level world) {
+ super(type, world);
+ }
+
+ public PhantomFlames(Level world, Phantom phantom) {
+ this(EntityType.LLAMA_SPIT, world);
+ setOwner(phantom.getRider() != null ? phantom.getRider() : phantom);
+ this.phantom = phantom;
+ this.setPos(
+ phantom.getX() - (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(phantom.yBodyRot * (float) (Math.PI / 180.0)),
+ phantom.getEyeY() - 0.10000000149011612D,
+ phantom.getZ() + (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(phantom.yBodyRot * (float) (Math.PI / 180.0)));
+ }
+
+ @Override
+ public boolean canSaveToDisk() {
+ return false;
+ }
+
+ public void tick() {
+ projectileTick();
+
+ Vec3 mot = this.getDeltaMovement();
+ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+
+ this.preHitTargetOrDeflectSelf(hitResult);
+
+ double x = this.getX() + mot.x;
+ double y = this.getY() + mot.y;
+ double z = this.getZ() + mot.z;
+
+ this.updateRotation();
+
+ Vec3 motDouble = mot.scale(2.0);
+ for (int i = 0; i < 5; i++) {
+ ((ServerLevel) level()).sendParticlesSource(null, ParticleTypes.FLAME,
+ true, false,
+ getX() + random.nextFloat() / 2 - 0.25F,
+ getY() + random.nextFloat() / 2 - 0.25F,
+ getZ() + random.nextFloat() / 2 - 0.25F,
+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D);
+ }
+
+ if (++ticksLived > 20) {
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ } else if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) {
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ } else if (this.isInWaterOrBubble()) {
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ } else {
+ this.setDeltaMovement(mot.scale(0.99D));
+ if (!this.isNoGravity()) {
+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D));
+ }
+
+ this.setPos(x, y, z);
+ }
+ }
+
+ @Override
+ public void shoot(double x, double y, double z, float speed, float inaccuracy) {
+ setDeltaMovement(new Vec3(x, y, z).normalize().add(
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy)
+ .scale(speed));
+ }
+
+ @Override
+ protected void onHitEntity(EntityHitResult entityHitResult) {
+ Level world = this.level();
+
+ if (world instanceof ServerLevel worldserver) {
+ Entity shooter = this.getOwner();
+ if (shooter instanceof LivingEntity) {
+ Entity target = entityHitResult.getEntity();
+ if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) {
+ boolean hurt = target.hurtServer(worldserver, target.damageSources().mobProjectile(this, (LivingEntity) shooter), worldserver.purpurConfig.phantomFlameDamage);
+ if (hurt && worldserver.purpurConfig.phantomFlameFireTime > 0) {
+ target.igniteForSeconds(worldserver.purpurConfig.phantomFlameFireTime);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onHitBlock(BlockHitResult blockHitResult) {
+ Level world = this.level();
+
+ if (world instanceof ServerLevel worldserver) {
+ if (this.hitCancelled) {
+ return;
+ }
+ if (this.canGrief) {
+ BlockState state = worldserver.getBlockState(blockHitResult.getBlockPos());
+ state.onProjectileHit(worldserver, state, blockHitResult, this);
+ }
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ }
+ }
+}
diff --git a/org/purpurmc/purpur/gui/GUIColor.java b/org/purpurmc/purpur/gui/GUIColor.java
new file mode 100644
index 0000000000000000000000000000000000000000..550222758bf0e7deff26a6e813a860b7be365e87
--- /dev/null
+++ b/org/purpurmc/purpur/gui/GUIColor.java
@@ -0,0 +1,58 @@
+package org.purpurmc.purpur.gui;
+
+import net.md_5.bungee.api.ChatColor;
+
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
+
+public enum GUIColor {
+ BLACK(ChatColor.BLACK, new Color(0x000000)),
+ DARK_BLUE(ChatColor.DARK_BLUE, new Color(0x0000AA)),
+ DARK_GREEN(ChatColor.DARK_GREEN, new Color(0x00AA00)),
+ DARK_AQUA(ChatColor.DARK_AQUA, new Color(0x009999)),
+ DARK_RED(ChatColor.DARK_RED, new Color(0xAA0000)),
+ DARK_PURPLE(ChatColor.DARK_PURPLE, new Color(0xAA00AA)),
+ GOLD(ChatColor.GOLD, new Color(0xBB8800)),
+ GRAY(ChatColor.GRAY, new Color(0x888888)),
+ DARK_GRAY(ChatColor.DARK_GRAY, new Color(0x444444)),
+ BLUE(ChatColor.BLUE, new Color(0x5555FF)),
+ GREEN(ChatColor.GREEN, new Color(0x55FF55)),
+ AQUA(ChatColor.AQUA, new Color(0x55DDDD)),
+ RED(ChatColor.RED, new Color(0xFF5555)),
+ LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, new Color(0xFF55FF)),
+ YELLOW(ChatColor.YELLOW, new Color(0xFFBB00)),
+ WHITE(ChatColor.WHITE, new Color(0xBBBBBB));
+
+ private final ChatColor chat;
+ private final Color color;
+
+ private static final Map<ChatColor, GUIColor> BY_CHAT = new HashMap<>();
+
+ GUIColor(ChatColor chat, Color color) {
+ this.chat = chat;
+ this.color = color;
+ }
+
+ public Color getColor() {
+ return color;
+ }
+
+ public ChatColor getChatColor() {
+ return chat;
+ }
+
+ public String getCode() {
+ return chat.toString();
+ }
+
+ public static GUIColor getColor(ChatColor chat) {
+ return BY_CHAT.get(chat);
+ }
+
+ static {
+ for (GUIColor color : values()) {
+ BY_CHAT.put(color.chat, color);
+ }
+ }
+}
diff --git a/org/purpurmc/purpur/gui/JColorTextPane.java b/org/purpurmc/purpur/gui/JColorTextPane.java
new file mode 100644
index 0000000000000000000000000000000000000000..d75fb5e77eff27d86135ed7d605dbc250b660f7d
--- /dev/null
+++ b/org/purpurmc/purpur/gui/JColorTextPane.java
@@ -0,0 +1,83 @@
+package org.purpurmc.purpur.gui;
+
+import com.google.common.collect.Sets;
+import javax.swing.UIManager;
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.api.chat.TextComponent;
+
+import javax.swing.JTextPane;
+import javax.swing.Timer;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+import java.util.Set;
+
+public class JColorTextPane extends JTextPane {
+ private static final GUIColor DEFAULT_COLOR;
+ static {
+ DEFAULT_COLOR = UIManager.getSystemLookAndFeelClassName().equals("com.sun.java.swing.plaf.gtk.GTKLookAndFeel")
+ ? GUIColor.WHITE : GUIColor.BLACK;
+ }
+
+
+ public void append(String msg) {
+ // TODO: update to use adventure instead
+ BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, DEFAULT_COLOR.getChatColor());
+ for (BaseComponent component : components) {
+ String text = component.toPlainText();
+ if (text == null || text.isEmpty()) {
+ continue;
+ }
+
+ GUIColor guiColor = GUIColor.getColor(component.getColor());
+
+ StyleContext context = StyleContext.getDefaultStyleContext();
+ AttributeSet attr = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, guiColor.getColor());
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Bold, component.isBold() || guiColor != DEFAULT_COLOR);
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Italic, component.isItalic());
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Underline, component.isUnderlined());
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.StrikeThrough, component.isStrikethrough());
+ //attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Blink, component.isObfuscated()); // no such thing as Blink, sadly
+
+ try {
+ int pos = getDocument().getLength();
+ getDocument().insertString(pos, text, attr);
+
+ if (component.isObfuscated()) {
+ // dirty hack to blink some text
+ Blink blink = new Blink(pos, text.length(), attr, context.addAttribute(attr, StyleConstants.Foreground, getBackground()));
+ BLINKS.add(blink);
+ }
+ } catch (BadLocationException ignore) {
+ }
+ }
+ }
+
+ private static final Set<Blink> BLINKS = Sets.newHashSet();
+ private static boolean SYNC_BLINK;
+
+ static {
+ new Timer(500, e -> {
+ SYNC_BLINK = !SYNC_BLINK;
+ BLINKS.forEach(Blink::blink);
+ }).start();
+ }
+
+ public class Blink {
+ private final int start, length;
+ private final AttributeSet attr1, attr2;
+
+ private Blink(int start, int length, AttributeSet attr1, AttributeSet attr2) {
+ this.start = start;
+ this.length = length;
+ this.attr1 = attr1;
+ this.attr2 = attr2;
+ }
+
+ private void blink() {
+ getStyledDocument().setCharacterAttributes(start, length, SYNC_BLINK ? attr1 : attr2, true);
+ }
+ }
+}
diff --git a/org/purpurmc/purpur/item/GlowBerryItem.java b/org/purpurmc/purpur/item/GlowBerryItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..b257f35caa13b660854cf17f41fd8fba1d56c458
--- /dev/null
+++ b/org/purpurmc/purpur/item/GlowBerryItem.java
@@ -0,0 +1,26 @@
+package org.purpurmc.purpur.item;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.effect.MobEffectInstance;
+import net.minecraft.world.effect.MobEffects;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.item.BlockItem;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import org.bukkit.event.entity.EntityPotionEffectEvent;
+
+public class GlowBerryItem extends BlockItem {
+ public GlowBerryItem(Block block, Properties settings) {
+ super(block, settings);
+ }
+
+ @Override
+ public ItemStack finishUsingItem(ItemStack stack, Level world, LivingEntity user) {
+ ItemStack result = super.finishUsingItem(stack, world, user);
+ if (world.purpurConfig.glowBerriesEatGlowDuration > 0 && user instanceof ServerPlayer player) {
+ player.addEffect(new MobEffectInstance(MobEffects.GLOWING, world.purpurConfig.glowBerriesEatGlowDuration), EntityPotionEffectEvent.Cause.FOOD);
+ }
+ return result;
+ }
+}
diff --git a/org/purpurmc/purpur/item/SpawnerItem.java b/org/purpurmc/purpur/item/SpawnerItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed50cb2115401c9039df4136caf5a087a5f5991c
--- /dev/null
+++ b/org/purpurmc/purpur/item/SpawnerItem.java
@@ -0,0 +1,40 @@
+package org.purpurmc.purpur.item;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.BlockItem;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.component.CustomData;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+
+public class SpawnerItem extends BlockItem {
+
+ public SpawnerItem(Block block, Properties settings) {
+ super(block, settings);
+ }
+
+ @Override
+ protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, Player player, ItemStack stack, BlockState state) {
+ boolean handled = super.updateCustomBlockEntityTag(pos, level, player, stack, state);
+ if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.place.spawners")) {
+ BlockEntity blockEntity = level.getBlockEntity(pos);
+ if (blockEntity instanceof SpawnerBlockEntity spawner) {
+ CompoundTag customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
+ if (customData.contains("Purpur.mob_type")) {
+ EntityType.byString(customData.getString("Purpur.mob_type")).ifPresent(type -> spawner.getSpawner().setEntityId(type, level, level.random, pos));
+ } else if (customData.contains("Purpur.SpawnData")) {
+ net.minecraft.world.level.SpawnData.CODEC.parse(net.minecraft.nbt.NbtOps.INSTANCE, customData.getCompound("Purpur.SpawnData")).result()
+ .ifPresent(spawnData -> spawner.getSpawner().nextSpawnData = spawnData);
+ }
+ }
+ }
+ return handled;
+ }
+}
diff --git a/org/purpurmc/purpur/network/ClientboundBeehivePayload.java b/org/purpurmc/purpur/network/ClientboundBeehivePayload.java
new file mode 100644
index 0000000000000000000000000000000000000000..793a3ea45fe04e84725926f17615c26e008b0ce4
--- /dev/null
+++ b/org/purpurmc/purpur/network/ClientboundBeehivePayload.java
@@ -0,0 +1,27 @@
+package org.purpurmc.purpur.network;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
+import net.minecraft.resources.ResourceLocation;
+import org.jetbrains.annotations.NotNull;
+
+public record ClientboundBeehivePayload(BlockPos pos, int numOfBees) implements CustomPacketPayload {
+ public static final StreamCodec<FriendlyByteBuf, ClientboundBeehivePayload> STREAM_CODEC = CustomPacketPayload.codec(ClientboundBeehivePayload::write, ClientboundBeehivePayload::new);
+ public static final Type<ClientboundBeehivePayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("purpur", "beehive_s2c"));
+
+ public ClientboundBeehivePayload(FriendlyByteBuf friendlyByteBuf) {
+ this(friendlyByteBuf.readBlockPos(), friendlyByteBuf.readInt());
+ }
+
+ private void write(FriendlyByteBuf friendlyByteBuf) {
+ friendlyByteBuf.writeBlockPos(this.pos);
+ friendlyByteBuf.writeInt(this.numOfBees);
+ }
+
+ @Override
+ public @NotNull Type<? extends CustomPacketPayload> type() {
+ return TYPE;
+ }
+}
diff --git a/org/purpurmc/purpur/network/ServerboundBeehivePayload.java b/org/purpurmc/purpur/network/ServerboundBeehivePayload.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa72769e06061609e1e658a0250e99c8cb026c0e
--- /dev/null
+++ b/org/purpurmc/purpur/network/ServerboundBeehivePayload.java
@@ -0,0 +1,26 @@
+package org.purpurmc.purpur.network;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
+import net.minecraft.resources.ResourceLocation;
+import org.jetbrains.annotations.NotNull;
+
+public record ServerboundBeehivePayload(BlockPos pos) implements CustomPacketPayload {
+ public static final StreamCodec<FriendlyByteBuf, ServerboundBeehivePayload> STREAM_CODEC = CustomPacketPayload.codec(ServerboundBeehivePayload::write, ServerboundBeehivePayload::new);
+ public static final Type<ServerboundBeehivePayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("purpur", "beehive_c2s"));
+
+ public ServerboundBeehivePayload(FriendlyByteBuf friendlyByteBuf) {
+ this(friendlyByteBuf.readBlockPos());
+ }
+
+ private void write(FriendlyByteBuf friendlyByteBuf) {
+ friendlyByteBuf.writeBlockPos(this.pos);
+ }
+
+ @Override
+ public @NotNull Type<? extends CustomPacketPayload> type() {
+ return TYPE;
+ }
+}
diff --git a/org/purpurmc/purpur/task/BeehiveTask.java b/org/purpurmc/purpur/task/BeehiveTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..664f9d5e1ce5e2787bf699bd11758b9e3aa8ed3a
--- /dev/null
+++ b/org/purpurmc/purpur/task/BeehiveTask.java
@@ -0,0 +1,67 @@
+package org.purpurmc.purpur.task;
+
+import io.netty.buffer.Unpooled;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.PluginBase;
+import org.bukkit.plugin.messaging.PluginMessageListener;
+import org.jetbrains.annotations.NotNull;
+import org.purpurmc.purpur.network.ClientboundBeehivePayload;
+import org.purpurmc.purpur.network.ServerboundBeehivePayload;
+import org.purpurmc.purpur.util.MinecraftInternalPlugin;
+
+public class BeehiveTask implements PluginMessageListener {
+
+ private static BeehiveTask instance;
+
+ public static BeehiveTask instance() {
+ if (instance == null) {
+ instance = new BeehiveTask();
+ }
+ return instance;
+ }
+
+ private final PluginBase plugin = new MinecraftInternalPlugin();
+
+ private BeehiveTask() {
+ }
+
+ public void register() {
+ Bukkit.getMessenger().registerOutgoingPluginChannel(this.plugin, ClientboundBeehivePayload.TYPE.id().toString());
+ Bukkit.getMessenger().registerIncomingPluginChannel(this.plugin, ServerboundBeehivePayload.TYPE.id().toString(), this);
+ }
+
+ public void unregister() {
+ Bukkit.getMessenger().unregisterOutgoingPluginChannel(this.plugin, ClientboundBeehivePayload.TYPE.id().toString());
+ Bukkit.getMessenger().unregisterIncomingPluginChannel(this.plugin, ServerboundBeehivePayload.TYPE.id().toString());
+ }
+
+ @Override
+ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] bytes) {
+ FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.copiedBuffer(bytes));
+ ServerboundBeehivePayload payload = ServerboundBeehivePayload.STREAM_CODEC.decode(byteBuf);
+
+ ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle();
+
+ // targeted block info max range specified in client at net.minecraft.client.gui.hud.DebugHud#render
+ if (!payload.pos().getCenter().closerThan(serverPlayer.position(), 20)) return; // Targeted Block info max range is 20
+ if (serverPlayer.level().getChunkIfLoaded(payload.pos()) == null) return;
+
+ BlockEntity blockEntity = serverPlayer.level().getBlockEntity(payload.pos());
+ if (!(blockEntity instanceof BeehiveBlockEntity beehive)) {
+ return;
+ }
+
+ ClientboundBeehivePayload customPacketPayload = new ClientboundBeehivePayload(payload.pos(), beehive.getOccupantCount());
+ FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
+ ClientboundBeehivePayload.STREAM_CODEC.encode(friendlyByteBuf, customPacketPayload);
+ byte[] byteArray = new byte[friendlyByteBuf.readableBytes()];
+ friendlyByteBuf.readBytes(byteArray);
+ player.sendPluginMessage(this.plugin, customPacketPayload.type().id().toString(), byteArray);
+ }
+}
diff --git a/org/purpurmc/purpur/task/BossBarTask.java b/org/purpurmc/purpur/task/BossBarTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c3d4cd52db93b97a40321030a70ebc282c9636b
--- /dev/null
+++ b/org/purpurmc/purpur/task/BossBarTask.java
@@ -0,0 +1,121 @@
+package org.purpurmc.purpur.task;
+
+import net.kyori.adventure.bossbar.BossBar;
+import net.minecraft.server.level.ServerPlayer;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+import org.purpurmc.purpur.util.MinecraftInternalPlugin;
+
+public abstract class BossBarTask extends BukkitRunnable {
+ private final Map<UUID, BossBar> bossbars = new HashMap<>();
+ private boolean started;
+
+ abstract BossBar createBossBar();
+
+ abstract void updateBossBar(BossBar bossbar, Player player);
+
+ @Override
+ public void run() {
+ Iterator<Map.Entry<UUID, BossBar>> iter = bossbars.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry<UUID, BossBar> entry = iter.next();
+ Player player = Bukkit.getPlayer(entry.getKey());
+ if (player == null) {
+ iter.remove();
+ continue;
+ }
+ updateBossBar(entry.getValue(), player);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ super.cancel();
+ new HashSet<>(this.bossbars.keySet()).forEach(uuid -> {
+ Player player = Bukkit.getPlayer(uuid);
+ if (player != null) {
+ removePlayer(player);
+ }
+ });
+ this.bossbars.clear();
+ }
+
+ public boolean removePlayer(Player player) {
+ BossBar bossbar = this.bossbars.remove(player.getUniqueId());
+ if (bossbar != null) {
+ player.hideBossBar(bossbar);
+ return true;
+ }
+ return false;
+ }
+
+ public void addPlayer(Player player) {
+ removePlayer(player);
+ BossBar bossbar = createBossBar();
+ this.bossbars.put(player.getUniqueId(), bossbar);
+ this.updateBossBar(bossbar, player);
+ player.showBossBar(bossbar);
+ }
+
+ public boolean hasPlayer(UUID uuid) {
+ return this.bossbars.containsKey(uuid);
+ }
+
+ public boolean togglePlayer(Player player) {
+ if (removePlayer(player)) {
+ return false;
+ }
+ addPlayer(player);
+ return true;
+ }
+
+ public void start() {
+ stop();
+ this.runTaskTimerAsynchronously(new MinecraftInternalPlugin(), 1, 1);
+ started = true;
+ }
+
+ public void stop() {
+ if (started) {
+ cancel();
+ }
+ }
+
+ public static void startAll() {
+ RamBarTask.instance().start();
+ TPSBarTask.instance().start();
+ CompassTask.instance().start();
+ }
+
+ public static void stopAll() {
+ RamBarTask.instance().stop();
+ TPSBarTask.instance().stop();
+ CompassTask.instance().stop();
+ }
+
+ public static void addToAll(ServerPlayer player) {
+ Player bukkit = player.getBukkitEntity();
+ if (player.ramBar()) {
+ RamBarTask.instance().addPlayer(bukkit);
+ }
+ if (player.tpsBar()) {
+ TPSBarTask.instance().addPlayer(bukkit);
+ }
+ if (player.compassBar()) {
+ CompassTask.instance().addPlayer(bukkit);
+ }
+ }
+
+ public static void removeFromAll(Player player) {
+ RamBarTask.instance().removePlayer(player);
+ TPSBarTask.instance().removePlayer(player);
+ CompassTask.instance().removePlayer(player);
+ }
+}
diff --git a/org/purpurmc/purpur/task/CompassTask.java b/org/purpurmc/purpur/task/CompassTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..bece7eefc8ba8822b433835526251d2fb916c025
--- /dev/null
+++ b/org/purpurmc/purpur/task/CompassTask.java
@@ -0,0 +1,68 @@
+package org.purpurmc.purpur.task;
+
+import net.kyori.adventure.bossbar.BossBar;
+import net.kyori.adventure.text.Component;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.item.Items;
+import org.bukkit.entity.Player;
+import org.purpurmc.purpur.PurpurConfig;
+
+public class CompassTask extends BossBarTask {
+ private static CompassTask instance;
+
+ private int tick = 0;
+
+ public static CompassTask instance() {
+ if (instance == null) {
+ instance = new CompassTask();
+ }
+ return instance;
+ }
+
+ @Override
+ public void run() {
+ if (++tick < PurpurConfig.commandCompassBarTickInterval) {
+ return;
+ }
+ tick = 0;
+
+ MinecraftServer.getServer().getAllLevels().forEach((level) -> {
+ if (level.purpurConfig.compassItemShowsBossBar) {
+ level.players().forEach(player -> {
+ if (!player.compassBar()) {
+ if (player.getMainHandItem().getItem() != Items.COMPASS && player.getOffhandItem().getItem() != Items.COMPASS) {
+ removePlayer(player.getBukkitEntity());
+ } else if (!hasPlayer(player.getUUID())) {
+ addPlayer(player.getBukkitEntity());
+ }
+ }
+ });
+ }
+ });
+
+ super.run();
+ }
+
+ @Override
+ BossBar createBossBar() {
+ return BossBar.bossBar(Component.text(""), PurpurConfig.commandCompassBarProgressPercent, PurpurConfig.commandCompassBarProgressColor, PurpurConfig.commandCompassBarProgressOverlay);
+ }
+
+ @Override
+ void updateBossBar(BossBar bossbar, Player player) {
+ float yaw = player.getLocation().getYaw();
+ int length = PurpurConfig.commandCompassBarTitle.length();
+ int pos = (int) ((normalize(yaw) * (length / 720F)) + (length / 2F));
+ bossbar.name(Component.text(PurpurConfig.commandCompassBarTitle.substring(pos - 25, pos + 25)));
+ }
+
+ private float normalize(float yaw) {
+ while (yaw < -180.0F) {
+ yaw += 360.0F;
+ }
+ while (yaw > 180.0F) {
+ yaw -= 360.0F;
+ }
+ return yaw;
+ }
+}
diff --git a/org/purpurmc/purpur/task/RamBarTask.java b/org/purpurmc/purpur/task/RamBarTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e98c0ae73e2c40002a72b5d0d246ffa0c3ab38f
--- /dev/null
+++ b/org/purpurmc/purpur/task/RamBarTask.java
@@ -0,0 +1,117 @@
+package org.purpurmc.purpur.task;
+
+import net.kyori.adventure.bossbar.BossBar;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import org.bukkit.entity.Player;
+import org.purpurmc.purpur.PurpurConfig;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryUsage;
+
+public class RamBarTask extends BossBarTask {
+ private static RamBarTask instance;
+ private long allocated = 0L;
+ private long used = 0L;
+ private long xmx = 0L;
+ private long xms = 0L;
+ private float percent = 0F;
+ private int tick = 0;
+
+ public static RamBarTask instance() {
+ if (instance == null) {
+ instance = new RamBarTask();
+ }
+ return instance;
+ }
+
+ @Override
+ BossBar createBossBar() {
+ return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandRamBarProgressOverlay);
+ }
+
+ @Override
+ void updateBossBar(BossBar bossbar, Player player) {
+ bossbar.progress(getBossBarProgress());
+ bossbar.color(getBossBarColor());
+ bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandRamBarTitle,
+ Placeholder.component("allocated", format(this.allocated)),
+ Placeholder.component("used", format(this.used)),
+ Placeholder.component("xmx", format(this.xmx)),
+ Placeholder.component("xms", format(this.xms)),
+ Placeholder.unparsed("percent", ((int) (this.percent * 100)) + "%")
+ ));
+ }
+
+ @Override
+ public void run() {
+ if (++this.tick < PurpurConfig.commandRamBarTickInterval) {
+ return;
+ }
+ this.tick = 0;
+
+ MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
+
+ this.allocated = heap.getCommitted();
+ this.used = heap.getUsed();
+ this.xmx = heap.getMax();
+ this.xms = heap.getInit();
+ this.percent = Math.max(Math.min((float) this.used / this.xmx, 1.0F), 0.0F);
+
+ super.run();
+ }
+
+ private float getBossBarProgress() {
+ return this.percent;
+ }
+
+ private BossBar.Color getBossBarColor() {
+ if (this.percent < 0.5F) {
+ return PurpurConfig.commandRamBarProgressColorGood;
+ } else if (this.percent < 0.75F) {
+ return PurpurConfig.commandRamBarProgressColorMedium;
+ } else {
+ return PurpurConfig.commandRamBarProgressColorLow;
+ }
+ }
+
+ public Component format(long v) {
+ String color;
+ if (this.percent < 0.60F) {
+ color = PurpurConfig.commandRamBarTextColorGood;
+ } else if (this.percent < 0.85F) {
+ color = PurpurConfig.commandRamBarTextColorMedium;
+ } else {
+ color = PurpurConfig.commandRamBarTextColorLow;
+ }
+ String value;
+ if (v < 1024) {
+ value = v + "B";
+ } else {
+ int z = (63 - Long.numberOfLeadingZeros(v)) / 10;
+ value = String.format("%.1f%s", (double) v / (1L << (z * 10)), "BKMGTPE".charAt(z));
+ }
+ return MiniMessage.miniMessage().deserialize(color, Placeholder.unparsed("text", value));
+ }
+
+ public long getAllocated() {
+ return this.allocated;
+ }
+
+ public long getUsed() {
+ return this.used;
+ }
+
+ public long getXmx() {
+ return this.xmx;
+ }
+
+ public long getXms() {
+ return this.xms;
+ }
+
+ public float getPercent() {
+ return this.percent;
+ }
+}
diff --git a/org/purpurmc/purpur/task/TPSBarTask.java b/org/purpurmc/purpur/task/TPSBarTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..8769993e7ca59da309087051a3cd38fc562c15d1
--- /dev/null
+++ b/org/purpurmc/purpur/task/TPSBarTask.java
@@ -0,0 +1,142 @@
+package org.purpurmc.purpur.task;
+
+import net.kyori.adventure.bossbar.BossBar;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import org.purpurmc.purpur.PurpurConfig;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+public class TPSBarTask extends BossBarTask {
+ private static TPSBarTask instance;
+ private double tps = 20.0D;
+ private double mspt = 0.0D;
+ private int tick = 0;
+
+ public static TPSBarTask instance() {
+ if (instance == null) {
+ instance = new TPSBarTask();
+ }
+ return instance;
+ }
+
+ @Override
+ BossBar createBossBar() {
+ return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandTPSBarProgressOverlay);
+ }
+
+ @Override
+ void updateBossBar(BossBar bossbar, Player player) {
+ bossbar.progress(getBossBarProgress());
+ bossbar.color(getBossBarColor());
+ bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandTPSBarTitle,
+ Placeholder.component("tps", getTPSColor()),
+ Placeholder.component("mspt", getMSPTColor()),
+ Placeholder.component("ping", getPingColor(player.getPing()))
+ ));
+ }
+
+ @Override
+ public void run() {
+ if (++tick < PurpurConfig.commandTPSBarTickInterval) {
+ return;
+ }
+ tick = 0;
+
+ this.tps = Math.max(Math.min(Bukkit.getTPS()[0], 20.0D), 0.0D);
+ this.mspt = Bukkit.getAverageTickTime();
+
+ super.run();
+ }
+
+ private float getBossBarProgress() {
+ if (PurpurConfig.commandTPSBarProgressFillMode == FillMode.MSPT) {
+ return Math.max(Math.min((float) mspt / 50.0F, 1.0F), 0.0F);
+ } else {
+ return Math.max(Math.min((float) tps / 20.0F, 1.0F), 0.0F);
+ }
+ }
+
+ private BossBar.Color getBossBarColor() {
+ if (isGood(PurpurConfig.commandTPSBarProgressFillMode)) {
+ return PurpurConfig.commandTPSBarProgressColorGood;
+ } else if (isMedium(PurpurConfig.commandTPSBarProgressFillMode)) {
+ return PurpurConfig.commandTPSBarProgressColorMedium;
+ } else {
+ return PurpurConfig.commandTPSBarProgressColorLow;
+ }
+ }
+
+ private boolean isGood(FillMode mode) {
+ return isGood(mode, 0);
+ }
+
+ private boolean isGood(FillMode mode, int ping) {
+ if (mode == FillMode.MSPT) {
+ return mspt < 40;
+ } else if (mode == FillMode.TPS) {
+ return tps >= 19;
+ } else if (mode == FillMode.PING) {
+ return ping < 100;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean isMedium(FillMode mode) {
+ return isMedium(mode, 0);
+ }
+
+ private boolean isMedium(FillMode mode, int ping) {
+ if (mode == FillMode.MSPT) {
+ return mspt < 50;
+ } else if (mode == FillMode.TPS) {
+ return tps >= 15;
+ } else if (mode == FillMode.PING) {
+ return ping < 200;
+ } else {
+ return false;
+ }
+ }
+
+ private Component getTPSColor() {
+ String color;
+ if (isGood(FillMode.TPS)) {
+ color = PurpurConfig.commandTPSBarTextColorGood;
+ } else if (isMedium(FillMode.TPS)) {
+ color = PurpurConfig.commandTPSBarTextColorMedium;
+ } else {
+ color = PurpurConfig.commandTPSBarTextColorLow;
+ }
+ return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", tps)));
+ }
+
+ private Component getMSPTColor() {
+ String color;
+ if (isGood(FillMode.MSPT)) {
+ color = PurpurConfig.commandTPSBarTextColorGood;
+ } else if (isMedium(FillMode.MSPT)) {
+ color = PurpurConfig.commandTPSBarTextColorMedium;
+ } else {
+ color = PurpurConfig.commandTPSBarTextColorLow;
+ }
+ return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", mspt)));
+ }
+
+ private Component getPingColor(int ping) {
+ String color;
+ if (isGood(FillMode.PING, ping)) {
+ color = PurpurConfig.commandTPSBarTextColorGood;
+ } else if (isMedium(FillMode.PING, ping)) {
+ color = PurpurConfig.commandTPSBarTextColorMedium;
+ } else {
+ color = PurpurConfig.commandTPSBarTextColorLow;
+ }
+ return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%s", ping)));
+ }
+
+ public enum FillMode {
+ TPS, MSPT, PING
+ }
+}
diff --git a/org/purpurmc/purpur/tool/Actionable.java b/org/purpurmc/purpur/tool/Actionable.java
new file mode 100644
index 0000000000000000000000000000000000000000..e18c37f06730da9d3055d5215e813b1477c1e70e
--- /dev/null
+++ b/org/purpurmc/purpur/tool/Actionable.java
@@ -0,0 +1,24 @@
+package org.purpurmc.purpur.tool;
+
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.block.Block;
+
+import java.util.Map;
+
+public abstract class Actionable {
+ private final Block into;
+ private final Map<Item, Double> drops;
+
+ public Actionable(Block into, Map<Item, Double> drops) {
+ this.into = into;
+ this.drops = drops;
+ }
+
+ public Block into() {
+ return into;
+ }
+
+ public Map<Item, Double> drops() {
+ return drops;
+ }
+}
diff --git a/org/purpurmc/purpur/tool/Flattenable.java b/org/purpurmc/purpur/tool/Flattenable.java
new file mode 100644
index 0000000000000000000000000000000000000000..345d4ee4ff0b78bd1050959711a4f5d16a5e8aee
--- /dev/null
+++ b/org/purpurmc/purpur/tool/Flattenable.java
@@ -0,0 +1,12 @@
+package org.purpurmc.purpur.tool;
+
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.block.Block;
+
+import java.util.Map;
+
+public class Flattenable extends Actionable {
+ public Flattenable(Block into, Map<Item, Double> drops) {
+ super(into, drops);
+ }
+}
diff --git a/org/purpurmc/purpur/tool/Strippable.java b/org/purpurmc/purpur/tool/Strippable.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf5402214f41af9c09bd6c5c4f45d330516d742e
--- /dev/null
+++ b/org/purpurmc/purpur/tool/Strippable.java
@@ -0,0 +1,12 @@
+package org.purpurmc.purpur.tool;
+
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.block.Block;
+
+import java.util.Map;
+
+public class Strippable extends Actionable {
+ public Strippable(Block into, Map<Item, Double> drops) {
+ super(into, drops);
+ }
+}
diff --git a/org/purpurmc/purpur/tool/Tillable.java b/org/purpurmc/purpur/tool/Tillable.java
new file mode 100644
index 0000000000000000000000000000000000000000..715f6dd44480347eebced43c11bc364e05727498
--- /dev/null
+++ b/org/purpurmc/purpur/tool/Tillable.java
@@ -0,0 +1,50 @@
+package org.purpurmc.purpur.tool;
+
+import net.minecraft.world.item.HoeItem;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.context.UseOnContext;
+import net.minecraft.world.level.block.Block;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
+
+public class Tillable extends Actionable {
+ private final Condition condition;
+
+ public Tillable(Condition condition, Block into, Map<Item, Double> drops) {
+ super(into, drops);
+ this.condition = condition;
+ }
+
+ public Condition condition() {
+ return condition;
+ }
+
+ public enum Condition {
+ AIR_ABOVE(HoeItem::onlyIfAirAbove),
+ ALWAYS((useOnContext) -> true);
+
+ private final Predicate<UseOnContext> predicate;
+
+ Condition(Predicate<UseOnContext> predicate) {
+ this.predicate = predicate;
+ }
+
+ public Predicate<UseOnContext> predicate() {
+ return predicate;
+ }
+
+ private static final Map<String, Condition> BY_NAME = new HashMap<>();
+
+ static {
+ for (Condition condition : values()) {
+ BY_NAME.put(condition.name(), condition);
+ }
+ }
+
+ public static Condition get(String name) {
+ return BY_NAME.get(name.toUpperCase(java.util.Locale.ROOT));
+ }
+ }
+}
diff --git a/org/purpurmc/purpur/tool/Waxable.java b/org/purpurmc/purpur/tool/Waxable.java
new file mode 100644
index 0000000000000000000000000000000000000000..64adb13b29b6757dcf227a55588da70ecabe083f
--- /dev/null
+++ b/org/purpurmc/purpur/tool/Waxable.java
@@ -0,0 +1,12 @@
+package org.purpurmc.purpur.tool;
+
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.block.Block;
+
+import java.util.Map;
+
+public class Waxable extends Actionable {
+ public Waxable(Block into, Map<Item, Double> drops) {
+ super(into, drops);
+ }
+}
diff --git a/org/purpurmc/purpur/tool/Weatherable.java b/org/purpurmc/purpur/tool/Weatherable.java
new file mode 100644
index 0000000000000000000000000000000000000000..b7586f494528f30eb0da82420d3bcf5b83a1a902
--- /dev/null
+++ b/org/purpurmc/purpur/tool/Weatherable.java
@@ -0,0 +1,12 @@
+package org.purpurmc.purpur.tool;
+
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.block.Block;
+
+import java.util.Map;
+
+public class Weatherable extends Actionable {
+ public Weatherable(Block into, Map<Item, Double> drops) {
+ super(into, drops);
+ }
+}
diff --git a/org/purpurmc/purpur/util/MinecraftInternalPlugin.java b/org/purpurmc/purpur/util/MinecraftInternalPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..77a18f3048b18d38c5bd0129065efff27fa774fa
--- /dev/null
+++ b/org/purpurmc/purpur/util/MinecraftInternalPlugin.java
@@ -0,0 +1,149 @@
+package org.purpurmc.purpur.util;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
+import org.bukkit.Server;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.generator.BiomeProvider;
+import org.bukkit.generator.ChunkGenerator;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginBase;
+import org.bukkit.plugin.PluginDescriptionFile;
+import org.bukkit.plugin.PluginLoader;
+import org.bukkit.plugin.PluginLogger;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+
+public class MinecraftInternalPlugin extends PluginBase {
+ private boolean enabled = true;
+
+ private final String pluginName;
+ private PluginDescriptionFile pdf;
+
+ public MinecraftInternalPlugin() {
+ this.pluginName = "Minecraft";
+ pdf = new PluginDescriptionFile(pluginName, "1.0", "nms");
+ }
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @Override
+ public File getDataFolder() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public PluginDescriptionFile getDescription() {
+ return pdf;
+ }
+
+ @Override
+ public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() {
+ return pdf;
+ }
+
+ @Override
+ public FileConfiguration getConfig() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public InputStream getResource(String filename) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void saveConfig() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void saveDefaultConfig() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void saveResource(String resourcePath, boolean replace) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void reloadConfig() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public PluginLogger getLogger() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public PluginLoader getPluginLoader() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public Server getServer() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public void onDisable() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void onLoad() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void onEnable() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public boolean isNaggable() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public void setNaggable(boolean canNag) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public @NotNull LifecycleEventManager<Plugin> getLifecycleManager() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+}