diff --git a/docs/README.md b/docs/README.md
index fd9ad59741..df333931d5 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -5,13 +5,13 @@
+[![discord-widget][]][discord-invite]
+[![forge-version][]][forge-commit]
+
# Ketting
Ketting is a fork of [MinecraftForge](https://github.com/MinecraftForge/MinecraftForge/)
that enables the use of Bukkit plugins on Forge servers.
-
-[ ![discord-widget][] ][discord-invite]
-[ ![forge-version][] ][forge-commit]
## Notice
@@ -70,4 +70,4 @@ To create a jar, run
```bash
./gradlew kettingJar
```
-(this might take a while, but when it's done, you can find the jar in `build/libs` under `projects/forge`)
\ No newline at end of file
+(this might take a while, but when it's done, you can find the jar in `build/libs` under `projects/forge`)
diff --git a/patches/minecraft/net/minecraft/world/entity/Mob.java.patch b/patches/minecraft/net/minecraft/world/entity/Mob.java.patch
index 57082a37e6..64ffd598e3 100644
--- a/patches/minecraft/net/minecraft/world/entity/Mob.java.patch
+++ b/patches/minecraft/net/minecraft/world/entity/Mob.java.patch
@@ -1,42 +1,177 @@
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
-@@ -117,6 +_,9 @@
+@@ -22,6 +_,7 @@
+ import net.minecraft.network.syncher.SynchedEntityData;
+ import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.sounds.SoundEvent;
+ import net.minecraft.tags.TagKey;
+ import net.minecraft.util.Mth;
+@@ -71,6 +_,9 @@
+ import net.minecraft.world.level.material.Fluid;
+ import net.minecraft.world.level.pathfinder.BlockPathTypes;
+ import net.minecraft.world.phys.AABB;
++import org.bukkit.craftbukkit.v1_20_R2.entity.CraftLivingEntity;
++import org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory;
++import org.bukkit.event.entity.*;
+
+ public abstract class Mob extends LivingEntity implements Targeting {
+ private static final EntityDataAccessor DATA_MOB_FLAGS_ID = SynchedEntityData.defineId(Mob.class, EntityDataSerializers.BYTE);
+@@ -95,21 +_,21 @@
+ protected JumpControl jumpControl;
+ private final BodyRotationControl bodyRotationControl;
+ protected PathNavigation navigation;
+- public final GoalSelector goalSelector;
+- public final GoalSelector targetSelector;
++ public GoalSelector goalSelector;
++ public GoalSelector targetSelector;
+ @Nullable
+ private LivingEntity target;
+ private final Sensing sensing;
+ private final NonNullList handItems = NonNullList.withSize(2, ItemStack.EMPTY);
+- protected final float[] handDropChances = new float[2];
++ public final float[] handDropChances = new float[2];
+ private final NonNullList armorItems = NonNullList.withSize(4, ItemStack.EMPTY);
+- protected final float[] armorDropChances = new float[4];
++ public final float[] armorDropChances = new float[4];
+ private boolean canPickUpLoot;
+ private boolean persistenceRequired;
+ private final Map pathfindingMalus = Maps.newEnumMap(BlockPathTypes.class);
+ @Nullable
+- private ResourceLocation lootTable;
+- private long lootTableSeed;
++ public ResourceLocation lootTable;
++ public long lootTableSeed;
+ @Nullable
+ private Entity leashHolder;
+ private int delayedLeashHolderId;
+@@ -117,6 +_,11 @@
private CompoundTag leashInfoTag;
private BlockPos restrictCenter = BlockPos.ZERO;
private float restrictRadius = -1.0F;
+ @Nullable
+ private MobSpawnType spawnType;
+ private boolean spawnCancelled = false;
++
++ public boolean aware = true; // CraftBukkit
protected Mob(EntityType extends Mob> p_21368_, Level p_21369_) {
super(p_21368_, p_21369_);
-@@ -231,7 +_,10 @@
+@@ -136,6 +_,12 @@
+
+ }
+
++ // CraftBukkit start
++ public void setPersistenceRequired(boolean persistenceRequired) {
++ this.persistenceRequired = persistenceRequired;
++ }
++ // CraftBukkit end
++
+ protected void registerGoals() {
+ }
+
+@@ -231,7 +_,43 @@
}
public void setTarget(@Nullable LivingEntity p_21544_) {
- this.target = p_21544_;
++ // CraftBukkit start - fire event
++ setTarget(p_21544_, EntityTargetEvent.TargetReason.UNKNOWN, true);
++ }
++
++ public boolean setTarget(@Nullable LivingEntity p_21544_, EntityTargetEvent.TargetReason reason, boolean fireEvent) {
++ if (getTarget() == p_21544_) return false;
++ if (fireEvent) {
++ if (reason == EntityTargetEvent.TargetReason.UNKNOWN && getTarget() != null && p_21544_ == null) {
++ reason = getTarget().isAlive() ? EntityTargetEvent.TargetReason.FORGOT_TARGET : EntityTargetEvent.TargetReason.TARGET_DIED;
++ }
++ if (reason == EntityTargetEvent.TargetReason.UNKNOWN) {
++ this.level().getCraftServer().getLogger().log(java.util.logging.Level.WARNING, "Unknown target reason, please report on the issue tracker", new Exception());
++ }
++ CraftLivingEntity ctarget = null;
++ if (p_21544_ != null) {
++ ctarget = (CraftLivingEntity) p_21544_.getBukkitEntity();
++ }
++ EntityTargetLivingEntityEvent event = new EntityTargetLivingEntityEvent(this.getBukkitEntity(), ctarget, reason);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ if (event.getTarget() != null) {
++ p_21544_ = ((CraftLivingEntity) event.getTarget()).getHandle();
++ } else {
++ p_21544_ = null;
++ }
++ }
++
+ net.minecraftforge.event.entity.living.LivingChangeTargetEvent changeTargetEvent = net.minecraftforge.common.ForgeHooks.onLivingChangeTarget(this, p_21544_, net.minecraftforge.event.entity.living.LivingChangeTargetEvent.LivingTargetType.MOB_TARGET);
+ if(!changeTargetEvent.isCanceled()) {
-+ this.target = changeTargetEvent.getNewTarget();
++ this.target = changeTargetEvent.getNewTarget();
++ return true;
+ }
++ return false;
++ // CraftBukkit end
}
public boolean canAttackType(EntityType> p_21399_) {
-@@ -429,6 +_,9 @@
+@@ -358,6 +_,12 @@
+ return null;
+ }
+
++ // CraftBukkit start - Add delegate method
++ public SoundEvent getAmbientSound0() {
++ return getAmbientSound();
++ }
++ // CraftBukkit end
++
+ public void addAdditionalSaveData(CompoundTag p_21484_) {
+ super.addAdditionalSaveData(p_21484_);
+ p_21484_.putBoolean("CanPickUpLoot", this.canPickUpLoot());
+@@ -429,15 +_,29 @@
p_21484_.putBoolean("NoAI", this.isNoAi());
}
+ if (this.spawnType != null) {
+ p_21484_.putString("forge:spawn_type", this.spawnType.name());
+ }
++
++ p_21484_.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
}
public void readAdditionalSaveData(CompoundTag p_21450_) {
-@@ -481,6 +_,14 @@
+ super.readAdditionalSaveData(p_21450_);
++
++ // CraftBukkit start - If looting or persistence is false only use it if it was set after we started using it
+ if (p_21450_.contains("CanPickUpLoot", 1)) {
+- this.setCanPickUpLoot(p_21450_.getBoolean("CanPickUpLoot"));
++ boolean data = p_21450_.getBoolean("CanPickUpLoot");
++ if (isLevelAtLeast(p_21450_, 1) || data) {
++ this.setCanPickUpLoot(data);
++ }
+ }
+
+- this.persistenceRequired = p_21450_.getBoolean("PersistenceRequired");
++ boolean data = p_21450_.getBoolean("PersistenceRequired");
++ if (isLevelAtLeast(p_21450_, 1) || data) {
++ this.persistenceRequired = data;
++ }
++ // CraftBukkit end
+ if (p_21450_.contains("ArmorItems", 9)) {
+ ListTag listtag = p_21450_.getList("ArmorItems", 10);
+
+@@ -481,6 +_,20 @@
}
this.setNoAi(p_21450_.getBoolean("NoAI"));
+
++ // CraftBukkit start
++ if (p_21450_.contains("Bukkit.Aware")) {
++ this.aware = p_21450_.getBoolean("Bukkit.Aware");
++ }
++ // CraftBukkit end
++
+ if (p_21450_.contains("forge:spawn_type")) {
+ try {
+ this.spawnType = MobSpawnType.valueOf(p_21450_.getString("forge:spawn_type"));
@@ -58,6 +193,48 @@
for(ItemEntity itementity : this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate((double)vec3i.getX(), (double)vec3i.getY(), (double)vec3i.getZ()))) {
if (!itementity.isRemoved() && !itementity.getItem().isEmpty() && !itementity.hasPickUpDelay() && this.wantsToPickUp(itementity.getItem())) {
this.pickUpItem(itementity);
+@@ -539,7 +_,7 @@
+
+ protected void pickUpItem(ItemEntity p_21471_) {
+ ItemStack itemstack = p_21471_.getItem();
+- ItemStack itemstack1 = this.equipItemIfPossible(itemstack.copy());
++ ItemStack itemstack1 = this.equipItemIfPossible(itemstack.copy(), p_21471_); // CraftBukkit - add item
+ if (!itemstack1.isEmpty()) {
+ this.onItemPickup(p_21471_);
+ this.take(p_21471_, itemstack1.getCount());
+@@ -552,6 +_,12 @@
+ }
+
+ public ItemStack equipItemIfPossible(ItemStack p_255842_) {
++ // CraftBukkit start - add item
++ return this.equipItemIfPossible(p_255842_, null);
++ }
++
++ public ItemStack equipItemIfPossible(ItemStack p_255842_, ItemEntity entity) {
++ // CraftBukkit end
+ EquipmentSlot equipmentslot = getEquipmentSlotForItem(p_255842_);
+ ItemStack itemstack = this.getItemBySlot(equipmentslot);
+ boolean flag = this.canReplaceCurrentItem(p_255842_, itemstack);
+@@ -561,10 +_,18 @@
+ flag = itemstack.isEmpty();
+ }
+
+- if (flag && this.canHoldItem(p_255842_)) {
++ // CraftBukkit start
++ boolean canPickup = flag && this.canHoldItem(p_255842_);
++ if (entity != null) {
++ canPickup = !org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory.callEntityPickupItemEvent(this, entity, 0, !canPickup).isCancelled();
++ }
++ if (canPickup) {
++ // CraftBukkit end
+ double d0 = (double)this.getEquipmentDropChance(equipmentslot);
+ if (!itemstack.isEmpty() && (double)Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d0) {
++ this.forceDrops = true; // CraftBukkit
+ this.spawnAtLocation(itemstack);
++ this.forceDrops = false; // CraftBukkit
+ }
+
+ if (equipmentslot.isArmor() && p_255842_.getCount() > 1) {
@@ -698,6 +_,14 @@
this.discard();
} else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
@@ -73,6 +250,14 @@
if (entity != null) {
double d0 = entity.distanceToSqr(this);
int i = this.getType().getCategory().getDespawnDistance();
+@@ -722,6 +_,7 @@
+
+ protected final void serverAiStep() {
+ ++this.noActionTime;
++ if (!this.aware) return; // CraftBukkit
+ this.level().getProfiler().push("sensing");
+ this.sensing.tick();
+ this.level().getProfiler().pop();
@@ -1031,6 +_,16 @@
}
@@ -98,6 +283,113 @@
return p_21437_;
}
+@@ -1080,6 +_,12 @@
+ if (!this.isAlive()) {
+ return InteractionResult.PASS;
+ } else if (this.getLeashHolder() == p_21420_) {
++ // CraftBukkit start - fire PlayerUnleashEntityEvent
++ if (CraftEventFactory.callPlayerUnleashEntityEvent(this, p_21420_, p_21421_).isCancelled()) {
++ ((ServerPlayer) p_21420_).connection.send(new ClientboundSetEntityLinkPacket(this, this.getLeashHolder()));
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
+ this.dropLeash(true, !p_21420_.getAbilities().instabuild);
+ this.gameEvent(GameEvent.ENTITY_INTERACT, p_21420_);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+@@ -1103,6 +_,12 @@
+ private InteractionResult checkAndHandleImportantInteractions(Player p_21500_, InteractionHand p_21501_) {
+ ItemStack itemstack = p_21500_.getItemInHand(p_21501_);
+ if (itemstack.is(Items.LEAD) && this.canBeLeashed(p_21500_)) {
++ // CraftBukkit start - fire PlayerLeashEntityEvent
++ if (CraftEventFactory.callPlayerLeashEntityEvent(this, p_21500_, p_21500_, p_21501_).isCancelled()) {
++ ((ServerPlayer) p_21500_).connection.send(new ClientboundSetEntityLinkPacket(this, this.getLeashHolder()));
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
+ this.setLeashedTo(p_21500_, true);
+ itemstack.shrink(1);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+@@ -1171,8 +_,15 @@
+ return this.restrictRadius != -1.0F;
+ }
+
++ // CraftBukkit start
+ @Nullable
+ public T convertTo(EntityType p_21407_, boolean p_21408_) {
++ return this.convertTo(p_21407_, p_21408_, EntityTransformEvent.TransformReason.UNKNOWN, CreatureSpawnEvent.SpawnReason.DEFAULT);
++ }
++
++ @Nullable
++ public T convertTo(EntityType p_21407_, boolean p_21408_, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) {
++ // CraftBukkit end
+ if (this.isRemoved()) {
+ return (T)null;
+ } else {
+@@ -1205,7 +_,12 @@
+ }
+ }
+
+- this.level().addFreshEntity(t);
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityTransformEvent(this, t, transformReason).isCancelled()) {
++ return null;
++ }
++ this.level().addFreshEntity(t, spawnReason);
++ // CraftBukkit end
+ if (this.isPassenger()) {
+ Entity entity = this.getVehicle();
+ this.stopRiding();
+@@ -1225,6 +_,7 @@
+
+ if (this.leashHolder != null) {
+ if (!this.isAlive() || !this.leashHolder.isAlive()) {
++ this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? EntityUnleashEvent.UnleashReason.PLAYER_UNLEASH : EntityUnleashEvent.UnleashReason.HOLDER_GONE)); // CraftBukkit
+ this.dropLeash(true, true);
+ }
+
+@@ -1236,7 +_,9 @@
+ this.leashHolder = null;
+ this.leashInfoTag = null;
+ if (!this.level().isClientSide && p_21457_) {
++ this.forceDrops = true; // CraftBukkit
+ this.spawnAtLocation(Items.LEAD);
++ this.forceDrops = false; // CraftBukkit
+ }
+
+ if (!this.level().isClientSide && p_21456_ && this.level() instanceof ServerLevel) {
+@@ -1284,6 +_,7 @@
+ public boolean startRiding(Entity p_21396_, boolean p_21397_) {
+ boolean flag = super.startRiding(p_21396_, p_21397_);
+ if (flag && this.isLeashed()) {
++ this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN)); // CraftBukkit
+ this.dropLeash(true, true);
+ }
+
+@@ -1306,7 +_,9 @@
+ }
+
+ if (this.tickCount > 100) {
++ this.forceDrops = true; // CraftBukkit
+ this.spawnAtLocation(Items.LEAD);
++ this.forceDrops = false; // CraftBukkit
+ this.leashInfoTag = null;
+ }
+ }
+@@ -1379,6 +_,14 @@
+
+ int i = EnchantmentHelper.getFireAspect(this);
+ if (i > 0) {
++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item
++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), p_21372_.getBukkitEntity(), i * 4);
++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
++
++ if (!combustEvent.isCancelled()) {
++ p_21372_.setSecondsOnFire(combustEvent.getDuration(), false);
++ }
++ // CraftBukkit end
+ p_21372_.setSecondsOnFire(i * 4);
+ }
+
@@ -1425,15 +_,25 @@
return false;
}
@@ -125,6 +417,14 @@
public void removeFreeWill() {
this.removeAllGoals((p_262562_) -> {
return true;
+@@ -1447,6 +_,7 @@
+
+ protected void removeAfterChangingDimensions() {
+ super.removeAfterChangingDimensions();
++ this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN)); // CraftBukkit
+ this.dropLeash(true, false);
+ this.getAllSlots().forEach((p_278936_) -> {
+ if (!p_278936_.isEmpty()) {
@@ -1460,5 +_,40 @@
public ItemStack getPickResult() {
SpawnEggItem spawneggitem = SpawnEggItem.byId(this.getType());