From 401e42cd6e6beb4f1f640cfd971abb0fc0fafa01 Mon Sep 17 00:00:00 2001 From: Ben Kerllenevich Date: Wed, 8 Jun 2022 15:21:48 -0400 Subject: [PATCH] all the patches are done --- gradle.properties | 2 +- ....patch => 0001-Build-System-Changes.patch} | 0 ...s.patch => 0002-Purpur-config-files.patch} | 0 ...patch => 0003-Purpur-client-support.patch} | 0 ...s.patch => 0004-Default-permissions.patch} | 0 ...006-Ridables.patch => 0005-Ridables.patch} | 0 ...ch => 0006-Allow-inventory-resizing.patch} | 0 ...8-Llama-API.patch => 0007-Llama-API.patch} | 0 ...{0009-AFK-API.patch => 0008-AFK-API.patch} | 0 ...atch => 0009-Bring-back-server-name.patch} | 0 ...t.patch => 0010-ExecuteCommandEvent.patch} | 0 ... 0011-LivingEntity-safeFallDistance.patch} | 0 ...old.patch => 0012-Lagging-threshold.patch} | 0 ...13-PlayerSetSpawnerTypeWithEggEvent.patch} | 0 ...ch => 0014-EMC-MonsterEggSpawnEvent.patch} | 0 ...ch => 0015-Player-invulnerabilities.patch} | 0 ...7-Anvil-API.patch => 0016-Anvil-API.patch} | 0 ... 0017-ItemStack-convenience-methods.patch} | 0 ...d-to-crystals-and-crystals-shoot-ph.patch} | 0 ...atch => 0019-ChatColor-conveniences.patch} | 0 ...020-LivingEntity-broadcastItemBreak.patch} | 0 ...atch => 0021-Item-entity-immunities.patch} | 0 ...t-Improve-output-of-plugins-command.patch} | 0 ...able-zombie-aggressiveness-towards-.patch} | 0 ...-to-recipe-s-ExactChoice-ingredient.patch} | 0 ...25-Alphabetize-in-game-plugins-list.patch} | 0 ...lf-API.patch => 0026-Rabid-Wolf-API.patch} | 0 ...tch => 0027-PlayerBookTooLargeEvent.patch} | 0 ...herite-armor-grants-fire-resistance.patch} | 0 ...029-Add-EntityTeleportHinderedEvent.patch} | 0 ...tment-target-for-bows-and-crossbows.patch} | 0 ...> 0031-Iron-golem-poppy-calms-anger.patch} | 0 ...32-API-for-any-mob-to-burn-daylight.patch} | 0 ...atch => 0033-Flying-Fall-Damage-API.patch} | 0 ...d-back-player-spawned-endermite-API.patch} | 0 ... 0035-Fix-default-permission-system.patch} | 0 ...oner-API.patch => 0036-Summoner-API.patch} | 0 ...037-Clean-up-version-command-output.patch} | 0 ... => 0038-Extended-OfflinePlayer-API.patch} | 0 ...he-ability-to-add-combustible-items.patch} | 0 ....patch => 0040-Potion-NamespacedKey.patch} | 0 ...ne-API.patch => 0041-Grindstone-API.patch} | 0 ...Shears-can-have-looting-enchantment.patch} | 0 ... => 0043-Lobotomize-stuck-villagers.patch} | 0 ...0273-Add-config-for-snow-on-blue-ice.patch | 39 + ...0144-Add-config-for-snow-on-blue-ice.patch | 46 - ...sh-added-classes-to-junit-exemptions.patch | 0 patches/todo/pufferfish/0006-Ridables.patch | 6344 ----------------- .../0264-Lobotomize-stuck-villagers.patch | 143 - 49 files changed, 40 insertions(+), 6534 deletions(-) rename patches/api/{0002-Build-System-Changes.patch => 0001-Build-System-Changes.patch} (100%) rename patches/api/{0003-Purpur-config-files.patch => 0002-Purpur-config-files.patch} (100%) rename patches/api/{0004-Purpur-client-support.patch => 0003-Purpur-client-support.patch} (100%) rename patches/api/{0005-Default-permissions.patch => 0004-Default-permissions.patch} (100%) rename patches/api/{0006-Ridables.patch => 0005-Ridables.patch} (100%) rename patches/api/{0007-Allow-inventory-resizing.patch => 0006-Allow-inventory-resizing.patch} (100%) rename patches/api/{0008-Llama-API.patch => 0007-Llama-API.patch} (100%) rename patches/api/{0009-AFK-API.patch => 0008-AFK-API.patch} (100%) rename patches/api/{0010-Bring-back-server-name.patch => 0009-Bring-back-server-name.patch} (100%) rename patches/api/{0011-ExecuteCommandEvent.patch => 0010-ExecuteCommandEvent.patch} (100%) rename patches/api/{0012-LivingEntity-safeFallDistance.patch => 0011-LivingEntity-safeFallDistance.patch} (100%) rename patches/api/{0013-Lagging-threshold.patch => 0012-Lagging-threshold.patch} (100%) rename patches/api/{0014-PlayerSetSpawnerTypeWithEggEvent.patch => 0013-PlayerSetSpawnerTypeWithEggEvent.patch} (100%) rename patches/api/{0015-EMC-MonsterEggSpawnEvent.patch => 0014-EMC-MonsterEggSpawnEvent.patch} (100%) rename patches/api/{0016-Player-invulnerabilities.patch => 0015-Player-invulnerabilities.patch} (100%) rename patches/api/{0017-Anvil-API.patch => 0016-Anvil-API.patch} (100%) rename patches/api/{0018-ItemStack-convenience-methods.patch => 0017-ItemStack-convenience-methods.patch} (100%) rename patches/api/{0019-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch => 0018-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch} (100%) rename patches/api/{0020-ChatColor-conveniences.patch => 0019-ChatColor-conveniences.patch} (100%) rename patches/api/{0021-LivingEntity-broadcastItemBreak.patch => 0020-LivingEntity-broadcastItemBreak.patch} (100%) rename patches/api/{0022-Item-entity-immunities.patch => 0021-Item-entity-immunities.patch} (100%) rename patches/api/{0023-Spigot-Improve-output-of-plugins-command.patch => 0022-Spigot-Improve-output-of-plugins-command.patch} (100%) rename patches/api/{0024-Add-option-to-disable-zombie-aggressiveness-towards-.patch => 0023-Add-option-to-disable-zombie-aggressiveness-towards-.patch} (100%) rename patches/api/{0025-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch => 0024-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch} (100%) rename patches/api/{0026-Alphabetize-in-game-plugins-list.patch => 0025-Alphabetize-in-game-plugins-list.patch} (100%) rename patches/api/{0027-Rabid-Wolf-API.patch => 0026-Rabid-Wolf-API.patch} (100%) rename patches/api/{0028-PlayerBookTooLargeEvent.patch => 0027-PlayerBookTooLargeEvent.patch} (100%) rename patches/api/{0029-Full-netherite-armor-grants-fire-resistance.patch => 0028-Full-netherite-armor-grants-fire-resistance.patch} (100%) rename patches/api/{0030-Add-EntityTeleportHinderedEvent.patch => 0029-Add-EntityTeleportHinderedEvent.patch} (100%) rename patches/api/{0031-Add-enchantment-target-for-bows-and-crossbows.patch => 0030-Add-enchantment-target-for-bows-and-crossbows.patch} (100%) rename patches/api/{0032-Iron-golem-poppy-calms-anger.patch => 0031-Iron-golem-poppy-calms-anger.patch} (100%) rename patches/api/{0033-API-for-any-mob-to-burn-daylight.patch => 0032-API-for-any-mob-to-burn-daylight.patch} (100%) rename patches/api/{0034-Flying-Fall-Damage-API.patch => 0033-Flying-Fall-Damage-API.patch} (100%) rename patches/api/{0035-Add-back-player-spawned-endermite-API.patch => 0034-Add-back-player-spawned-endermite-API.patch} (100%) rename patches/api/{0036-Fix-default-permission-system.patch => 0035-Fix-default-permission-system.patch} (100%) rename patches/api/{0037-Summoner-API.patch => 0036-Summoner-API.patch} (100%) rename patches/api/{0038-Clean-up-version-command-output.patch => 0037-Clean-up-version-command-output.patch} (100%) rename patches/api/{0039-Extended-OfflinePlayer-API.patch => 0038-Extended-OfflinePlayer-API.patch} (100%) rename patches/api/{0040-Added-the-ability-to-add-combustible-items.patch => 0039-Added-the-ability-to-add-combustible-items.patch} (100%) rename patches/api/{0041-Potion-NamespacedKey.patch => 0040-Potion-NamespacedKey.patch} (100%) rename patches/api/{0042-Grindstone-API.patch => 0041-Grindstone-API.patch} (100%) rename patches/api/{0043-Shears-can-have-looting-enchantment.patch => 0042-Shears-can-have-looting-enchantment.patch} (100%) rename patches/api/{0044-Lobotomize-stuck-villagers.patch => 0043-Lobotomize-stuck-villagers.patch} (100%) create mode 100644 patches/server/0273-Add-config-for-snow-on-blue-ice.patch delete mode 100644 patches/todo/0144-Add-config-for-snow-on-blue-ice.patch rename patches/{ => todo}/api/0001-Add-pufferfish-added-classes-to-junit-exemptions.patch (100%) delete mode 100644 patches/todo/pufferfish/0006-Ridables.patch delete mode 100644 patches/todo/pufferfish/0264-Lobotomize-stuck-villagers.patch diff --git a/gradle.properties b/gradle.properties index 4f72f20a1..60c461319 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ group = org.purpurmc.purpur version = 1.19-R0.1-SNAPSHOT -paperCommit = a93aa05bf8ed7d293f4ecba432491ab481eec05d +paperCommit = 29e918948a445eb0da7c2ec36a689731d9f90125 org.gradle.caching = true org.gradle.parallel = true diff --git a/patches/api/0002-Build-System-Changes.patch b/patches/api/0001-Build-System-Changes.patch similarity index 100% rename from patches/api/0002-Build-System-Changes.patch rename to patches/api/0001-Build-System-Changes.patch diff --git a/patches/api/0003-Purpur-config-files.patch b/patches/api/0002-Purpur-config-files.patch similarity index 100% rename from patches/api/0003-Purpur-config-files.patch rename to patches/api/0002-Purpur-config-files.patch diff --git a/patches/api/0004-Purpur-client-support.patch b/patches/api/0003-Purpur-client-support.patch similarity index 100% rename from patches/api/0004-Purpur-client-support.patch rename to patches/api/0003-Purpur-client-support.patch diff --git a/patches/api/0005-Default-permissions.patch b/patches/api/0004-Default-permissions.patch similarity index 100% rename from patches/api/0005-Default-permissions.patch rename to patches/api/0004-Default-permissions.patch diff --git a/patches/api/0006-Ridables.patch b/patches/api/0005-Ridables.patch similarity index 100% rename from patches/api/0006-Ridables.patch rename to patches/api/0005-Ridables.patch diff --git a/patches/api/0007-Allow-inventory-resizing.patch b/patches/api/0006-Allow-inventory-resizing.patch similarity index 100% rename from patches/api/0007-Allow-inventory-resizing.patch rename to patches/api/0006-Allow-inventory-resizing.patch diff --git a/patches/api/0008-Llama-API.patch b/patches/api/0007-Llama-API.patch similarity index 100% rename from patches/api/0008-Llama-API.patch rename to patches/api/0007-Llama-API.patch diff --git a/patches/api/0009-AFK-API.patch b/patches/api/0008-AFK-API.patch similarity index 100% rename from patches/api/0009-AFK-API.patch rename to patches/api/0008-AFK-API.patch diff --git a/patches/api/0010-Bring-back-server-name.patch b/patches/api/0009-Bring-back-server-name.patch similarity index 100% rename from patches/api/0010-Bring-back-server-name.patch rename to patches/api/0009-Bring-back-server-name.patch diff --git a/patches/api/0011-ExecuteCommandEvent.patch b/patches/api/0010-ExecuteCommandEvent.patch similarity index 100% rename from patches/api/0011-ExecuteCommandEvent.patch rename to patches/api/0010-ExecuteCommandEvent.patch diff --git a/patches/api/0012-LivingEntity-safeFallDistance.patch b/patches/api/0011-LivingEntity-safeFallDistance.patch similarity index 100% rename from patches/api/0012-LivingEntity-safeFallDistance.patch rename to patches/api/0011-LivingEntity-safeFallDistance.patch diff --git a/patches/api/0013-Lagging-threshold.patch b/patches/api/0012-Lagging-threshold.patch similarity index 100% rename from patches/api/0013-Lagging-threshold.patch rename to patches/api/0012-Lagging-threshold.patch diff --git a/patches/api/0014-PlayerSetSpawnerTypeWithEggEvent.patch b/patches/api/0013-PlayerSetSpawnerTypeWithEggEvent.patch similarity index 100% rename from patches/api/0014-PlayerSetSpawnerTypeWithEggEvent.patch rename to patches/api/0013-PlayerSetSpawnerTypeWithEggEvent.patch diff --git a/patches/api/0015-EMC-MonsterEggSpawnEvent.patch b/patches/api/0014-EMC-MonsterEggSpawnEvent.patch similarity index 100% rename from patches/api/0015-EMC-MonsterEggSpawnEvent.patch rename to patches/api/0014-EMC-MonsterEggSpawnEvent.patch diff --git a/patches/api/0016-Player-invulnerabilities.patch b/patches/api/0015-Player-invulnerabilities.patch similarity index 100% rename from patches/api/0016-Player-invulnerabilities.patch rename to patches/api/0015-Player-invulnerabilities.patch diff --git a/patches/api/0017-Anvil-API.patch b/patches/api/0016-Anvil-API.patch similarity index 100% rename from patches/api/0017-Anvil-API.patch rename to patches/api/0016-Anvil-API.patch diff --git a/patches/api/0018-ItemStack-convenience-methods.patch b/patches/api/0017-ItemStack-convenience-methods.patch similarity index 100% rename from patches/api/0018-ItemStack-convenience-methods.patch rename to patches/api/0017-ItemStack-convenience-methods.patch diff --git a/patches/api/0019-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch b/patches/api/0018-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch similarity index 100% rename from patches/api/0019-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch rename to patches/api/0018-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch diff --git a/patches/api/0020-ChatColor-conveniences.patch b/patches/api/0019-ChatColor-conveniences.patch similarity index 100% rename from patches/api/0020-ChatColor-conveniences.patch rename to patches/api/0019-ChatColor-conveniences.patch diff --git a/patches/api/0021-LivingEntity-broadcastItemBreak.patch b/patches/api/0020-LivingEntity-broadcastItemBreak.patch similarity index 100% rename from patches/api/0021-LivingEntity-broadcastItemBreak.patch rename to patches/api/0020-LivingEntity-broadcastItemBreak.patch diff --git a/patches/api/0022-Item-entity-immunities.patch b/patches/api/0021-Item-entity-immunities.patch similarity index 100% rename from patches/api/0022-Item-entity-immunities.patch rename to patches/api/0021-Item-entity-immunities.patch diff --git a/patches/api/0023-Spigot-Improve-output-of-plugins-command.patch b/patches/api/0022-Spigot-Improve-output-of-plugins-command.patch similarity index 100% rename from patches/api/0023-Spigot-Improve-output-of-plugins-command.patch rename to patches/api/0022-Spigot-Improve-output-of-plugins-command.patch diff --git a/patches/api/0024-Add-option-to-disable-zombie-aggressiveness-towards-.patch b/patches/api/0023-Add-option-to-disable-zombie-aggressiveness-towards-.patch similarity index 100% rename from patches/api/0024-Add-option-to-disable-zombie-aggressiveness-towards-.patch rename to patches/api/0023-Add-option-to-disable-zombie-aggressiveness-towards-.patch diff --git a/patches/api/0025-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch b/patches/api/0024-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch similarity index 100% rename from patches/api/0025-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch rename to patches/api/0024-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch diff --git a/patches/api/0026-Alphabetize-in-game-plugins-list.patch b/patches/api/0025-Alphabetize-in-game-plugins-list.patch similarity index 100% rename from patches/api/0026-Alphabetize-in-game-plugins-list.patch rename to patches/api/0025-Alphabetize-in-game-plugins-list.patch diff --git a/patches/api/0027-Rabid-Wolf-API.patch b/patches/api/0026-Rabid-Wolf-API.patch similarity index 100% rename from patches/api/0027-Rabid-Wolf-API.patch rename to patches/api/0026-Rabid-Wolf-API.patch diff --git a/patches/api/0028-PlayerBookTooLargeEvent.patch b/patches/api/0027-PlayerBookTooLargeEvent.patch similarity index 100% rename from patches/api/0028-PlayerBookTooLargeEvent.patch rename to patches/api/0027-PlayerBookTooLargeEvent.patch diff --git a/patches/api/0029-Full-netherite-armor-grants-fire-resistance.patch b/patches/api/0028-Full-netherite-armor-grants-fire-resistance.patch similarity index 100% rename from patches/api/0029-Full-netherite-armor-grants-fire-resistance.patch rename to patches/api/0028-Full-netherite-armor-grants-fire-resistance.patch diff --git a/patches/api/0030-Add-EntityTeleportHinderedEvent.patch b/patches/api/0029-Add-EntityTeleportHinderedEvent.patch similarity index 100% rename from patches/api/0030-Add-EntityTeleportHinderedEvent.patch rename to patches/api/0029-Add-EntityTeleportHinderedEvent.patch diff --git a/patches/api/0031-Add-enchantment-target-for-bows-and-crossbows.patch b/patches/api/0030-Add-enchantment-target-for-bows-and-crossbows.patch similarity index 100% rename from patches/api/0031-Add-enchantment-target-for-bows-and-crossbows.patch rename to patches/api/0030-Add-enchantment-target-for-bows-and-crossbows.patch diff --git a/patches/api/0032-Iron-golem-poppy-calms-anger.patch b/patches/api/0031-Iron-golem-poppy-calms-anger.patch similarity index 100% rename from patches/api/0032-Iron-golem-poppy-calms-anger.patch rename to patches/api/0031-Iron-golem-poppy-calms-anger.patch diff --git a/patches/api/0033-API-for-any-mob-to-burn-daylight.patch b/patches/api/0032-API-for-any-mob-to-burn-daylight.patch similarity index 100% rename from patches/api/0033-API-for-any-mob-to-burn-daylight.patch rename to patches/api/0032-API-for-any-mob-to-burn-daylight.patch diff --git a/patches/api/0034-Flying-Fall-Damage-API.patch b/patches/api/0033-Flying-Fall-Damage-API.patch similarity index 100% rename from patches/api/0034-Flying-Fall-Damage-API.patch rename to patches/api/0033-Flying-Fall-Damage-API.patch diff --git a/patches/api/0035-Add-back-player-spawned-endermite-API.patch b/patches/api/0034-Add-back-player-spawned-endermite-API.patch similarity index 100% rename from patches/api/0035-Add-back-player-spawned-endermite-API.patch rename to patches/api/0034-Add-back-player-spawned-endermite-API.patch diff --git a/patches/api/0036-Fix-default-permission-system.patch b/patches/api/0035-Fix-default-permission-system.patch similarity index 100% rename from patches/api/0036-Fix-default-permission-system.patch rename to patches/api/0035-Fix-default-permission-system.patch diff --git a/patches/api/0037-Summoner-API.patch b/patches/api/0036-Summoner-API.patch similarity index 100% rename from patches/api/0037-Summoner-API.patch rename to patches/api/0036-Summoner-API.patch diff --git a/patches/api/0038-Clean-up-version-command-output.patch b/patches/api/0037-Clean-up-version-command-output.patch similarity index 100% rename from patches/api/0038-Clean-up-version-command-output.patch rename to patches/api/0037-Clean-up-version-command-output.patch diff --git a/patches/api/0039-Extended-OfflinePlayer-API.patch b/patches/api/0038-Extended-OfflinePlayer-API.patch similarity index 100% rename from patches/api/0039-Extended-OfflinePlayer-API.patch rename to patches/api/0038-Extended-OfflinePlayer-API.patch diff --git a/patches/api/0040-Added-the-ability-to-add-combustible-items.patch b/patches/api/0039-Added-the-ability-to-add-combustible-items.patch similarity index 100% rename from patches/api/0040-Added-the-ability-to-add-combustible-items.patch rename to patches/api/0039-Added-the-ability-to-add-combustible-items.patch diff --git a/patches/api/0041-Potion-NamespacedKey.patch b/patches/api/0040-Potion-NamespacedKey.patch similarity index 100% rename from patches/api/0041-Potion-NamespacedKey.patch rename to patches/api/0040-Potion-NamespacedKey.patch diff --git a/patches/api/0042-Grindstone-API.patch b/patches/api/0041-Grindstone-API.patch similarity index 100% rename from patches/api/0042-Grindstone-API.patch rename to patches/api/0041-Grindstone-API.patch diff --git a/patches/api/0043-Shears-can-have-looting-enchantment.patch b/patches/api/0042-Shears-can-have-looting-enchantment.patch similarity index 100% rename from patches/api/0043-Shears-can-have-looting-enchantment.patch rename to patches/api/0042-Shears-can-have-looting-enchantment.patch diff --git a/patches/api/0044-Lobotomize-stuck-villagers.patch b/patches/api/0043-Lobotomize-stuck-villagers.patch similarity index 100% rename from patches/api/0044-Lobotomize-stuck-villagers.patch rename to patches/api/0043-Lobotomize-stuck-villagers.patch diff --git a/patches/server/0273-Add-config-for-snow-on-blue-ice.patch b/patches/server/0273-Add-config-for-snow-on-blue-ice.patch new file mode 100644 index 000000000..790e1f456 --- /dev/null +++ b/patches/server/0273-Add-config-for-snow-on-blue-ice.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 8 Jun 2022 15:19:41 -0400 +Subject: [PATCH] Add config for snow on blue ice + + +diff --git a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java +index 14e00c7feb1c051d56a3d27cd00dcef072dd771a..4952fb1aaaafb55baa0fddb389f966a120a4786c 100644 +--- a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java +@@ -81,6 +81,12 @@ public class SnowLayerBlock extends Block { + public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { + BlockState iblockdata1 = world.getBlockState(pos.below()); + ++ // Purpur start ++ if (iblockdata1.is(Blocks.BLUE_ICE) && !world.getWorldBorder().world.purpurConfig.snowOnBlueIce) { ++ return false; ++ } ++ // Purpur end ++ + return iblockdata1.is(BlockTags.SNOW_LAYER_CANNOT_SURVIVE_ON) ? false : (iblockdata1.is(BlockTags.SNOW_LAYER_CAN_SURVIVE_ON) ? true : Block.isFaceFull(iblockdata1.getCollisionShape(world, pos.below()), Direction.UP) || iblockdata1.is((Block) this) && (Integer) iblockdata1.getValue(SnowLayerBlock.LAYERS) == 8); + } + +diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java +index 322eecc6688df6c3c56d88632993ad7f960156c1..e4dd5b270441276c18d8d7578f8ad8519278fafd 100644 +--- a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java ++++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java +@@ -878,9 +878,11 @@ public class PurpurWorldConfig { + + 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 kelpMaxGrowthAge = 25; diff --git a/patches/todo/0144-Add-config-for-snow-on-blue-ice.patch b/patches/todo/0144-Add-config-for-snow-on-blue-ice.patch deleted file mode 100644 index 70dc2427e..000000000 --- a/patches/todo/0144-Add-config-for-snow-on-blue-ice.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Fri, 5 Mar 2021 17:59:05 -0600 -Subject: [PATCH] Add config for snow on blue ice - - -diff --git a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -index fbbb0155bd66f1daa160e03f866741d903e1869e..86f7b4117b8b9bd36e38962e3290f7b909ed9b29 100644 ---- a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -@@ -75,7 +75,18 @@ public class SnowLayerBlock extends Block { - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - BlockState iblockdata1 = world.getBlockState(pos.below()); - -- return !iblockdata1.is(Blocks.ICE) && !iblockdata1.is(Blocks.PACKED_ICE) && !iblockdata1.is(Blocks.BARRIER) ? (!iblockdata1.is(Blocks.HONEY_BLOCK) && !iblockdata1.is(Blocks.SOUL_SAND) ? Block.isFaceFull(iblockdata1.getCollisionShape(world, pos.below()), Direction.UP) || iblockdata1.is((Block) this) && (Integer) iblockdata1.getValue(SnowLayerBlock.LAYERS) == 8 : true) : false; -+ // Purpur start - rewrite this whole return to make more sense -+ if (iblockdata1.is(Blocks.ICE) || iblockdata1.is(Blocks.PACKED_ICE) || iblockdata1.is(Blocks.BARRIER)) { -+ return false; -+ } -+ if (iblockdata1.is(Blocks.BLUE_ICE) && !world.getWorldBorder().world.purpurConfig.snowOnBlueIce) { -+ return false; -+ } -+ if (iblockdata1.is(Blocks.HONEY_BLOCK) || iblockdata1.is(Blocks.SOUL_SAND)) { -+ return true; -+ } -+ return Block.isFaceFull(iblockdata1.getCollisionShape(world, pos.below()), Direction.UP) || iblockdata1.is(this) && iblockdata1.getValue(LAYERS) == 8; -+ // Purpur end - } - - @Override -diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -index eee1d4fd7901e341908ff1b6bf0e5d5c6df3e428..df0397ea8bc4396ede6b357577677b9ae1c2a557 100644 ---- a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -@@ -512,6 +512,11 @@ public class PurpurWorldConfig { - furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.use-lava-from-underneath", furnaceUseLavaFromUnderneath); - } - -+ public boolean snowOnBlueIce = true; -+ private void iceSettings() { -+ snowOnBlueIce = getBoolean("blocks.blue_ice.allow-snow-formation", snowOnBlueIce); -+ } -+ - public boolean lavaInfinite = false; - public int lavaInfiniteRequiredSources = 2; - public int lavaSpeedNether = 10; diff --git a/patches/api/0001-Add-pufferfish-added-classes-to-junit-exemptions.patch b/patches/todo/api/0001-Add-pufferfish-added-classes-to-junit-exemptions.patch similarity index 100% rename from patches/api/0001-Add-pufferfish-added-classes-to-junit-exemptions.patch rename to patches/todo/api/0001-Add-pufferfish-added-classes-to-junit-exemptions.patch diff --git a/patches/todo/pufferfish/0006-Ridables.patch b/patches/todo/pufferfish/0006-Ridables.patch deleted file mode 100644 index ea26220e0..000000000 --- a/patches/todo/pufferfish/0006-Ridables.patch +++ /dev/null @@ -1,6344 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 5 Jul 2020 22:19:49 -0500 -Subject: [PATCH] Ridables - - -diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index 2808acafaa0e3b448e75c40c04ba932ef9723da9..fda35ae6da0b4afbff90a9356ef63dd38c8a0d95 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -41,6 +41,12 @@ public class BlockPos extends Vec3i { - private static final int X_OFFSET = 38; - // Paper end - -+ // Purpur start -+ public BlockPos(net.minecraft.world.entity.Entity entity) { -+ super(entity.getX(), entity.getY(), entity.getZ()); -+ } -+ // Purpur end -+ - public BlockPos(int x, int y, int z) { - super(x, y, z); - } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3cd0a4f4fc1ac3bc935368db0340e14b495942a0..4a4e138b232dfa23bdab81fd93e3d580e6067f62 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1575,6 +1575,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper -+ worldserver.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - - this.profiler.push(() -> { -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 93c1f9e3e9d62cc993fedb93fe43cbeed7d39542..8066a3372d21823357942771adc0b4768f505ea2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -213,6 +213,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public boolean hasPhysicsEvent = true; // Paper - public boolean hasEntityMoveEvent = false; // Paper - private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) -+ public boolean hasRidableMoveEvent = false; // Purpur - public static Throwable getAddToWorldStackTrace(Entity entity) { - return new Throwable(entity + " Added to world at " + new java.util.Date()); - } -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 23ef3a12dcd184edeaad3f939d2a9cbffa7fd35e..923a0afad1b2b14941c94332b373a704c32160a8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -661,6 +661,15 @@ public class ServerPlayer extends Player { - this.trackStartFallingPosition(); - this.trackEnteredOrExitedLavaOnVehicle(); - this.advancements.flushDirty(this); -+ -+ // Purpur start -+ 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 - } - - public void doTick() { -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index c95b2547fc32ff36c7a9915aea645be3ebb0f9e4..57ea6f010b38ef3f9f7f349b56e1c948b5f835a3 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2476,6 +2476,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - - ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); - -+ player.processClick(enumhand); // Purpur -+ - // Entity in bucket - SPIGOT-4048 and SPIGOT-6859 - if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { - ServerGamePacketListenerImpl.this.send(new ClientboundAddMobPacket((LivingEntity) entity)); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index e7bbd01ad8d070979fb2066db1b06068ef10b56d..8dda017467fb9a41fb1ca6a3bdd4cc31e23dd848 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -300,7 +300,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - private final Set tags; - private final double[] pistonDeltas; - private long pistonDeltasGameTime; -- private EntityDimensions dimensions; -+ protected EntityDimensions dimensions; // Purpur - private -> protected - private float eyeHeight; - public boolean isInPowderSnow; - public boolean wasInPowderSnow; -@@ -2673,6 +2673,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - this.passengers = ImmutableList.copyOf(list); - } - -+ // Purpur start -+ if (isRidable() && this.passengers.get(0) == entity && entity instanceof Player player) { -+ onMount(player); -+ this.rider = player; -+ } -+ // Purpur end - } - return true; // CraftBukkit - } -@@ -2713,6 +2719,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - return false; - } - // Spigot end -+ -+ // Purpur start -+ if (this.rider != null && this.passengers.get(0) == this.rider) { -+ onDismount(this.rider); -+ this.rider = null; -+ } -+ // Purpur end -+ - if (this.passengers.size() == 1 && this.passengers.get(0) == entity) { - this.passengers = ImmutableList.of(); - } else { -@@ -4441,4 +4455,45 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - return ((ServerChunkCache) level.getChunkSource()).isPositionTicking(this); - } - // Paper end -+ -+ // Purpur start -+ @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 - } -diff --git a/src/main/java/net/minecraft/world/entity/GlowSquid.java b/src/main/java/net/minecraft/world/entity/GlowSquid.java -index e82a9d41a0710c0bd8a3fa2f8ee1704f7e1e56df..2ce9b08535cf3783e6823de3489433003a1aae0d 100644 ---- a/src/main/java/net/minecraft/world/entity/GlowSquid.java -+++ b/src/main/java/net/minecraft/world/entity/GlowSquid.java -@@ -23,6 +23,23 @@ public class GlowSquid extends Squid { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.glowSquidRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.glowSquidControllable; -+ } -+ // Purpur end -+ - @Override - protected ParticleOptions getInkParticle() { - return ParticleTypes.GLOW_SQUID_INK; -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 33fbb85228f1570e40f2a825489362626bea3912..fa4abf237820665ce25cebc6ecaeef2940bf33c7 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -219,9 +219,9 @@ public abstract class LivingEntity extends Entity { - protected int deathScore; - 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; -@@ -285,7 +285,7 @@ public abstract class LivingEntity extends Entity { - this.effectsDirty = true; - this.useItem = ItemStack.EMPTY; - this.lastClimbablePos = Optional.empty(); -- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type)); -+ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type), this); // Purpur - this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit - // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor - this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue()); -@@ -336,6 +336,7 @@ public abstract class LivingEntity extends Entity { - public static AttributeSupplier.Builder createLivingAttributes() { - return AttributeSupplier.builder().add(Attributes.MAX_HEALTH).add(Attributes.KNOCKBACK_RESISTANCE).add(Attributes.MOVEMENT_SPEED).add(Attributes.ARMOR).add(Attributes.ARMOR_TOUGHNESS); - } -+ public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur - - @Override - protected void checkFallDamage(double heightDifference, boolean onGround, BlockState landedState, BlockPos landedPosition) { -@@ -2616,7 +2617,7 @@ public abstract class LivingEntity extends Entity { - } - - protected long lastJumpTime = 0L; // Paper -- protected void jumpFromGround() { -+ public void jumpFromGround() { // Purpur - protected -> public - double d0 = (double) this.getJumpPower() + this.getJumpBoostPower(); - Vec3 vec3d = this.getDeltaMovement(); - // Paper start -@@ -3366,8 +3367,10 @@ public abstract class LivingEntity extends Entity { - this.pushEntities(); - this.level.getProfiler().pop(); - // Paper start -- if (((ServerLevel) this.level).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { -- if (this.xo != getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -+ // Purpur start -+ 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 net.minecraft.world.entity.player.Player)) { -+ // Purpur end - 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()); -@@ -3377,6 +3380,21 @@ public abstract class LivingEntity extends Entity { - absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); - } - } -+ // Purpur start -+ 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(), (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 - } - // Paper end - if (!this.level.isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 8ee0a80e3427ecef9fa15b7388a8287e26a25fd6..f72b0defbc25ec8efc7346e56a6d1e68aac5a902 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -141,8 +141,8 @@ public abstract class Mob extends LivingEntity { - this.restrictRadius = -1.0F; - this.goalSelector = new GoalSelector(world.getProfilerSupplier()); - this.targetSelector = new GoalSelector(world.getProfilerSupplier()); -- this.lookControl = new LookControl(this); -- this.moveControl = new MoveControl(this); -+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur -+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur - this.jumpControl = new JumpControl(this); - this.bodyRotationControl = this.createBodyControl(); - this.navigation = this.createNavigation(world); -@@ -1309,7 +1309,7 @@ public abstract class Mob extends LivingEntity { - protected void onOffspringSpawnedFromEgg(Player player, Mob child) {} - - protected InteractionResult mobInteract(Player player, InteractionHand hand) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } - - public boolean isWithinRestriction() { -@@ -1670,4 +1670,52 @@ public abstract class Mob extends LivingEntity { - - return itemmonsteregg == null ? null : new ItemStack(itemmonsteregg); - } -+ -+ // Purpur start -+ public double getMaxY() { -+ return level.getHeight(); -+ } -+ -+ public InteractionResult tryRide(Player player, InteractionHand hand) { -+ if (!isRidable()) { -+ return InteractionResult.PASS; -+ } -+ 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." + getType().id)) { -+ player.sendMessage(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 - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 9bce290eb0c2cfef4896a3f2076c80bf3d76bd56..ddaa0050ef78597072634a212d845e1817497fe5 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -23,14 +23,21 @@ public class AttributeMap { - private final Set dirtyAttributes = Sets.newHashSet(); - private final AttributeSupplier supplier; - private final java.util.function.Function createInstance; // Pufferfish -+ private final net.minecraft.world.entity.LivingEntity entity; // Purpur - - public AttributeMap(AttributeSupplier defaultAttributes) { -+ // Purpur start -+ this(defaultAttributes, null); -+ } -+ public AttributeMap(AttributeSupplier defaultAttributes, net.minecraft.world.entity.LivingEntity entity) { -+ this.entity = entity; -+ // Purpur end - this.supplier = defaultAttributes; - this.createInstance = attribute -> this.supplier.createInstance(this::onAttributeModified, attribute); // Pufferfish - } - - private void onAttributeModified(AttributeInstance instance) { -- if (instance.getAttribute().isClientSyncable()) { -+ if (instance.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute()))) { // Purpur - this.dirtyAttributes.add(instance); - } - -@@ -42,7 +49,7 @@ public class AttributeMap { - - public Collection getSyncableAttributes() { - return this.attributes.values().stream().filter((attribute) -> { -- return attribute.getAttribute().isClientSyncable(); -+ return attribute.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(attribute.getAttribute())); // Purpur - }).collect(Collectors.toList()); - } - -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java -index 8e93595515aed7fb69a065ffd97d34d3083f5e52..8ad5fecd7bcc6737b0f90f32e9d67e489a02ef03 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java -@@ -74,7 +74,86 @@ import org.slf4j.Logger; - - public class DefaultAttributes { - private static final Logger LOGGER = LogUtils.getLogger(); -- private static final Map, AttributeSupplier> SUPPLIERS = ImmutableMap., AttributeSupplier>builder().put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build()).put(EntityType.AXOLOTL, Axolotl.createAttributes().build()).put(EntityType.BAT, Bat.createAttributes().build()).put(EntityType.BEE, Bee.createAttributes().build()).put(EntityType.BLAZE, Blaze.createAttributes().build()).put(EntityType.CAT, Cat.createAttributes().build()).put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build()).put(EntityType.CHICKEN, Chicken.createAttributes().build()).put(EntityType.COD, AbstractFish.createAttributes().build()).put(EntityType.COW, Cow.createAttributes().build()).put(EntityType.CREEPER, Creeper.createAttributes().build()).put(EntityType.DOLPHIN, Dolphin.createAttributes().build()).put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).put(EntityType.DROWNED, Zombie.createAttributes().build()).put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build()).put(EntityType.ENDERMAN, EnderMan.createAttributes().build()).put(EntityType.ENDERMITE, Endermite.createAttributes().build()).put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build()).put(EntityType.EVOKER, Evoker.createAttributes().build()).put(EntityType.FOX, Fox.createAttributes().build()).put(EntityType.GHAST, Ghast.createAttributes().build()).put(EntityType.GIANT, Giant.createAttributes().build()).put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build()).put(EntityType.GOAT, Goat.createAttributes().build()).put(EntityType.GUARDIAN, Guardian.createAttributes().build()).put(EntityType.HOGLIN, Hoglin.createAttributes().build()).put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build()).put(EntityType.HUSK, Zombie.createAttributes().build()).put(EntityType.ILLUSIONER, Illusioner.createAttributes().build()).put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build()).put(EntityType.LLAMA, Llama.createAttributes().build()).put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build()).put(EntityType.MOOSHROOM, Cow.createAttributes().build()).put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).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.PIG, Pig.createAttributes().build()).put(EntityType.PIGLIN, Piglin.createAttributes().build()).put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()).put(EntityType.PILLAGER, Pillager.createAttributes().build()).put(EntityType.PLAYER, Player.createAttributes().build()).put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build()).put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build()).put(EntityType.RABBIT, Rabbit.createAttributes().build()).put(EntityType.RAVAGER, Ravager.createAttributes().build()).put(EntityType.SALMON, AbstractFish.createAttributes().build()).put(EntityType.SHEEP, Sheep.createAttributes().build()).put(EntityType.SHULKER, Shulker.createAttributes().build()).put(EntityType.SILVERFISH, Silverfish.createAttributes().build()).put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build()).put(EntityType.SLIME, Monster.createMonsterAttributes().build()).put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build()).put(EntityType.SPIDER, Spider.createAttributes().build()).put(EntityType.SQUID, Squid.createAttributes().build()).put(EntityType.STRAY, AbstractSkeleton.createAttributes().build()).put(EntityType.STRIDER, Strider.createAttributes().build()).put(EntityType.TRADER_LLAMA, Llama.createAttributes().build()).put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build()).put(EntityType.TURTLE, Turtle.createAttributes().build()).put(EntityType.VEX, Vex.createAttributes().build()).put(EntityType.VILLAGER, Villager.createAttributes().build()).put(EntityType.VINDICATOR, Vindicator.createAttributes().build()).put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()).put(EntityType.WITCH, Witch.createAttributes().build()).put(EntityType.WITHER, WitherBoss.createAttributes().build()).put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.WOLF, Wolf.createAttributes().build()).put(EntityType.ZOGLIN, Zoglin.createAttributes().build()).put(EntityType.ZOMBIE, Zombie.createAttributes().build()).put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build()).put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build()).put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()).build(); -+ -+ // Purpur start -+ private static final Map, AttributeSupplier> SUPPLIERS = ImmutableMap., AttributeSupplier>builder() -+ .put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build()) -+ .put(EntityType.AXOLOTL, Axolotl.createAttributes().build()) -+ .put(EntityType.BAT, Bat.createAttributes().build()) -+ .put(EntityType.BEE, Bee.createAttributes().build()) -+ .put(EntityType.BLAZE, Blaze.createAttributes().build()) -+ .put(EntityType.CAT, Cat.createAttributes().build()) -+ .put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build()) -+ .put(EntityType.CHICKEN, Chicken.createAttributes().build()) -+ .put(EntityType.COD, AbstractFish.createAttributes().build()) -+ .put(EntityType.COW, Cow.createAttributes().build()) -+ .put(EntityType.CREEPER, Creeper.createAttributes().build()) -+ .put(EntityType.DOLPHIN, Dolphin.createAttributes().build()) -+ .put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build()) -+ .put(EntityType.DROWNED, Zombie.createAttributes().build()) -+ .put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build()) -+ .put(EntityType.ENDERMAN, EnderMan.createAttributes().build()) -+ .put(EntityType.ENDERMITE, Endermite.createAttributes().build()) -+ .put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build()) -+ .put(EntityType.EVOKER, Evoker.createAttributes().build()) -+ .put(EntityType.FOX, Fox.createAttributes().build()) -+ .put(EntityType.GHAST, Ghast.createAttributes().build()) -+ .put(EntityType.GIANT, Giant.createAttributes().build()) -+ .put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build()) -+ .put(EntityType.GOAT, Goat.createAttributes().build()) -+ .put(EntityType.GUARDIAN, Guardian.createAttributes().build()) -+ .put(EntityType.HOGLIN, Hoglin.createAttributes().build()) -+ .put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build()) -+ .put(EntityType.HUSK, Zombie.createAttributes().build()) -+ .put(EntityType.ILLUSIONER, Illusioner.createAttributes().build()) -+ .put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build()) -+ .put(EntityType.LLAMA, Llama.createAttributes().build()) -+ .put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build()) -+ .put(EntityType.MOOSHROOM, Cow.createAttributes().build()) -+ .put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build()) -+ .put(EntityType.OCELOT, Ocelot.createAttributes().build()) -+ .put(EntityType.PANDA, Panda.createAttributes().build()) -+ .put(EntityType.PARROT, Parrot.createAttributes().build()) -+ .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur -+ .put(EntityType.PIG, Pig.createAttributes().build()) -+ .put(EntityType.PIGLIN, Piglin.createAttributes().build()) -+ .put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()) -+ .put(EntityType.PILLAGER, Pillager.createAttributes().build()) -+ .put(EntityType.PLAYER, Player.createAttributes().build()) -+ .put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build()) -+ .put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build()) -+ .put(EntityType.RABBIT, Rabbit.createAttributes().build()) -+ .put(EntityType.RAVAGER, Ravager.createAttributes().build()) -+ .put(EntityType.SALMON, AbstractFish.createAttributes().build()) -+ .put(EntityType.SHEEP, Sheep.createAttributes().build()) -+ .put(EntityType.SHULKER, Shulker.createAttributes().build()) -+ .put(EntityType.SILVERFISH, Silverfish.createAttributes().build()) -+ .put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build()) -+ .put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build()) -+ .put(EntityType.SLIME, Monster.createMonsterAttributes().build()) -+ .put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build()) -+ .put(EntityType.SPIDER, Spider.createAttributes().build()) -+ .put(EntityType.SQUID, Squid.createAttributes().build()) -+ .put(EntityType.STRAY, AbstractSkeleton.createAttributes().build()) -+ .put(EntityType.STRIDER, Strider.createAttributes().build()) -+ .put(EntityType.TRADER_LLAMA, Llama.createAttributes().build()) -+ .put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build()) -+ .put(EntityType.TURTLE, Turtle.createAttributes().build()) -+ .put(EntityType.VEX, Vex.createAttributes().build()) -+ .put(EntityType.VILLAGER, Villager.createAttributes().build()) -+ .put(EntityType.VINDICATOR, Vindicator.createAttributes().build()) -+ .put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()) -+ .put(EntityType.WITCH, Witch.createAttributes().build()) -+ .put(EntityType.WITHER, WitherBoss.createAttributes().build()) -+ .put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()) -+ .put(EntityType.WOLF, Wolf.createAttributes().build()) -+ .put(EntityType.ZOGLIN, Zoglin.createAttributes().build()) -+ .put(EntityType.ZOMBIE, Zombie.createAttributes().build()) -+ .put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build()) -+ .put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build()) -+ .put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()) -+ .build(); -+ // Purpur end - - public static AttributeSupplier getSupplier(EntityType type) { - return SUPPLIERS.get(type); -diff --git a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java -index e3028cf5f88da6ab6d6d762e8011d45fd1d8eff8..5af8786692d3e978b3cb939d0a24bf7e24b149b5 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java -+++ b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java -@@ -29,6 +29,20 @@ public class MoveControl implements Control { - this.mob = entity; - } - -+ // Purpur start -+ 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 -+ - public boolean hasWanted() { - return this.operation == MoveControl.Operation.MOVE_TO; - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java -index 7df56705a4a0de2dc4ff7ab133fc26612c219162..60d21d6171b9af20a4c6fcc0d564a31aaa4ecdba 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java -+++ b/src/main/java/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 - 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 - if (this.lookAtCooldown > 0) { - --this.lookAtCooldown; - this.getYRotD().ifPresent((yaw) -> { -@@ -32,9 +32,9 @@ public class SmoothSwimmingLookControl extends LookControl { - } - - float f = Mth.wrapDegrees(this.mob.yHeadRot - this.mob.yBodyRot); -- if (f < (float)(-this.maxYRotFromCenter)) { -+ if (f < (float) (-this.maxYRotFromCenter)) { - this.mob.yBodyRot -= 4.0F; -- } else if (f > (float)this.maxYRotFromCenter) { -+ } else if (f > (float) this.maxYRotFromCenter) { - this.mob.yBodyRot += 4.0F; - } - -diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -index f11eb6df9f095109378caaf0fd16575bfb2e4976..ca053df9d4d9120c7bd926b3d2d511b71332b5df 100644 ---- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java -+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -@@ -18,6 +18,7 @@ import net.minecraft.world.entity.EntityDimensions; - import net.minecraft.world.entity.EntityType; - import net.minecraft.world.entity.Mob; - import net.minecraft.world.entity.MobSpawnType; -+import net.minecraft.world.entity.MoverType; - import net.minecraft.world.entity.Pose; - import net.minecraft.world.entity.ai.attributes.AttributeSupplier; - import net.minecraft.world.entity.ai.attributes.Attributes; -@@ -41,9 +42,56 @@ public class Bat extends AmbientCreature { - - public Bat(EntityType type, Level world) { - super(type, world); -+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.075F); // Purpur - this.setResting(true); - } - -+ // Purpur start -+ @Override -+ public boolean shouldSendAttribute(net.minecraft.world.entity.ai.attributes.Attribute attribute) { return attribute != Attributes.FLYING_SPEED; } // Fixes log spam on clients -+ -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.batRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.batRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.batControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level.purpurConfig.batMaxY; -+ } -+ -+ @Override -+ public void onMount(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(MoverType.SELF, mot.multiply(speed, 0.25, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ // Purpur end -+ - @Override - public boolean isFlapping() { - return !this.isResting() && this.tickCount % Bat.TICKS_PER_FLAP == 0; -@@ -93,7 +141,7 @@ public class Bat extends AmbientCreature { - protected void pushEntities() {} - - public static AttributeSupplier.Builder createAttributes() { -- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D); -+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - } - - public boolean isResting() { -@@ -125,6 +173,14 @@ public class Bat extends AmbientCreature { - - @Override - protected void customServerAiStep() { -+ // Purpur start -+ if (getRider() != null && this.isControllable()) { -+ Vec3 mot = getDeltaMovement(); -+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z()); -+ return; -+ } -+ // Purpur end -+ - super.customServerAiStep(); - BlockPos blockposition = this.blockPosition(); - BlockPos blockposition1 = blockposition.above(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java -index 1f85f34c1e50f34fb270d2fac7d307c82a550bfa..324f52edd95b5f9a498e46def8c14435cfd00abb 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java -@@ -94,7 +94,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - @Override - protected void registerGoals() { - super.registerGoals(); -- this.goalSelector.addGoal(0, new PanicGoal(this, 1.25D)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 1.6D, 1.4D, EntitySelector.NO_SPECTATORS::test)); - this.goalSelector.addGoal(4, new AbstractFish.FishSwimGoal(this)); - } -@@ -107,7 +107,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - @Override - public void travel(Vec3 movementInput) { - if (this.isEffectiveAi() && this.isInWater()) { -- this.moveRelative(0.01F, movementInput); -+ this.moveRelative(getRider() != null ? getSpeed() : 0.01F, movementInput); // Purpur - this.move(MoverType.SELF, this.getDeltaMovement()); - this.setDeltaMovement(this.getDeltaMovement().scale(0.9D)); - if (this.getTarget() == null) { -@@ -166,7 +166,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - protected void playStepSound(BlockPos pos, BlockState state) { - } - -- static class FishMoveControl extends MoveControl { -+ static class FishMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - private final AbstractFish fish; - - FishMoveControl(AbstractFish owner) { -@@ -174,14 +174,22 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - this.fish = owner; - } - -+ // Purpur start - @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 -+ -+ @Override -+ public void vanillaTick() { // Purpur - if (this.fish.isEyeInFluid(FluidTags.WATER)) { - this.fish.setDeltaMovement(this.fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); - } - - 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 - this.fish.setSpeed(Mth.lerp(0.125F, this.fish.getSpeed(), f)); - double d = this.wantedX - this.fish.getX(); - double e = this.wantedY - this.fish.getY(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index 79a9fa2a2257925685e45329365d2828ccd4c48c..793e7c659af32e8bdc07bd7ecfa31fb5d302ad22 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -42,6 +42,7 @@ import net.minecraft.world.entity.EntityType; - import net.minecraft.world.entity.LivingEntity; - import net.minecraft.world.entity.Mob; - import net.minecraft.world.entity.MobType; -+import net.minecraft.world.entity.MoverType; - import net.minecraft.world.entity.NeutralMob; - import net.minecraft.world.entity.PathfinderMob; - import net.minecraft.world.entity.Pose; -@@ -142,6 +143,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - public Bee(EntityType type, Level world) { - super(type, world); - this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); -+ final org.purpurmc.purpur.controller.FlyingMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.25F, false); // Purpur - // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279 - class BeeFlyingMoveControl extends FlyingMoveControl { - public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { -@@ -150,11 +152,24 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - - @Override - public void tick() { -+ // Purpur start -+ if (mob.getRider() != null && mob.isControllable()) { -+ flyingController.purpurTick(mob.getRider()); -+ return; -+ } -+ // Purpur end - if (this.mob.getY() <= Bee.this.level.getMinBuildHeight()) { - this.mob.setNoGravity(false); - } - super.tick(); - } -+ -+ // Purpur start -+ @Override -+ public boolean hasWanted() { -+ return mob.getRider() != null || !mob.isControllable() || super.hasWanted(); -+ } -+ // Purpur end - } - this.moveControl = new BeeFlyingMoveControl(this, 20, true); - // Paper end -@@ -166,6 +181,40 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - this.setPathfindingMalus(BlockPathTypes.FENCE, -1.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.beeRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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(MoverType.SELF, mot.multiply(speed, speed, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ // Purpur end -+ - @Override - protected void defineSynchedData() { - super.defineSynchedData(); -@@ -180,6 +229,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 - this.goalSelector.addGoal(0, new Bee.BeeAttackGoal(this, 1.399999976158142D, true)); - this.goalSelector.addGoal(1, new Bee.BeeEnterHiveGoal()); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); -@@ -195,6 +245,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 - 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)); -@@ -879,16 +930,16 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - } - } - -- private class BeeLookControl extends LookControl { -+ private class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - - BeeLookControl(Mob entity) { - super(entity); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (!Bee.this.isAngry()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java -index 9f5180271ca8a790aa52763ac46d31b905c9d477..2edd9efb8bf71071807ac1ff5b58bc4b555ecc6b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java -@@ -124,6 +124,31 @@ public class Cat extends TamableAnimal { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.catRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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 -+ - public ResourceLocation getResourceLocation() { - return (ResourceLocation) Cat.TEXTURE_BY_TYPE.getOrDefault(this.getCatType(), (ResourceLocation) Cat.TEXTURE_BY_TYPE.get(0)); - } -@@ -132,6 +157,7 @@ public class Cat extends TamableAnimal { - protected void registerGoals() { - this.temptGoal = new Cat.CatTemptGoal(this, 0.6D, Cat.TEMPT_INGREDIENT, true); - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new SitWhenOrderedToGoal(this)); - this.goalSelector.addGoal(2, new Cat.CatRelaxOnOwnerGoal(this)); - this.goalSelector.addGoal(3, this.temptGoal); -@@ -143,6 +169,7 @@ public class Cat extends TamableAnimal { - this.goalSelector.addGoal(10, new BreedGoal(this, 0.8D)); - this.goalSelector.addGoal(11, new WaterAvoidingRandomStrollGoal(this, 0.8D, 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 - this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Rabbit.class, false, (Predicate) null)); - this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR)); - } -@@ -404,6 +431,7 @@ public class Cat extends TamableAnimal { - - @Override - public InteractionResult mobInteract(Player player, InteractionHand hand) { -+ if (getRider() != null) return InteractionResult.PASS; // Purpur - ItemStack itemstack = player.getItemInHand(hand); - Item item = itemstack.getItem(); - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Chicken.java b/src/main/java/net/minecraft/world/entity/animal/Chicken.java -index a23568df8d6a700f8a21cda8f0c04742ccf2b837..ddb199c003d07aa0172c09de32e82337baab64bf 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Chicken.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Chicken.java -@@ -52,9 +52,27 @@ public class Chicken extends Animal { - this.setPathfindingMalus(BlockPathTypes.WATER, 0.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.chickenRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.chickenRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.chickenControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D)); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new TemptGoal(this, 1.0D, Chicken.FOOD_ITEMS, false)); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cod.java b/src/main/java/net/minecraft/world/entity/animal/Cod.java -index 824e5e4fe7619ae46061c3c978c9a044db8c84ab..545ef4ab037b3173cc7377ad8147a44a2050d56f 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cod.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cod.java -@@ -13,6 +13,23 @@ public class Cod extends AbstractSchoolingFish { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.codRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.codControllable; -+ } -+ // Purpur end -+ - @Override - public ItemStack getBucketItemStack() { - return new ItemStack(Items.COD_BUCKET); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cow.java b/src/main/java/net/minecraft/world/entity/animal/Cow.java -index 425c6da0de40983b0870c9fd1b53f16b6a11c34c..8f046fb76b6967e72abde523ffb790e0f46b80e0 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java -@@ -40,9 +40,27 @@ public class Cow extends Animal { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.cowRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.cowRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.cowControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new PanicGoal(this, 2.0D)); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.of(Items.WHEAT), false)); -@@ -83,6 +101,7 @@ public class Cow extends Animal { - - @Override - public InteractionResult mobInteract(Player player, InteractionHand hand) { -+ if (getRider() != null) return InteractionResult.PASS; // Purpur - ItemStack itemstack = player.getItemInHand(hand); - - if (itemstack.is(Items.BUCKET) && !this.isBaby()) { -@@ -90,7 +109,7 @@ public class Cow extends Animal { - org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level, player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand); // Paper - add enumHand - - if (event.isCancelled()) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } - // CraftBukkit end - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -index b2baf0741691c9942e0cb7c161ccba528c2f6d4e..4a535ea98a9a786eeebe26c21b86a8c3286b57f3 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -@@ -78,14 +78,87 @@ public class Dolphin extends WaterAnimal { - public static final Predicate ALLOWED_ITEMS = (entityitem) -> { - return !entityitem.hasPickUpDelay() && entityitem.isAlive() && entityitem.isInWater(); - }; -+ private int spitCooldown; // Purpur - - public Dolphin(EntityType type, Level world) { - super(type, world); -- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); -+ // Purpur start -+ 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 - this.lookControl = new SmoothSwimmingLookControl(this, 10); - this.setCanPickUpLoot(true); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.dolphinRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @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.DolphinSpit spit = new org.purpurmc.purpur.entity.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 -+ - @Nullable - @Override - public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { -@@ -160,6 +233,7 @@ public class Dolphin extends WaterAnimal { - protected void registerGoals() { - this.goalSelector.addGoal(0, new BreathAirGoal(this)); - this.goalSelector.addGoal(0, new TryFindWaterGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new Dolphin.DolphinSwimToTreasureGoal(this)); - this.goalSelector.addGoal(2, new Dolphin.DolphinSwimWithPlayerGoal(this, 4.0D)); - this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0D, 10)); -@@ -170,6 +244,7 @@ public class Dolphin extends WaterAnimal { - 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.0D, 1.0D)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Guardian.class})).setAlertOthers()); - } - -@@ -221,7 +296,7 @@ public class Dolphin extends WaterAnimal { - - @Override - protected boolean canRide(Entity entity) { -- return true; -+ return boardingCooldown <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs; - } - - @Override -@@ -256,6 +331,11 @@ public class Dolphin extends WaterAnimal { - @Override - public void tick() { - super.tick(); -+ // Purpur start -+ if (spitCooldown > 0) { -+ spitCooldown--; -+ } -+ // Purpur end - if (this.isNoAi()) { - this.setAirSupply(this.getMaxAirSupply()); - } else { -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index e36c01533dc85541c91f7a55690fae46f770b516..c389a2b368b7c5ae77293e8315302f0b08134a4c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -140,6 +140,44 @@ public class Fox extends Animal { - this.setCanPickUpLoot(true); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.foxRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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 -+ - @Override - protected void defineSynchedData() { - super.defineSynchedData(); -@@ -159,6 +197,7 @@ public class Fox extends Animal { - return entityliving instanceof AbstractSchoolingFish; - }); - this.goalSelector.addGoal(0, new Fox.FoxFloatGoal()); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(0, new ClimbOnTopOfPowderSnowGoal(this, this.level)); - this.goalSelector.addGoal(1, new Fox.FaceplantGoal()); - this.goalSelector.addGoal(2, new Fox.FoxPanicGoal(2.2D)); -@@ -185,6 +224,7 @@ public class Fox extends Animal { - 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 - this.targetSelector.addGoal(3, new Fox.DefendTrustedTargetGoal(LivingEntity.class, false, false, (entityliving) -> { - return Fox.TRUSTED_TARGET_SELECTOR.test(entityliving) && !this.trusts(entityliving.getUUID()); - })); -@@ -744,16 +784,16 @@ public class Fox extends Animal { - return new Vec3(0.0D, (double) (0.55F * this.getEyeHeight()), (double) (this.getBbWidth() * 0.4F)); - } - -- public class FoxLookControl extends LookControl { -+ public class FoxLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - - public FoxLookControl() { - super(Fox.this); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (!Fox.this.isSleeping()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - } - - } -@@ -764,16 +804,16 @@ public class Fox extends Animal { - } - } - -- private class FoxMoveControl extends MoveControl { -+ private class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - - public FoxMoveControl() { - super(Fox.this); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (Fox.this.canMove()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -index 8299b48bad7a38d4310ca93b1de37d6c9170fc09..6b56e324900d809615d80f38528f302db60cfbc9 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -@@ -70,8 +70,27 @@ public class IronGolem extends AbstractGolem implements NeutralMob { - this.maxUpStep = 1.0F; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.ironGolemRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.ironGolemRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.ironGolemControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { -+ if (level.purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true)); - this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9D, 32.0F)); - this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6D, false)); -@@ -79,6 +98,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 - this.targetSelector.addGoal(1, new DefendVillageTargetGoal(this)); - this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); -@@ -267,13 +287,13 @@ public class IronGolem extends AbstractGolem implements NeutralMob { - ItemStack itemstack = player.getItemInHand(hand); - - if (!itemstack.is(Items.IRON_INGOT)) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } else { - float f = this.getHealth(); - - this.heal(25.0F); - if (this.getHealth() == f) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } else { - float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F; - -diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -index 6d4dc4b09278eca509f86655c6562fb4b05d5069..9d47d010c3917616732ff10a1b0166f410cf6c25 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -@@ -64,6 +64,23 @@ public class MushroomCow extends Cow implements Shearable { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.mooshroomRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.mooshroomRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.mooshroomControllable; -+ } -+ // Purpur end -+ - @Override - public float getWalkTargetValue(BlockPos pos, LevelReader world) { - return world.getBlockState(pos.below()).is(Blocks.MYCELIUM) ? 10.0F : world.getBrightness(pos) - 0.5F; -@@ -125,7 +142,7 @@ public class MushroomCow extends Cow implements Shearable { - } else if (itemstack.is(Items.SHEARS) && this.readyForShearing()) { - // CraftBukkit start - if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } - // CraftBukkit end - this.shear(SoundSource.PLAYERS); -@@ -146,7 +163,7 @@ public class MushroomCow extends Cow implements Shearable { - Optional> optional = this.getEffectFromItemStack(itemstack); - - if (!optional.isPresent()) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } - - Pair pair = (Pair) optional.get(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -index fc2c18a3bfd395882a5f4a08c23b382845d9ccd2..22e0c5dee4efa2e041671c980851db46eabe9402 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -@@ -68,6 +68,23 @@ public class Ocelot extends Animal { - this.reassessTrustingGoals(); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.ocelotRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.ocelotRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.ocelotControllable; -+ } -+ // Purpur end -+ - public boolean isTrusting() { - return (Boolean) this.entityData.get(Ocelot.DATA_TRUSTING); - } -@@ -99,12 +116,14 @@ public class Ocelot extends Animal { - protected void registerGoals() { - this.temptGoal = new Ocelot.OcelotTemptGoal(this, 0.6D, Ocelot.TEMPT_INGREDIENT, true); - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - 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.8D)); - this.goalSelector.addGoal(10, new WaterAvoidingRandomStrollGoal(this, 0.8D, 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 - 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)); - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index 8db4fc59b99694d2b15af188fc1c39ccf0090a8d..c477671b68d243dbead2067066e56044a5a38e7c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -107,6 +107,32 @@ public class Panda extends Animal { - - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.pandaRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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 -+ - @Override - public boolean canTakeItem(ItemStack stack) { - EquipmentSlot enumitemslot = Mob.getEquipmentSlotForItem(stack); -@@ -262,6 +288,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 - this.goalSelector.addGoal(2, new Panda.PandaPanicGoal(this, 2.0D)); - this.goalSelector.addGoal(2, new Panda.PandaBreedGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new Panda.PandaAttackGoal(this, 1.2000000476837158D, true)); -@@ -277,6 +304,7 @@ public class Panda extends Animal { - this.goalSelector.addGoal(12, new Panda.PandaRollGoal(this)); - this.goalSelector.addGoal(13, new FollowParentGoal(this, 1.25D)); - this.goalSelector.addGoal(14, new WaterAvoidingRandomStrollGoal(this, 1.0D)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new Panda.PandaHurtByTargetGoal(this, new Class[0])).setAlertOthers(new Class[0])); - } - -@@ -621,7 +649,7 @@ public class Panda extends Animal { - ItemStack itemstack = player.getItemInHand(hand); - - if (this.isScared()) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } else if (this.isOnBack()) { - this.setOnBack(false); - return InteractionResult.sidedSuccess(this.level.isClientSide); -@@ -640,7 +668,7 @@ public class Panda extends Animal { - this.gameEvent(GameEvent.MOB_INTERACT, this.eyeBlockPosition()); - } else { - if (this.level.isClientSide || this.isSitting() || this.isInWater()) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } - - this.tryToSit(); -@@ -657,7 +685,7 @@ public class Panda extends Animal { - - return InteractionResult.SUCCESS; - } else { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } - } - -@@ -697,7 +725,7 @@ public class Panda extends Animal { - return !this.isOnBack() && !this.isScared() && !this.isEating() && !this.isRolling() && !this.isSitting(); - } - -- private static class PandaMoveControl extends MoveControl { -+ private static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - - private final Panda panda; - -@@ -707,9 +735,9 @@ public class Panda extends Animal { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (this.panda.canPerformAction()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -index eb66cfebce7e8bb9ebce066e5aabff256a8a518d..19af87b2496178be9d2d09ba16ac511a1882d2ed 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -@@ -124,12 +124,68 @@ public class Parrot extends ShoulderRidingEntity implements FlyingAnimal { - - public Parrot(EntityType type, Level world) { - super(type, world); -- this.moveControl = new FlyingMoveControl(this, 10, false); -+ // Purpur start -+ 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 - this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F); - this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, -1.0F); - this.setPathfindingMalus(BlockPathTypes.COCOA, -1.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.parrotRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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 -+ - @Nullable - @Override - public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { -@@ -148,8 +204,10 @@ public class Parrot extends ShoulderRidingEntity implements FlyingAnimal { - - @Override - protected void registerGoals() { -- this.goalSelector.addGoal(0, new PanicGoal(this, 1.25D)); -+ //this.goalSelector.addGoal(0, new PanicGoal(this, 1.25D)); // Purpur - move down - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur -+ this.goalSelector.addGoal(1, new PanicGoal(this, 1.25D)); // Purpur - 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.0D, 5.0F, 1.0F, true)); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Pig.java b/src/main/java/net/minecraft/world/entity/animal/Pig.java -index f24b87ff18d4255289c8130f32fd205014ee2747..c4c0d7958156b0d587999032c8f9fac9fd689c72 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Pig.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Pig.java -@@ -64,9 +64,27 @@ public class Pig extends Animal implements ItemSteerable, Saddleable { - this.steering = new ItemBasedSteering(this.entityData, Pig.DATA_BOOST_TIME, Pig.DATA_SADDLE_ID); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.pigRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.pigRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.pigControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new PanicGoal(this, 1.25D)); - this.goalSelector.addGoal(3, new BreedGoal(this, 1.0D)); - this.goalSelector.addGoal(4, new TemptGoal(this, 1.2D, Ingredient.of(Items.CARROT_ON_A_STICK), false)); -diff --git a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java -index 1495e0e2d298b501251c5bceaedf7ae123d2f69c..2c723250eea3454d6b7aafb307612a2c06c42b73 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java -+++ b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java -@@ -60,11 +60,40 @@ public class PolarBear extends Animal implements NeutralMob { - private int remainingPersistentAngerTime; - @Nullable - private UUID persistentAngerTarget; -+ private int standTimer = 0; // Purpur - - public PolarBear(EntityType type, Level world) { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.polarBearRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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 -+ - @Override - public AgeableMob getBreedOffspring(ServerLevel world, AgeableMob entity) { - return EntityType.POLAR_BEAR.create(world); -@@ -79,12 +108,14 @@ public class PolarBear extends Animal implements NeutralMob { - 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 - this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal()); - this.goalSelector.addGoal(1, new PolarBear.PolarBearPanicGoal()); - this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D)); - this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0D)); - 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 - 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)); -@@ -201,6 +232,11 @@ public class PolarBear extends Animal implements NeutralMob { - this.updatePersistentAnger((ServerLevel)this.level, true); - } - -+ // Purpur start -+ if (isStanding() && --standTimer <= 0) { -+ setStanding(false); -+ } -+ // Purpur end - } - - @Override -@@ -230,6 +266,7 @@ public class PolarBear extends Animal implements NeutralMob { - - public void setStanding(boolean warning) { - this.entityData.set(DATA_STANDING_ID, warning); -+ standTimer = warning ? 20 : -1; // Purpur - } - - public float getStandingAnimationScale(float tickDelta) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -index ce02552c1b3c62cf9f48425838a129a3ec40a049..71929e5fe8851ad5620f25b6ed5058653231d769 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -@@ -45,6 +45,23 @@ public class Pufferfish extends AbstractFish { - this.refreshDimensions(); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.pufferfishRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.pufferfishControllable; -+ } -+ // Purpur end -+ - @Override - protected void defineSynchedData() { - super.defineSynchedData(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -index 51001c18803a5a2c8a1c4ce258348142e56c2275..0819503e8651afa9ff42df7077667eaa3a310df4 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -@@ -84,6 +84,7 @@ public class Rabbit extends Animal { - private boolean wasOnGround; - private int jumpDelayTicks; - int moreCarrotTicks; -+ private boolean actualJump; // Purpur - - public Rabbit(EntityType type, Level world) { - super(type, world); -@@ -92,6 +93,51 @@ public class Rabbit extends Animal { - this.initializePathFinderGoals(); // CraftBukkit - moved code - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.rabbitRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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 -+ - // CraftBukkit start - code from constructor - public void initializePathFinderGoals(){ - this.setSpeedModifier(0.0D); -@@ -101,6 +147,7 @@ public class Rabbit extends Animal { - @Override - public void registerGoals() { - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level)); - this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2D)); - this.goalSelector.addGoal(2, new BreedGoal(this, 0.8D)); -@@ -115,6 +162,13 @@ public class Rabbit extends Animal { - - @Override - protected float getJumpPower() { -+ if (getRider() != null && this.isControllable()) { -+ if (getForwardMot() < 0) { -+ setSpeed(getForwardMot() * 2F); -+ } -+ return actualJump ? 0.5F : 0.3F; -+ } -+ // Purpur end - if (!this.horizontalCollision && (!this.moveControl.hasWanted() || this.moveControl.getWantedY() <= this.getY() + 0.5D)) { - Path pathentity = this.navigation.getPath(); - -@@ -133,7 +187,7 @@ public class Rabbit extends Animal { - } - - @Override -- protected void jumpFromGround() { -+ public void jumpFromGround() { // Purpur - protected -> public - super.jumpFromGround(); - double d0 = this.moveControl.getSpeedModifier(); - -@@ -183,6 +237,13 @@ public class Rabbit extends Animal { - - @Override - public void customServerAiStep() { -+ // Purpur start -+ if (getRider() != null && this.isControllable()) { -+ handleJumping(); -+ return; -+ } -+ // Purpur end -+ - if (this.jumpDelayTicks > 0) { - --this.jumpDelayTicks; - } -@@ -453,7 +514,7 @@ public class Rabbit extends Animal { - } - } - -- private static class RabbitMoveControl extends MoveControl { -+ private static class RabbitMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - - private final Rabbit rabbit; - private double nextJumpSpeed; -@@ -464,14 +525,14 @@ public class Rabbit extends Animal { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (this.rabbit.onGround && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl) this.rabbit.jumpControl).wantJump()) { - this.rabbit.setSpeedModifier(0.0D); - } else if (this.hasWanted()) { - this.rabbit.setSpeedModifier(this.nextJumpSpeed); - } - -- super.tick(); -+ super.vanillaTick(); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/animal/Salmon.java b/src/main/java/net/minecraft/world/entity/animal/Salmon.java -index 0af79daa357f53a8871e293b57e16c099e5d3f64..59cb21bc3166f4cc77b962c253ed786fd04090de 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Salmon.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Salmon.java -@@ -13,6 +13,23 @@ public class Salmon extends AbstractSchoolingFish { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.salmonRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.salmonControllable; -+ } -+ // Purpur end -+ - @Override - public int getMaxSchoolSize() { - return 5; -diff --git a/src/main/java/net/minecraft/world/entity/animal/Sheep.java b/src/main/java/net/minecraft/world/entity/animal/Sheep.java -index 1d4e504434ba9730101588c0bb0aab8f1ef6a7db..e267e7b3b4ba96f5423327a336088942dcb4e14c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Sheep.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Sheep.java -@@ -116,10 +116,28 @@ public class Sheep extends Animal implements Shearable { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.sheepRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.sheepRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.sheepControllable; -+ } -+ // Purpur end -+ - @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 - this.goalSelector.addGoal(1, new PanicGoal(this, 1.25D)); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new TemptGoal(this, 1.1D, Ingredient.of(Items.WHEAT), false)); -diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -index 24f66a24d5a4698d141e697db50da5794747be7b..3ead4cc2fa875b8839410d7b4b5871e5b1d39074 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -@@ -54,12 +54,31 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.snowGolemRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.snowGolemRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.snowGolemControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25D, 20, 10.0F)); - 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 - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entityliving) -> { - return entityliving instanceof Enemy; - })); -@@ -113,6 +132,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - return; - } - -+ if (getRider() != null && this.isControllable() && !level.purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden - BlockState iblockdata = Blocks.SNOW.defaultBlockState(); - - for (int l = 0; l < 4; ++l) { -@@ -155,7 +175,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - if (itemstack.is(Items.SHEARS) && this.readyForShearing()) { - // CraftBukkit start - if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } - // CraftBukkit end - this.shear(SoundSource.PLAYERS); -@@ -168,7 +188,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - - return InteractionResult.sidedSuccess(this.level.isClientSide); - } else { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java -index a51424d29ac353cf1bec4d1484db0acb63bebba5..30687391baacd693fa3bb9e3efbd492d4e4d32c8 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Squid.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java -@@ -50,9 +50,37 @@ public class Squid extends WaterAnimal { - this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.squidRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.squidControllable; -+ } -+ -+ protected 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 -+ - @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 - this.goalSelector.addGoal(1, new Squid.SquidFleeGoal()); - } - -@@ -244,6 +272,38 @@ public class Squid extends WaterAnimal { - - @Override - public void tick() { -+ // Purpur start -+ 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.setMovementVector((float) dir.getX(), (float) dir.getY(), (float) dir.getZ()); -+ } else { -+ squid.setMovementVector(0.0F, 0.0F, 0.0F); -+ } -+ return; -+ } -+ // Purpur end -+ - int i = this.squid.getNoActionTime(); - - if (i > 100) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -index fd6175ce96860402a4e458ce0d5d81be6f7bf99d..6295c88832dd2f4ceec67f4f4a3477629494ccf7 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -@@ -49,6 +49,23 @@ public class TropicalFish extends AbstractSchoolingFish { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.tropicalFishRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.tropicalFishControllable; -+ } -+ // Purpur end -+ - public static String getPredefinedName(int variant) { - return "entity.minecraft.tropical_fish.predefined." + variant; - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index ac17fd4454730db831cf9b781963062db8614bb7..bd370b91e482913971816dac5f04f494c3951563 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -84,6 +84,23 @@ public class Turtle extends Animal { - this.maxUpStep = 1.0F; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.turtleRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.turtleRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.turtleControllable; -+ } -+ // Purpur end -+ - public void setHomePos(BlockPos pos) { - this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... - } -@@ -186,6 +203,7 @@ public class Turtle extends Animal { - - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2D)); - this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0D)); - this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0D)); -@@ -343,13 +361,15 @@ public class Turtle extends Animal { - org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit - } - -- private static class TurtleMoveControl extends MoveControl { -+ private static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - - private final Turtle turtle; -+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - - TurtleMoveControl(Turtle turtle) { - super(turtle); - this.turtle = turtle; -+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(turtle, 0.25D); // Purpur - } - - private void updateSpeed() { -@@ -368,8 +388,18 @@ public class Turtle extends Animal { - - } - -+ // Purpur start -+ public void purpurTick(Player rider) { -+ if (turtle.isInWater()) { -+ waterController.purpurTick(rider); -+ } else { -+ super.purpurTick(rider); -+ } -+ } -+ // Purpur end -+ - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - this.updateSpeed(); - if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) { - double d0 = this.wantedX - this.turtle.getX(); -@@ -382,7 +412,7 @@ public class Turtle extends Animal { - - 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)); - - this.turtle.setSpeed(Mth.lerp(0.125F, this.turtle.getSpeed(), f1)); - this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0D, (double) this.turtle.getSpeed() * d1 * 0.1D, 0.0D)); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java -index 249ef89342d2811614507090b79250adf78e33ce..b4825647058df6803be24ea30028c95179a34fb8 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java -@@ -102,9 +102,32 @@ public class Wolf extends TamableAnimal implements NeutralMob { - this.setPathfindingMalus(BlockPathTypes.DANGER_POWDER_SNOW, -1.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.wolfRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.wolfRidableInWater; -+ } -+ -+ public void onMount(Player rider) { -+ super.onMount(rider); -+ setInSittingPose(false); -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.wolfControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new Wolf.WolfPanicGoal(1.5D)); - this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this)); - this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal<>(this, Llama.class, 24.0F, 1.5D, 1.5D)); -@@ -116,6 +139,7 @@ public class Wolf extends TamableAnimal implements NeutralMob { - 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 - this.targetSelector.addGoal(1, new OwnerHurtByTargetGoal(this)); - this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this)); - this.targetSelector.addGoal(3, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); -diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -index bdc65bf098fd3107e36d3738583455e87af82cf4..89b6eedb23bea9239dadb8e5e06549eb7d6c3a26 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -@@ -99,6 +99,28 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { - this.maxUpStep = 1.0F; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.axolotlRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @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 -+ } -+ // Purpur end -+ - @Override - public Map getModelRotationValues() { - return this.modelRotationValues; -@@ -530,14 +552,22 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { - private static class AxolotlMoveControl extends SmoothSwimmingMoveControl { - - private final Axolotl axolotl; -+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - - 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 - } - - @Override - public void tick() { -+ // Purpur start -+ if (axolotl.getRider() != null && axolotl.isControllable()) { -+ waterController.purpurTick(axolotl.getRider()); -+ return; -+ } -+ // Purpur end - if (!this.axolotl.isPlayingDead()) { - super.tick(); - } -@@ -552,9 +582,9 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (!Axolotl.this.isPlayingDead()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 042e852cd440e62003b5e72d92728e2dc602f279..ea82e3f2dc219c11de7841358a6d2e1408825387 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -69,6 +69,23 @@ public class Goat extends Animal { - this.setPathfindingMalus(BlockPathTypes.DANGER_POWDER_SNOW, -1.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.goatRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.goatRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.goatControllable; -+ } -+ // Purpur end -+ - @Override - protected Brain.Provider brainProvider() { - return Brain.provider(Goat.MEMORY_TYPES, Goat.SENSOR_TYPES); -@@ -145,7 +162,7 @@ public class Goat extends Animal { - @Override - protected void customServerAiStep() { - this.level.getProfiler().push("goatBrain"); -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick((ServerLevel) this.level, this); - this.level.getProfiler().pop(); - this.level.getProfiler().push("goatActivityUpdate"); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index a726006888bbbdb290bcda3ac4fd45d68ba51b79..e73045a7046850f10d79809c53b6cdd9ceb8775a 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -114,12 +114,22 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - - protected AbstractHorse(EntityType type, Level world) { - super(type, world); -+ 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.maxUpStep = 1.0F; - this.createInventory(); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return false; // vanilla handles -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - this.goalSelector.addGoal(1, new PanicGoal(this, 1.2D)); - this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D)); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D, AbstractHorse.class)); -@@ -127,6 +137,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - this.goalSelector.addGoal(6, new WaterAvoidingRandomStrollGoal(this, 0.7D)); - 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.HorseHasRider(this)); // Purpur - this.addBehaviourGoals(); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java -index 4e68ba557e86250b61ce0b20eacb3bcd36a23806..db4d5d5e38a44f85c343b632c914f2252c30a3c3 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java -@@ -15,6 +15,13 @@ public class Donkey extends AbstractChestedHorse { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.donkeyRidableInWater; -+ } -+ // Purpur end -+ - @Override - protected SoundEvent getAmbientSound() { - super.getAmbientSound(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java -index 7af7ff4accbcc1b28d96c94e9c3301142541f4f0..a91152df3d443d70b1aa9aff97b3746e0331c0a6 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java -@@ -39,6 +39,13 @@ public class Horse extends AbstractHorse { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.horseRidableInWater; -+ } -+ // Purpur end -+ - @Override - protected void randomizeAttributes() { - this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double)this.generateRandomMaxHealth()); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -index ff18a7615c2798b1dc9106154577a49d1ae70d43..2b72dc5c76fe717bd8643a6ac45fdbb8d36bd632 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -@@ -71,7 +71,51 @@ public class Llama extends AbstractChestedHorse implements RangedAttackMob { - - public Llama(EntityType type, Level world) { - super(type, world); -+ // Purpur start -+ 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 -+ } -+ -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.llamaRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.llamaRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.llamaControllable; -+ } -+ -+ @Override -+ public boolean isSaddled() { -+ return super.isSaddled() || (isTamed() && getSwag() != null); - } -+ // Purpur end - - public boolean isTraderLlama() { - return false; -@@ -123,6 +167,7 @@ public class Llama extends AbstractChestedHorse implements RangedAttackMob { - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.LlamaHasRider(this)); // Purpur - this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D)); - this.goalSelector.addGoal(2, new LlamaFollowCaravanGoal(this, 2.0999999046325684D)); - this.goalSelector.addGoal(3, new RangedAttackGoal(this, 1.25D, 40, 20.0F)); -@@ -133,6 +178,7 @@ public class Llama extends AbstractChestedHorse implements RangedAttackMob { - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.7D)); - 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 - this.targetSelector.addGoal(1, new Llama.LlamaHurtByTargetGoal(this)); - this.targetSelector.addGoal(2, new Llama.LlamaAttackWolfGoal(this)); - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Mule.java b/src/main/java/net/minecraft/world/entity/animal/horse/Mule.java -index 51821a71bb0eb9cd22c657a46ef148c84a2355c1..a67061b875468777ee6256c0b28894c62ebb926c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Mule.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Mule.java -@@ -14,6 +14,13 @@ public class Mule extends AbstractChestedHorse { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.muleRidableInWater; -+ } -+ // Purpur end -+ - @Override - protected SoundEvent getAmbientSound() { - super.getAmbientSound(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java -index 80340a7543ff8287621c90b99b85886a5483810a..235dae1b47641fb69e6b34e419797a1a8bf499f2 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java -@@ -30,6 +30,13 @@ public class SkeletonHorse extends AbstractHorse { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isTamed() { -+ return true; -+ } -+ // Purpur end -+ - public static AttributeSupplier.Builder createAttributes() { - return createBaseHorseAttributes().add(Attributes.MAX_HEALTH, 15.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D); - } -@@ -40,7 +47,7 @@ public class SkeletonHorse extends AbstractHorse { - } - - @Override -- protected void addBehaviourGoals() {} -+ protected void addBehaviourGoals() { if (level.purpurConfig.skeletonHorseCanSwim) goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); } // Purpur - - @Override - protected SoundEvent getAmbientSound() { -@@ -135,7 +142,7 @@ public class SkeletonHorse extends AbstractHorse { - - @Override - public boolean rideableUnderWater() { -- return true; -+ return level.purpurConfig.skeletonHorseRidableInWater; // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/TraderLlama.java b/src/main/java/net/minecraft/world/entity/animal/horse/TraderLlama.java -index 7dbdb6529350b34c0260ca50dfff2b4583fd9c2a..a4a88be0cbf94b633f486ce57d4fccd38002ac9f 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/TraderLlama.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/TraderLlama.java -@@ -27,6 +27,28 @@ public class TraderLlama extends Llama { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.traderLlamaRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.traderLlamaRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.traderLlamaControllable; -+ } -+ -+ @Override -+ public boolean isSaddled() { -+ return super.isSaddled() || isTamed(); -+ } -+ // Purpur end -+ - @Override - public boolean isTraderLlama() { - return true; -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/ZombieHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/ZombieHorse.java -index 0b9fc77b86b82cebdaead4f1884182ecfdc98b09..77d05979331af91a1fbe619d1c26fa883146add8 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/ZombieHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/ZombieHorse.java -@@ -22,6 +22,18 @@ public class ZombieHorse extends AbstractHorse { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.zombieHorseRidableInWater; -+ } -+ -+ @Override -+ public boolean isTamed() { -+ return true; -+ } -+ // Purpur end -+ - public static AttributeSupplier.Builder createAttributes() { - return createBaseHorseAttributes().add(Attributes.MAX_HEALTH, 15.0D).add(Attributes.MOVEMENT_SPEED, (double)0.2F); - } -@@ -92,5 +104,6 @@ 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 - } - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java b/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java -index 305a891e4b51d1031d9e9238ff00e2ea7de8d954..84625d09df800fcfd477fc493fb5f8246567b7e8 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java -+++ b/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java -@@ -21,6 +21,13 @@ public class EnderDragonPart extends Entity { - this.name = name; - } - -+ // Purpur start -+ @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 -+ - @Override - protected void defineSynchedData() { - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 8c4db2f0e5158872879da52a96bc592145e52e13..114e5296645a06e222c5ed977e984f9611c27789 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -104,6 +104,7 @@ public class EnderDragon extends Mob implements Enemy { - @Nullable - private BlockPos podium; - // Paper end -+ private boolean hadRider; // Purpur - - public EnderDragon(EntityType entitytypes, Level world) { - super(EntityType.ENDER_DRAGON, world); -@@ -118,8 +119,50 @@ public class EnderDragon extends Mob implements Enemy { - } - - this.phaseManager = new EnderDragonPhaseManager(this); -+ -+ // Purpur start -+ 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.yRot - 180F, rider.xRotO * 0.5F); -+ } -+ }; -+ // Purpur end -+ } -+ -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.enderDragonRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.enderDragonRidableInWater; - } - -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.enderDragonControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level.purpurConfig.enderDragonMaxY; -+ } -+ // Purpur end -+ - public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); - } -@@ -181,6 +224,37 @@ public class EnderDragon extends Mob implements Enemy { - - @Override - public void aiStep() { -+ // Purpur start -+ 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 -+ - this.processFlappingMovement(); - if (this.level.isClientSide) { - this.setHealth(this.getHealth()); -@@ -194,6 +268,8 @@ public class EnderDragon extends Mob implements Enemy { - float f; - - if (this.isDeadOrDying()) { -+ if (hasRider) ejectPassengers(); // Purpur -+ - float f1 = (this.random.nextFloat() - 0.5F) * 8.0F; - - f = (this.random.nextFloat() - 0.5F) * 4.0F; -@@ -206,9 +282,9 @@ public class EnderDragon extends Mob implements Enemy { - - f = 0.2F / ((float) vec3d.horizontalDistance() * 10.0F + 1.0F); - f *= (float) Math.pow(2.0D, vec3d.y); -- if (this.phaseManager.getCurrentPhase().isSitting()) { -+ if (!hasRider && this.phaseManager.getCurrentPhase().isSitting()) { // Purpur - this.flapTime += 0.1F; -- } else if (this.inWall) { -+ } else if (!hasRider && this.inWall) { // Purpur - this.flapTime += f * 0.5F; - } else { - this.flapTime += f; -@@ -253,7 +329,7 @@ public class EnderDragon extends Mob implements Enemy { - } - - this.phaseManager.getCurrentPhase().doClientTick(); -- } else { -+ } else if (!hasRider) { // Purpur - DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase(); - - idragoncontroller.doServerTick(); -@@ -322,7 +398,7 @@ public class EnderDragon extends Mob implements Enemy { - this.tickPart(this.body, (double) (f11 * 0.5F), 0.0D, (double) (-f12 * 0.5F)); - this.tickPart(this.wing1, (double) (f12 * 4.5F), 2.0D, (double) (f11 * 4.5F)); - this.tickPart(this.wing2, (double) (f12 * -4.5F), 2.0D, (double) (f11 * -4.5F)); -- if (!this.level.isClientSide && this.hurtTime == 0) { -+ if (!hasRider && !this.level.isClientSide && this.hurtTime == 0) { // Purpur - this.knockBack(this.level.getEntities((Entity) this, this.wing1.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); - this.knockBack(this.level.getEntities((Entity) this, this.wing2.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); - this.hurt(this.level.getEntities((Entity) this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); -@@ -366,7 +442,7 @@ public class EnderDragon extends Mob implements Enemy { - } - - if (!this.level.isClientSide) { -- this.inWall = this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox()); -+ this.inWall = !hasRider && this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox()); // Purpur - if (this.dragonFight != null) { - this.dragonFight.updateDragon(this); - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 563a028018421cbafdeff398175a32d6830134af..c475a8f53e7076f7655cebc3ac420eabee166674 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -84,6 +84,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable(); - }; - private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); -+ private int shootCooldown = 0; // Purpur - // Paper start - private boolean canPortal = false; - -@@ -105,16 +106,123 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - navigationflying.setCanOpenDoors(false); - navigationflying.setCanFloat(true); - navigationflying.setCanPassDoors(true); -+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.1F); // Purpur - return navigationflying; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.witherRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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); -+ WitherSkull skull = new WitherSkull(level, this, x - headX, y - headY, z - headZ) { -+ @Override -+ public boolean canHitEntity(Entity target) { -+ // do not hit rider -+ return target != rider && super.canHitEntity(target); -+ } -+ }; -+ skull.setPosRaw(headX, headY, headZ); -+ level.addFreshEntity(skull); -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(0, new WitherBoss.WitherDoNothingGoal()); - this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 40, 20.0F)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomFlyingGoal(this, 1.0D)); - 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 - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 0, false, false, WitherBoss.LIVING_ENTITY_SELECTOR)); - } -@@ -256,6 +364,16 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - - @Override - protected void customServerAiStep() { -+ // Purpur start -+ 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 -+ - int i; - - if (this.getInvulnerableTicks() > 0) { -@@ -583,11 +701,11 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - } - - public int getAlternativeTarget(int headIndex) { -- return (Integer) this.entityData.get((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex)); -+ return getRider() != null && this.isControllable() ? 0 : this.entityData.get(WitherBoss.DATA_TARGETS.get(headIndex)); // Purpur - } - - public void setAlternativeTarget(int headIndex, int id) { -- this.entityData.set((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex), id); -+ if (getRider() == null || !this.isControllable()) this.entityData.set(WitherBoss.DATA_TARGETS.get(headIndex), id); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -index e23fe546291e670f89447398507d08a0a07efa85..a60ca5d69a9734c7979f953bd56c05c7016356d2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -68,12 +68,14 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(2, new RestrictSunGoal(this)); - this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Wolf.class, 6.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - 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 - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Blaze.java b/src/main/java/net/minecraft/world/entity/monster/Blaze.java -index 40b387bfa1dbdb8dd2b4d23e9c130645469e1af0..6454992aa70e1476eaea67a8d9a1c27ab66f088d 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java -@@ -32,6 +32,7 @@ public class Blaze extends Monster { - - public Blaze(EntityType type, Level world) { - super(type, world); -+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); - this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); - this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F); -@@ -39,19 +40,55 @@ public class Blaze extends Monster { - this.xpReward = 10; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.blazeRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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 -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(4, new Blaze.BlazeAttackGoal(this)); - this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D)); - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 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 - 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.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D); -+ return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - } - - @Override -@@ -106,6 +143,14 @@ public class Blaze extends Monster { - - @Override - protected void customServerAiStep() { -+ // Purpur start -+ if (getRider() != null && this.isControllable()) { -+ Vec3 mot = getDeltaMovement(); -+ setDeltaMovement(mot.x(), getVerticalMot() > 0 ? 0.07D : -0.07D, mot.z()); -+ return; -+ } -+ // Purpur end -+ - --this.nextHeightOffsetChangeTick; - if (this.nextHeightOffsetChangeTick <= 0) { - this.nextHeightOffsetChangeTick = 100; -diff --git a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java -index d980b906d9206560741576fa4153c57212f307a0..790d4abbc8e0b288e20e72e63f8190e938e2fecb 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java -@@ -28,6 +28,23 @@ public class CaveSpider extends Spider { - return Spider.createAttributes().add(Attributes.MAX_HEALTH, 12.0D); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.caveSpiderRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.caveSpiderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.caveSpiderControllable; -+ } -+ // Purpur end -+ - @Override - public boolean doHurtTarget(Entity target) { - if (super.doHurtTarget(target)) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -index f9193f8cd3b0d0e689a523c7142a897c342c2931..1476372a78092b103c037028f08cb1eae9464706 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -@@ -60,21 +60,98 @@ public class Creeper extends Monster implements PowerableMob { - public int maxSwell = 30; - public int explosionRadius = 3; - private int droppedSkulls; -+ // Purpur start -+ private int spacebarCharge = 0; -+ private int prevSpacebarCharge = 0; -+ private int powerToggleDelay = 0; -+ // Purpur end - - public Creeper(EntityType type, Level world) { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.creeperRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.creeperRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.creeperControllable; -+ } -+ -+ @Override -+ protected void customServerAiStep() { -+ 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(); -+ } -+ -+ @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 -+ - @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 - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D)); - 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 - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); - } -@@ -318,6 +395,7 @@ public class Creeper extends Monster implements PowerableMob { - 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(Creeper.DATA_IS_IGNITED, event.isIgnited()); -+ if (!event.isIgnited()) setSwellDir(-1); // Purpur - } - } - // Paper end -diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -index 0a318583a5fc5ecb50f858d93d357baf1edf5a93..3ea303558940ceb925babdd2ce019f730ad12aed 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -@@ -68,6 +68,23 @@ public class Drowned extends Zombie implements RangedAttackMob { - this.groundNavigation = new GroundPathNavigation(this, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.drownedRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.drownedRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.drownedControllable; -+ } -+ // Purpur end -+ - @Override - protected void addBehaviourGoals() { - this.goalSelector.addGoal(1, new Drowned.DrownedGoToWaterGoal(this, 1.0D)); -@@ -254,8 +271,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - this.searchingForLand = targetingUnderwater; - } - -- private static class DrownedMoveControl extends MoveControl { -- -+ private static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - private final Drowned drowned; - - public DrownedMoveControl(Drowned drowned) { -@@ -264,7 +280,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - LivingEntity entityliving = this.drowned.getTarget(); - - if (this.drowned.wantsToSwim() && this.drowned.isInWater()) { -@@ -287,7 +303,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - - 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 - float f2 = Mth.lerp(0.125F, this.drowned.getSpeed(), f1); - - this.drowned.setSpeed(f2); -@@ -297,7 +313,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0D, -0.008D, 0.0D)); - } - -- super.tick(); -+ super.vanillaTick(); // Purpur - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -index ee9194ffb3cc6d660d4f99a3914ede7e4a3643fe..7e79fe61e4fd0d26f62a0515c437370ccb0abf58 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -@@ -30,6 +30,23 @@ public class ElderGuardian extends Guardian { - - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.elderGuardianRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.elderGuardianControllable; -+ } -+ // Purpur end -+ - public static AttributeSupplier.Builder createAttributes() { - return Guardian.createAttributes().add(Attributes.MOVEMENT_SPEED, 0.30000001192092896D).add(Attributes.ATTACK_DAMAGE, 8.0D).add(Attributes.MAX_HEALTH, 80.0D); - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -index 1d9c542f3fe7da7bb9ea8b13496651a7b3dcde07..5117838e37730f81f8089d467e861adc3edd2829 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -87,9 +87,27 @@ public class EnderMan extends Monster implements NeutralMob { - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.endermanRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.endermanRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.endermanControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new EnderMan.EndermanFreezeWhenLookedAt(this)); - this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F)); -@@ -97,6 +115,7 @@ 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 - this.targetSelector.addGoal(1, new EnderMan.EndermanLookForPlayerGoal(this, this::isAngryAt)); - this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, true, false)); -@@ -278,7 +297,7 @@ public class EnderMan extends Monster implements NeutralMob { - - @Override - protected void customServerAiStep() { -- if (this.level.isDay() && this.tickCount >= this.targetChangeTime + 600) { -+ if ((getRider() == null || !this.isControllable()) && this.level.isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - no random teleporting - float f = this.getBrightness(); - - if (f > 0.5F && this.level.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper -@@ -384,6 +403,7 @@ public class EnderMan extends Monster implements NeutralMob { - public boolean hurt(DamageSource source, float amount) { - if (this.isInvulnerableTo(source)) { - return false; -+ } else if (getRider() != null && this.isControllable()) { return super.hurt(source, amount); // Purpur - no teleporting on damage - } else if (source instanceof IndirectEntityDamageSource) { - Entity entity = source.getDirectEntity(); - boolean flag; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Endermite.java b/src/main/java/net/minecraft/world/entity/monster/Endermite.java -index 313afd946c31f1e68dfdc465e53def261c1453e1..97b4342af7232bdf89382bda6a5ea50f5882cedc 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Endermite.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Endermite.java -@@ -37,14 +37,33 @@ public class Endermite extends Monster { - this.xpReward = 3; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.endermiteRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.endermiteRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.endermiteControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level)); - this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - 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 - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this)).setAlertOthers()); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Evoker.java b/src/main/java/net/minecraft/world/entity/monster/Evoker.java -index 7f695625134ba55f85c54efddd049e167e0d1d8b..169a19b605a4b7edda0d1d04e83d9bb5ea554b99 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java -@@ -48,10 +48,28 @@ public class Evoker extends SpellcasterIllager { - this.xpReward = 10; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.evokerRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.evokerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.evokerControllable; -+ } -+ // Purpur end -+ - @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 - this.goalSelector.addGoal(1, new Evoker.EvokerCastingSpellGoal()); - this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 0.6D, 1.0D)); - this.goalSelector.addGoal(4, new Evoker.EvokerSummonSpellGoal()); -@@ -60,6 +78,7 @@ public class Evoker extends SpellcasterIllager { - this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); - 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 - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{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/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java -index 77a5f8339df5a46967713b51570735de828f0b49..edc38e172712ebd79c1bf427d8898ab0f8244874 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java -@@ -44,11 +44,47 @@ public class Ghast extends FlyingMob implements Enemy { - this.moveControl = new Ghast.GhastMoveControl(this); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.ghastRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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 -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - 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 - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> { - return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; - })); -@@ -96,7 +132,7 @@ public class Ghast extends FlyingMob implements Enemy { - } - - public static AttributeSupplier.Builder createAttributes() { -- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D); -+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - } - - @Override -@@ -153,7 +189,7 @@ public class Ghast extends FlyingMob implements Enemy { - return 2.6F; - } - -- private static class GhastMoveControl extends MoveControl { -+ private static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - - private final Ghast ghast; - private int floatDuration; -@@ -164,7 +200,7 @@ public class Ghast extends FlyingMob implements Enemy { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (this.operation == MoveControl.Operation.MOVE_TO) { - if (this.floatDuration-- <= 0) { - this.floatDuration += this.ghast.getRandom().nextInt(5) + 2; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Giant.java b/src/main/java/net/minecraft/world/entity/monster/Giant.java -index 0d578ab12c874bd2daccc4322a3fe1abafa4bc18..8730d6be52a2edeccceb2ac3e6af12cdb20be5c2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Giant.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Giant.java -@@ -14,6 +14,29 @@ public class Giant extends Monster { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.giantRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.giantRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.giantControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); -+ } -+ // Purpur end -+ - @Override - protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) { - return 10.440001F; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Guardian.java b/src/main/java/net/minecraft/world/entity/monster/Guardian.java -index 8377fa2f583e88082ac5e908951e1ffc1ff973d3..a11e0a181323f1c409f583a8c647f433ad611d79 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java -@@ -65,14 +65,40 @@ public class Guardian extends Monster { - this.xpReward = 10; - this.setPathfindingMalus(BlockPathTypes.WATER, 0.0F); - this.moveControl = new Guardian.GuardianMoveControl(this); -+ // Purpur start -+ 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 - this.clientSideTailAnimation = this.random.nextFloat(); - this.clientSideTailAnimationO = this.clientSideTailAnimation; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.guardianRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return true; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.guardianControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - MoveTowardsRestrictionGoal moveTowardsRestrictionGoal = new MoveTowardsRestrictionGoal(this, 1.0D); - this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(4, new Guardian.GuardianAttackGoal(this)); - this.goalSelector.addGoal(5, moveTowardsRestrictionGoal); - this.goalSelector.addGoal(7, this.randomStrollGoal); -@@ -81,6 +107,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 - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, true, false, new Guardian.GuardianAttackSelector(this))); - } - -@@ -330,7 +357,7 @@ public class Guardian extends Monster { - @Override - public void travel(Vec3 movementInput) { - if (this.isEffectiveAi() && this.isInWater()) { -- this.moveRelative(0.1F, movementInput); -+ this.moveRelative(getRider() != null && this.isControllable() ? getSpeed() : 0.1F, movementInput); // Purpur - this.move(MoverType.SELF, this.getDeltaMovement()); - this.setDeltaMovement(this.getDeltaMovement().scale(0.9D)); - if (!this.isMoving() && this.getTarget() == null) { -@@ -437,7 +464,7 @@ public class Guardian extends Monster { - } - } - -- static class GuardianMoveControl extends MoveControl { -+ static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - private final Guardian guardian; - - public GuardianMoveControl(Guardian guardian) { -@@ -445,8 +472,17 @@ public class Guardian extends Monster { - this.guardian = guardian; - } - -+ // Purpur start - @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 -+ -+ @Override -+ public void vanillaTick() { // Purpur - 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 d = vec3.length(); -@@ -456,7 +492,7 @@ public class Guardian extends Monster { - float h = (float)(Mth.atan2(vec3.z, vec3.x) * (double)(180F / (float)Math.PI)) - 90.0F; - this.guardian.setYRot(this.rotlerp(this.guardian.getYRot(), h, 90.0F)); - this.guardian.yBodyRot = this.guardian.getYRot(); -- float i = (float)(this.speedModifier * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float i = (float)(this.getSpeedModifier() * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - float j = Mth.lerp(0.125F, this.guardian.getSpeed(), i); - this.guardian.setSpeed(j); - double k = Math.sin((double)(this.guardian.tickCount + this.guardian.getId()) * 0.5D) * 0.05D; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Husk.java b/src/main/java/net/minecraft/world/entity/monster/Husk.java -index 4f683b4d35b5b07fffe344fab6ae15f5ccb6baf2..80b0d8145e0901af15a7b5e423e3d605a2803dd1 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Husk.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java -@@ -22,6 +22,23 @@ public class Husk extends Zombie { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.huskRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.huskRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.huskControllable; -+ } -+ // Purpur end -+ - public static boolean checkHuskSpawnRules(EntityType type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) { - return checkMonsterSpawnRules(type, world, spawnReason, pos, random) && (spawnReason == MobSpawnType.SPAWNER || world.canSeeSky(pos)); - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java -index 86f7fdd42461db151221d2c0d5cff6953392fa80..98c85e31a2238cbfa31c12214ade49c4ba181702 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java -@@ -59,10 +59,28 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { - - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.illusionerRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.illusionerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.illusionerControllable; -+ } -+ // Purpur end -+ - @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 - this.goalSelector.addGoal(1, new SpellcasterIllager.SpellcasterCastingSpellGoal()); - this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal()); - this.goalSelector.addGoal(5, new Illusioner.IllusionerBlindnessSpellGoal()); -@@ -70,6 +88,7 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { - this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); - 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 - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{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/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java -index d7bbfddb69685698fdacbc7b5f36204bd873e7cc..3e9ab5db23799e67e28d7fc88d4c3ad96bc44fdc 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java -+++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java -@@ -27,6 +27,28 @@ public class MagmaCube extends Slime { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.magmaCubeRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.magmaCubeRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.magmaCubeControllable; -+ } -+ -+ @Override -+ public float getJumpPower() { -+ return 0.42F * this.getBlockJumpFactor(); // from EntityLiving -+ } -+ // Purpur end -+ - public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, (double)0.2F); - } -@@ -57,7 +79,7 @@ public class MagmaCube extends Slime { - } - - @Override -- protected ResourceLocation getDefaultLootTable() { -+ public ResourceLocation getDefaultLootTable() { // Purpur - decompile fix - return this.isTiny() ? BuiltInLootTables.EMPTY : this.getType().getDefaultLootTable(); - } - -@@ -77,10 +99,11 @@ public class MagmaCube extends Slime { - } - - @Override -- protected void jumpFromGround() { -+ public void jumpFromGround() { // Purpur - protected -> public - Vec3 vec3 = this.getDeltaMovement(); - this.setDeltaMovement(vec3.x, (double)(this.getJumpPower() + (float)this.getSize() * 0.1F), vec3.z); - this.hasImpulse = true; -+ this.actualJump = false; // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -index b55640ed068becd40cbb7d7507b4e67ae9ee792a..79c2f0482a7adbd50e1063edf2839f4fb3ab0847 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -@@ -60,6 +60,64 @@ public class Phantom extends FlyingMob implements Enemy { - this.lookControl = new Phantom.PhantomLookControl(this); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.phantomRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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.PhantomFlames flames = new org.purpurmc.purpur.entity.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 -+ - @Override - public boolean isFlapping() { - return (this.getUniqueFlapTickOffset() + this.tickCount) % Phantom.TICKS_PER_FLAP == 0; -@@ -72,9 +130,11 @@ public class Phantom extends FlyingMob implements Enemy { - - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new Phantom.PhantomAttackStrategyGoal()); - this.goalSelector.addGoal(2, new Phantom.PhantomSweepAttackGoal()); - this.goalSelector.addGoal(3, new Phantom.PhantomCircleAroundAnchorGoal()); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new Phantom.PhantomAttackPlayerTargetGoal()); - } - -@@ -145,6 +205,7 @@ public class Phantom extends FlyingMob implements Enemy { - @Override - public void aiStep() { - if (this.isAlive() && shouldBurnInDay && this.isSunBurnTick()) { // Paper - Configurable Burning -+ if (getRider() == null || !this.isControllable()) // Purpur - this.setSecondsOnFire(8); - } - -@@ -264,7 +325,7 @@ public class Phantom extends FlyingMob implements Enemy { - private AttackPhase() {} - } - -- private class PhantomMoveControl extends MoveControl { -+ private class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - - private float speed = 0.1F; - -@@ -272,8 +333,19 @@ public class Phantom extends FlyingMob implements Enemy { - super(entity); - } - -+ // Purpur start -+ 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 -+ - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (Phantom.this.horizontalCollision) { - Phantom.this.setYRot(Phantom.this.getYRot() + 180.0F); - this.speed = 0.1F; -@@ -319,14 +391,20 @@ public class Phantom extends FlyingMob implements Enemy { - } - } - -- private class PhantomLookControl extends LookControl { -+ private class PhantomLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - - public PhantomLookControl(Mob entity) { - super(entity); - } - -+ // Purpur start -+ public void purpurTick(Player rider) { -+ setYawPitch(rider.yRot, -rider.xRotO * 0.75F); -+ } -+ // Purpur end -+ - @Override -- public void tick() {} -+ public void vanillaTick() {} // Purpur - } - - private class PhantomBodyRotationControl extends BodyRotationControl { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Pillager.java b/src/main/java/net/minecraft/world/entity/monster/Pillager.java -index d77e6d1722bbaeaa921d56fbcb2fdf9757fe74fe..9223c5cc4d5a1755020eb91ec7fea4943ffe499e 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java -@@ -63,15 +63,34 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.pillagerRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.pillagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.pillagerControllable; -+ } -+ // Purpur end -+ - @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 - this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F)); - this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0D, 8.0F)); - this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); - 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 - 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/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -index 4a7c6277301aacf83ee1ff5678c97d001e0cb2b9..aa33c5c709babb88d017d47e43de7a06954b940a 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -69,14 +69,39 @@ public class Ravager extends Raider { - this.xpReward = 20; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.ravagerRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.ravagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.ravagerControllable; -+ } -+ -+ @Override -+ public void onMount(Player rider) { -+ super.onMount(rider); -+ getNavigation().stop(); -+ } -+ // Purpur end -+ - @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 - this.goalSelector.addGoal(4, new Ravager.RavagerMeleeAttackGoal()); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4D)); - 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 - this.targetSelector.addGoal(2, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entityliving) -> { -@@ -150,7 +175,7 @@ public class Ravager extends Raider { - @Override - public void aiStep() { - super.aiStep(); -- if (this.isAlive()) { -+ if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur - if (this.isImmobile()) { - this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0D); - } else { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -index a9dfe190f46230077e2e1bf9aacbf5375651f216..9507e4817e62c749726e4cf9914f51da0aeaba71 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -@@ -92,12 +92,31 @@ public class Shulker extends AbstractGolem implements Enemy { - this.lookControl = new Shulker.ShulkerLookControl(this); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.shulkerRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.shulkerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.shulkerControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - 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 - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{this.getClass()})).setAlertOthers()); - this.targetSelector.addGoal(2, new Shulker.ShulkerNearestAttackGoal(this)); - this.targetSelector.addGoal(3, new Shulker.ShulkerDefenseAttackGoal(this)); -@@ -598,7 +617,7 @@ public class Shulker extends AbstractGolem implements Enemy { - return b0 != 16 && b0 <= 15 ? DyeColor.byId(b0) : null; - } - -- private class ShulkerLookControl extends LookControl { -+ private class ShulkerLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - - public ShulkerLookControl(Mob entity) { - super(entity); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -index 97c2c1647fa12650e5963c7c1c746fec2429e3d7..46539619fedda76185dc40d24f74df0b93067d61 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -@@ -42,14 +42,33 @@ public class Silverfish extends Monster { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.silverfishRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.silverfishRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.silverfishControllable; -+ } -+ // Purpur end -+ - @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 - this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level)); - this.goalSelector.addGoal(3, this.friendsGoal); - this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(5, new Silverfish.SilverfishMergeWithStoneGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -index 3a3f3358c4bbd16bdcadc56c6a865ecfb942ad54..e73644f453bed8523bfad47764018362a416b5ea 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -@@ -25,6 +25,23 @@ public class Skeleton extends AbstractSkeleton { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.skeletonRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.skeletonRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.skeletonControllable; -+ } -+ // Purpur end -+ - @Override - protected void defineSynchedData() { - super.defineSynchedData(); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index 2e819d32915941bd77034ce599eb787610a6d666..a76cc2a88a14db90901869b9bcb24e9e045501c9 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -65,18 +65,55 @@ public class Slime extends Mob implements Enemy { - public float squish; - public float oSquish; - private boolean wasOnGround; -+ protected boolean actualJump; // Purpur - - public Slime(EntityType type, Level world) { - super(type, world); - this.moveControl = new Slime.SlimeMoveControl(this); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.slimeRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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 -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - 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 - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> { - return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; - })); -@@ -371,11 +408,12 @@ public class Slime extends Mob implements Enemy { - } - - @Override -- protected void jumpFromGround() { -+ public void jumpFromGround() { // Purpur - protected -> public - Vec3 vec3d = this.getDeltaMovement(); - - this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z); - this.hasImpulse = true; -+ this.actualJump = false; // Purpur - } - - @Nullable -@@ -408,7 +446,7 @@ public class Slime extends Mob implements Enemy { - return super.getDimensions(pose).scale(0.255F * (float) this.getSize()); - } - -- private static class SlimeMoveControl extends MoveControl { -+ private static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - - private float yRot; - private int jumpDelay; -@@ -427,21 +465,33 @@ public class Slime extends Mob implements Enemy { - } - - public void setWantedMovement(double speed) { -- this.speedModifier = speed; -+ this.setSpeedModifier(speed); // Purpur - this.operation = MoveControl.Operation.MOVE_TO; - } - - @Override - public void tick() { -+ // Purpur start -+ 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 - 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 - this.mob.setZza(0.0F); - } else { - this.operation = MoveControl.Operation.WAIT; - if (this.mob.isOnGround()) { -- 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 - if (this.jumpDelay-- <= 0) { - this.jumpDelay = this.slime.getJumpDelay(); - if (this.isAggressive) { -@@ -458,7 +508,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 - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index 05b6c07c0705c7d8741c77baa87982e8e278dc97..a1870354e1ea5d4ef4248c04f9fe8a3dc93b9d8b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -51,14 +51,33 @@ public class Spider extends Monster { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.spiderRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.spiderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.spiderControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - 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.8D)); - 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 - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(2, new Spider.SpiderTargetGoal<>(this, Player.class)); - this.targetSelector.addGoal(3, new Spider.SpiderTargetGoal<>(this, IronGolem.class)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Stray.java b/src/main/java/net/minecraft/world/entity/monster/Stray.java -index baf614d2dbfb54d25fe06b28709ba2b7176c5213..b97862f653fe0aca93335fdd1ef0cf871626a58c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Stray.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java -@@ -21,6 +21,23 @@ public class Stray extends AbstractSkeleton { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.strayRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.strayRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.strayControllable; -+ } -+ // Purpur end -+ - public static boolean checkStraySpawnRules(EntityType type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) { - BlockPos blockPos = pos; - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java -index 0676f452280a9148546c35d1113932b5adc1fc3d..f809b68b0250670c07a2e0ed0f62efa8499b133f 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Strider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java -@@ -96,6 +96,23 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.striderRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.striderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.striderControllable; -+ } -+ // Purpur end -+ - public static boolean checkStriderSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) { - BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); - -@@ -157,6 +174,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - @Override - protected void registerGoals() { - this.panicGoal = new PanicGoal(this, 1.65D); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, this.panicGoal); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); - this.temptGoal = new TemptGoal(this, 1.4D, Strider.TEMPT_ITEMS, false); -@@ -437,7 +455,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - if (!enuminteractionresult.consumesAction()) { - ItemStack itemstack = player.getItemInHand(hand); - -- return itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : InteractionResult.PASS; -+ return itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : tryRide(player, hand); // Purpur - } else { - if (flag && !this.isSilent()) { - this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.STRIDER_EAT, this.getSoundSource(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java -index 1271b6d6a5aa4d9f46aba30d2cc912b942e71176..0f67b219e761712e9befbef4a6012a5e247c9770 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vex.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java -@@ -58,6 +58,50 @@ public class Vex extends Monster { - this.xpReward = 3; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.vexRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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(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 -+ - @Override - public boolean isFlapping() { - return this.tickCount % Vex.TICKS_PER_FLAP == 0; -@@ -71,7 +115,7 @@ public class Vex extends Monster { - - @Override - public void tick() { -- this.noPhysics = true; -+ this.noPhysics = getRider() == null || !this.isControllable(); // Purpur - super.tick(); - this.noPhysics = false; - this.setNoGravity(true); -@@ -86,17 +130,19 @@ public class Vex extends Monster { - 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 - 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 - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{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.0D).add(Attributes.ATTACK_DAMAGE, 4.0D); -+ return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur; - } - - @Override -@@ -217,14 +263,14 @@ public class Vex extends Monster { - this.setDropChance(EquipmentSlot.MAINHAND, 0.0F); - } - -- private class VexMoveControl extends MoveControl { -+ private class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - - public VexMoveControl(Vex entityvex) { - super(entityvex); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (this.operation == MoveControl.Operation.MOVE_TO) { - Vec3 vec3d = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ()); - double d0 = vec3d.length(); -@@ -233,7 +279,7 @@ public class Vex extends Monster { - this.operation = MoveControl.Operation.WAIT; - Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5D)); - } else { -- Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.speedModifier * 0.05D / d0))); -+ Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.getSpeedModifier() * 0.05D / d0))); // Purpur - if (Vex.this.getTarget() == null) { - Vec3 vec3d1 = Vex.this.getDeltaMovement(); - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -index 430492facbaa80471875da07bf4b9b601777d1fd..daf76124bfb812141ba7cf66a30599e9a01d4fcc 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -@@ -58,15 +58,34 @@ public class Vindicator extends AbstractIllager { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.vindicatorRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.vindicatorRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.vindicatorControllable; -+ } -+ // Purpur end -+ - @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 - this.goalSelector.addGoal(1, new Vindicator.VindicatorBreakDoorGoal(this)); - this.goalSelector.addGoal(2, new AbstractIllager.RaiderOpenDoorGoal(this)); - this.goalSelector.addGoal(3, new Raider.HoldGroundAttackGoal(this, 10.0F)); - this.goalSelector.addGoal(4, new Vindicator.VindicatorMeleeAttackGoal(this)); -- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur -+ 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)); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java -index f9eb4a3a37454de78c65f895a82e67a854b6909b..7775f1428a455f0d52f43ed07102cb242f6d23ad 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java -@@ -59,6 +59,23 @@ public class Witch extends Raider implements RangedAttackMob { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.witchRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.witchRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.witchControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - super.registerGoals(); -@@ -67,10 +84,12 @@ public class Witch extends Raider implements RangedAttackMob { - }); - this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, (Predicate) null); - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 60, 10.0F)); - this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - 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 - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[]{Raider.class})); - this.targetSelector.addGoal(2, this.healRaidersGoal); - this.targetSelector.addGoal(3, this.attackPlayersGoal); -diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -index 6acc46c3a6fe7648d2cc4d0aaef063633c74c20d..54020eefe310f7cf653e97dfb525713d098e85ed 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -@@ -34,6 +34,23 @@ public class WitherSkeleton extends AbstractSkeleton { - this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.witherSkeletonRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.witherSkeletonRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.witherSkeletonControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractPiglin.class, true)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -index 056e4c2f7f632dadc0015710c032a0b718d46ff9..a6aaffc41a39be4ae1da44d54a27d324927e47d0 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -@@ -68,6 +68,23 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { - this.xpReward = 5; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.zoglinRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.zoglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.zoglinControllable; -+ } -+ // Purpur end -+ - @Override - protected Brain.Provider brainProvider() { - return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); -@@ -90,11 +107,11 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { - } - - private static void initIdleActivity(Brain brain) { -- brain.addActivity(Activity.IDLE, 10, ImmutableList.of(new StartAttacking<>(Zoglin::findNearestValidAttackTarget), new RunSometimes(new SetEntityLookTarget(8.0F), UniformInt.of(30, 60)), new RunOne(ImmutableList.of(Pair.of(new RandomStroll(0.4F), 2), Pair.of(new SetWalkTargetFromLookTarget(0.4F, 3), 2), Pair.of(new DoNothing(30, 60), 1))))); -+ brain.addActivity(Activity.IDLE, 10, ImmutableList.of(new StartAttacking<>(Zoglin::findNearestValidAttackTarget), new RunSometimes<>(new SetEntityLookTarget(8.0F), UniformInt.of(30, 60)), new RunOne<>(ImmutableList.of(Pair.of(new RandomStroll(0.4F), 2), Pair.of(new SetWalkTargetFromLookTarget(0.4F, 3), 2), Pair.of(new DoNothing(30, 60), 1))))); // Purpur - decompile fix - } - - private static void initFightActivity(Brain brain) { -- brain.addActivityAndRemoveMemoryWhenStopped(Activity.FIGHT, 10, ImmutableList.of(new SetWalkTargetFromAttackTargetIfTargetOutOfReach(1.0F), new RunIf<>(Zoglin::isAdult, new MeleeAttack(40)), new RunIf<>(Zoglin::isBaby, new MeleeAttack(15)), new StopAttackingIfTargetInvalid()), MemoryModuleType.ATTACK_TARGET); -+ brain.addActivityAndRemoveMemoryWhenStopped(Activity.FIGHT, 10, ImmutableList.of(new SetWalkTargetFromAttackTargetIfTargetOutOfReach(1.0F), new RunIf<>(Zoglin::isAdult, new MeleeAttack(40)), new RunIf<>(Zoglin::isBaby, new MeleeAttack(15)), new StopAttackingIfTargetInvalid<>()), MemoryModuleType.ATTACK_TARGET); // Purpur - decompile fix - } - - private Optional findNearestValidAttackTarget() { -@@ -183,7 +200,7 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { - - @Override - public Brain getBrain() { -- return super.getBrain(); -+ return (Brain) super.getBrain(); // Purpur - decompile error - } - - protected void updateActivity() { -@@ -200,6 +217,7 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { - @Override - protected void customServerAiStep() { - this.level.getProfiler().push("zoglinBrain"); -+ if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider - this.getBrain().tick((ServerLevel)this.level, this); - this.level.getProfiler().pop(); - this.updateActivity(); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index cd88413f30632307faba63572915656b6a8469f7..8d121dcb646d6d77bde48540b14c5d3770d0b276 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -104,11 +104,30 @@ public class Zombie extends Monster { - this(EntityType.ZOMBIE, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.zombieRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.zombieRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.zombieControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - if (level.paperConfig.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - 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 - this.addBehaviourGoals(); - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -index fc6639bd33a13c84c84603db7f9bbb1c5df239a4..fff7aeab25f3a48136f0e5d75cc66a1348091178 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -79,6 +79,23 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - }); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.zombieVillagerRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.zombieVillagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.zombieVillagerControllable; -+ } -+ // Purpur end -+ - @Override - protected void defineSynchedData() { - super.defineSynchedData(); -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -index 6fe49abb9d9a273787828e72061e16a7b053e50a..c2cd88e8ef4b543635b02e1fdd2ac257f64a05c2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -@@ -59,6 +59,23 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.zombifiedPiglinRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.zombifiedPiglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.zombifiedPiglinControllable; -+ } -+ // Purpur end -+ - @Override - public void setPersistentAngerTarget(@Nullable UUID angryAt) { - this.persistentAngerTarget = angryAt; -diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -index 127c839c4f9b0ade39065588e275984ea639380b..cb1d7f09195473da382aadb82ffe552a95052254 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -@@ -67,6 +67,23 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { - this.xpReward = 5; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.hoglinRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.hoglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.hoglinControllable; -+ } -+ // Purpur end -+ - @Override - public boolean canBeLeashed(Player player) { - return !this.isLeashed(); -@@ -130,7 +147,7 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { - @Override - protected void customServerAiStep() { - this.level.getProfiler().push("hoglinBrain"); -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick((ServerLevel)this.level, this); - this.level.getProfiler().pop(); - HoglinAi.updateActivity(this); -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -index 05e05793b1fd10cceb4b19ac60c8a10b0c5b7d45..d2c7a7e38a3bac24a176f51d7c143ecedd4ce215 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -@@ -96,6 +96,23 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - this.xpReward = 5; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.piglinRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.piglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.piglinControllable; -+ } -+ // Purpur end -+ - @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -@@ -292,7 +309,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - @Override - protected void customServerAiStep() { - this.level.getProfiler().push("piglinBrain"); -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick((ServerLevel) this.level, this); - this.level.getProfiler().pop(); - PiglinAi.updateActivity(this); -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -index ea0040a3494709efb4819c7530dbcc37aa62e86a..8ad63ae82a8b6a26c03e53b6d043128a8e038863 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -@@ -40,6 +40,23 @@ public class PiglinBrute extends AbstractPiglin { - this.xpReward = 20; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.piglinBruteRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.piglinBruteRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.piglinBruteControllable; -+ } -+ // Purpur end -+ - public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 50.0D).add(Attributes.MOVEMENT_SPEED, (double)0.35F).add(Attributes.ATTACK_DAMAGE, 7.0D); - } -@@ -69,7 +86,7 @@ public class PiglinBrute extends AbstractPiglin { - - @Override - public Brain getBrain() { -- return super.getBrain(); -+ return (Brain) super.getBrain(); // Purpur - decompile error - } - - @Override -@@ -85,6 +102,7 @@ public class PiglinBrute extends AbstractPiglin { - @Override - protected void customServerAiStep() { - this.level.getProfiler().push("piglinBruteBrain"); -+ if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider - this.getBrain().tick((ServerLevel)this.level, this); - this.level.getProfiler().pop(); - PiglinBruteAi.updateActivity(this); -diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 63903bd02c6f31d3190a20d106997d4f7b777df8..c6d232f73dd1cfe5c88848e5a89f8716c3b7dee4 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -155,6 +155,28 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - this.setVillagerData(this.getVillagerData().setType(type).setProfession(VillagerProfession.NONE)); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.villagerRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return 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)); -+ } -+ // Purpur end -+ - @Override - public Brain getBrain() { - return (Brain) super.getBrain(); // CraftBukkit - decompile error -@@ -252,7 +274,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - this.level.getProfiler().push("villagerBrain"); - // Pufferfish start - if (!inactive) { -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick((ServerLevel) this.level, this); // Paper - } - // Pufferfish end -@@ -312,7 +334,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isSleeping()) { - if (this.isBaby()) { - this.setUnhappy(); -- return InteractionResult.sidedSuccess(this.level.isClientSide); -+ return tryRide(player, hand); // Purpur - } else { - boolean flag = this.getOffers().isEmpty(); - -@@ -325,8 +347,9 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - - if (flag) { -- return InteractionResult.sidedSuccess(this.level.isClientSide); -+ return tryRide(player, hand); // Purpur - } else { -+ if (level.purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur - if (!this.level.isClientSide && !this.offers.isEmpty()) { - this.startTrading(player); - } -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -index 86e1ba898d6b92735258419fa74352e5116226dc..2ae9a5da91ad20f1b566a686dcc8d4f14c62ecc5 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -66,6 +66,23 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set. - } - -+ // Purpur - start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.wanderingTraderRidable; -+ } -+ -+ @Override -+ public boolean rideableUnderWater() { -+ return level.purpurConfig.wanderingTraderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.wanderingTraderControllable; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -@@ -113,8 +130,9 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - } - - if (this.getOffers().isEmpty()) { -- return InteractionResult.sidedSuccess(this.level.isClientSide); -+ return tryRide(player, hand); // Purpur - } else { -+ if (level.purpurConfig.wanderingTraderRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur - if (!this.level.isClientSide) { - this.setTradingPlayer(player); - this.openTradingScreen(player, this.getDisplayName(), 1); -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 2483d7df7f1bf94344afd38b37602c645a4a2dff..41b1468d3c5f3c4ed11fe1eeb742a7f3826955c7 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -192,6 +192,19 @@ public abstract class Player extends LivingEntity { - } - // CraftBukkit end - -+ // Purpur start -+ 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 -+ - public Player(Level world, BlockPos pos, float yaw, GameProfile profile) { - super(EntityType.PLAYER, world); - this.lastItemInMainHand = ItemStack.EMPTY; -diff --git a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -index 4132c1113f5437a776e5e3c1cb306904775aed88..1a945a32c3d3705a318ebca72a365931a8c001b7 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -@@ -27,6 +27,12 @@ public class LlamaSpit extends Projectile { - this.setPos(owner.getX() - (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(owner.yBodyRot * 0.017453292F), owner.getEyeY() - 0.10000000149011612D, owner.getZ() + (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(owner.yBodyRot * 0.017453292F)); - } - -+ // Purpur start -+ public void super_tick() { -+ super.tick(); -+ } -+ // Purpur end -+ - @Override - public void tick() { - super.tick(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index a92755211e3d42934b5efaa3f201c6c19ab7d2b4..318456621faf484dbad479b2873ae647e0c0f037 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1311,4 +1311,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed. - } - // Paper end -+ -+ // Purpur start -+ @Override -+ public org.bukkit.entity.Player getRider() { -+ Player rider = getHandle().getRider(); -+ return rider != null ? (org.bukkit.entity.Player) rider.getBukkitEntity() : null; -+ } -+ -+ @Override -+ public boolean hasRider() { -+ return getHandle().getRider() != null; -+ } -+ -+ @Override -+ public boolean isRidable() { -+ return getHandle().isRidable(); -+ } -+ -+ @Override -+ public boolean isRidableInWater() { -+ return getHandle().rideableUnderWater(); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index ceacb62aef0dd0f73f1542a1898ceb62c3f4c367..97108c2317a38e11d45fdbef448dca5cb3ddad1c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -543,6 +543,15 @@ public class CraftEventFactory { - } - craftServer.getPluginManager().callEvent(event); - -+ // Purpur start -+ if (who != null) { -+ switch (action) { -+ case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> who.processClick(InteractionHand.MAIN_HAND); -+ case RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR -> who.processClick(InteractionHand.OFF_HAND); -+ } -+ } -+ // Purpur end -+ - return event; - } - -@@ -979,6 +988,7 @@ public class CraftEventFactory { - damageCause = DamageCause.ENTITY_EXPLOSION; - } - event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API -+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - } - event.setCancelled(cancelled); - -@@ -1083,6 +1093,7 @@ public class CraftEventFactory { - } else { - entity.lastDamageCancelled = true; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled - } -+ damager.getHandle().processClick(InteractionHand.MAIN_HAND); // Purpur - return event; - } - -@@ -1142,6 +1153,7 @@ public class CraftEventFactory { - EntityDamageEvent event; - if (damager != null) { - event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions, critical); // Paper - add critical damage API -+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - } else { - event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); - } -diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -index b3bfc56859d00f9e27bd1d230dd19b92985b5718..e8150d1bec60f7e32d5475c8402fd7b53df359e7 100644 ---- a/src/main/java/org/purpurmc/purpur/PurpurConfig.java -+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -@@ -167,4 +167,9 @@ public class PurpurConfig { - } - return builder.build(); - } -+ -+ public static String cannotRideMob = "You cannot mount that mob"; -+ private static void messages() { -+ cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob); -+ } - } -diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -index 0ce93836629522c2ff2a57226583009302271daf..969c78aef40aae497a76dd7f3e9c230577a8d31f 100644 ---- a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -@@ -88,4 +88,668 @@ public class PurpurWorldConfig { - final Map value = PurpurConfig.getMap("world-settings." + worldName + "." + path, null); - return value.isEmpty() ? fallback : value; - } -+ -+ public boolean babiesAreRidable = true; -+ public boolean untamedTamablesAreRidable = true; -+ public boolean useNightVisionWhenRiding = false; -+ 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); -+ } -+ -+ public boolean axolotlRidable = false; -+ public boolean axolotlControllable = true; -+ private void axolotlSettings() { -+ axolotlRidable = getBoolean("mobs.axolotl.ridable", axolotlRidable); -+ axolotlControllable = getBoolean("mobs.axolotl.controllable", axolotlControllable); -+ } -+ -+ public boolean batRidable = false; -+ public boolean batRidableInWater = false; -+ public boolean batControllable = true; -+ public double batMaxY = 320D; -+ 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); -+ } -+ -+ public boolean beeRidable = false; -+ public boolean beeRidableInWater = false; -+ public boolean beeControllable = true; -+ public double beeMaxY = 320D; -+ 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); -+ } -+ -+ public boolean blazeRidable = false; -+ public boolean blazeRidableInWater = false; -+ public boolean blazeControllable = true; -+ public double blazeMaxY = 320D; -+ 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); -+ } -+ -+ public boolean catRidable = false; -+ public boolean catRidableInWater = false; -+ public boolean catControllable = true; -+ private void catSettings() { -+ catRidable = getBoolean("mobs.cat.ridable", catRidable); -+ catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); -+ catControllable = getBoolean("mobs.cat.controllable", catControllable); -+ } -+ -+ public boolean caveSpiderRidable = false; -+ public boolean caveSpiderRidableInWater = false; -+ public boolean caveSpiderControllable = true; -+ 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); -+ } -+ -+ public boolean chickenRidable = false; -+ public boolean chickenRidableInWater = false; -+ public boolean chickenControllable = true; -+ private void chickenSettings() { -+ chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); -+ chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); -+ chickenControllable = getBoolean("mobs.chicken.controllable", chickenControllable); -+ } -+ -+ public boolean codRidable = false; -+ public boolean codControllable = true; -+ private void codSettings() { -+ codRidable = getBoolean("mobs.cod.ridable", codRidable); -+ codControllable = getBoolean("mobs.cod.controllable", codControllable); -+ } -+ -+ public boolean cowRidable = false; -+ public boolean cowRidableInWater = false; -+ public boolean cowControllable = true; -+ private void cowSettings() { -+ cowRidable = getBoolean("mobs.cow.ridable", cowRidable); -+ cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); -+ cowControllable = getBoolean("mobs.cow.controllable", cowControllable); -+ } -+ -+ public boolean creeperRidable = false; -+ public boolean creeperRidableInWater = false; -+ public boolean creeperControllable = true; -+ private void creeperSettings() { -+ creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); -+ creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); -+ creeperControllable = getBoolean("mobs.creeper.controllable", creeperControllable); -+ } -+ -+ public boolean dolphinRidable = false; -+ public boolean dolphinControllable = true; -+ public int dolphinSpitCooldown = 20; -+ public float dolphinSpitSpeed = 1.0F; -+ public float dolphinSpitDamage = 2.0F; -+ 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); -+ } -+ -+ public boolean donkeyRidableInWater = false; -+ private void donkeySettings() { -+ donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater); -+ } -+ -+ public boolean drownedRidable = false; -+ public boolean drownedRidableInWater = false; -+ public boolean drownedControllable = true; -+ private void drownedSettings() { -+ drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); -+ drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); -+ drownedControllable = getBoolean("mobs.drowned.controllable", drownedControllable); -+ } -+ -+ public boolean elderGuardianRidable = false; -+ public boolean elderGuardianControllable = true; -+ private void elderGuardianSettings() { -+ elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable); -+ elderGuardianControllable = getBoolean("mobs.elder_guardian.controllable", elderGuardianControllable); -+ } -+ -+ public boolean enderDragonRidable = false; -+ public boolean enderDragonRidableInWater = false; -+ public boolean enderDragonControllable = true; -+ public double enderDragonMaxY = 320D; -+ 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); -+ } -+ -+ public boolean endermanRidable = false; -+ public boolean endermanRidableInWater = false; -+ public boolean endermanControllable = true; -+ private void endermanSettings() { -+ endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); -+ endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); -+ endermanControllable = getBoolean("mobs.enderman.controllable", endermanControllable); -+ } -+ -+ public boolean endermiteRidable = false; -+ public boolean endermiteRidableInWater = false; -+ public boolean endermiteControllable = true; -+ private void endermiteSettings() { -+ endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable); -+ endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater); -+ endermiteControllable = getBoolean("mobs.endermite.controllable", endermiteControllable); -+ } -+ -+ public boolean evokerRidable = false; -+ public boolean evokerRidableInWater = false; -+ public boolean evokerControllable = true; -+ private void evokerSettings() { -+ evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable); -+ evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater); -+ evokerControllable = getBoolean("mobs.evoker.controllable", evokerControllable); -+ } -+ -+ public boolean foxRidable = false; -+ public boolean foxRidableInWater = false; -+ public boolean foxControllable = true; -+ private void foxSettings() { -+ foxRidable = getBoolean("mobs.fox.ridable", foxRidable); -+ foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); -+ foxControllable = getBoolean("mobs.fox.controllable", foxControllable); -+ } -+ -+ public boolean ghastRidable = false; -+ public boolean ghastRidableInWater = false; -+ public boolean ghastControllable = true; -+ public double ghastMaxY = 320D; -+ 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); -+ } -+ -+ public boolean giantRidable = false; -+ public boolean giantRidableInWater = false; -+ public boolean giantControllable = true; -+ private void giantSettings() { -+ giantRidable = getBoolean("mobs.giant.ridable", giantRidable); -+ giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater); -+ giantControllable = getBoolean("mobs.giant.controllable", giantControllable); -+ } -+ -+ public boolean glowSquidRidable = false; -+ public boolean glowSquidControllable = true; -+ private void glowSquidSettings() { -+ glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable); -+ glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable); -+ } -+ -+ public boolean goatRidable = false; -+ public boolean goatRidableInWater = false; -+ public boolean goatControllable = true; -+ private void goatSettings() { -+ goatRidable = getBoolean("mobs.goat.ridable", goatRidable); -+ goatRidableInWater = getBoolean("mobs.goat.ridable-in-water", goatRidableInWater); -+ goatControllable = getBoolean("mobs.goat.controllable", goatControllable); -+ } -+ -+ public boolean guardianRidable = false; -+ public boolean guardianControllable = true; -+ private void guardianSettings() { -+ guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable); -+ guardianControllable = getBoolean("mobs.guardian.controllable", guardianControllable); -+ } -+ -+ public boolean hoglinRidable = false; -+ public boolean hoglinRidableInWater = false; -+ public boolean hoglinControllable = true; -+ private void hoglinSettings() { -+ hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable); -+ hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater); -+ hoglinControllable = getBoolean("mobs.hoglin.controllable", hoglinControllable); -+ } -+ -+ public boolean horseRidableInWater = false; -+ private void horseSettings() { -+ horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater); -+ } -+ -+ public boolean huskRidable = false; -+ public boolean huskRidableInWater = false; -+ public boolean huskControllable = true; -+ private void huskSettings() { -+ huskRidable = getBoolean("mobs.husk.ridable", huskRidable); -+ huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater); -+ huskControllable = getBoolean("mobs.husk.controllable", huskControllable); -+ } -+ -+ public boolean illusionerRidable = false; -+ public boolean illusionerRidableInWater = false; -+ public boolean illusionerControllable = true; -+ private void illusionerSettings() { -+ illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable); -+ illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater); -+ illusionerControllable = getBoolean("mobs.illusioner.controllable", illusionerControllable); -+ } -+ -+ public boolean ironGolemRidable = false; -+ public boolean ironGolemRidableInWater = false; -+ public boolean ironGolemControllable = true; -+ public boolean ironGolemCanSwim = 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); -+ } -+ -+ public boolean llamaRidable = false; -+ public boolean llamaRidableInWater = false; -+ public boolean llamaControllable = true; -+ private void llamaSettings() { -+ llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); -+ llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); -+ llamaControllable = getBoolean("mobs.llama.controllable", llamaControllable); -+ } -+ -+ public boolean magmaCubeRidable = false; -+ public boolean magmaCubeRidableInWater = false; -+ public boolean magmaCubeControllable = true; -+ 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); -+ } -+ -+ public boolean mooshroomRidable = false; -+ public boolean mooshroomRidableInWater = false; -+ public boolean mooshroomControllable = true; -+ private void mooshroomSettings() { -+ mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable); -+ mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater); -+ mooshroomControllable = getBoolean("mobs.mooshroom.controllable", mooshroomControllable); -+ } -+ -+ public boolean muleRidableInWater = false; -+ private void muleSettings() { -+ muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater); -+ } -+ -+ public boolean ocelotRidable = false; -+ public boolean ocelotRidableInWater = false; -+ public boolean ocelotControllable = true; -+ private void ocelotSettings() { -+ ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable); -+ ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater); -+ ocelotControllable = getBoolean("mobs.ocelot.controllable", ocelotControllable); -+ } -+ -+ public boolean pandaRidable = false; -+ public boolean pandaRidableInWater = false; -+ public boolean pandaControllable = true; -+ private void pandaSettings() { -+ pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable); -+ pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater); -+ pandaControllable = getBoolean("mobs.panda.controllable", pandaControllable); -+ } -+ -+ public boolean parrotRidable = false; -+ public boolean parrotRidableInWater = false; -+ public boolean parrotControllable = true; -+ public double parrotMaxY = 320D; -+ 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); -+ } -+ -+ public boolean phantomRidable = false; -+ public boolean phantomRidableInWater = false; -+ public boolean phantomControllable = true; -+ public double phantomMaxY = 320D; -+ public float phantomFlameDamage = 1.0F; -+ public int phantomFlameFireTime = 8; -+ public boolean phantomAllowGriefing = false; -+ 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); -+ } -+ -+ public boolean pigRidable = false; -+ public boolean pigRidableInWater = false; -+ public boolean pigControllable = true; -+ private void pigSettings() { -+ pigRidable = getBoolean("mobs.pig.ridable", pigRidable); -+ pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); -+ pigControllable = getBoolean("mobs.pig.controllable", pigControllable); -+ } -+ -+ public boolean piglinRidable = false; -+ public boolean piglinRidableInWater = false; -+ public boolean piglinControllable = true; -+ private void piglinSettings() { -+ piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable); -+ piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater); -+ piglinControllable = getBoolean("mobs.piglin.controllable", piglinControllable); -+ } -+ -+ public boolean piglinBruteRidable = false; -+ public boolean piglinBruteRidableInWater = false; -+ public boolean piglinBruteControllable = true; -+ 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); -+ } -+ -+ public boolean pillagerRidable = false; -+ public boolean pillagerRidableInWater = false; -+ public boolean pillagerControllable = true; -+ private void pillagerSettings() { -+ pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable); -+ pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater); -+ pillagerControllable = getBoolean("mobs.pillager.controllable", pillagerControllable); -+ } -+ -+ public boolean polarBearRidable = false; -+ public boolean polarBearRidableInWater = false; -+ public boolean polarBearControllable = true; -+ 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); -+ } -+ -+ public boolean pufferfishRidable = false; -+ public boolean pufferfishControllable = true; -+ private void pufferfishSettings() { -+ pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable); -+ pufferfishControllable = getBoolean("mobs.pufferfish.controllable", pufferfishControllable); -+ } -+ -+ public boolean rabbitRidable = false; -+ public boolean rabbitRidableInWater = false; -+ public boolean rabbitControllable = true; -+ private void rabbitSettings() { -+ rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); -+ rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); -+ rabbitControllable = getBoolean("mobs.rabbit.controllable", rabbitControllable); -+ } -+ -+ public boolean ravagerRidable = false; -+ public boolean ravagerRidableInWater = false; -+ public boolean ravagerControllable = true; -+ private void ravagerSettings() { -+ ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); -+ ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); -+ ravagerControllable = getBoolean("mobs.ravager.controllable", ravagerControllable); -+ } -+ -+ public boolean salmonRidable = false; -+ public boolean salmonControllable = true; -+ private void salmonSettings() { -+ salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable); -+ salmonControllable = getBoolean("mobs.salmon.controllable", salmonControllable); -+ } -+ -+ public boolean sheepRidable = false; -+ public boolean sheepRidableInWater = false; -+ public boolean sheepControllable = true; -+ private void sheepSettings() { -+ sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); -+ sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); -+ sheepControllable = getBoolean("mobs.sheep.controllable", sheepControllable); -+ } -+ -+ public boolean shulkerRidable = false; -+ public boolean shulkerRidableInWater = false; -+ public boolean shulkerControllable = true; -+ private void shulkerSettings() { -+ shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable); -+ shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater); -+ shulkerControllable = getBoolean("mobs.shulker.controllable", shulkerControllable); -+ } -+ -+ public boolean silverfishRidable = false; -+ public boolean silverfishRidableInWater = false; -+ public boolean silverfishControllable = true; -+ private void silverfishSettings() { -+ silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable); -+ silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater); -+ silverfishControllable = getBoolean("mobs.silverfish.controllable", silverfishControllable); -+ } -+ -+ public boolean skeletonRidable = false; -+ public boolean skeletonRidableInWater = false; -+ public boolean skeletonControllable = true; -+ private void skeletonSettings() { -+ skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable); -+ skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater); -+ skeletonControllable = getBoolean("mobs.skeleton.controllable", skeletonControllable); -+ } -+ -+ public boolean skeletonHorseRidableInWater = true; -+ public boolean skeletonHorseCanSwim = false; -+ private void skeletonHorseSettings() { -+ skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater); -+ skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim); -+ } -+ -+ public boolean slimeRidable = false; -+ public boolean slimeRidableInWater = false; -+ public boolean slimeControllable = true; -+ private void slimeSettings() { -+ slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable); -+ slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater); -+ slimeControllable = getBoolean("mobs.slime.controllable", slimeControllable); -+ } -+ -+ public boolean snowGolemRidable = false; -+ public boolean snowGolemRidableInWater = false; -+ public boolean snowGolemControllable = true; -+ public boolean snowGolemLeaveTrailWhenRidden = 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); -+ } -+ -+ public boolean squidRidable = false; -+ public boolean squidControllable = true; -+ private void squidSettings() { -+ squidRidable = getBoolean("mobs.squid.ridable", squidRidable); -+ squidControllable = getBoolean("mobs.squid.controllable", squidControllable); -+ } -+ -+ public boolean spiderRidable = false; -+ public boolean spiderRidableInWater = false; -+ public boolean spiderControllable = true; -+ private void spiderSettings() { -+ spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable); -+ spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater); -+ spiderControllable = getBoolean("mobs.spider.controllable", spiderControllable); -+ } -+ -+ public boolean strayRidable = false; -+ public boolean strayRidableInWater = false; -+ public boolean strayControllable = true; -+ private void straySettings() { -+ strayRidable = getBoolean("mobs.stray.ridable", strayRidable); -+ strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater); -+ strayControllable = getBoolean("mobs.stray.controllable", strayControllable); -+ } -+ -+ public boolean striderRidable = false; -+ public boolean striderRidableInWater = false; -+ public boolean striderControllable = true; -+ private void striderSettings() { -+ striderRidable = getBoolean("mobs.strider.ridable", striderRidable); -+ striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); -+ striderControllable = getBoolean("mobs.strider.controllable", striderControllable); -+ } -+ -+ public boolean traderLlamaRidable = false; -+ public boolean traderLlamaRidableInWater = false; -+ public boolean traderLlamaControllable = true; -+ 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); -+ } -+ -+ public boolean tropicalFishRidable = false; -+ public boolean tropicalFishControllable = true; -+ private void tropicalFishSettings() { -+ tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable); -+ tropicalFishControllable = getBoolean("mobs.tropical_fish.controllable", tropicalFishControllable); -+ } -+ -+ public boolean turtleRidable = false; -+ public boolean turtleRidableInWater = false; -+ public boolean turtleControllable = true; -+ private void turtleSettings() { -+ turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable); -+ turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater); -+ turtleControllable = getBoolean("mobs.turtle.controllable", turtleControllable); -+ } -+ -+ public boolean vexRidable = false; -+ public boolean vexRidableInWater = false; -+ public boolean vexControllable = true; -+ public double vexMaxY = 320D; -+ 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); -+ } -+ -+ public boolean villagerRidable = false; -+ public boolean villagerRidableInWater = false; -+ public boolean villagerControllable = true; -+ private void villagerSettings() { -+ villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); -+ villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -+ villagerControllable = getBoolean("mobs.villager.controllable", villagerControllable); -+ } -+ -+ public boolean vindicatorRidable = false; -+ public boolean vindicatorRidableInWater = false; -+ public boolean vindicatorControllable = true; -+ private void vindicatorSettings() { -+ vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable); -+ vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater); -+ vindicatorControllable = getBoolean("mobs.vindicator.controllable", vindicatorControllable); -+ } -+ -+ public boolean wanderingTraderRidable = false; -+ public boolean wanderingTraderRidableInWater = false; -+ public boolean wanderingTraderControllable = true; -+ 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); -+ } -+ -+ public boolean witchRidable = false; -+ public boolean witchRidableInWater = false; -+ public boolean witchControllable = true; -+ private void witchSettings() { -+ witchRidable = getBoolean("mobs.witch.ridable", witchRidable); -+ witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater); -+ witchControllable = getBoolean("mobs.witch.controllable", witchControllable); -+ } -+ -+ public boolean witherRidable = false; -+ public boolean witherRidableInWater = false; -+ public boolean witherControllable = true; -+ public double witherMaxY = 320D; -+ 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); -+ } -+ -+ public boolean witherSkeletonRidable = false; -+ public boolean witherSkeletonRidableInWater = false; -+ public boolean witherSkeletonControllable = true; -+ 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); -+ } -+ -+ public boolean wolfRidable = false; -+ public boolean wolfRidableInWater = false; -+ public boolean wolfControllable = true; -+ private void wolfSettings() { -+ wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); -+ wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); -+ wolfControllable = getBoolean("mobs.wolf.controllable", wolfControllable); -+ } -+ -+ public boolean zoglinRidable = false; -+ public boolean zoglinRidableInWater = false; -+ public boolean zoglinControllable = true; -+ private void zoglinSettings() { -+ zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable); -+ zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater); -+ zoglinControllable = getBoolean("mobs.zoglin.controllable", zoglinControllable); -+ } -+ -+ public boolean zombieRidable = false; -+ public boolean zombieRidableInWater = false; -+ public boolean zombieControllable = true; -+ private void zombieSettings() { -+ zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); -+ zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); -+ zombieControllable = getBoolean("mobs.zombie.controllable", zombieControllable); -+ } -+ -+ public boolean zombieHorseRidableInWater = false; -+ public boolean zombieHorseCanSwim = false; -+ private void zombieHorseSettings() { -+ zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater); -+ zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim); -+ } -+ -+ public boolean zombieVillagerRidable = false; -+ public boolean zombieVillagerRidableInWater = false; -+ public boolean zombieVillagerControllable = true; -+ 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); -+ } -+ -+ public boolean zombifiedPiglinRidable = false; -+ public boolean zombifiedPiglinRidableInWater = false; -+ public boolean zombifiedPiglinControllable = true; -+ 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); -+ } - } -diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..315d76526a1a9e95d29384e4598d8d77a27466c7 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java -@@ -0,0 +1,63 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.entity.player.Player; -+ -+public class FlyingMoveControllerWASD extends MoveControllerWASD { -+ protected final float groundSpeedModifier; -+ protected int tooHighCooldown = 0; -+ protected boolean setGravityFlag = true; -+ -+ public FlyingMoveControllerWASD(Mob entity) { -+ this(entity, 1.0F); -+ } -+ -+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier) { -+ this(entity, groundSpeedModifier, true); -+ } -+ -+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, boolean setGravityFlag) { -+ super(entity); -+ this.groundSpeedModifier = groundSpeedModifier; -+ this.setGravityFlag = setGravityFlag; -+ } -+ -+ @Override -+ public void purpurTick(Player rider) { -+ float forward = Math.max(0.0F, rider.getForwardMot()); -+ float vertical = forward == 0.0F ? 0.0F : -(rider.xRotO / 45.0F); -+ float strafe = rider.getStrafeMot(); -+ -+ if (rider.jumping && 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! -+ } -+ -+ if (setGravityFlag) { -+ 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/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9383c07fa53141127106a1f289366a040960d52e ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java -@@ -0,0 +1,63 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+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) { -+ float forward = rider.getForwardMot(); -+ float strafe = rider.getStrafeMot() * 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 (rider.jumping && 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/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b8c25c96e95dd5ec3ad9fa4c41bd6c08e144832d ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java -@@ -0,0 +1,76 @@ -+package org.purpurmc.purpur.controller; -+ -+ -+import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; -+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(); -+ -+ entity.tracker.broadcast(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)); -+ } -+ -+ 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/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..21fd6ea2a482758a3016e3bc2cdebe2d89267481 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java -@@ -0,0 +1,89 @@ -+package org.purpurmc.purpur.controller; -+ -+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.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) { -+ float forward = rider.getForwardMot() * 0.5F; -+ float strafe = rider.getStrafeMot() * 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 (rider.jumping && 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/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ba2a37dad43e238e54632975abea8ee6fafaa9e0 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java -@@ -0,0 +1,50 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+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) { -+ float forward = rider.getForwardMot(); -+ float strafe = rider.getStrafeMot() * 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/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d55f7611599b2a339293688861100cb8dae9f6c6 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java -@@ -0,0 +1,99 @@ -+package org.purpurmc.purpur.entity; -+ -+import net.minecraft.core.particles.ParticleTypes; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.damagesource.DamageSource; -+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; -+ -+public class DolphinSpit extends LlamaSpit { -+ public LivingEntity dolphin; -+ public int ticksLived; -+ -+ public DolphinSpit(EntityType type, Level world) { -+ super(type, world); -+ } -+ -+ public DolphinSpit(Level world, Dolphin dolphin) { -+ this(EntityType.LLAMA_SPIT, world); -+ setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin); -+ this.dolphin = dolphin; -+ this.setPos( -+ dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(dolphin.yBodyRot * 0.017453292F), -+ dolphin.getEyeY() - 0.10000000149011612D, -+ dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(dolphin.yBodyRot * 0.017453292F)); -+ } -+ -+ public void tick() { -+ super_tick(); -+ -+ Vec3 mot = this.getDeltaMovement(); -+ HitResult hitResult = ProjectileUtil.getHitResult(this, this::canHitEntity); -+ -+ this.preOnHit(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).sendParticles(null, ParticleTypes.BUBBLE, -+ 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, true); -+ } -+ -+ if (++ticksLived > 20) { -+ this.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(DamageSource.indirectMobAttack(this, (LivingEntity) shooter).setProjectile(), 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(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2eca8317e991ec46cc88a4c7d6d8b50152ba4ea7 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java -@@ -0,0 +1,114 @@ -+package org.purpurmc.purpur.entity; -+ -+import net.minecraft.core.particles.ParticleTypes; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.damagesource.DamageSource; -+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 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 * 0.017453292F), -+ phantom.getEyeY() - 0.10000000149011612D, -+ phantom.getZ() + (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(phantom.yBodyRot * 0.017453292F)); -+ } -+ -+ public void tick() { -+ super_tick(); -+ -+ Vec3 mot = this.getDeltaMovement(); -+ HitResult hitResult = ProjectileUtil.getHitResult(this, this::canHitEntity); -+ -+ this.preOnHit(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).sendParticles(null, ParticleTypes.FLAME, -+ 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, true); -+ } -+ -+ if (++ticksLived > 20) { -+ this.discard(); -+ } else if (this.level.getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) { -+ this.discard(); -+ } else if (this.isInWaterOrBubble()) { -+ this.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) { -+ Entity target = entityHitResult.getEntity(); -+ if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) { -+ boolean hurt = target.hurt(DamageSource.indirectMobAttack(this, (LivingEntity) shooter).setProjectile(), level.purpurConfig.phantomFlameDamage); -+ if (hurt && level.purpurConfig.phantomFlameFireTime > 0) { -+ target.setSecondsOnFire(level.purpurConfig.phantomFlameFireTime); -+ } -+ } -+ } -+ } -+ -+ @Override -+ protected void onHitBlock(BlockHitResult blockHitResult) { -+ if (this.hitCancelled) { -+ return; -+ } -+ if (this.canGrief) { -+ BlockState state = this.level.getBlockState(blockHitResult.getBlockPos()); -+ state.onProjectileHit(this.level, state, blockHitResult, this); -+ } -+ this.discard(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8babdaddd8b33278aea0369dbbeeb445abe45016 ---- /dev/null -+++ b/src/main/java/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/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..432f4f3d82af2f19820890b68d33189a9f2c69f9 ---- /dev/null -+++ b/src/main/java/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/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..18a95e043cbffa65eeaaf65ff7695e5dc939820c ---- /dev/null -+++ b/src/main/java/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/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java -index b2d510459bcf90a3611f3d91dae4ccc3d29b4079..7a052f6deaa30f8a177a2aaf172f9da6c308a22b 100644 ---- a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java -+++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java -@@ -37,7 +37,7 @@ public class VanillaMobGoalTest { - } - - List> classes; -- try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) { -+ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft", "org.purpurmc.purpur.entity.ai").scan()) { // Purpur - classes = scanResult.getSubclasses(net.minecraft.world.entity.ai.goal.Goal.class.getName()).loadClasses(); - } - diff --git a/patches/todo/pufferfish/0264-Lobotomize-stuck-villagers.patch b/patches/todo/pufferfish/0264-Lobotomize-stuck-villagers.patch deleted file mode 100644 index 5935dd80c..000000000 --- a/patches/todo/pufferfish/0264-Lobotomize-stuck-villagers.patch +++ /dev/null @@ -1,143 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 3 Dec 2020 17:56:18 -0600 -Subject: [PATCH] Lobotomize stuck villagers - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 59bfaf90d448223c5aee9b18d2915539f54f0d4b..b5db8212aa6b20fbaa8ea7dbcd14c9cc13460fe0 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -140,6 +140,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - return villageplacetype == PoiType.MEETING; - }); - private final int brainTickOffset; // Purpur -+ private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur -+ private int notLobotomizedCount = 0; // Purpur - - public long nextGolemPanic = -1; // Pufferfish - -@@ -198,6 +200,47 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - protected boolean isAlwaysExperienceDropper() { - return this.level.purpurConfig.villagerAlwaysDropExp; - } -+ -+ private boolean checkLobotomized() { -+ int interval = this.level.purpurConfig.villagerLobotomizeCheckInterval; -+ if (this.notLobotomizedCount > 3) { -+ // check half as often if not lobotomized for the last 3+ consecutive checks -+ interval *= 2; -+ } -+ if ((this.level.getGameTime() + brainTickOffset) % interval == 0) { -+ // offset Y for short blocks like dirt_path/farmland -+ this.isLobotomized = !canTravelFrom(new BlockPos(getX(), getY() + 0.0625D, getZ())); -+ -+ 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) { -+ 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 - - @Override -@@ -295,6 +338,15 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - protected void customServerAiStep() { mobTick(false); } - protected void mobTick(boolean inactive) { - this.level.getProfiler().push("villagerBrain"); -+ // Purpur start -+ if (this.level.purpurConfig.villagerLobotomizeEnabled) { -+ // treat as inactive if lobotomized -+ inactive = inactive || checkLobotomized(); -+ } else { -+ // clean up state for API -+ this.isLobotomized = false; -+ } -+ // Purpur end - // Pufferfish start - if (!inactive) { - // Purpur start -@@ -305,6 +357,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - this.getBrain().tick((ServerLevel) this.level, this); // Paper - } - // Pufferfish end -+ // Purpur start -+ else if (this.isLobotomized && shouldRestock()) { -+ // make sure we restock if needed when lobotomized -+ restock(); -+ } -+ // Purpur end - this.level.getProfiler().pop(); - if (this.assignProfessionWhenSpawned) { - this.assignProfessionWhenSpawned = false; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index f0b910df1ee471b4d72d97c6197ab14f2854976e..6ce32a52d621a0c2629568ea07e445f50160d97d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -@@ -194,4 +194,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { - getHandle().getGossips().getReputations().clear(); - } - // Paper end -+ -+ // Purpur start -+ @Override -+ public boolean isLobotomized() { -+ return getHandle().isLobotomized(); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -index a5ec57fc61835e9dd69bc2222084e13018c459bb..2dc8f909431735aa31dd9ac93cba93dbab98b405 100644 ---- a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -@@ -2666,6 +2666,8 @@ public class PurpurWorldConfig { - public boolean villagerAllowTrading = true; - public boolean villagerAlwaysDropExp = false; - public int villagerMinimumDemand = 0; -+ public boolean villagerLobotomizeEnabled = false; -+ public int villagerLobotomizeCheckInterval = 100; - private void villagerSettings() { - villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); - villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -@@ -2691,6 +2693,17 @@ public class PurpurWorldConfig { - 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); - } - - public boolean vindicatorRidable = false;