diff --git a/dist/pom.xml b/dist/pom.xml
index d9973220e..5c5dcb30a 100644
--- a/dist/pom.xml
+++ b/dist/pom.xml
@@ -5,7 +5,7 @@
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
citizens
pom
@@ -87,5 +87,12 @@
jar
compile
+
+ ${project.groupId}
+ citizens-v1_17_R1
+ ${project.version}
+ jar
+ compile
+
diff --git a/main/pom.xml b/main/pom.xml
index a7296973d..30a76d533 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -7,13 +7,13 @@
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
citizens-main
UTF-8
- 1.16.5-R0.1-SNAPSHOT
+ 1.17-R0.1-SNAPSHOT
${project.version}
1.4.12
7.0.4
diff --git a/main/src/main/java/net/citizensnpcs/util/NMS.java b/main/src/main/java/net/citizensnpcs/util/NMS.java
index fe2ab9678..c78475152 100644
--- a/main/src/main/java/net/citizensnpcs/util/NMS.java
+++ b/main/src/main/java/net/citizensnpcs/util/NMS.java
@@ -164,6 +164,37 @@ public class NMS {
return null;
}
+ public static MethodHandle getFirstMethodHandle(Class> clazz, boolean log, Class>... params) {
+ if (clazz == null)
+ return null;
+ try {
+ Method first = null;
+ for (Method method : clazz.getDeclaredMethods()) {
+ Class>[] paramTypes = method.getParameterTypes();
+ if (paramTypes.length == params.length) {
+ first = method;
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (paramTypes[i] != params[i]) {
+ first = null;
+ }
+ }
+ if (first != null) {
+ break;
+ }
+ }
+ }
+ if (first == null)
+ return null;
+ first.setAccessible(true);
+ return LOOKUP.unreflect(first);
+ } catch (Exception e) {
+ if (log) {
+ Messaging.logTr(Messages.ERROR_GETTING_METHOD, e.getLocalizedMessage());
+ }
+ }
+ return null;
+ }
+
public static GameProfileRepository getGameProfileRepository() {
return BRIDGE.getGameProfileRepository();
}
@@ -320,7 +351,12 @@ public class NMS {
}
public static void loadBridge(String rev) throws Exception {
- Class> entity = Class.forName("net.minecraft.server.v" + rev + ".Entity");
+ Class> entity = null;
+ try {
+ entity = Class.forName("net.minecraft.server.v" + rev + ".Entity");
+ } catch (ClassNotFoundException ex) {
+ entity = Class.forName("net.minecraft.world.entity.Entity");
+ }
giveReflectiveAccess(entity, NMS.class);
BRIDGE = (NMSBridge) Class.forName("net.citizensnpcs.nms.v" + rev + ".util.NMSImpl").getConstructor()
.newInstance();
@@ -518,7 +554,6 @@ public class NMS {
private static Object UNSAFE;
private static MethodHandle UNSAFE_FIELD_OFFSET;
private static MethodHandle UNSAFE_PUT_OBJECT;
-
private static MethodHandle UNSAFE_STATIC_FIELD_OFFSET;
static {
diff --git a/pom.xml b/pom.xml
index bf9f9f096..879d033e3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,10 +7,10 @@
pom
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
Unknown
- 2.0.27
+ 2.0.28
clean package install
@@ -25,6 +25,7 @@
v1_14_R1
v1_15_R1
v1_16_R3
+ v1_17_R1
dist
\ No newline at end of file
diff --git a/v1_10_R1/pom.xml b/v1_10_R1/pom.xml
index 75ac2553f..183c1ab50 100644
--- a/v1_10_R1/pom.xml
+++ b/v1_10_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
citizens-v1_10_R1
diff --git a/v1_11_R1/pom.xml b/v1_11_R1/pom.xml
index 7e6777beb..aa2fdee81 100644
--- a/v1_11_R1/pom.xml
+++ b/v1_11_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
citizens-v1_11_R1
diff --git a/v1_12_R1/pom.xml b/v1_12_R1/pom.xml
index 53f42c526..b1ec9e1e8 100644
--- a/v1_12_R1/pom.xml
+++ b/v1_12_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
citizens-v1_12_R1
diff --git a/v1_13_R2/pom.xml b/v1_13_R2/pom.xml
index d4abef809..88f1536a4 100644
--- a/v1_13_R2/pom.xml
+++ b/v1_13_R2/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
citizens-v1_13_R2
diff --git a/v1_14_R1/pom.xml b/v1_14_R1/pom.xml
index a9c73dd2f..240f2303b 100644
--- a/v1_14_R1/pom.xml
+++ b/v1_14_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
citizens-v1_14_R1
diff --git a/v1_15_R1/pom.xml b/v1_15_R1/pom.xml
index 2b273aac5..67a188623 100644
--- a/v1_15_R1/pom.xml
+++ b/v1_15_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
citizens-v1_15_R1
diff --git a/v1_15_R1/src/main/java/net/citizensnpcs/nms/v1_15_R1/entity/PufferFishController.java b/v1_15_R1/src/main/java/net/citizensnpcs/nms/v1_15_R1/entity/PufferFishController.java
index 3642b15ce..f4c56cba1 100644
--- a/v1_15_R1/src/main/java/net/citizensnpcs/nms/v1_15_R1/entity/PufferFishController.java
+++ b/v1_15_R1/src/main/java/net/citizensnpcs/nms/v1_15_R1/entity/PufferFishController.java
@@ -121,7 +121,7 @@ public class PufferFishController extends MobEntityController {
@Override
public boolean doAITick() {
- return false;
+ return npc == null ? super.doAITick() : false;
}
@Override
diff --git a/v1_16_R3/pom.xml b/v1_16_R3/pom.xml
index f1d5ff30e..80888db1b 100644
--- a/v1_16_R3/pom.xml
+++ b/v1_16_R3/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.27-SNAPSHOT
+ 2.0.28-SNAPSHOT
citizens-v1_16_R3
diff --git a/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/PufferFishController.java b/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/PufferFishController.java
index 282dbeeb1..10e5951ff 100644
--- a/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/PufferFishController.java
+++ b/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/PufferFishController.java
@@ -123,7 +123,7 @@ public class PufferFishController extends MobEntityController {
@Override
public boolean doAITick() {
- return false;
+ return npc == null ? super.doAITick() : false;
}
@Override
diff --git a/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/TraderLlamaController.java b/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/TraderLlamaController.java
index a68088f1e..114130c08 100644
--- a/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/TraderLlamaController.java
+++ b/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/TraderLlamaController.java
@@ -205,8 +205,8 @@ public class TraderLlamaController extends MobEntityController {
super.mobTick();
}
try {
- if (bF != null) {
- bF.invoke(this, 10); // DespawnDelay
+ if (bw != null) {
+ bw.invoke(this, 10); // DespawnDelay
}
} catch (Throwable e) {
}
@@ -223,7 +223,7 @@ public class TraderLlamaController extends MobEntityController {
return super.n(entity);
}
- private static final MethodHandle bF = NMS.getSetter(EntityLlamaTrader.class, "bF");
+ private static final MethodHandle bw = NMS.getSetter(EntityLlamaTrader.class, "bw");
}
public static class TraderLlamaNPC extends CraftTraderLlama implements ForwardingNPCHolder {
diff --git a/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/nonliving/FishingHookController.java b/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/nonliving/FishingHookController.java
index eb3449340..5aaf131ff 100644
--- a/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/nonliving/FishingHookController.java
+++ b/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/entity/nonliving/FishingHookController.java
@@ -110,7 +110,7 @@ public class FishingHookController extends MobEntityController {
((EntityHuman) getShooter()).inventory.items.set(((EntityHuman) getShooter()).inventory.itemInHandIndex,
new ItemStack(Items.FISHING_ROD, 1));
try {
- D.invoke(this, 0);
+ G.invoke(this, 0);
} catch (Throwable e) {
e.printStackTrace();
}
@@ -120,7 +120,7 @@ public class FishingHookController extends MobEntityController {
}
}
- private static MethodHandle D = NMS.getSetter(EntityFishingHook.class, "d");
+ private static MethodHandle G = NMS.getSetter(EntityFishingHook.class, "g");
}
public static class FishingHookNPC extends CraftFishHook implements ForwardingNPCHolder {
diff --git a/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/util/CitizensBlockBreaker.java b/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/util/CitizensBlockBreaker.java
index c0760d0ea..41b680c4e 100644
--- a/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/util/CitizensBlockBreaker.java
+++ b/v1_16_R3/src/main/java/net/citizensnpcs/nms/v1_16_R3/util/CitizensBlockBreaker.java
@@ -58,7 +58,7 @@ public class CitizensBlockBreaker extends BlockBreaker {
}
private float getStrength(IBlockData block) {
- float base = block.getBlock().a(block, null, new BlockPosition(0, 0, 0));
+ float base = block.h(null, new BlockPosition(0, 0, 0));
return base < 0.0F ? 0.0F : (!isDestroyable(block) ? 1.0F / base / 100.0F : strengthMod(block) / base / 30.0F);
}
diff --git a/v1_17_R1/pom.xml b/v1_17_R1/pom.xml
new file mode 100644
index 000000000..9cecf9595
--- /dev/null
+++ b/v1_17_R1/pom.xml
@@ -0,0 +1,119 @@
+
+
+ 4.0.0
+
+ net.citizensnpcs
+ citizens-parent
+ 2.0.28-SNAPSHOT
+
+ citizens-v1_17_R1
+
+
+ UTF-8
+ 1.17-R0.1-SNAPSHOT
+
+
+
+
+ everything
+ http://repo.citizensnpcs.co
+
+
+
+
+
+ ${project.groupId}
+ citizens-main
+ ${project.version}
+ provided
+
+
+ org.spigotmc
+ spigot
+ ${craftbukkit.version}
+ remapped-mojang
+ provided
+
+
+
+
+ clean package install
+ ${basedir}/src/main/java
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ eclipse
+
+ 1.8
+
+
+
+ org.codehaus.plexus
+ plexus-compiler-eclipse
+ 2.8.6
+
+
+
+
+
+ net.md-5
+ specialsource-maven-plugin
+ 1.2.2
+
+
+ package
+
+ remap
+
+ remap-obf
+
+ org.spigotmc:minecraft-server:1.17-R0.1-SNAPSHOT:txt:maps-mojang
+ true
+ org.spigotmc:spigot:1.17-R0.1-SNAPSHOT:jar:remapped-mojang
+ true
+ remapped-obf
+
+
+
+ package
+
+ remap
+
+ remap-spigot
+
+ ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
+ org.spigotmc:minecraft-server:1.17-R0.1-SNAPSHOT:csrg:maps-spigot
+ org.spigotmc:spigot:1.17-R0.1-SNAPSHOT:jar:remapped-obf
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.2.0
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.1
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/AxolotlController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/AxolotlController.java
new file mode 100644
index 000000000..e55db0d71
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/AxolotlController.java
@@ -0,0 +1,221 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftAxolotl;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
+import net.minecraft.world.entity.ai.navigation.PathNavigation;
+import net.minecraft.world.entity.animal.axolotl.Axolotl;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class AxolotlController extends MobEntityController {
+ public AxolotlController() {
+ super(EntityAxolotlNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Axolotl getBukkitEntity() {
+ return (org.bukkit.entity.Axolotl) super.getBukkitEntity();
+ }
+
+ public static class AxolotlNPC extends CraftAxolotl implements ForwardingNPCHolder {
+ public AxolotlNPC(EntityAxolotlNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityAxolotlNPC extends Axolotl implements NPCHolder {
+ private final CitizensNPC npc;
+ private MoveControl oldMoveController;
+
+ public EntityAxolotlNPC(EntityType extends Axolotl> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityAxolotlNPC(EntityType extends Axolotl> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new MoveControl(this);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ protected PathNavigation createNavigation(Level world) {
+ return new GroundPathNavigation(this, world);
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new AxolotlNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isInWater() {
+ return npc == null ? super.isInWater() : false;
+ }
+
+ @Override
+ public boolean isInWaterRainOrBubble() {
+ return npc == null ? super.isInWaterRainOrBubble() : true;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/BatController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/BatController.java
new file mode 100644
index 000000000..bb6960455
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/BatController.java
@@ -0,0 +1,168 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftBat;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ambient.Bat;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+
+public class BatController extends MobEntityController {
+ public BatController() {
+ super(EntityBatNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Bat getBukkitEntity() {
+ return (org.bukkit.entity.Bat) super.getBukkitEntity();
+ }
+
+ public static class BatNPC extends CraftBat implements ForwardingNPCHolder {
+ public BatNPC(EntityBatNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityBatNPC extends Bat implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityBatNPC(EntityType extends Bat> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityBatNPC(EntityType extends Bat> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ setFlying(false);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc == null) {
+ super.customServerAiStep();
+ } else {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (!npc.useMinecraftAI()) {
+ NMSImpl.updateAI(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ public SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new BatNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null) {
+ return super.isLeashed();
+ }
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(net.minecraft.world.entity.Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ public void setFlying(boolean flying) {
+ setResting(flying);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/BeeController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/BeeController.java
new file mode 100644
index 000000000..bde20787e
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/BeeController.java
@@ -0,0 +1,164 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftBee;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.Bee;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+
+public class BeeController extends MobEntityController {
+ public BeeController() {
+ super(EntityBeeNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Bee getBukkitEntity() {
+ return (org.bukkit.entity.Bee) super.getBukkitEntity();
+ }
+
+ public static class BeeNPC extends CraftBee implements ForwardingNPCHolder {
+ public BeeNPC(EntityBeeNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityBeeNPC extends Bee implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityBeeNPC(EntityType extends Bee> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityBeeNPC(EntityType extends Bee> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc == null) {
+ super.customServerAiStep();
+ } else {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (!npc.useMinecraftAI()) {
+ NMSImpl.updateAI(this);
+ }
+ NMSImpl.updateAI(this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ public SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new BeeNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null) {
+ return super.isLeashed();
+ }
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/BlazeController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/BlazeController.java
new file mode 100644
index 000000000..5ea55eb18
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/BlazeController.java
@@ -0,0 +1,157 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftBlaze;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Blaze;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+
+public class BlazeController extends MobEntityController {
+ public BlazeController() {
+ super(BlazeNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Blaze getBukkitEntity() {
+ return (org.bukkit.entity.Blaze) super.getBukkitEntity();
+ }
+
+ public static class BlazeNPC extends CraftBlaze implements ForwardingNPCHolder {
+ public BlazeNPC(EntityBlazeNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityBlazeNPC extends Blaze implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityBlazeNPC(EntityType extends Blaze> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityBlazeNPC(EntityType extends Blaze> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new BlazeNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CatController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CatController.java
new file mode 100644
index 000000000..9b27a7358
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CatController.java
@@ -0,0 +1,206 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftCat;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.Cat;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class CatController extends MobEntityController {
+ public CatController() {
+ super(EntityCatNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Cat getBukkitEntity() {
+ return (org.bukkit.entity.Cat) super.getBukkitEntity();
+ }
+
+ public static class CatNPC extends CraftCat implements ForwardingNPCHolder {
+ public CatNPC(EntityCatNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityCatNPC extends Cat implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityCatNPC(EntityType extends Cat> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityCatNPC(EntityType extends Cat> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new CatNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CaveSpiderController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CaveSpiderController.java
new file mode 100644
index 000000000..734810890
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CaveSpiderController.java
@@ -0,0 +1,205 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftCaveSpider;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.CaveSpider;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class CaveSpiderController extends MobEntityController {
+ public CaveSpiderController() {
+ super(EntityCaveSpiderNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.CaveSpider getBukkitEntity() {
+ return (org.bukkit.entity.CaveSpider) super.getBukkitEntity();
+ }
+
+ public static class CaveSpiderNPC extends CraftCaveSpider implements ForwardingNPCHolder {
+ public CaveSpiderNPC(EntityCaveSpiderNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityCaveSpiderNPC extends CaveSpider implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityCaveSpiderNPC(EntityType extends CaveSpider> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityCaveSpiderNPC(EntityType extends CaveSpider> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new CaveSpiderNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null) {
+ return super.isLeashed();
+ }
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault)) {
+ return super.isLeashed();
+ }
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ChickenController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ChickenController.java
new file mode 100644
index 000000000..aeeb290c9
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ChickenController.java
@@ -0,0 +1,215 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftChicken;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.Chicken;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class ChickenController extends MobEntityController {
+ public ChickenController() {
+ super(EntityChickenNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Chicken getBukkitEntity() {
+ return (org.bukkit.entity.Chicken) super.getBukkitEntity();
+ }
+
+ public static class ChickenNPC extends CraftChicken implements ForwardingNPCHolder {
+ public ChickenNPC(EntityChickenNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityChickenNPC extends Chicken implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityChickenNPC(EntityType extends Chicken> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityChickenNPC(EntityType extends Chicken> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ if (npc != null) {
+ this.eggTime = 100;
+ }
+ super.aiStep();
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ChickenNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CodController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CodController.java
new file mode 100644
index 000000000..24d3cd7c4
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CodController.java
@@ -0,0 +1,237 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftCod;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.animal.Cod;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class CodController extends MobEntityController {
+ public CodController() {
+ super(EntityCodNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Cod getBukkitEntity() {
+ return (org.bukkit.entity.Cod) super.getBukkitEntity();
+ }
+
+ public static class CodNPC extends CraftCod implements ForwardingNPCHolder {
+ public CodNPC(EntityCodNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityCodNPC extends Cod implements NPCHolder {
+ private final CitizensNPC npc;
+ private MoveControl oldMoveController;
+
+ public EntityCodNPC(EntityType extends Cod> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityCodNPC(EntityType extends Cod> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new MoveControl(this);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ boolean lastInWater = this.verticalCollision;
+ if (npc != null) {
+ this.verticalCollision = false;
+ }
+ super.aiStep();
+ if (npc != null) {
+ this.verticalCollision = lastInWater;
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ if (!npc.useMinecraftAI()) {
+ NMSImpl.setNotInSchool(this);
+ }
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new CodNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ protected InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc == null || !npc.isProtected())
+ return super.mobInteract(entityhuman, enumhand);
+ ItemStack itemstack = entityhuman.getItemInHand(enumhand);
+ if (itemstack.getItem() == Items.WATER_BUCKET && isAlive()) {
+ return InteractionResult.FAIL;
+ }
+ return super.mobInteract(entityhuman, enumhand);
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CowController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CowController.java
new file mode 100644
index 000000000..cafa52cb8
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CowController.java
@@ -0,0 +1,222 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftCow;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.Cow;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class CowController extends MobEntityController {
+ public CowController() {
+ super(EntityCowNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Cow getBukkitEntity() {
+ return (org.bukkit.entity.Cow) super.getBukkitEntity();
+ }
+
+ public static class CowNPC extends CraftCow implements ForwardingNPCHolder {
+ public CowNPC(EntityCowNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityCowNPC extends Cow implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityCowNPC(EntityType extends Cow> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityCowNPC(EntityType extends Cow> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new CowNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc == null || !npc.isProtected())
+ return super.mobInteract(entityhuman, enumhand);
+ ItemStack itemstack = entityhuman.getItemInHand(enumhand);
+ if (itemstack.getItem() == Items.BUCKET && !entityhuman.getAbilities().instabuild && !this.isBaby()) {
+ return InteractionResult.FAIL;
+ }
+ return super.mobInteract(entityhuman, enumhand);
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CreeperController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CreeperController.java
new file mode 100644
index 000000000..11ce937de
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/CreeperController.java
@@ -0,0 +1,216 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftCreeper;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LightningBolt;
+import net.minecraft.world.entity.monster.Creeper;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class CreeperController extends MobEntityController {
+ public CreeperController() {
+ super(EntityCreeperNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Creeper getBukkitEntity() {
+ return (org.bukkit.entity.Creeper) super.getBukkitEntity();
+ }
+
+ public static class CreeperNPC extends CraftCreeper implements ForwardingNPCHolder {
+ public CreeperNPC(EntityCreeperNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityCreeperNPC extends Creeper implements NPCHolder {
+ private boolean allowPowered;
+ private final CitizensNPC npc;
+
+ public EntityCreeperNPC(EntityType extends Creeper> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityCreeperNPC(EntityType extends Creeper> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new CreeperNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ public void setAllowPowered(boolean allowPowered) {
+ this.allowPowered = allowPowered;
+ }
+
+ @Override
+ public void thunderHit(ServerLevel worldserver, LightningBolt entitylightning) {
+ if (npc == null || allowPowered) {
+ super.thunderHit(worldserver, entitylightning);
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/DolphinController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/DolphinController.java
new file mode 100644
index 000000000..ae9a2e7d6
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/DolphinController.java
@@ -0,0 +1,221 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftDolphin;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
+import net.minecraft.world.entity.ai.navigation.PathNavigation;
+import net.minecraft.world.entity.animal.Dolphin;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class DolphinController extends MobEntityController {
+ public DolphinController() {
+ super(EntityDolphinNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Dolphin getBukkitEntity() {
+ return (org.bukkit.entity.Dolphin) super.getBukkitEntity();
+ }
+
+ public static class DolphinNPC extends CraftDolphin implements ForwardingNPCHolder {
+ public DolphinNPC(EntityDolphinNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityDolphinNPC extends Dolphin implements NPCHolder {
+ private final CitizensNPC npc;
+ private MoveControl oldMoveController;
+
+ public EntityDolphinNPC(EntityType extends Dolphin> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityDolphinNPC(EntityType extends Dolphin> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new MoveControl(this);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ protected PathNavigation createNavigation(Level world) {
+ return new GroundPathNavigation(this, world);
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new DolphinNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isInWater() {
+ return npc == null ? super.isInWater() : false;
+ }
+
+ @Override
+ public boolean isInWaterRainOrBubble() {
+ return npc == null ? super.isInWaterRainOrBubble() : true;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/DrownedController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/DrownedController.java
new file mode 100644
index 000000000..4da2d0382
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/DrownedController.java
@@ -0,0 +1,193 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftDrowned;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Drowned;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class DrownedController extends MobEntityController {
+ public DrownedController() {
+ super(EntityDrownedNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Drowned getBukkitEntity() {
+ return (org.bukkit.entity.Drowned) super.getBukkitEntity();
+ }
+
+ public static class DrownedNPC extends CraftDrowned implements ForwardingNPCHolder {
+ public DrownedNPC(EntityDrownedNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityDrownedNPC extends Drowned implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityDrownedNPC(EntityType extends Drowned> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityDrownedNPC(EntityType extends Drowned> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new DrownedNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EnderDragonController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EnderDragonController.java
new file mode 100644
index 000000000..bd5c03287
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EnderDragonController.java
@@ -0,0 +1,177 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEnderDragon;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.Vec3;
+
+public class EnderDragonController extends MobEntityController {
+ public EnderDragonController() {
+ super(EntityEnderDragonNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.EnderDragon getBukkitEntity() {
+ return (org.bukkit.entity.EnderDragon) super.getBukkitEntity();
+ }
+
+ public static class EnderDragonNPC extends CraftEnderDragon implements ForwardingNPCHolder {
+ public EnderDragonNPC(EntityEnderDragonNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityEnderDragonNPC extends EnderDragon implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityEnderDragonNPC(EntityType extends EnderDragon> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityEnderDragonNPC(EntityType extends EnderDragon> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ if (npc != null) {
+ npc.update();
+ NMSImpl.updateMinecraftAIState(npc, this);
+ }
+ if (npc != null && !npc.useMinecraftAI()) {
+ Vec3 mot = getDeltaMovement();
+ if (mot.x != 0 || mot.y != 0 || mot.z != 0) {
+ mot = mot.multiply(0.98, 0.98, 0.98);
+ setYRot(getCorrectYaw(getX() + mot.x, getZ() + mot.z));
+ setPos(getX() + mot.x, getY() + mot.y, getZ() + mot.z);
+ setDeltaMovement(mot);
+ }
+ } else {
+ super.aiStep();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EnderDragonNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ private float getCorrectYaw(double tX, double tZ) {
+ if (getZ() > tZ)
+ return (float) (-Math.toDegrees(Math.atan((getX() - tX) / (getZ() - tZ))));
+ if (getZ() < tZ) {
+ return (float) (-Math.toDegrees(Math.atan((getX() - tX) / (getX() - tZ)))) + 180.0F;
+ }
+ return getYRot();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EndermanController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EndermanController.java
new file mode 100644
index 000000000..a6378485b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EndermanController.java
@@ -0,0 +1,218 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import java.util.Optional;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEnderman;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.EnderMan;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class EndermanController extends MobEntityController {
+ public EndermanController() {
+ super(EntityEndermanNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Enderman getBukkitEntity() {
+ return (org.bukkit.entity.Enderman) super.getBukkitEntity();
+ }
+
+ public static class EndermanNPC extends CraftEnderman implements ForwardingNPCHolder {
+ public EndermanNPC(EntityEndermanNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityEndermanNPC extends EnderMan implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityEndermanNPC(EntityType extends EnderMan> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityEndermanNPC(EntityType extends EnderMan> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ try {
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EndermanNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public Optional safeTeleport(double d0, double d1, double d2, boolean flag,
+ PlayerTeleportEvent.TeleportCause cause) {
+ if (npc == null) {
+ return super.safeTeleport(d0, d1, d2, flag, cause);
+ }
+ return Optional.of(false);
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EndermiteController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EndermiteController.java
new file mode 100644
index 000000000..e2357dfcc
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EndermiteController.java
@@ -0,0 +1,201 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEndermite;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Endermite;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class EndermiteController extends MobEntityController {
+ public EndermiteController() {
+ super(EntityEndermiteNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Endermite getBukkitEntity() {
+ return (org.bukkit.entity.Endermite) super.getBukkitEntity();
+ }
+
+ public static class EndermiteNPC extends CraftEndermite implements ForwardingNPCHolder {
+ public EndermiteNPC(EntityEndermiteNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityEndermiteNPC extends Endermite implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityEndermiteNPC(EntityType extends Endermite> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityEndermiteNPC(EntityType extends Endermite> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null)
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EndermiteNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EntityHumanNPC.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EntityHumanNPC.java
new file mode 100644
index 000000000..e0f838b59
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EntityHumanNPC.java
@@ -0,0 +1,620 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.net.Socket;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.metadata.MetadataValue;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.util.Vector;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.mojang.authlib.GameProfile;
+import com.mojang.datafixers.util.Pair;
+
+import net.citizensnpcs.Settings.Setting;
+import net.citizensnpcs.api.CitizensAPI;
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.api.trait.trait.Inventory;
+import net.citizensnpcs.nms.v1_17_R1.network.EmptyNetHandler;
+import net.citizensnpcs.nms.v1_17_R1.network.EmptyNetworkManager;
+import net.citizensnpcs.nms.v1_17_R1.network.EmptySocket;
+import net.citizensnpcs.nms.v1_17_R1.util.EmptyAdvancementDataPlayer;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerControllerJump;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerLookControl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerNavigation;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerlistTracker;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.npc.skin.SkinPacketTracker;
+import net.citizensnpcs.npc.skin.SkinnableEntity;
+import net.citizensnpcs.trait.Gravity;
+import net.citizensnpcs.trait.SkinTrait;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.PacketFlow;
+import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.ServerPlayerGameMode;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.ai.attributes.Attribute;
+import net.minecraft.world.entity.ai.attributes.AttributeInstance;
+import net.minecraft.world.entity.ai.attributes.AttributeMap;
+import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.ai.navigation.PathNavigation;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.GameType;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.pathfinder.BlockPathTypes;
+import net.minecraft.world.phys.Vec3;
+
+public class EntityHumanNPC extends ServerPlayer implements NPCHolder, SkinnableEntity {
+ private final Map bz = Maps.newEnumMap(BlockPathTypes.class);
+ private PlayerControllerJump controllerJump;
+ private PlayerLookControl controllerLook;
+ private PlayerMoveControl controllerMove;
+ private final Map equipmentCache = Maps.newEnumMap(EquipmentSlot.class);
+ private int jumpTicks = 0;
+ private PlayerNavigation navigation;
+ private final CitizensNPC npc;
+ private final Location packetLocationCache = new Location(null, 0, 0, 0);
+ private PlayerlistTracker playerlistTracker;
+ private final SkinPacketTracker skinTracker;
+ private int updateCounter = 0;
+
+ public EntityHumanNPC(MinecraftServer minecraftServer, ServerLevel world, GameProfile gameProfile, NPC npc) {
+ super(minecraftServer, world, gameProfile);
+ this.npc = (CitizensNPC) npc;
+
+ if (npc != null) {
+ skinTracker = new SkinPacketTracker(this);
+ try {
+ GAMEMODE_SETTING.invoke(gameMode, GameType.SURVIVAL, null);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ initialise(minecraftServer);
+ } else {
+ skinTracker = null;
+ }
+ }
+
+ @Override
+ public boolean broadcastToPlayer(ServerPlayer entityplayer) {
+ if (npc != null && playerlistTracker == null) {
+ return false;
+ }
+ return super.broadcastToPlayer(entityplayer);
+ }
+
+ public boolean canCutCorner(BlockPathTypes pathtype) {
+ return (pathtype != BlockPathTypes.DANGER_FIRE && pathtype != BlockPathTypes.DANGER_CACTUS
+ && pathtype != BlockPathTypes.DANGER_OTHER && pathtype != BlockPathTypes.WALKABLE_DOOR);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void die(DamageSource damagesource) {
+ // players that die are not normally removed from the world. when the
+ // NPC dies, we are done with the instance and it should be removed.
+ if (dead) {
+ return;
+ }
+ super.die(damagesource);
+ Bukkit.getScheduler().runTaskLater(CitizensAPI.getPlugin(), new Runnable() {
+ @Override
+ public void run() {
+ ((ServerLevel) level).getChunkProvider().removeEntity(EntityHumanNPC.this);
+ }
+ }, 35); // give enough time for death and smoke animation
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ public void doTick() {
+ if (npc == null) {
+ super.doTick();
+ return;
+ }
+ super.tick();
+ boolean navigating = npc.getNavigator().isNavigating();
+ if (!navigating && getBukkitEntity() != null
+ && (!npc.hasTrait(Gravity.class) || npc.getOrAddTrait(Gravity.class).hasGravity())
+ && Util.isLoaded(getBukkitEntity().getLocation(LOADED_LOCATION))) {
+ travel(new Vec3(0, 0, 0));
+ }
+ Vec3 mot = getDeltaMovement();
+ if (Math.abs(mot.x) < EPSILON && Math.abs(mot.y) < EPSILON && Math.abs(mot.z) < EPSILON) {
+ setDeltaMovement(new Vec3(0, 0, 0));
+ }
+ if (navigating) {
+ if (!NMSImpl.isNavigationFinished(navigation)) {
+ NMSImpl.updateNavigation(navigation);
+ }
+ moveOnCurrentHeading();
+ }
+ NMSImpl.updateAI(this);
+
+ if (isSpectator()) {
+ this.noPhysics = true;
+ this.onGround = false;
+ }
+
+ if (this.hurtTime > 0)
+ this.hurtTime--;
+ if (this.invulnerableTime > 0)
+ this.invulnerableTime--;
+ if (isDeadOrDying())
+ tickDeath();
+ if (this.lastHurtByPlayerTime > 0) {
+ this.lastHurtByPlayerTime--;
+ } else {
+ this.lastHurtByPlayer = null;
+ }
+ if (this.lastHurtByMob != null) {
+ if (!this.lastHurtByMob.isAlive()) {
+ setLastHurtByMob((LivingEntity) null);
+ } else if (this.tickCount - this.lastHurtByMobTimestamp > 100) {
+ setLastHurtByMob((LivingEntity) null);
+ }
+ }
+ tickEffects();
+ this.animStepO = this.animStep;
+ this.yBodyRotO = this.yBodyRot;
+ this.yHeadRotO = this.yHeadRot;
+ this.yRotO = getYRot();
+ this.xRotO = getXRot();
+ }
+
+ @Override
+ public Packet> getAddEntityPacket() {
+ if (playerlistTracker != null) {
+ playerlistTracker.updateLastPlayer();
+ }
+ return super.getAddEntityPacket();
+ }
+
+ @Override
+ public CraftPlayer getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PlayerNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ public PlayerControllerJump getControllerJump() {
+ return controllerJump;
+ }
+
+ public PlayerMoveControl getMoveControl() {
+ return controllerMove;
+ }
+
+ public PathNavigation getNavigation() {
+ return navigation;
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public float getPathfindingMalus(BlockPathTypes pathtype) {
+ return this.bz.containsKey(pathtype) ? this.bz.get(pathtype).floatValue() : pathtype.getMalus();
+ }
+
+ @Override
+ public GameProfile getProfile() {
+ return super.getGameProfile();
+ }
+
+ @Override
+ public String getSkinName() {
+ String skinName = npc.getOrAddTrait(SkinTrait.class).getSkinName();
+ if (skinName == null) {
+ skinName = npc.getName();
+ }
+ return skinName.toLowerCase();
+ }
+
+ @Override
+ public SkinPacketTracker getSkinTracker() {
+ return skinTracker;
+ }
+
+ @Override
+ public Component getTabListDisplayName() {
+ if (Setting.REMOVE_PLAYERS_FROM_PLAYER_LIST.asBoolean()) {
+ return new TextComponent("");
+ }
+ return super.getTabListDisplayName();
+ }
+
+ @Override
+ public boolean hurt(DamageSource damagesource, float f) {
+ // knock back velocity is cancelled and sent to client for handling when
+ // the entity is a player. there is no client so make this happen
+ // manually.
+ boolean damaged = super.hurt(damagesource, f);
+ if (damaged && hurtMarked) {
+ hurtMarked = false;
+ Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(), new Runnable() {
+ @Override
+ public void run() {
+ EntityHumanNPC.this.hurtMarked = true;
+ }
+ });
+ }
+ return damaged;
+ }
+
+ private void initialise(MinecraftServer minecraftServer) {
+ Socket socket = new EmptySocket();
+ EmptyNetworkManager conn = null;
+ try {
+ conn = new EmptyNetworkManager(PacketFlow.CLIENTBOUND);
+ connection = new EmptyNetHandler(minecraftServer, conn, this);
+ conn.setListener(connection);
+ socket.close();
+ } catch (IOException e) {
+ // swallow
+ }
+
+ AttributeInstance range = getAttribute(Attributes.FOLLOW_RANGE);
+ if (range == null) {
+ try {
+ AttributeSupplier provider = (AttributeSupplier) ATTRIBUTE_SUPPLIER.invoke(getAttributes());
+ Map all = Maps
+ .newHashMap((Map) ATTRIBUTE_PROVIDER_MAP.invoke(provider));
+ all.put(Attributes.FOLLOW_RANGE,
+ new AttributeInstance(Attributes.FOLLOW_RANGE, new Consumer() {
+ @Override
+ public void accept(AttributeInstance att) {
+ throw new UnsupportedOperationException(
+ "Tried to change value for default attribute instance FOLLOW_RANGE");
+ }
+ }));
+ ATTRIBUTE_PROVIDER_MAP_SETTER.invoke(provider, ImmutableMap.copyOf(all));
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ range = getAttribute(Attributes.FOLLOW_RANGE);
+ }
+ range.setBaseValue(Setting.DEFAULT_PATHFINDING_RANGE.asDouble());
+
+ controllerJump = new PlayerControllerJump(this);
+ controllerLook = new PlayerLookControl(this);
+ controllerMove = new PlayerMoveControl(this);
+ navigation = new PlayerNavigation(this, level);
+ this.invulnerableTime = 0;
+ NMS.setStepHeight(getBukkitEntity(), 1); // the default (0) breaks step climbing
+ setSkinFlags((byte) 0xFF);
+
+ EmptyAdvancementDataPlayer.clear(this.getAdvancements());
+ NMSImpl.setAdvancement(this.getBukkitEntity(),
+ new EmptyAdvancementDataPlayer(minecraftServer.getFixerUpper(), minecraftServer.getPlayerList(),
+ minecraftServer.getAdvancements(), CitizensAPI.getDataFolder().getParentFile(), this));
+ }
+
+ @Override
+ public boolean isInWall() {
+ if (npc == null || noPhysics || isSleeping()) {
+ return super.isInWall();
+ }
+ return Util.inBlock(getBukkitEntity());
+ }
+
+ public boolean isNavigating() {
+ return npc.getNavigator().isNavigating();
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable() : npc.data().get(NPC.COLLIDABLE_METADATA, true);
+ }
+
+ private void moveOnCurrentHeading() {
+ if (jumping) {
+ if (onGround && jumpTicks == 0) {
+ jumpFromGround();
+ jumpTicks = 10;
+ }
+ } else {
+ jumpTicks = 0;
+ }
+ xxa *= 0.98F;
+ zza *= 0.98F;
+ travel(new Vec3(this.xxa, this.yya, this.zza));
+ NMS.setHeadYaw(getBukkitEntity(), getYRot());
+ if (jumpTicks > 0) {
+ jumpTicks--;
+ }
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public void remove(RemovalReason reason) {
+ super.remove(reason);
+ getAdvancements().save();
+ }
+
+ public void setMoveDestination(double x, double y, double z, double speed) {
+ controllerMove.setWantedPosition(x, y, z, speed);
+ }
+
+ public void setPathfindingMalus(BlockPathTypes pathtype, float f) {
+ this.bz.put(pathtype, Float.valueOf(f));
+ }
+
+ public void setShouldJump() {
+ controllerJump.jump();
+ }
+
+ @Override
+ public void setSkinFlags(byte flags) {
+ this.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, flags);
+ }
+
+ @Override
+ public void setSkinName(String name) {
+ npc.getOrAddTrait(SkinTrait.class).setSkinName(name);
+ }
+
+ @Override
+ public void setSkinName(String name, boolean forceUpdate) {
+ npc.getOrAddTrait(SkinTrait.class).setSkinName(name, forceUpdate);
+ }
+
+ @Override
+ public void setSkinPersistent(String skinName, String signature, String data) {
+ npc.getOrAddTrait(SkinTrait.class).setSkinPersistent(skinName, signature, data);
+ }
+
+ public void setTargetLook(Entity target, float yawOffset, float renderOffset) {
+ controllerLook.a(target, yawOffset, renderOffset);
+ }
+
+ public void setTargetLook(Location target) {
+ controllerLook.a(target.getX(), target.getY(), target.getZ());
+ }
+
+ public void setTracked(PlayerlistTracker tracker) {
+ this.playerlistTracker = tracker;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc == null)
+ return;
+ noPhysics = isSpectator();
+ if (updateCounter + 1 > Setting.PACKET_UPDATE_DELAY.asInt()) {
+ effectsDirty = true;
+ }
+ Bukkit.getServer().getPluginManager().unsubscribeFromPermission("bukkit.broadcast.user", getBukkitEntity());
+
+ boolean navigating = npc.getNavigator().isNavigating();
+ updatePackets(navigating);
+
+ if (invulnerableTime > 0) {
+ --invulnerableTime;
+ }
+
+ npc.update();
+ /*
+ double diff = this.yaw - this.aK;
+ if (diff != 40 && diff != -40) {
+ ++this.yawUpdateRequiredTicks;
+ }
+ if (this.yawUpdateRequiredTicks > 5) {
+ this.yaw = (diff > -40 && diff < 0) || (diff > 0 && diff > 40) ? this.aK - 40 : this.aK + 40;
+ this.yawUpdateRequiredTicks = 0;
+ }
+ */
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ public void updateAI() {
+ controllerMove.tick();
+ controllerLook.a();
+ controllerJump.b();
+ }
+
+ private void updatePackets(boolean navigating) {
+ updateCounter++;
+ boolean itemChanged = false;
+ for (EquipmentSlot slot : EquipmentSlot.values()) {
+ ItemStack equipment = getItemBySlot(slot);
+ ItemStack cache = equipmentCache.get(slot);
+ if (!(cache == null && equipment == null)
+ && (cache == null ^ equipment == null || !ItemStack.isSame(cache, equipment))) {
+ itemChanged = true;
+ }
+ equipmentCache.put(slot, equipment);
+ }
+ if (updateCounter++ <= Setting.PACKET_UPDATE_DELAY.asInt() && !itemChanged)
+ return;
+
+ updateCounter = 0;
+ Location current = getBukkitEntity().getLocation(packetLocationCache);
+ Packet>[] packets = new Packet[1];
+ List> vals = Lists.newArrayList();
+ for (EquipmentSlot slot : EquipmentSlot.values()) {
+ vals.add(new Pair(slot, getItemBySlot(slot)));
+ }
+ packets[0] = new ClientboundSetEquipmentPacket(getId(), vals);
+ NMSImpl.sendPacketsNearby(getBukkitEntity(), current, packets);
+ }
+
+ public void updatePathfindingRange(float pathfindingRange) {
+ this.navigation.setRange(pathfindingRange);
+ }
+
+ public static class PlayerNPC extends CraftPlayer implements NPCHolder, SkinnableEntity {
+ private final CraftServer cserver;
+ private final CitizensNPC npc;
+
+ private PlayerNPC(EntityHumanNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ this.npc = entity.npc;
+ this.cserver = (CraftServer) Bukkit.getServer();
+ npc.getOrAddTrait(Inventory.class);
+ }
+
+ @Override
+ public Player getBukkitEntity() {
+ return this;
+ }
+
+ @Override
+ public EntityHumanNPC getHandle() {
+ return (EntityHumanNPC) this.entity;
+ }
+
+ @Override
+ public List getMetadata(String metadataKey) {
+ return cserver.getEntityMetadata().getMetadata(this, metadataKey);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public String getSkinName() {
+ return ((SkinnableEntity) this.entity).getSkinName();
+ }
+
+ @Override
+ public SkinPacketTracker getSkinTracker() {
+ return ((SkinnableEntity) this.entity).getSkinTracker();
+ }
+
+ @Override
+ public boolean hasMetadata(String metadataKey) {
+ return cserver.getEntityMetadata().hasMetadata(this, metadataKey);
+ }
+
+ @Override
+ public void removeMetadata(String metadataKey, Plugin owningPlugin) {
+ cserver.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin);
+ }
+
+ @Override
+ public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {
+ cserver.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue);
+ }
+
+ @Override
+ public void setSkinFlags(byte flags) {
+ ((SkinnableEntity) this.entity).setSkinFlags(flags);
+ }
+
+ @Override
+ public void setSkinName(String name) {
+ ((SkinnableEntity) this.entity).setSkinName(name);
+ }
+
+ @Override
+ public void setSkinName(String skinName, boolean forceUpdate) {
+ ((SkinnableEntity) this.entity).setSkinName(skinName, forceUpdate);
+ }
+
+ @Override
+ public void setSkinPersistent(String skinName, String signature, String data) {
+ ((SkinnableEntity) this.entity).setSkinPersistent(skinName, signature, data);
+ }
+ }
+
+ private static final MethodHandle ATTRIBUTE_PROVIDER_MAP = NMS.getGetter(AttributeSupplier.class, "a");
+ private static final MethodHandle ATTRIBUTE_PROVIDER_MAP_SETTER = NMS.getFinalSetter(AttributeSupplier.class, "a");
+ private static final MethodHandle ATTRIBUTE_SUPPLIER = NMS.getGetter(AttributeMap.class, "d");
+ private static final float EPSILON = 0.005F;
+ private static final MethodHandle GAMEMODE_SETTING = NMS.getFirstMethodHandle(ServerPlayerGameMode.class, true,
+ GameType.class, GameType.class);
+ private static final Location LOADED_LOCATION = new Location(null, 0, 0, 0);
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EvokerController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EvokerController.java
new file mode 100644
index 000000000..a8ef0b8a2
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/EvokerController.java
@@ -0,0 +1,194 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEvoker;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Evoker;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class EvokerController extends MobEntityController {
+ public EvokerController() {
+ super(EntityEvokerNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Evoker getBukkitEntity() {
+ return (org.bukkit.entity.Evoker) super.getBukkitEntity();
+ }
+
+ public static class EntityEvokerNPC extends Evoker implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityEvokerNPC(EntityType extends Evoker> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityEvokerNPC(EntityType extends Evoker> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EvokerNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class EvokerNPC extends CraftEvoker implements ForwardingNPCHolder {
+ public EvokerNPC(EntityEvokerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/FoxController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/FoxController.java
new file mode 100644
index 000000000..7d83760b4
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/FoxController.java
@@ -0,0 +1,206 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftFox;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.Fox;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class FoxController extends MobEntityController {
+ public FoxController() {
+ super(EntityFoxNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Fox getBukkitEntity() {
+ return (org.bukkit.entity.Fox) super.getBukkitEntity();
+ }
+
+ public static class EntityFoxNPC extends Fox implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityFoxNPC(EntityType extends Fox> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityFoxNPC(EntityType extends Fox> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new FoxNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class FoxNPC extends CraftFox implements ForwardingNPCHolder {
+ public FoxNPC(EntityFoxNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GhastController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GhastController.java
new file mode 100644
index 000000000..476f5328b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GhastController.java
@@ -0,0 +1,166 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftGhast;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Ghast;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+
+public class GhastController extends MobEntityController {
+ public GhastController() {
+ super(EntityGhastNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Ghast getBukkitEntity() {
+ return (org.bukkit.entity.Ghast) super.getBukkitEntity();
+ }
+
+ public static class EntityGhastNPC extends Ghast implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityGhastNPC(EntityType extends Ghast> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityGhastNPC(EntityType extends Ghast> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ npc.update();
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (!npc.useMinecraftAI()) {
+ NMSImpl.updateAI(this);
+ }
+ }
+ super.customServerAiStep();
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new GhastNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isAutoSpinAttack() {
+ return npc != null;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+ }
+
+ public static class GhastNPC extends CraftGhast implements ForwardingNPCHolder {
+ public GhastNPC(EntityGhastNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GiantController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GiantController.java
new file mode 100644
index 000000000..ddd31d8c2
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GiantController.java
@@ -0,0 +1,202 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftGiant;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Giant;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class GiantController extends MobEntityController {
+ public GiantController() {
+ super(EntityGiantNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Giant getBukkitEntity() {
+ return (org.bukkit.entity.Giant) super.getBukkitEntity();
+ }
+
+ public static class EntityGiantNPC extends Giant implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityGiantNPC(EntityType extends Giant> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityGiantNPC(EntityType extends Giant> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new GiantNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class GiantNPC extends CraftGiant implements ForwardingNPCHolder {
+ public GiantNPC(EntityGiantNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GlowSquidController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GlowSquidController.java
new file mode 100644
index 000000000..3aea55543
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GlowSquidController.java
@@ -0,0 +1,202 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftGlowSquid;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.GlowSquid;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class GlowSquidController extends MobEntityController {
+ public GlowSquidController() {
+ super(EntityGlowSquidNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.GlowSquid getBukkitEntity() {
+ return (org.bukkit.entity.GlowSquid) super.getBukkitEntity();
+ }
+
+ public static class EntityGlowSquidNPC extends GlowSquid implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityGlowSquidNPC(EntityType extends GlowSquid> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityGlowSquidNPC(EntityType extends GlowSquid> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new GlowSquidNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class GlowSquidNPC extends CraftGlowSquid implements ForwardingNPCHolder {
+ public GlowSquidNPC(EntityGlowSquidNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GoatController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GoatController.java
new file mode 100644
index 000000000..fc7813d39
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GoatController.java
@@ -0,0 +1,206 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftGoat;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.goat.Goat;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class GoatController extends MobEntityController {
+ public GoatController() {
+ super(EntityGoatNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Goat getBukkitEntity() {
+ return (org.bukkit.entity.Goat) super.getBukkitEntity();
+ }
+
+ public static class EntityGoatNPC extends Goat implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityGoatNPC(EntityType extends Goat> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityGoatNPC(EntityType extends Goat> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new GoatNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class GoatNPC extends CraftGoat implements ForwardingNPCHolder {
+ public GoatNPC(EntityGoatNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GuardianController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GuardianController.java
new file mode 100644
index 000000000..320ec5070
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GuardianController.java
@@ -0,0 +1,209 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftGuardian;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Guardian;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class GuardianController extends MobEntityController {
+ public GuardianController() {
+ super(EntityGuardianNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Guardian getBukkitEntity() {
+ return (org.bukkit.entity.Guardian) super.getBukkitEntity();
+ }
+
+ public static class EntityGuardianNPC extends Guardian implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityGuardianNPC(EntityType extends Guardian> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityGuardianNPC(EntityType extends Guardian> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ if (npc == null) {
+ super.aiStep();
+ } else {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (!npc.useMinecraftAI()) {
+ NMSImpl.updateAI(this);
+ } else {
+ super.aiStep();
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new GuardianNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class GuardianNPC extends CraftGuardian implements ForwardingNPCHolder {
+ public GuardianNPC(EntityGuardianNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GuardianElderController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GuardianElderController.java
new file mode 100644
index 000000000..bb140d3c6
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/GuardianElderController.java
@@ -0,0 +1,209 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftElderGuardian;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.ElderGuardian;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class GuardianElderController extends MobEntityController {
+ public GuardianElderController() {
+ super(EntityGuardianElderNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.ElderGuardian getBukkitEntity() {
+ return (org.bukkit.entity.ElderGuardian) super.getBukkitEntity();
+ }
+
+ public static class EntityGuardianElderNPC extends ElderGuardian implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityGuardianElderNPC(EntityType extends ElderGuardian> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityGuardianElderNPC(EntityType extends ElderGuardian> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ if (npc == null) {
+ super.aiStep();
+ } else {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (!npc.useMinecraftAI()) {
+ NMSImpl.updateAI(this);
+ } else {
+ super.aiStep();
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new GuardianElderNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class GuardianElderNPC extends CraftElderGuardian implements ForwardingNPCHolder {
+ public GuardianElderNPC(EntityGuardianElderNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HoglinController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HoglinController.java
new file mode 100644
index 000000000..dcae5dcc1
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HoglinController.java
@@ -0,0 +1,196 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftHoglin;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.hoglin.Hoglin;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class HoglinController extends MobEntityController {
+ public HoglinController() {
+ super(EntityHoglinNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Hoglin getBukkitEntity() {
+ return (org.bukkit.entity.Hoglin) super.getBukkitEntity();
+ }
+
+ public static class EntityHoglinNPC extends Hoglin implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityHoglinNPC(EntityType extends Hoglin> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityHoglinNPC(EntityType extends Hoglin> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ setImmuneToZombification(true);
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new HoglinNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class HoglinNPC extends CraftHoglin implements ForwardingNPCHolder {
+ public HoglinNPC(EntityHoglinNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseController.java
new file mode 100644
index 000000000..46df28001
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseController.java
@@ -0,0 +1,252 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftHorse;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.Controllable;
+import net.citizensnpcs.trait.HorseModifiers;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.animal.horse.Horse;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseController extends MobEntityController {
+ public HorseController() {
+ super(EntityHorseNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Horse getBukkitEntity() {
+ return (org.bukkit.entity.Horse) super.getBukkitEntity();
+ }
+
+ @Override
+ public void spawn(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.spawn(at, npc);
+ }
+
+ public static class EntityHorseNPC extends Horse implements NPCHolder {
+ private double baseMovementSpeed;
+ private boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+ private boolean riding;
+
+ public EntityHorseNPC(EntityType extends Horse> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityHorseNPC(EntityType extends Horse> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ org.bukkit.entity.Horse horse = (org.bukkit.entity.Horse) getBukkitEntity();
+ horse.setDomestication(horse.getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ public boolean canBeControlledByRider() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.canBeControlledByRider();
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc == null)
+ return;
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.hasTrait(Controllable.class) && npc.getOrAddTrait(Controllable.class).isEnabled()) {
+ riding = getBukkitEntity().getPassengers().size() > 0;
+ getAttribute(Attributes.MOVEMENT_SPEED)
+ .setBaseValue(baseMovementSpeed * npc.getNavigator().getDefaultParameters().speedModifier());
+ } else {
+ riding = false;
+ }
+ if (riding) {
+ if (npc.getNavigator().isNavigating()) {
+ org.bukkit.entity.Entity basePassenger = passengers.get(0).getBukkitEntity();
+ NMS.look(basePassenger, getYRot(), getXRot());
+ }
+ setFlag(4, true); // datawatcher method
+ }
+ NMS.setStepHeight(getBukkitEntity(), 1);
+ npc.update();
+
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new HorseNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class HorseNPC extends CraftHorse implements ForwardingNPCHolder {
+ public HorseNPC(EntityHorseNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseDonkeyController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseDonkeyController.java
new file mode 100644
index 000000000..caf67d85e
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseDonkeyController.java
@@ -0,0 +1,251 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftDonkey;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.Controllable;
+import net.citizensnpcs.trait.HorseModifiers;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.animal.horse.Donkey;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseDonkeyController extends MobEntityController {
+ public HorseDonkeyController() {
+ super(EntityHorseDonkeyNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Donkey getBukkitEntity() {
+ return (org.bukkit.entity.Donkey) super.getBukkitEntity();
+ }
+
+ @Override
+ public void spawn(Location at, NPC npc) {
+ npc.addTrait(HorseModifiers.class);
+ super.spawn(at, npc);
+ }
+
+ public static class EntityHorseDonkeyNPC extends Donkey implements NPCHolder {
+ private double baseMovementSpeed;
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+ private boolean riding;
+
+ public EntityHorseDonkeyNPC(EntityType extends Donkey> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityHorseDonkeyNPC(EntityType extends Donkey> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ ((org.bukkit.entity.Donkey) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.Donkey) getBukkitEntity()).getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ public boolean canBeControlledByRider() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.canBeControlledByRider();
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.hasTrait(Controllable.class) && npc.getOrAddTrait(Controllable.class).isEnabled()) {
+ riding = getBukkitEntity().getPassengers().size() > 0;
+ getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(
+ baseMovementSpeed * npc.getNavigator().getDefaultParameters().speedModifier());
+ } else {
+ riding = false;
+ }
+ if (riding) {
+ if (npc.getNavigator().isNavigating()) {
+ org.bukkit.entity.Entity basePassenger = passengers.get(0).getBukkitEntity();
+ NMS.look(basePassenger, getYRot(), getXRot());
+ }
+ setFlag(4, true); // datawatcher method
+ }
+ NMS.setStepHeight(getBukkitEntity(), 1);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new HorseDonkeyNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class HorseDonkeyNPC extends CraftDonkey implements ForwardingNPCHolder {
+ public HorseDonkeyNPC(EntityHorseDonkeyNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseMuleController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseMuleController.java
new file mode 100644
index 000000000..3731fcd24
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseMuleController.java
@@ -0,0 +1,251 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftMule;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.Controllable;
+import net.citizensnpcs.trait.HorseModifiers;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.animal.horse.Mule;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseMuleController extends MobEntityController {
+ public HorseMuleController() {
+ super(EntityHorseMuleNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Mule getBukkitEntity() {
+ return (org.bukkit.entity.Mule) super.getBukkitEntity();
+ }
+
+ @Override
+ public void spawn(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.spawn(at, npc);
+ }
+
+ public static class EntityHorseMuleNPC extends Mule implements NPCHolder {
+ private double baseMovementSpeed;
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+ private boolean riding;
+
+ public EntityHorseMuleNPC(EntityType extends Mule> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityHorseMuleNPC(EntityType extends Mule> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ ((org.bukkit.entity.Mule) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.Mule) getBukkitEntity()).getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ public boolean canBeControlledByRider() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.canBeControlledByRider();
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.hasTrait(Controllable.class) && npc.getOrAddTrait(Controllable.class).isEnabled()) {
+ riding = getBukkitEntity().getPassengers().size() > 0;
+ getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(
+ baseMovementSpeed * npc.getNavigator().getDefaultParameters().speedModifier());
+ } else {
+ riding = false;
+ }
+ if (riding) {
+ if (npc.getNavigator().isNavigating()) {
+ org.bukkit.entity.Entity basePassenger = passengers.get(0).getBukkitEntity();
+ NMS.look(basePassenger, getYRot(), getXRot());
+ }
+ setFlag(4, true); // datawatcher method
+ }
+ NMS.setStepHeight(getBukkitEntity(), 1);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new HorseMuleNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class HorseMuleNPC extends CraftMule implements ForwardingNPCHolder {
+ public HorseMuleNPC(EntityHorseMuleNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseSkeletonController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseSkeletonController.java
new file mode 100644
index 000000000..41a4e09c9
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseSkeletonController.java
@@ -0,0 +1,251 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSkeletonHorse;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.Controllable;
+import net.citizensnpcs.trait.HorseModifiers;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.animal.horse.SkeletonHorse;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseSkeletonController extends MobEntityController {
+ public HorseSkeletonController() {
+ super(EntityHorseSkeletonNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.SkeletonHorse getBukkitEntity() {
+ return (org.bukkit.entity.SkeletonHorse) super.getBukkitEntity();
+ }
+
+ @Override
+ public void spawn(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.spawn(at, npc);
+ }
+
+ public static class EntityHorseSkeletonNPC extends SkeletonHorse implements NPCHolder {
+ private double baseMovementSpeed;
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+ private boolean riding;
+
+ public EntityHorseSkeletonNPC(EntityType extends SkeletonHorse> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityHorseSkeletonNPC(EntityType extends SkeletonHorse> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ ((org.bukkit.entity.SkeletonHorse) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.SkeletonHorse) getBukkitEntity()).getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ public boolean canBeControlledByRider() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.canBeControlledByRider();
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.hasTrait(Controllable.class) && npc.getOrAddTrait(Controllable.class).isEnabled()) {
+ riding = getBukkitEntity().getPassengers().size() > 0;
+ getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(
+ baseMovementSpeed * npc.getNavigator().getDefaultParameters().speedModifier());
+ } else {
+ riding = false;
+ }
+ if (riding) {
+ if (npc.getNavigator().isNavigating()) {
+ org.bukkit.entity.Entity basePassenger = passengers.get(0).getBukkitEntity();
+ NMS.look(basePassenger, getYRot(), getXRot());
+ }
+ setFlag(4, true); // datawatcher method
+ }
+ NMS.setStepHeight(getBukkitEntity(), 1);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new HorseSkeletonNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class HorseSkeletonNPC extends CraftSkeletonHorse implements ForwardingNPCHolder {
+ public HorseSkeletonNPC(EntityHorseSkeletonNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseZombieController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseZombieController.java
new file mode 100644
index 000000000..3d4e50c9b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HorseZombieController.java
@@ -0,0 +1,251 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftZombieHorse;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.Controllable;
+import net.citizensnpcs.trait.HorseModifiers;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.animal.horse.ZombieHorse;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseZombieController extends MobEntityController {
+ public HorseZombieController() {
+ super(EntityHorseZombieNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.ZombieHorse getBukkitEntity() {
+ return (org.bukkit.entity.ZombieHorse) super.getBukkitEntity();
+ }
+
+ @Override
+ public void spawn(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.spawn(at, npc);
+ }
+
+ public static class EntityHorseZombieNPC extends ZombieHorse implements NPCHolder {
+ private double baseMovementSpeed;
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+ private boolean riding;
+
+ public EntityHorseZombieNPC(EntityType extends ZombieHorse> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityHorseZombieNPC(EntityType extends ZombieHorse> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ ((org.bukkit.entity.ZombieHorse) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.ZombieHorse) getBukkitEntity()).getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ public boolean canBeControlledByRider() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.canBeControlledByRider();
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.hasTrait(Controllable.class) && npc.getOrAddTrait(Controllable.class).isEnabled()) {
+ riding = getBukkitEntity().getPassengers().size() > 0;
+ getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(
+ baseMovementSpeed * npc.getNavigator().getDefaultParameters().speedModifier());
+ } else {
+ riding = false;
+ }
+ if (riding) {
+ if (npc.getNavigator().isNavigating()) {
+ org.bukkit.entity.Entity basePassenger = passengers.get(0).getBukkitEntity();
+ NMS.look(basePassenger, getYRot(), getXRot());
+ }
+ setFlag(4, true); // datawatcher method
+ }
+ NMS.setStepHeight(getBukkitEntity(), 1);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new HorseZombieNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class HorseZombieNPC extends CraftZombieHorse implements ForwardingNPCHolder {
+ public HorseZombieNPC(EntityHorseZombieNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HumanController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HumanController.java
new file mode 100644
index 000000000..0042da2c0
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/HumanController.java
@@ -0,0 +1,96 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftWorld;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+
+import com.mojang.authlib.GameProfile;
+
+import net.citizensnpcs.Settings.Setting;
+import net.citizensnpcs.api.CitizensAPI;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.npc.AbstractEntityController;
+import net.citizensnpcs.npc.skin.Skin;
+import net.citizensnpcs.npc.skin.SkinnableEntity;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+
+public class HumanController extends AbstractEntityController {
+ public HumanController() {
+ super();
+ }
+
+ @Override
+ protected Entity createEntity(final Location at, final NPC npc) {
+ final ServerLevel nmsWorld = ((CraftWorld) at.getWorld()).getHandle();
+ String coloredName = npc.getFullName();
+ String name = coloredName.length() > 16 ? coloredName.substring(0, 16) : coloredName;
+ UUID uuid = npc.getUniqueId();
+ if (uuid.version() == 4) { // clear version
+ long msb = uuid.getMostSignificantBits();
+ msb &= ~0x0000000000004000L;
+ msb |= 0x0000000000002000L;
+ uuid = new UUID(msb, uuid.getLeastSignificantBits());
+ }
+
+ String teamName = Util.getTeamName(uuid);
+ if (npc.requiresNameHologram()) {
+ name = teamName;
+ }
+
+ if (Setting.USE_SCOREBOARD_TEAMS.asBoolean()) {
+ Util.generateTeamFor(npc, name, teamName);
+ }
+
+ final GameProfile profile = new GameProfile(uuid, name);
+ final EntityHumanNPC handle = new EntityHumanNPC(MinecraftServer.getServer(), nmsWorld, profile, npc);
+
+ Skin skin = handle.getSkinTracker().getSkin();
+ if (skin != null) {
+ skin.apply(handle);
+ }
+
+ Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
+ @Override
+ public void run() {
+ if (getBukkitEntity() == null || !getBukkitEntity().isValid()
+ || getBukkitEntity() != handle.getBukkitEntity())
+ return;
+ boolean removeFromPlayerList = npc.data().get("removefromplayerlist",
+ Setting.REMOVE_PLAYERS_FROM_PLAYER_LIST.asBoolean());
+ NMS.addOrRemoveFromPlayerList(getBukkitEntity(), removeFromPlayerList);
+ }
+ }, 20);
+
+ handle.getBukkitEntity().setSleepingIgnored(true);
+
+ return handle.getBukkitEntity();
+ }
+
+ @Override
+ public Player getBukkitEntity() {
+ return (Player) super.getBukkitEntity();
+ }
+
+ @Override
+ public void remove() {
+ Player entity = getBukkitEntity();
+ if (entity != null) {
+ if (Setting.USE_SCOREBOARD_TEAMS.asBoolean()) {
+ Util.removeTeamFor(NMS.getNPC(entity), entity.getName());
+ }
+ NMS.removeFromWorld(entity);
+ SkinnableEntity npc = entity instanceof SkinnableEntity ? (SkinnableEntity) entity : null;
+ npc.getSkinTracker().onRemoveNPC();
+ }
+ NMS.remove(entity);
+ // Paper decided to break Spigot compatibility.
+ // super.remove();
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/IllusionerController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/IllusionerController.java
new file mode 100644
index 000000000..9e3131481
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/IllusionerController.java
@@ -0,0 +1,194 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftIllusioner;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Illusioner;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class IllusionerController extends MobEntityController {
+ public IllusionerController() {
+ super(EntityIllusionerNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Illusioner getBukkitEntity() {
+ return (org.bukkit.entity.Illusioner) super.getBukkitEntity();
+ }
+
+ public static class EntityIllusionerNPC extends Illusioner implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityIllusionerNPC(EntityType extends Illusioner> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityIllusionerNPC(EntityType extends Illusioner> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new IllusionerNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class IllusionerNPC extends CraftIllusioner implements ForwardingNPCHolder {
+ public IllusionerNPC(EntityIllusionerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/IronGolemController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/IronGolemController.java
new file mode 100644
index 000000000..4e8fb04ac
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/IronGolemController.java
@@ -0,0 +1,202 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftIronGolem;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.IronGolem;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class IronGolemController extends MobEntityController {
+ public IronGolemController() {
+ super(EntityIronGolemNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.IronGolem getBukkitEntity() {
+ return (org.bukkit.entity.IronGolem) super.getBukkitEntity();
+ }
+
+ public static class EntityIronGolemNPC extends IronGolem implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityIronGolemNPC(EntityType extends IronGolem> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityIronGolemNPC(EntityType extends IronGolem> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new IronGolemNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class IronGolemNPC extends CraftIronGolem implements ForwardingNPCHolder {
+ public IronGolemNPC(EntityIronGolemNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/LlamaController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/LlamaController.java
new file mode 100644
index 000000000..2191d6683
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/LlamaController.java
@@ -0,0 +1,223 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftLlama;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.HorseModifiers;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.horse.Llama;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class LlamaController extends MobEntityController {
+ public LlamaController() {
+ super(EntityLlamaNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Llama getBukkitEntity() {
+ return (org.bukkit.entity.Llama) super.getBukkitEntity();
+ }
+
+ @Override
+ public void spawn(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.spawn(at, npc);
+ }
+
+ public static class EntityLlamaNPC extends Llama implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityLlamaNPC(EntityType extends Llama> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityLlamaNPC(EntityType extends Llama> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ ((org.bukkit.entity.Llama) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.Llama) getBukkitEntity()).getMaxDomestication());
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc == null) {
+ super.customServerAiStep();
+ } else {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI()) {
+ super.customServerAiStep();
+ }
+ NMS.setStepHeight(getBukkitEntity(), 1);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new LlamaNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class LlamaNPC extends CraftLlama implements ForwardingNPCHolder {
+ public LlamaNPC(EntityLlamaNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/MagmaCubeController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/MagmaCubeController.java
new file mode 100644
index 000000000..14361db3b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/MagmaCubeController.java
@@ -0,0 +1,222 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftMagmaCube;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.monster.MagmaCube;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class MagmaCubeController extends MobEntityController {
+ public MagmaCubeController() {
+ super(EntityMagmaCubeNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.MagmaCube getBukkitEntity() {
+ return (org.bukkit.entity.MagmaCube) super.getBukkitEntity();
+ }
+
+ public static class EntityMagmaCubeNPC extends MagmaCube implements NPCHolder {
+ private final CitizensNPC npc;
+ private MoveControl oldMoveController;
+
+ public EntityMagmaCubeNPC(EntityType extends MagmaCube> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMagmaCubeNPC(EntityType extends MagmaCube> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ setSize(3, true);
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new MagmaCubeNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void playerTouch(Player human) {
+ if (npc == null) {
+ super.playerTouch(human);
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class MagmaCubeNPC extends CraftMagmaCube implements ForwardingNPCHolder {
+ public MagmaCubeNPC(EntityMagmaCubeNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/MobEntityController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/MobEntityController.java
new file mode 100644
index 000000000..7b5a929be
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/MobEntityController.java
@@ -0,0 +1,71 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import java.lang.reflect.Constructor;
+import java.util.Map;
+
+import org.bukkit.Location;
+import org.bukkit.block.BlockFace;
+import org.bukkit.craftbukkit.v1_17_R1.CraftWorld;
+import org.bukkit.entity.Entity;
+
+import com.google.common.collect.Maps;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.AbstractEntityController;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.level.Level;
+
+public abstract class MobEntityController extends AbstractEntityController {
+ private final Class> clazz;
+ private final Constructor> constructor;
+
+ protected MobEntityController(Class> clazz) {
+ super(clazz);
+ this.constructor = getConstructor(clazz);
+ this.clazz = clazz;
+ }
+
+ @Override
+ protected Entity createEntity(Location at, NPC npc) {
+ EntityType> type = NMSImpl.getEntityType(clazz);
+ net.minecraft.world.entity.Entity entity = createEntityFromClass(type, ((CraftWorld) at.getWorld()).getHandle(),
+ npc);
+ entity.absMoveTo(at.getX(), at.getY(), at.getZ(), at.getYaw(), at.getPitch());
+
+ // entity.onGround isn't updated right away - we approximate here so
+ // that things like pathfinding still work *immediately* after spawn.
+ org.bukkit.Material beneath = at.getBlock().getRelative(BlockFace.DOWN).getType();
+ if (beneath.isSolid()) {
+ entity.setOnGround(true);
+ }
+ try {
+ NMSImpl.UUID_FIELD.invoke(entity, npc.getUniqueId());
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return entity.getBukkitEntity();
+ }
+
+ private net.minecraft.world.entity.Entity createEntityFromClass(Object... args) {
+ try {
+ return (net.minecraft.world.entity.Entity) constructor.newInstance(args);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ private static Constructor> getConstructor(Class> clazz) {
+ Constructor> constructor = CONSTRUCTOR_CACHE.get(clazz);
+ if (constructor != null)
+ return constructor;
+ try {
+ return clazz.getConstructor(EntityType.class, Level.class, NPC.class);
+ } catch (Exception ex) {
+ throw new IllegalStateException("unable to find an entity constructor");
+ }
+ }
+
+ private static final Map, Constructor>> CONSTRUCTOR_CACHE = Maps.newHashMap();
+}
\ No newline at end of file
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/MushroomCowController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/MushroomCowController.java
new file mode 100644
index 000000000..b07c5a8e6
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/MushroomCowController.java
@@ -0,0 +1,216 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftMushroomCow;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.MushroomCow;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class MushroomCowController extends MobEntityController {
+ public MushroomCowController() {
+ super(EntityMushroomCowNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.MushroomCow getBukkitEntity() {
+ return (org.bukkit.entity.MushroomCow) super.getBukkitEntity();
+ }
+
+ public static class EntityMushroomCowNPC extends MushroomCow implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityMushroomCowNPC(EntityType extends MushroomCow> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMushroomCowNPC(EntityType extends MushroomCow> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new MushroomCowNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc == null || !npc.isProtected())
+ return super.mobInteract(entityhuman, enumhand);
+ return InteractionResult.FAIL;
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class MushroomCowNPC extends CraftMushroomCow implements ForwardingNPCHolder {
+ public MushroomCowNPC(EntityMushroomCowNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/OcelotController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/OcelotController.java
new file mode 100644
index 000000000..c2cbb9f18
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/OcelotController.java
@@ -0,0 +1,213 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftOcelot;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.Pose;
+import net.minecraft.world.entity.animal.Ocelot;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class OcelotController extends MobEntityController {
+ public OcelotController() {
+ super(EntityOcelotNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Ocelot getBukkitEntity() {
+ return (org.bukkit.entity.Ocelot) super.getBukkitEntity();
+ }
+
+ public static class EntityOcelotNPC extends Ocelot implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityOcelotNPC(EntityType extends Ocelot> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityOcelotNPC(EntityType extends Ocelot> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ Pose old = this.getPose();
+ boolean restorePose = !this.getMoveControl().hasWanted();
+ super.customServerAiStep();
+ if (restorePose) {
+ this.setPose(old);
+ }
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new OcelotNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class OcelotNPC extends CraftOcelot implements ForwardingNPCHolder {
+ public OcelotNPC(EntityOcelotNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PandaController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PandaController.java
new file mode 100644
index 000000000..02a835192
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PandaController.java
@@ -0,0 +1,206 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPanda;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.Panda;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class PandaController extends MobEntityController {
+ public PandaController() {
+ super(EntityPandaNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Panda getBukkitEntity() {
+ return (org.bukkit.entity.Panda) super.getBukkitEntity();
+ }
+
+ public static class EntityPandaNPC extends Panda implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityPandaNPC(EntityType extends Panda> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPandaNPC(EntityType extends Panda> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PandaNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class PandaNPC extends CraftPanda implements ForwardingNPCHolder {
+ public PandaNPC(EntityPandaNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ParrotController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ParrotController.java
new file mode 100644
index 000000000..68269cac0
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ParrotController.java
@@ -0,0 +1,177 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftParrot;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.Parrot;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+
+public class ParrotController extends MobEntityController {
+ public ParrotController() {
+ super(EntityParrotNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Parrot getBukkitEntity() {
+ return (org.bukkit.entity.Parrot) super.getBukkitEntity();
+ }
+
+ public static class EntityParrotNPC extends Parrot implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityParrotNPC(EntityType extends Parrot> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityParrotNPC(EntityType extends Parrot> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc == null) {
+ super.customServerAiStep();
+ } else {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (!npc.useMinecraftAI()) {
+ NMSImpl.updateAI(this);
+ } else {
+ super.customServerAiStep();
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ public SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ParrotNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null) {
+ return super.isLeashed();
+ }
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ // block feeding
+ if (npc == null || !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) {
+ return super.mobInteract(entityhuman, enumhand);
+ }
+ return InteractionResult.FAIL;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+ }
+
+ public static class ParrotNPC extends CraftParrot implements ForwardingNPCHolder {
+ public ParrotNPC(EntityParrotNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PhantomController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PhantomController.java
new file mode 100644
index 000000000..893012db6
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PhantomController.java
@@ -0,0 +1,239 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPhantom;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.Difficulty;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.control.LookControl;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.monster.Phantom;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.storage.WorldData;
+import net.minecraft.world.phys.Vec3;
+
+public class PhantomController extends MobEntityController {
+ public PhantomController() {
+ super(EntityPhantomNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Phantom getBukkitEntity() {
+ return (org.bukkit.entity.Phantom) super.getBukkitEntity();
+ }
+
+ public static class EntityPhantomNPC extends Phantom implements NPCHolder {
+ private final CitizensNPC npc;
+ private LookControl oldLookController;
+ private MoveControl oldMoveController;
+
+ public EntityPhantomNPC(EntityType extends Phantom> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPhantomNPC(EntityType extends Phantom> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ setNoAi(true);
+ this.oldMoveController = this.moveControl;
+ this.oldLookController = this.lookControl;
+ this.moveControl = new MoveControl(this);
+ this.lookControl = new LookControl(this);
+ // TODO: phantom pitch reversed
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ super.aiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ this.lookControl = this.oldLookController;
+ setNoAi(false);
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ this.lookControl = new LookControl(this);
+ setNoAi(true);
+ }
+ if (npc.isProtected()) {
+ this.setSecondsOnFire(0);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PhantomNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean isSunBurnTick() {
+ if (npc == null || !npc.isProtected())
+ return super.isSunBurnTick();
+ return false;
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ // avoid suicide
+ boolean resetDifficulty = this.level.getDifficulty() == Difficulty.PEACEFUL;
+ if (npc != null && resetDifficulty) {
+ ((WorldData) this.level.getLevelData()).setDifficulty(Difficulty.NORMAL);
+ }
+ super.tick();
+ if (npc != null && resetDifficulty) {
+ ((WorldData) this.level.getLevelData()).setDifficulty(Difficulty.PEACEFUL);
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class PhantomNPC extends CraftPhantom implements ForwardingNPCHolder {
+ public PhantomNPC(EntityPhantomNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PigController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PigController.java
new file mode 100644
index 000000000..f762e6c69
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PigController.java
@@ -0,0 +1,222 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPig;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LightningBolt;
+import net.minecraft.world.entity.animal.Pig;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class PigController extends MobEntityController {
+ public PigController() {
+ super(EntityPigNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Pig getBukkitEntity() {
+ return (org.bukkit.entity.Pig) super.getBukkitEntity();
+ }
+
+ public static class EntityPigNPC extends Pig implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityPigNPC(EntityType extends Pig> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPigNPC(EntityType extends Pig> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ public boolean canBeControlledByRider() {
+ // block carrot-on-a-stick behaviour
+ return npc == null ? super.canBeControlledByRider() : false;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PigNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void thunderHit(ServerLevel worldserver, LightningBolt entitylightning) {
+ if (npc == null) {
+ super.thunderHit(worldserver, entitylightning);
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class PigNPC extends CraftPig implements ForwardingNPCHolder {
+ public PigNPC(EntityPigNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PigZombieController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PigZombieController.java
new file mode 100644
index 000000000..10367fd20
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PigZombieController.java
@@ -0,0 +1,194 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPigZombie;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.ZombifiedPiglin;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class PigZombieController extends MobEntityController {
+
+ public PigZombieController() {
+ super(EntityPigZombieNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.PigZombie getBukkitEntity() {
+ return (org.bukkit.entity.PigZombie) super.getBukkitEntity();
+ }
+
+ public static class EntityPigZombieNPC extends ZombifiedPiglin implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityPigZombieNPC(EntityType extends ZombifiedPiglin> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPigZombieNPC(EntityType extends ZombifiedPiglin> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PigZombieNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class PigZombieNPC extends CraftPigZombie implements ForwardingNPCHolder {
+ public PigZombieNPC(EntityPigZombieNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PiglinBruteController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PiglinBruteController.java
new file mode 100644
index 000000000..edfa47a8e
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PiglinBruteController.java
@@ -0,0 +1,199 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import java.util.TreeMap;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPiglinBrute;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.piglin.PiglinBrute;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class PiglinBruteController extends MobEntityController {
+ public PiglinBruteController() {
+ super(EntityPiglinBruteNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.PiglinBrute getBukkitEntity() {
+ return (org.bukkit.entity.PiglinBrute) super.getBukkitEntity();
+ }
+
+ public static class EntityPiglinBruteNPC extends PiglinBrute implements NPCHolder {
+ private TreeMap, ?> behaviorMap;
+ private final CitizensNPC npc;
+
+ public EntityPiglinBruteNPC(EntityType extends PiglinBrute> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPiglinBruteNPC(EntityType extends PiglinBrute> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ setImmuneToZombification(true);
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PiglinBruteNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class PiglinBruteNPC extends CraftPiglinBrute implements ForwardingNPCHolder {
+ public PiglinBruteNPC(EntityPiglinBruteNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PiglinController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PiglinController.java
new file mode 100644
index 000000000..891f4c288
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PiglinController.java
@@ -0,0 +1,196 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPiglin;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.piglin.Piglin;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class PiglinController extends MobEntityController {
+ public PiglinController() {
+ super(EntityPiglinNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Piglin getBukkitEntity() {
+ return (org.bukkit.entity.Piglin) super.getBukkitEntity();
+ }
+
+ public static class EntityPiglinNPC extends Piglin implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityPiglinNPC(EntityType extends Piglin> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPiglinNPC(EntityType extends Piglin> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ setImmuneToZombification(true);
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PiglinNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class PiglinNPC extends CraftPiglin implements ForwardingNPCHolder {
+ public PiglinNPC(EntityPiglinNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PillagerController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PillagerController.java
new file mode 100644
index 000000000..e24d1ba05
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PillagerController.java
@@ -0,0 +1,206 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPillager;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Pillager;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class PillagerController extends MobEntityController {
+ public PillagerController() {
+ super(EntityPillagerNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Pillager getBukkitEntity() {
+ return (org.bukkit.entity.Pillager) super.getBukkitEntity();
+ }
+
+ public static class EntityPillagerNPC extends Pillager implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityPillagerNPC(EntityType extends Pillager> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPillagerNPC(EntityType extends Pillager> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PillagerNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class PillagerNPC extends CraftPillager implements ForwardingNPCHolder {
+ public PillagerNPC(EntityPillagerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PolarBearController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PolarBearController.java
new file mode 100644
index 000000000..ee4b66144
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PolarBearController.java
@@ -0,0 +1,172 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPolarBear;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.PolarBear;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+
+public class PolarBearController extends MobEntityController {
+ public PolarBearController() {
+ super(EntityPolarBearNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.PolarBear getBukkitEntity() {
+ return (org.bukkit.entity.PolarBear) super.getBukkitEntity();
+ }
+
+ public static class EntityPolarBearNPC extends PolarBear implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityPolarBearNPC(EntityType extends PolarBear> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPolarBearNPC(EntityType extends PolarBear> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PolarBearNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null) {
+ return super.isLeashed();
+ }
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+ }
+
+ public static class PolarBearNPC extends CraftPolarBear implements ForwardingNPCHolder {
+ public PolarBearNPC(EntityPolarBearNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PufferFishController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PufferFishController.java
new file mode 100644
index 000000000..b8e1c0012
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/PufferFishController.java
@@ -0,0 +1,275 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPufferFish;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.versioned.PufferFishTrait;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityDimensions;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.Pose;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.animal.Pufferfish;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class PufferFishController extends MobEntityController {
+ public PufferFishController() {
+ super(EntityPufferFishNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.PufferFish getBukkitEntity() {
+ return (org.bukkit.entity.PufferFish) super.getBukkitEntity();
+ }
+
+ public static class EntityPufferFishNPC extends Pufferfish implements NPCHolder {
+ private final CitizensNPC npc;
+ private MoveControl oldMoveController;
+
+ public EntityPufferFishNPC(EntityType extends Pufferfish> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPufferFishNPC(EntityType extends Pufferfish> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new MoveControl(this);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ boolean lastInWater = this.verticalCollision;
+ int lastPuffState = getPuffState();
+ if (npc != null) {
+ this.verticalCollision = false;
+ setPuffState(0);
+ }
+ super.aiStep();
+ if (npc != null) {
+ this.verticalCollision = lastInWater;
+ setPuffState(lastPuffState);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PufferFishNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ public EntityDimensions getDimensions(Pose entitypose) {
+ if (npc == null) {
+ return super.getDimensions(entitypose);
+ }
+ return super.getDimensions(entitypose).scale(1 / s(getPuffState())).scale(0.5F);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isEffectiveAi() {
+ return npc == null ? super.isEffectiveAi() : false;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ protected InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc == null || !npc.isProtected())
+ return super.mobInteract(entityhuman, enumhand);
+ ItemStack itemstack = entityhuman.getItemInHand(enumhand);
+ if (itemstack.getItem() == Items.WATER_BUCKET && isAlive()) {
+ return InteractionResult.FAIL;
+ }
+ return super.mobInteract(entityhuman, enumhand);
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ NMSImpl.resetPuffTicks(this);
+ }
+ super.tick();
+ PufferFishTrait trait = null;
+ if (npc != null && (trait = npc.getTraitNullable(PufferFishTrait.class)) != null) {
+ setPuffState(trait.getPuffState());
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ private static float s(int i) {
+ switch (i) {
+ case 0:
+ return 0.5F;
+ case 1:
+ return 0.7F;
+ default:
+ return 1.0F;
+ }
+ }
+ }
+
+ public static class PufferFishNPC extends CraftPufferFish implements ForwardingNPCHolder {
+ public PufferFishNPC(EntityPufferFishNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/RabbitController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/RabbitController.java
new file mode 100644
index 000000000..e0e7d289d
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/RabbitController.java
@@ -0,0 +1,229 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftRabbit;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+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.Rabbit;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class RabbitController extends MobEntityController {
+ public RabbitController() {
+ super(EntityRabbitNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Rabbit getBukkitEntity() {
+ return (org.bukkit.entity.Rabbit) super.getBukkitEntity();
+ }
+
+ public static class EntityRabbitNPC extends Rabbit implements NPCHolder {
+ boolean calledNMSHeight = false;
+
+ private final CitizensNPC npc;
+
+ public EntityRabbitNPC(EntityType extends Rabbit> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityRabbitNPC(EntityType extends Rabbit> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.getNavigator().isNavigating()) {
+ NMS.setShouldJump(getBukkitEntity());
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new RabbitNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public LivingEntity getTarget() {
+ return npc != null ? null : super.getTarget();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void setRabbitType(int i) {
+ if (npc != null) {
+ if (NMSImpl.getRabbitTypeField() == null)
+ return;
+ this.entityData.set(NMSImpl.getRabbitTypeField(), i);
+ return;
+ }
+ super.setRabbitType(i);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class RabbitNPC extends CraftRabbit implements ForwardingNPCHolder {
+ public RabbitNPC(EntityRabbitNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/RavagerController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/RavagerController.java
new file mode 100644
index 000000000..27f9967b3
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/RavagerController.java
@@ -0,0 +1,206 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftRavager;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Ravager;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class RavagerController extends MobEntityController {
+ public RavagerController() {
+ super(EntityRavagerNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Ravager getBukkitEntity() {
+ return (org.bukkit.entity.Ravager) super.getBukkitEntity();
+ }
+
+ public static class EntityRavagerNPC extends Ravager implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityRavagerNPC(EntityType extends Ravager> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityRavagerNPC(EntityType extends Ravager> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new RavagerNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class RavagerNPC extends CraftRavager implements ForwardingNPCHolder {
+ public RavagerNPC(EntityRavagerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SalmonController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SalmonController.java
new file mode 100644
index 000000000..b58dd5241
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SalmonController.java
@@ -0,0 +1,235 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSalmon;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.animal.Salmon;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SalmonController extends MobEntityController {
+ public SalmonController() {
+ super(EntitySalmonNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Salmon getBukkitEntity() {
+ return (org.bukkit.entity.Salmon) super.getBukkitEntity();
+ }
+
+ public static class EntitySalmonNPC extends Salmon implements NPCHolder {
+ private final CitizensNPC npc;
+ private MoveControl oldMoveController;
+
+ public EntitySalmonNPC(EntityType extends Salmon> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySalmonNPC(EntityType extends Salmon> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new MoveControl(this);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ boolean lastInWater = this.verticalCollision;
+ if (npc != null) {
+ this.verticalCollision = false;
+ }
+ super.aiStep();
+ if (npc != null) {
+ this.verticalCollision = lastInWater;
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ NMSImpl.setNotInSchool(this);
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SalmonNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ protected InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc == null || !npc.isProtected())
+ return super.mobInteract(entityhuman, enumhand);
+ ItemStack itemstack = entityhuman.getItemInHand(enumhand);
+ if (itemstack.getItem() == Items.WATER_BUCKET && isAlive()) {
+ return InteractionResult.FAIL;
+ }
+ return super.mobInteract(entityhuman, enumhand);
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class SalmonNPC extends CraftSalmon implements ForwardingNPCHolder {
+ public SalmonNPC(EntitySalmonNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SheepController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SheepController.java
new file mode 100644
index 000000000..29a5bfc0b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SheepController.java
@@ -0,0 +1,207 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSheep;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.Sheep;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SheepController extends MobEntityController {
+ public SheepController() {
+ super(EntitySheepNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Sheep getBukkitEntity() {
+ return (org.bukkit.entity.Sheep) super.getBukkitEntity();
+ }
+
+ public static class EntitySheepNPC extends Sheep implements NPCHolder {
+ boolean calledNMSHeight = false;
+
+ private final CitizensNPC npc;
+
+ public EntitySheepNPC(EntityType extends Sheep> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySheepNPC(EntityType extends Sheep> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SheepNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class SheepNPC extends CraftSheep implements ForwardingNPCHolder {
+ public SheepNPC(EntitySheepNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ShulkerController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ShulkerController.java
new file mode 100644
index 000000000..711b8bd3c
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ShulkerController.java
@@ -0,0 +1,219 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftShulker;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.control.BodyRotationControl;
+import net.minecraft.world.entity.monster.Shulker;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class ShulkerController extends MobEntityController {
+ public ShulkerController() {
+ super(EntityShulkerNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Shulker getBukkitEntity() {
+ return (org.bukkit.entity.Shulker) super.getBukkitEntity();
+ }
+
+ public static class EntityShulkerNPC extends Shulker implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityShulkerNPC(EntityType extends Shulker> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityShulkerNPC(EntityType extends Shulker> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ if (npc == null || npc.useMinecraftAI()) {
+ super.aiStep();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ protected BodyRotationControl createBodyControl() {
+ return new BodyRotationControl(this);
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ShulkerNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI()) {
+ super.tick();
+ }
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class ShulkerNPC extends CraftShulker implements ForwardingNPCHolder {
+ public ShulkerNPC(EntityShulkerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SilverfishController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SilverfishController.java
new file mode 100644
index 000000000..a53376b43
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SilverfishController.java
@@ -0,0 +1,200 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSilverfish;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Silverfish;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SilverfishController extends MobEntityController {
+ public SilverfishController() {
+ super(EntitySilverfishNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Silverfish getBukkitEntity() {
+ return (org.bukkit.entity.Silverfish) super.getBukkitEntity();
+ }
+
+ public static class EntitySilverfishNPC extends Silverfish implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySilverfishNPC(EntityType extends Silverfish> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySilverfishNPC(EntityType extends Silverfish> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null)
+ super.dismountTo(d0, d1, d2);
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SilverfishNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class SilverfishNPC extends CraftSilverfish implements ForwardingNPCHolder {
+ public SilverfishNPC(EntitySilverfishNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SkeletonController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SkeletonController.java
new file mode 100644
index 000000000..e7ad22a67
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SkeletonController.java
@@ -0,0 +1,202 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSkeleton;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Skeleton;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SkeletonController extends MobEntityController {
+ public SkeletonController() {
+ super(EntitySkeletonNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Skeleton getBukkitEntity() {
+ return (org.bukkit.entity.Skeleton) super.getBukkitEntity();
+ }
+
+ public static class EntitySkeletonNPC extends Skeleton implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySkeletonNPC(EntityType extends Skeleton> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySkeletonNPC(EntityType extends Skeleton> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SkeletonNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class SkeletonNPC extends CraftSkeleton implements ForwardingNPCHolder {
+ public SkeletonNPC(EntitySkeletonNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SkeletonStrayController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SkeletonStrayController.java
new file mode 100644
index 000000000..154e2c8d2
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SkeletonStrayController.java
@@ -0,0 +1,202 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftStray;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Stray;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SkeletonStrayController extends MobEntityController {
+ public SkeletonStrayController() {
+ super(EntityStrayNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Stray getBukkitEntity() {
+ return (org.bukkit.entity.Stray) super.getBukkitEntity();
+ }
+
+ public static class EntityStrayNPC extends Stray implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityStrayNPC(EntityType extends Stray> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityStrayNPC(EntityType extends Stray> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new StrayNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class StrayNPC extends CraftStray implements ForwardingNPCHolder {
+ public StrayNPC(EntityStrayNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SkeletonWitherController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SkeletonWitherController.java
new file mode 100644
index 000000000..1e938dcd8
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SkeletonWitherController.java
@@ -0,0 +1,202 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftWitherSkeleton;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.WitherSkeleton;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SkeletonWitherController extends MobEntityController {
+ public SkeletonWitherController() {
+ super(EntitySkeletonWitherNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.WitherSkeleton getBukkitEntity() {
+ return (org.bukkit.entity.WitherSkeleton) super.getBukkitEntity();
+ }
+
+ public static class EntitySkeletonWitherNPC extends WitherSkeleton implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySkeletonWitherNPC(EntityType extends WitherSkeleton> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySkeletonWitherNPC(EntityType extends WitherSkeleton> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SkeletonWitherNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class SkeletonWitherNPC extends CraftWitherSkeleton implements ForwardingNPCHolder {
+ public SkeletonWitherNPC(EntitySkeletonWitherNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SlimeController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SlimeController.java
new file mode 100644
index 000000000..cee80a3dc
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SlimeController.java
@@ -0,0 +1,223 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSlime;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.monster.Slime;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SlimeController extends MobEntityController {
+ public SlimeController() {
+ super(EntitySlimeNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Slime getBukkitEntity() {
+ return (org.bukkit.entity.Slime) super.getBukkitEntity();
+ }
+
+ public static class EntitySlimeNPC extends Slime implements NPCHolder {
+ private final CitizensNPC npc;
+ private MoveControl oldMoveController;
+
+ public EntitySlimeNPC(EntityType extends Slime> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySlimeNPC(EntityType extends Slime> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ setSize(3, true);
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SlimeNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void playerTouch(Player human) {
+ if (npc == null) {
+ super.playerTouch(human);
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class SlimeNPC extends CraftSlime implements ForwardingNPCHolder {
+ public SlimeNPC(EntitySlimeNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SnowmanController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SnowmanController.java
new file mode 100644
index 000000000..d39f4ef8d
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SnowmanController.java
@@ -0,0 +1,215 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSnowman;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.SnowGolem;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.GameRules;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SnowmanController extends MobEntityController {
+ public SnowmanController() {
+ super(EntitySnowmanNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Snowman getBukkitEntity() {
+ return (org.bukkit.entity.Snowman) super.getBukkitEntity();
+ }
+
+ public static class EntitySnowmanNPC extends SnowGolem implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySnowmanNPC(EntityType extends SnowGolem> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySnowmanNPC(EntityType extends SnowGolem> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ boolean allowsGriefing = this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+ if (npc != null) {
+ this.level.getGameRules().getRule(GameRules.RULE_MOBGRIEFING).set(false, getServer());
+ }
+ super.aiStep();
+ if (npc != null) {
+ this.level.getGameRules().getRule(GameRules.RULE_MOBGRIEFING).set(allowsGriefing, getServer());
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SnowmanNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class SnowmanNPC extends CraftSnowman implements ForwardingNPCHolder {
+ public SnowmanNPC(EntitySnowmanNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SpiderController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SpiderController.java
new file mode 100644
index 000000000..0abb71b8b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SpiderController.java
@@ -0,0 +1,203 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSpider;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Spider;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SpiderController extends MobEntityController {
+ public SpiderController() {
+ super(EntitySpiderNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Spider getBukkitEntity() {
+ return (org.bukkit.entity.Spider) super.getBukkitEntity();
+ }
+
+ public static class EntitySpiderNPC extends Spider implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySpiderNPC(EntityType extends Spider> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySpiderNPC(EntityType extends Spider> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SpiderNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ }
+
+ public static class SpiderNPC extends CraftSpider implements ForwardingNPCHolder {
+ public SpiderNPC(EntitySpiderNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SquidController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SquidController.java
new file mode 100644
index 000000000..1573db8ca
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/SquidController.java
@@ -0,0 +1,202 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSquid;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.Squid;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class SquidController extends MobEntityController {
+ public SquidController() {
+ super(EntitySquidNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Squid getBukkitEntity() {
+ return (org.bukkit.entity.Squid) super.getBukkitEntity();
+ }
+
+ public static class EntitySquidNPC extends Squid implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySquidNPC(EntityType extends Squid> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySquidNPC(EntityType extends Squid> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SquidNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class SquidNPC extends CraftSquid implements ForwardingNPCHolder {
+ public SquidNPC(EntitySquidNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/StriderController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/StriderController.java
new file mode 100644
index 000000000..644d67e27
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/StriderController.java
@@ -0,0 +1,193 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftStrider;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Strider;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class StriderController extends MobEntityController {
+ public StriderController() {
+ super(EntityStriderNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Strider getBukkitEntity() {
+ return (org.bukkit.entity.Strider) super.getBukkitEntity();
+ }
+
+ public static class EntityStriderNPC extends Strider implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityStriderNPC(EntityType extends Strider> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityStriderNPC(EntityType extends Strider> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new StriderNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class StriderNPC extends CraftStrider implements ForwardingNPCHolder {
+ public StriderNPC(EntityStriderNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/TraderLlamaController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/TraderLlamaController.java
new file mode 100644
index 000000000..cce2d9d5f
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/TraderLlamaController.java
@@ -0,0 +1,225 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftTraderLlama;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.HorseModifiers;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.horse.TraderLlama;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class TraderLlamaController extends MobEntityController {
+ public TraderLlamaController() {
+ super(EntityTraderLlamaNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.TraderLlama getBukkitEntity() {
+ return (org.bukkit.entity.TraderLlama) super.getBukkitEntity();
+ }
+
+ @Override
+ public void spawn(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.spawn(at, npc);
+ }
+
+ public static class EntityTraderLlamaNPC extends TraderLlama implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityTraderLlamaNPC(EntityType extends TraderLlama> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityTraderLlamaNPC(EntityType extends TraderLlama> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ ((org.bukkit.entity.TraderLlama) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.TraderLlama) getBukkitEntity()).getMaxDomestication());
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc == null) {
+ super.customServerAiStep();
+ } else {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI()) {
+ super.customServerAiStep();
+ }
+ NMSImpl.setDespawnDelay(this, 10);
+ NMS.setStepHeight(getBukkitEntity(), 1);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new TraderLlamaNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class TraderLlamaNPC extends CraftTraderLlama implements ForwardingNPCHolder {
+
+ public TraderLlamaNPC(EntityTraderLlamaNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/TropicalFishController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/TropicalFishController.java
new file mode 100644
index 000000000..284f62f0d
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/TropicalFishController.java
@@ -0,0 +1,237 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftTropicalFish;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.animal.TropicalFish;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class TropicalFishController extends MobEntityController {
+ public TropicalFishController() {
+ super(EntityTropicalFishNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.TropicalFish getBukkitEntity() {
+ return (org.bukkit.entity.TropicalFish) super.getBukkitEntity();
+ }
+
+ public static class EntityTropicalFishNPC extends TropicalFish implements NPCHolder {
+ private final CitizensNPC npc;
+ private MoveControl oldMoveController;
+
+ public EntityTropicalFishNPC(EntityType extends TropicalFish> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityTropicalFishNPC(EntityType extends TropicalFish> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new MoveControl(this);
+ }
+ }
+
+ @Override
+ public void aiStep() {
+ boolean lastInWater = this.verticalCollision;
+ if (npc != null) {
+ this.verticalCollision = false;
+ }
+ super.aiStep();
+ if (npc != null) {
+ this.verticalCollision = lastInWater;
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ if (!npc.useMinecraftAI()) {
+ NMSImpl.setNotInSchool(this);
+ }
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ }
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new TropicalFishNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ protected InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc == null || !npc.isProtected())
+ return super.mobInteract(entityhuman, enumhand);
+ ItemStack itemstack = entityhuman.getItemInHand(enumhand);
+ if (itemstack.getItem() == Items.WATER_BUCKET && isAlive()) {
+ return InteractionResult.FAIL;
+ }
+ return super.mobInteract(entityhuman, enumhand);
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class TropicalFishNPC extends CraftTropicalFish implements ForwardingNPCHolder {
+ public TropicalFishNPC(EntityTropicalFishNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/TurtleController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/TurtleController.java
new file mode 100644
index 000000000..fd8b2663f
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/TurtleController.java
@@ -0,0 +1,233 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftTurtle;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_17_R1.util.PlayerMoveControl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.control.JumpControl;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
+import net.minecraft.world.entity.ai.navigation.PathNavigation;
+import net.minecraft.world.entity.animal.Turtle;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class TurtleController extends MobEntityController {
+ public TurtleController() {
+ super(EntityTurtleNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Turtle getBukkitEntity() {
+ return (org.bukkit.entity.Turtle) super.getBukkitEntity();
+ }
+
+ public static class EntityTurtleNPC extends Turtle implements NPCHolder {
+ private final CitizensNPC npc;
+ private JumpControl oldJumpController;
+ private MoveControl oldMoveController;
+
+ public EntityTurtleNPC(EntityType extends Turtle> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityTurtleNPC(EntityType extends Turtle> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ this.oldMoveController = this.moveControl;
+ this.oldJumpController = this.jumpControl;
+ this.moveControl = new MoveControl(this);
+ this.jumpControl = new EmptyControllerJump(this);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ protected PathNavigation createNavigation(Level world) {
+ if (npc == null) {
+ return super.createNavigation(world);
+ }
+ return new GroundPathNavigation(this, world);
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ this.jumpControl = this.oldJumpController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new PlayerMoveControl(this);
+ this.jumpControl = new EmptyControllerJump(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new TurtleNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ static class EmptyControllerJump extends JumpControl {
+ public EmptyControllerJump(Mob var1) {
+ super(var1);
+ }
+
+ @Override
+ public void jump() {
+ this.jump = false;
+ }
+ }
+ }
+
+ public static class TurtleNPC extends CraftTurtle implements ForwardingNPCHolder {
+ public TurtleNPC(EntityTurtleNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/VexController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/VexController.java
new file mode 100644
index 000000000..261b21965
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/VexController.java
@@ -0,0 +1,160 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftVex;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Vex;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+
+public class VexController extends MobEntityController {
+ public VexController() {
+ super(EntityVexNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Vex getBukkitEntity() {
+ return (org.bukkit.entity.Vex) super.getBukkitEntity();
+ }
+
+ public static class EntityVexNPC extends Vex implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityVexNPC(EntityType extends Vex> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityVexNPC(EntityType extends Vex> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ setNoGravity(true);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new VexNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null) {
+ return super.isLeashed();
+ }
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+ }
+
+ public static class VexNPC extends CraftVex implements ForwardingNPCHolder {
+ public VexNPC(EntityVexNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/VillagerController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/VillagerController.java
new file mode 100644
index 000000000..7836ac16d
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/VillagerController.java
@@ -0,0 +1,255 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftVillager;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LightningBolt;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.npc.Villager;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.item.trading.MerchantOffers;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class VillagerController extends MobEntityController {
+ public VillagerController() {
+ super(EntityVillagerNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Villager getBukkitEntity() {
+ return (org.bukkit.entity.Villager) super.getBukkitEntity();
+ }
+
+ public static class EntityVillagerNPC extends Villager implements NPCHolder {
+ private boolean blockingATrade;
+ private boolean blockTrades = true;
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityVillagerNPC(EntityType extends Villager> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityVillagerNPC(EntityType extends Villager> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.3);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new VillagerNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public boolean isBlockingTrades() {
+ return blockTrades;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean isTrading() {
+ if (blockingATrade) {
+ blockingATrade = false;
+ return true;
+ }
+ return super.isTrading();
+ }
+
+ @Override
+ public InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc != null && blockTrades) {
+ blockingATrade = true;
+ MerchantOffers list = getOffers();
+ if (list != null) {
+ list.clear();
+ }
+ }
+ return super.mobInteract(entityhuman, enumhand);
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ public void setBlockTrades(boolean blocked) {
+ this.blockTrades = blocked;
+ }
+
+ @Override
+ public void thunderHit(ServerLevel worldserver, LightningBolt entitylightning) {
+ if (npc == null) {
+ super.thunderHit(worldserver, entitylightning);
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class VillagerNPC extends CraftVillager implements ForwardingNPCHolder {
+ public VillagerNPC(EntityVillagerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/VindicatorController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/VindicatorController.java
new file mode 100644
index 000000000..295c7dfb8
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/VindicatorController.java
@@ -0,0 +1,208 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftVindicator;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Vindicator;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class VindicatorController extends MobEntityController {
+ public VindicatorController() {
+ super(EntityVindicatorNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Vindicator getBukkitEntity() {
+ return (org.bukkit.entity.Vindicator) super.getBukkitEntity();
+ }
+
+ public static class EntityVindicatorNPC extends Vindicator implements NPCHolder {
+ boolean calledNMSHeight = false;
+
+ private final CitizensNPC npc;
+
+ public EntityVindicatorNPC(EntityType extends Vindicator> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityVindicatorNPC(EntityType extends Vindicator> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new VindicatorNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class VindicatorNPC extends CraftVindicator implements ForwardingNPCHolder {
+ public VindicatorNPC(EntityVindicatorNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WanderingTraderController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WanderingTraderController.java
new file mode 100644
index 000000000..0e177da6e
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WanderingTraderController.java
@@ -0,0 +1,253 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftWanderingTrader;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LightningBolt;
+import net.minecraft.world.entity.npc.WanderingTrader;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.item.trading.MerchantOffers;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class WanderingTraderController extends MobEntityController {
+ public WanderingTraderController() {
+ super(EntityWanderingTraderNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.WanderingTrader getBukkitEntity() {
+ return (org.bukkit.entity.WanderingTrader) super.getBukkitEntity();
+ }
+
+ public static class EntityWanderingTraderNPC extends WanderingTrader implements NPCHolder {
+ private boolean blockingATrade;
+ private boolean blockTrades = true;
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityWanderingTraderNPC(EntityType extends WanderingTrader> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityWanderingTraderNPC(EntityType extends WanderingTrader> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new WanderingTraderNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public boolean isBlockingTrades() {
+ return blockTrades;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean isTrading() {
+ if (blockingATrade) {
+ blockingATrade = false;
+ return true;
+ }
+ return super.isTrading();
+ }
+
+ @Override
+ public InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc != null && blockTrades) {
+ blockingATrade = true;
+ MerchantOffers list = getOffers();
+ if (list != null) {
+ list.clear();
+ }
+ }
+ return super.mobInteract(entityhuman, enumhand);
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ public void setBlockTrades(boolean blocked) {
+ this.blockTrades = blocked;
+ }
+
+ @Override
+ public void thunderHit(ServerLevel worldserver, LightningBolt entitylightning) {
+ if (npc == null) {
+ super.thunderHit(worldserver, entitylightning);
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class WanderingTraderNPC extends CraftWanderingTrader implements ForwardingNPCHolder {
+ public WanderingTraderNPC(EntityWanderingTraderNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WitchController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WitchController.java
new file mode 100644
index 000000000..0b5f93c1f
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WitchController.java
@@ -0,0 +1,202 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftWitch;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Witch;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class WitchController extends MobEntityController {
+ public WitchController() {
+ super(EntityWitchNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Witch getBukkitEntity() {
+ return (org.bukkit.entity.Witch) super.getBukkitEntity();
+ }
+
+ public static class EntityWitchNPC extends Witch implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityWitchNPC(EntityType extends Witch> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityWitchNPC(EntityType extends Witch> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new WitchNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class WitchNPC extends CraftWitch implements ForwardingNPCHolder {
+ public WitchNPC(EntityWitchNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WitherController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WitherController.java
new file mode 100644
index 000000000..74d85b79e
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WitherController.java
@@ -0,0 +1,166 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftWither;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.boss.wither.WitherBoss;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+
+public class WitherController extends MobEntityController {
+ public WitherController() {
+ super(EntityWitherNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Wither getBukkitEntity() {
+ return (org.bukkit.entity.Wither) super.getBukkitEntity();
+ }
+
+ public static class EntityWitherNPC extends WitherBoss implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityWitherNPC(EntityType extends WitherBoss> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityWitherNPC(EntityType extends WitherBoss> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void customServerAiStep() {
+ if (npc == null) {
+ super.customServerAiStep();
+ } else {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ if (npc.useMinecraftAI()) {
+ super.customServerAiStep();
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ public int getAlternativeTarget(int i) {
+ return npc == null ? super.getAlternativeTarget(i) : 0;
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new WitherNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+ }
+
+ public static class WitherNPC extends CraftWither implements ForwardingNPCHolder {
+ public WitherNPC(EntityWitherNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WolfController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WolfController.java
new file mode 100644
index 000000000..a8316618a
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/WolfController.java
@@ -0,0 +1,219 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftWolf;
+import org.bukkit.event.entity.EntityTargetEvent;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.sounds.SoundEvent;
+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.Wolf;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class WolfController extends MobEntityController {
+ public WolfController() {
+ super(EntityWolfNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Wolf getBukkitEntity() {
+ return (org.bukkit.entity.Wolf) super.getBukkitEntity();
+ }
+
+ public static class EntityWolfNPC extends Wolf implements NPCHolder {
+ boolean calledNMSHeight = false;
+ private final CitizensNPC npc;
+
+ public EntityWolfNPC(EntityType extends Wolf> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityWolfNPC(EntityType extends Wolf> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new WolfNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc != null && !calledNMSHeight) {
+ calledNMSHeight = true;
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject);
+ calledNMSHeight = false;
+ }
+
+ super.onSyncedDataUpdated(datawatcherobject);
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public boolean setGoalTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fire) {
+ return npc == null || this.equals(entityliving) ? super.setGoalTarget(entityliving, reason, fire) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class WolfNPC extends CraftWolf implements ForwardingNPCHolder {
+ public WolfNPC(EntityWolfNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+
+ @Override
+ public void setSitting(boolean sitting) {
+ getHandle().setInSittingPose(sitting);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZoglinController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZoglinController.java
new file mode 100644
index 000000000..d20cb0552
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZoglinController.java
@@ -0,0 +1,195 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftZoglin;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Zoglin;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class ZoglinController extends MobEntityController {
+ public ZoglinController() {
+ super(EntityZoglinNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Zoglin getBukkitEntity() {
+ return (org.bukkit.entity.Zoglin) super.getBukkitEntity();
+ }
+
+ public static class EntityZoglinNPC extends Zoglin implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityZoglinNPC(EntityType extends Zoglin> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityZoglinNPC(EntityType extends Zoglin> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ZoglinNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class ZoglinNPC extends CraftZoglin implements ForwardingNPCHolder {
+ public ZoglinNPC(EntityZoglinNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZombieController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZombieController.java
new file mode 100644
index 000000000..de0dd2ee1
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZombieController.java
@@ -0,0 +1,193 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftZombie;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Zombie;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class ZombieController extends MobEntityController {
+ public ZombieController() {
+ super(EntityZombieNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Zombie getBukkitEntity() {
+ return (org.bukkit.entity.Zombie) super.getBukkitEntity();
+ }
+
+ public static class EntityZombieNPC extends Zombie implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityZombieNPC(EntityType extends Zombie> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityZombieNPC(EntityType extends Zombie> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ZombieNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class ZombieNPC extends CraftZombie implements ForwardingNPCHolder {
+ public ZombieNPC(EntityZombieNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZombieHuskController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZombieHuskController.java
new file mode 100644
index 000000000..4bfc7a752
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZombieHuskController.java
@@ -0,0 +1,193 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftHusk;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.Husk;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class ZombieHuskController extends MobEntityController {
+ public ZombieHuskController() {
+ super(EntityZombieHuskNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Husk getBukkitEntity() {
+ return (org.bukkit.entity.Husk) super.getBukkitEntity();
+ }
+
+ public static class EntityZombieHuskNPC extends Husk implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityZombieHuskNPC(EntityType extends Husk> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityZombieHuskNPC(EntityType extends Husk> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ZombieHuskNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class ZombieHuskNPC extends CraftHusk implements ForwardingNPCHolder {
+ public ZombieHuskNPC(EntityZombieHuskNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZombieVillagerController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZombieVillagerController.java
new file mode 100644
index 000000000..1e8a010c8
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/ZombieVillagerController.java
@@ -0,0 +1,193 @@
+package net.citizensnpcs.nms.v1_17_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftVillagerZombie;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.NPCEnderTeleportEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.monster.ZombieVillager;
+import net.minecraft.world.entity.vehicle.AbstractMinecart;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class ZombieVillagerController extends MobEntityController {
+ public ZombieVillagerController() {
+ super(EntityZombieVillagerNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.ZombieVillager getBukkitEntity() {
+ return (org.bukkit.entity.ZombieVillager) super.getBukkitEntity();
+ }
+
+ public static class EntityZombieVillagerNPC extends ZombieVillager implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityZombieVillagerNPC(EntityType extends ZombieVillager> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityZombieVillagerNPC(EntityType extends ZombieVillager> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ NMSImpl.clearGoals(npc, goalSelector, targetSelector);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public boolean causeFallDamage(float f, float f1, DamageSource damagesource) {
+ if (npc == null || !npc.isFlyable()) {
+ return super.causeFallDamage(f, f1, damagesource);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected void checkFallDamage(double d0, boolean flag, BlockState iblockdata, BlockPos blockposition) {
+ if (npc == null || !npc.isFlyable()) {
+ super.checkFallDamage(d0, flag, iblockdata, blockposition);
+ }
+ }
+
+ @Override
+ public void customServerAiStep() {
+ super.customServerAiStep();
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ }
+ }
+
+ @Override
+ public void dismountTo(double d0, double d1, double d2) {
+ if (npc == null) {
+ super.dismountTo(d0, d1, d2);
+ return;
+ }
+ NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc);
+ Bukkit.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ super.dismountTo(d0, d1, d2);
+ }
+ }
+
+ @Override
+ public SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.AMBIENT_SOUND_METADATA);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ZombieVillagerNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.DEATH_SOUND_METADATA);
+ }
+
+ @Override
+ public SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.HURT_SOUND_METADATA);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ if (npc == null)
+ return super.isLeashed();
+ boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
+ if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
+ return super.isLeashed();
+ if (super.isLeashed()) {
+ dropLeash(true, false); // clearLeash with client update
+ }
+ return false; // shouldLeash
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null)
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+ }
+
+ public static class ZombieVillagerNPC extends CraftVillagerZombie implements ForwardingNPCHolder {
+ public ZombieVillagerNPC(EntityZombieVillagerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/AreaEffectCloudController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/AreaEffectCloudController.java
new file mode 100644
index 000000000..cd6d489f9
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/AreaEffectCloudController.java
@@ -0,0 +1,95 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftAreaEffectCloud;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.AreaEffectCloud;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.level.Level;
+
+public class AreaEffectCloudController extends MobEntityController {
+ public AreaEffectCloudController() {
+ super(EntityAreaEffectCloudNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.AreaEffectCloud getBukkitEntity() {
+ return (org.bukkit.entity.AreaEffectCloud) super.getBukkitEntity();
+ }
+
+ public static class AreaEffectCloudNPC extends CraftAreaEffectCloud implements ForwardingNPCHolder {
+ public AreaEffectCloudNPC(EntityAreaEffectCloudNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityAreaEffectCloudNPC extends AreaEffectCloud implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityAreaEffectCloudNPC(EntityType extends AreaEffectCloud> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityAreaEffectCloudNPC(EntityType extends AreaEffectCloud> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new AreaEffectCloudNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ArmorStandController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ArmorStandController.java
new file mode 100644
index 000000000..15c203a6b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ArmorStandController.java
@@ -0,0 +1,110 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftArmorStand;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.event.player.PlayerInteractEntityEvent;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.decoration.ArmorStand;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.Vec3;
+
+public class ArmorStandController extends MobEntityController {
+ public ArmorStandController() {
+ super(EntityArmorStandNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.ArmorStand getBukkitEntity() {
+ return (org.bukkit.entity.ArmorStand) super.getBukkitEntity();
+ }
+
+ public static class ArmorStandNPC extends CraftArmorStand implements ForwardingNPCHolder {
+ public ArmorStandNPC(EntityArmorStandNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityArmorStandNPC extends ArmorStand implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityArmorStandNPC(EntityType extends ArmorStand> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityArmorStandNPC(EntityType extends ArmorStand> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ArmorStandNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public InteractionResult interactAt(Player entityhuman, Vec3 vec3d, InteractionHand enumhand) {
+ if (npc == null) {
+ return super.interactAt(entityhuman, vec3d, enumhand);
+ }
+ PlayerInteractEntityEvent event = new PlayerInteractEntityEvent(
+ (org.bukkit.entity.Player) entityhuman.getBukkitEntity(), getBukkitEntity());
+ Bukkit.getPluginManager().callEvent(event);
+ return event.isCancelled() ? InteractionResult.FAIL : InteractionResult.SUCCESS;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/BoatController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/BoatController.java
new file mode 100644
index 000000000..b29c6a1f5
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/BoatController.java
@@ -0,0 +1,230 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftBoat;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.tags.FluidTags;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.MoverType;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.material.FluidState;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class BoatController extends MobEntityController {
+ public BoatController() {
+ super(EntityBoatNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Boat getBukkitEntity() {
+ return (org.bukkit.entity.Boat) super.getBukkitEntity();
+ }
+
+ public static class BoatNPC extends CraftBoat implements ForwardingNPCHolder {
+ public BoatNPC(EntityBoatNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityBoatNPC extends Boat implements NPCHolder {
+ private double aC;
+ private float aD;
+ private Status aE;
+ private Status aF;
+ private double ap;
+ private double ar;
+ private final CitizensNPC npc;
+
+ public EntityBoatNPC(EntityType extends Boat> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityBoatNPC(EntityType extends Boat> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new BoatNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ private Status getStatus() {
+ Status entityboat_Status = u();
+ if (entityboat_Status != null) {
+ this.aC = (getBoundingBox()).maxY;
+ return entityboat_Status;
+ }
+ if (t())
+ return Status.IN_WATER;
+ float f = getGroundFriction();
+ if (f > 0.0F) {
+ this.aD = f;
+ return Status.ON_LAND;
+ }
+ return Status.IN_AIR;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ private boolean t() {
+ boolean m = false;
+ AABB axisalignedbb = getBoundingBox();
+ int i = Mth.floor(axisalignedbb.minX);
+ int j = Mth.ceil(axisalignedbb.maxX);
+ int k = Mth.floor(axisalignedbb.minY);
+ int l = Mth.ceil(axisalignedbb.minY + 0.001D);
+ int i1 = Mth.floor(axisalignedbb.minZ);
+ int j1 = Mth.ceil(axisalignedbb.maxZ);
+ boolean flag = false;
+ this.aC = Double.MIN_VALUE;
+ BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+ for (int k1 = i; k1 < j; k1++) {
+ for (int l1 = k; l1 < l; l1++) {
+ for (int i2 = i1; i2 < j1; i2++) {
+ blockposition_mutableblockposition.set(k1, l1, i2);
+ FluidState fluid = this.level.getFluidState(blockposition_mutableblockposition);
+ if (fluid.is(FluidTags.WATER)) {
+ float f = l1 + fluid.getHeight(this.level, blockposition_mutableblockposition);
+ this.aC = Math.max(f, this.aC);
+ m = flag | ((axisalignedbb.minY < f) ? true : false);
+ }
+ }
+ }
+ }
+ return m;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ this.aF = this.aE;
+ aE = getStatus();
+ double d1 = isNoGravity() ? 0.0D : -0.04D;
+ double d2 = 0.0D;
+ this.ap = 0.05F;
+ if (this.aF == Status.IN_AIR && this.aE != Status.IN_AIR && this.aE != Status.ON_LAND) {
+ this.aC = getY(1.0D);
+ setPos(getX(), (getWaterLevelAbove() - getBbHeight()) + 0.101D, getZ());
+ setDeltaMovement(getDeltaMovement().multiply(1.0D, 0.0D, 1.0D));
+ this.aE = Status.IN_WATER;
+ } else {
+ if (this.aE == Status.IN_WATER) {
+ d2 = (this.aC - getY()) / getBbHeight();
+ this.ap = 0.9F;
+ } else if (this.aE == Status.UNDER_FLOWING_WATER) {
+ d1 = -7.0E-4D;
+ this.ap = 0.9F;
+ } else if (this.aE == Status.UNDER_WATER) {
+ d2 = 0.01D;
+ this.ap = 0.45F;
+ } else if (this.aE == Status.IN_AIR) {
+ this.ap = 0.9F;
+ } else if (this.aE == Status.ON_LAND) {
+ this.ap = this.aD;
+ if (getControllingPassenger() instanceof ServerPlayer) {
+ this.aD /= 2.0F;
+ }
+ }
+ Vec3 vec3d = getDeltaMovement();
+ setDeltaMovement(vec3d.x * this.ap, vec3d.y + d1, vec3d.z * this.ap);
+ this.ar *= this.ap;
+ if (d2 > 0.0D) {
+ Vec3 vec3d1 = getDeltaMovement();
+ setDeltaMovement(vec3d1.x, (vec3d1.y + d2 * 0.0615D), vec3d1.z);
+ }
+ }
+ move(MoverType.SELF, getDeltaMovement());
+ if (isVehicle()) {
+ setYRot((float) (getYRot() + this.ar));
+ }
+ } else {
+ super.tick();
+ }
+ }
+
+ private Status u() {
+ AABB axisalignedbb = getBoundingBox();
+ double d0 = axisalignedbb.maxY + 0.001D;
+ int i = Mth.floor(axisalignedbb.minX);
+ int j = Mth.ceil(axisalignedbb.maxX);
+ int k = Mth.floor(axisalignedbb.maxY);
+ int l = Mth.ceil(d0);
+ int i1 = Mth.floor(axisalignedbb.minZ);
+ int j1 = Mth.ceil(axisalignedbb.maxZ);
+ boolean flag = false;
+ BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+ for (int k1 = i; k1 < j; k1++) {
+ for (int l1 = k; l1 < l; l1++) {
+ for (int i2 = i1; i2 < j1; i2++) {
+ blockposition_mutableblockposition.set(k1, l1, i2);
+ FluidState fluid = this.level.getFluidState(blockposition_mutableblockposition);
+ if (fluid.is(FluidTags.WATER) && d0 < (blockposition_mutableblockposition.getY()
+ + fluid.getHeight(this.level, blockposition_mutableblockposition))) {
+ if (!fluid.isSource())
+ return Status.UNDER_FLOWING_WATER;
+ flag = true;
+ }
+ }
+ }
+ }
+ return flag ? Status.UNDER_WATER : null;
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/DragonFireballController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/DragonFireballController.java
new file mode 100644
index 000000000..c7dbcfe9f
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/DragonFireballController.java
@@ -0,0 +1,107 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftDragonFireball;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.DragonFireball;
+import net.minecraft.world.level.Level;
+
+public class DragonFireballController extends MobEntityController {
+ public DragonFireballController() {
+ super(EntityDragonFireballNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.DragonFireball getBukkitEntity() {
+ return (org.bukkit.entity.DragonFireball) super.getBukkitEntity();
+ }
+
+ public static class DragonFireballNPC extends CraftDragonFireball implements ForwardingNPCHolder {
+ public DragonFireballNPC(EntityDragonFireballNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityDragonFireballNPC extends DragonFireball implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityDragonFireballNPC(EntityType extends DragonFireball> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityDragonFireballNPC(EntityType extends DragonFireball> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new DragonFireballNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EggController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EggController.java
new file mode 100644
index 000000000..6f44648bb
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EggController.java
@@ -0,0 +1,113 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEgg;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.AbstractEntityController;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.ThrownEgg;
+import net.minecraft.world.level.Level;
+
+public class EggController extends AbstractEntityController {
+ public EggController() {
+ super(EntityEggNPC.class);
+ }
+
+ @Override
+ protected org.bukkit.entity.Entity createEntity(Location at, NPC npc) {
+ ServerLevel ws = ((CraftWorld) at.getWorld()).getHandle();
+ final EntityEggNPC handle = new EntityEggNPC(ws, npc, at.getX(), at.getY(), at.getZ());
+ return handle.getBukkitEntity();
+ }
+
+ @Override
+ public org.bukkit.entity.Egg getBukkitEntity() {
+ return (org.bukkit.entity.Egg) super.getBukkitEntity();
+ }
+
+ public static class EggNPC extends CraftEgg implements ForwardingNPCHolder {
+ public EggNPC(EntityEggNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityEggNPC extends ThrownEgg implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityEggNPC(EntityType extends ThrownEgg> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityEggNPC(EntityType extends ThrownEgg> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ public EntityEggNPC(Level level, NPC npc, double d0, double d1, double d2) {
+ super(level, d0, d1, d2);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EggNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EnderCrystalController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EnderCrystalController.java
new file mode 100644
index 000000000..7e02fdc7c
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EnderCrystalController.java
@@ -0,0 +1,95 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEnderCrystal;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
+import net.minecraft.world.level.Level;
+
+public class EnderCrystalController extends MobEntityController {
+ public EnderCrystalController() {
+ super(EntityEnderCrystalNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.EnderCrystal getBukkitEntity() {
+ return (org.bukkit.entity.EnderCrystal) super.getBukkitEntity();
+ }
+
+ public static class EnderCrystalNPC extends CraftEnderCrystal implements ForwardingNPCHolder {
+ public EnderCrystalNPC(EntityEnderCrystalNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityEnderCrystalNPC extends EndCrystal implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityEnderCrystalNPC(EntityType extends EndCrystal> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityEnderCrystalNPC(EntityType extends EndCrystal> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EnderCrystalNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EnderPearlController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EnderPearlController.java
new file mode 100644
index 000000000..b3890643b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EnderPearlController.java
@@ -0,0 +1,99 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEnderPearl;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.entity.EnderPearl;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.ThrownEnderpearl;
+import net.minecraft.world.level.Level;
+
+public class EnderPearlController extends MobEntityController {
+ public EnderPearlController() {
+ super(EntityEnderPearlNPC.class);
+ }
+
+ @Override
+ public EnderPearl getBukkitEntity() {
+ return (EnderPearl) super.getBukkitEntity();
+ }
+
+ public static class EnderPearlNPC extends CraftEnderPearl implements ForwardingNPCHolder {
+ public EnderPearlNPC(EntityEnderPearlNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityEnderPearlNPC extends ThrownEnderpearl implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityEnderPearlNPC(EntityType extends ThrownEnderpearl> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityEnderPearlNPC(EntityType extends ThrownEnderpearl> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EnderPearlNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EnderSignalController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EnderSignalController.java
new file mode 100644
index 000000000..bf5fd58c2
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EnderSignalController.java
@@ -0,0 +1,96 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEnderSignal;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.entity.EnderSignal;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.EyeOfEnder;
+import net.minecraft.world.level.Level;
+
+public class EnderSignalController extends MobEntityController {
+ public EnderSignalController() {
+ super(EntityEnderSignalNPC.class);
+ }
+
+ @Override
+ public EnderSignal getBukkitEntity() {
+ return (EnderSignal) super.getBukkitEntity();
+ }
+
+ public static class EnderSignalNPC extends CraftEnderSignal implements ForwardingNPCHolder {
+ public EnderSignalNPC(EntityEnderSignalNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityEnderSignalNPC extends EyeOfEnder implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityEnderSignalNPC(EntityType extends EyeOfEnder> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityEnderSignalNPC(EntityType extends EyeOfEnder> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EnderSignalNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EvokerFangsController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EvokerFangsController.java
new file mode 100644
index 000000000..6fef4074c
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/EvokerFangsController.java
@@ -0,0 +1,110 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEvokerFangs;
+import org.bukkit.event.player.PlayerInteractEntityEvent;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.projectile.EvokerFangs;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.Vec3;
+
+public class EvokerFangsController extends MobEntityController {
+ public EvokerFangsController() {
+ super(EntityEvokerFangsNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.EvokerFangs getBukkitEntity() {
+ return (org.bukkit.entity.EvokerFangs) super.getBukkitEntity();
+ }
+
+ public static class EntityEvokerFangsNPC extends EvokerFangs implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityEvokerFangsNPC(EntityType extends EvokerFangs> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityEvokerFangsNPC(EntityType extends EvokerFangs> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EvokerFangsNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public InteractionResult interactAt(Player entityhuman, Vec3 vec3d, InteractionHand enumhand) {
+ if (npc == null) {
+ return super.interactAt(entityhuman, vec3d, enumhand);
+ }
+ PlayerInteractEntityEvent event = new PlayerInteractEntityEvent(
+ (org.bukkit.entity.Player) entityhuman.getBukkitEntity(), getBukkitEntity());
+ Bukkit.getPluginManager().callEvent(event);
+ return event.isCancelled() ? InteractionResult.FAIL : InteractionResult.SUCCESS;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+ }
+
+ public static class EvokerFangsNPC extends CraftEvokerFangs implements ForwardingNPCHolder {
+ public EvokerFangsNPC(EntityEvokerFangsNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ExperienceOrbController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ExperienceOrbController.java
new file mode 100644
index 000000000..454bec521
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ExperienceOrbController.java
@@ -0,0 +1,95 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftExperienceOrb;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ExperienceOrb;
+import net.minecraft.world.level.Level;
+
+public class ExperienceOrbController extends MobEntityController {
+ public ExperienceOrbController() {
+ super(EntityExperienceOrbNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.ExperienceOrb getBukkitEntity() {
+ return (org.bukkit.entity.ExperienceOrb) super.getBukkitEntity();
+ }
+
+ public static class EntityExperienceOrbNPC extends ExperienceOrb implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityExperienceOrbNPC(EntityType extends ExperienceOrb> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityExperienceOrbNPC(EntityType extends ExperienceOrb> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ExperienceOrbNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class ExperienceOrbNPC extends CraftExperienceOrb implements ForwardingNPCHolder {
+ public ExperienceOrbNPC(EntityExperienceOrbNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/FallingBlockController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/FallingBlockController.java
new file mode 100644
index 000000000..9f92da884
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/FallingBlockController.java
@@ -0,0 +1,161 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftFallingBlock;
+import org.bukkit.craftbukkit.v1_17_R1.util.CraftMagicNumbers;
+import org.bukkit.entity.FallingBlock;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.DespawnReason;
+import net.citizensnpcs.api.event.SpawnReason;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.AbstractEntityController;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.MoverType;
+import net.minecraft.world.entity.item.FallingBlockEntity;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+public class FallingBlockController extends AbstractEntityController {
+ public FallingBlockController() {
+ super(EntityFallingBlockNPC.class);
+ }
+
+ @Override
+ protected org.bukkit.entity.Entity createEntity(Location at, NPC npc) {
+ ServerLevel ws = ((CraftWorld) at.getWorld()).getHandle();
+ Block id = Blocks.STONE;
+ int data = npc.data().get(NPC.ITEM_DATA_METADATA, npc.data().get("falling-block-data", 0));
+ // TODO: how to incorporate this - probably delete?
+ if (npc.data().has("falling-block-id") || npc.data().has(NPC.ITEM_ID_METADATA)) {
+ id = CraftMagicNumbers.getBlock(Material.getMaterial(
+ npc.data(). get(NPC.ITEM_ID_METADATA, npc.data(). get("falling-block-id"))));
+ }
+ final EntityFallingBlockNPC handle = new EntityFallingBlockNPC(ws, npc, at.getX(), at.getY(), at.getZ(),
+ id.defaultBlockState());
+ return handle.getBukkitEntity();
+ }
+
+ @Override
+ public FallingBlock getBukkitEntity() {
+ return (FallingBlock) super.getBukkitEntity();
+ }
+
+ public static class EntityFallingBlockNPC extends FallingBlockEntity implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityFallingBlockNPC(EntityType extends FallingBlockEntity> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityFallingBlockNPC(EntityType extends FallingBlockEntity> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ public EntityFallingBlockNPC(Level world, NPC npc, double d0, double d1, double d2, BlockState data) {
+ super(world, d0, d1, d2, data);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new FallingBlockNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ Vec3 mot = getDeltaMovement();
+ if (Math.abs(mot.x) > EPSILON || Math.abs(mot.y) > EPSILON || Math.abs(mot.z) > EPSILON) {
+ mot = mot.multiply(0.98, 0.98, 0.98);
+ setDeltaMovement(mot);
+ move(MoverType.SELF, mot);
+ }
+ } else {
+ super.tick();
+ }
+ }
+
+ private static final double EPSILON = 0.001;
+ }
+
+ public static class FallingBlockNPC extends CraftFallingBlock implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public FallingBlockNPC(EntityFallingBlockNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ this.npc = entity.npc;
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public void setType(Material material, int data) {
+ npc.data().setPersistent(NPC.ITEM_ID_METADATA, material.name());
+ npc.data().setPersistent(NPC.ITEM_DATA_METADATA, data);
+ if (npc.isSpawned()) {
+ npc.despawn(DespawnReason.PENDING_RESPAWN);
+ npc.spawn(npc.getStoredLocation(), SpawnReason.RESPAWN);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/FireworkController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/FireworkController.java
new file mode 100644
index 000000000..ca62e1b4d
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/FireworkController.java
@@ -0,0 +1,96 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftFirework;
+import org.bukkit.entity.Firework;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.FireworkRocketEntity;
+import net.minecraft.world.level.Level;
+
+public class FireworkController extends MobEntityController {
+ public FireworkController() {
+ super(EntityFireworkNPC.class);
+ }
+
+ @Override
+ public Firework getBukkitEntity() {
+ return (Firework) super.getBukkitEntity();
+ }
+
+ public static class EntityFireworkNPC extends FireworkRocketEntity implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityFireworkNPC(EntityType extends FireworkRocketEntity> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityFireworkNPC(EntityType extends FireworkRocketEntity> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new FireworkNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class FireworkNPC extends CraftFirework implements ForwardingNPCHolder {
+ public FireworkNPC(EntityFireworkNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/FishingHookController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/FishingHookController.java
new file mode 100644
index 000000000..97392cc12
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/FishingHookController.java
@@ -0,0 +1,119 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftFishHook;
+import org.bukkit.entity.FishHook;
+import org.bukkit.util.Vector;
+
+import com.mojang.authlib.GameProfile;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.FishingHook;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.Level;
+
+public class FishingHookController extends MobEntityController {
+ public FishingHookController() {
+ super(EntityFishingHookNPC.class);
+ }
+
+ @Override
+ public FishHook getBukkitEntity() {
+ return (FishHook) super.getBukkitEntity();
+ }
+
+ public static class EntityFishingHookNPC extends FishingHook implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityFishingHookNPC(EntityType extends FishingHook> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityFishingHookNPC(EntityType extends FishingHook> types, Level level, NPC npc) {
+ super(new ServerPlayer(level.getServer().getServer(), (ServerLevel) level,
+ new GameProfile(UUID.randomUUID(), "dummyfishhook")) {
+ }, level, 0, 0);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public double distanceToSqr(Entity entity) {
+ if (entity == getPlayerOwner()) {
+ return 0D;
+ }
+ return super.distanceToSqr(entity);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new FishingHookNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ ((ServerPlayer) getPlayerOwner()).setHealth(20F);
+ getPlayerOwner().unsetRemoved();
+ ((ServerPlayer) getPlayerOwner()).getInventory().items.set(
+ ((ServerPlayer) getPlayerOwner()).getInventory().selected, new ItemStack(Items.FISHING_ROD, 1));
+ NMSImpl.setLife(this, 0);
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class FishingHookNPC extends CraftFishHook implements ForwardingNPCHolder {
+ public FishingHookNPC(EntityFishingHookNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/GlowItemFrameController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/GlowItemFrameController.java
new file mode 100644
index 000000000..e35ec2cef
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/GlowItemFrameController.java
@@ -0,0 +1,139 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftGlowItemFrame;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.DespawnReason;
+import net.citizensnpcs.api.event.SpawnReason;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.entity.nonliving.ItemFrameController.EntityItemFrameNPC;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.decoration.GlowItemFrame;
+import net.minecraft.world.level.Level;
+
+public class GlowItemFrameController extends MobEntityController {
+ public GlowItemFrameController() {
+ super(EntityItemFrameNPC.class);
+ }
+
+ @Override
+ protected org.bukkit.entity.Entity createEntity(Location at, NPC npc) {
+ org.bukkit.entity.Entity e = super.createEntity(at, npc);
+ GlowItemFrame item = (GlowItemFrame) ((CraftEntity) e).getHandle();
+ item.setDirection(Direction.EAST);
+ item.pos = new BlockPos(at.getX(), at.getY(), at.getZ());
+ return e;
+ }
+
+ @Override
+ public org.bukkit.entity.GlowItemFrame getBukkitEntity() {
+ return (org.bukkit.entity.GlowItemFrame) super.getBukkitEntity();
+ }
+
+ public static class EntityGlowItemFrameNPC extends GlowItemFrame implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityGlowItemFrameNPC(EntityType extends GlowItemFrame> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityGlowItemFrameNPC(EntityType extends GlowItemFrame> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new GlowItemFrameNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public boolean survives() {
+ return npc == null || !npc.isProtected() ? super.survives() : true;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class GlowItemFrameNPC extends CraftGlowItemFrame implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public GlowItemFrameNPC(EntityGlowItemFrameNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ this.npc = entity.npc;
+ Material id = Material.STONE;
+ int data = npc.data().get(NPC.ITEM_DATA_METADATA, npc.data().get("falling-block-data", 0));
+ if (npc.data().has(NPC.ITEM_ID_METADATA)) {
+ id = Material.getMaterial(npc.data(). get(NPC.ITEM_ID_METADATA));
+ }
+ getItem().setType(id);
+ getItem().setDurability((short) data);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public void setType(Material material, int data) {
+ npc.data().setPersistent(NPC.ITEM_ID_METADATA, material.name());
+ npc.data().setPersistent(NPC.ITEM_DATA_METADATA, data);
+ if (npc.isSpawned()) {
+ npc.despawn(DespawnReason.PENDING_RESPAWN);
+ npc.spawn(npc.getStoredLocation(), SpawnReason.RESPAWN);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ItemController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ItemController.java
new file mode 100644
index 000000000..9f16b3f3b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ItemController.java
@@ -0,0 +1,142 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftItem;
+import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;
+import org.bukkit.entity.Item;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.DespawnReason;
+import net.citizensnpcs.api.event.SpawnReason;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.AbstractEntityController;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+
+public class ItemController extends AbstractEntityController {
+ public ItemController() {
+ super(EntityItemNPC.class);
+ }
+
+ @Override
+ protected org.bukkit.entity.Entity createEntity(Location at, NPC npc) {
+ ServerLevel ws = ((CraftWorld) at.getWorld()).getHandle();
+ Material id = Material.STONE;
+ int data = npc.data().get(NPC.ITEM_DATA_METADATA, npc.data().get("falling-block-data", 0));
+ if (npc.data().has(NPC.ITEM_ID_METADATA)) {
+ id = Material.getMaterial(npc.data(). get(NPC.ITEM_ID_METADATA));
+ }
+ final EntityItemNPC handle = new EntityItemNPC(ws, npc, at.getX(), at.getY(), at.getZ(),
+ CraftItemStack.asNMSCopy(new org.bukkit.inventory.ItemStack(id, 1, (short) data)));
+ return handle.getBukkitEntity();
+ }
+
+ @Override
+ public Item getBukkitEntity() {
+ return (Item) super.getBukkitEntity();
+ }
+
+ public static class EntityItemNPC extends ItemEntity implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityItemNPC(EntityType extends ItemEntity> types, Level level) {
+ super(types, level);
+ this.npc = null;
+ }
+
+ public EntityItemNPC(Level world, NPC npc, double x, double y, double z, ItemStack stack) {
+ super(world, x, y, z, stack);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ItemNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void playerTouch(Player entityhuman) {
+ if (npc == null) {
+ super.playerTouch(entityhuman);
+ }
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class ItemNPC extends CraftItem implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public ItemNPC(EntityItemNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ this.npc = entity.npc;
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public void setType(Material material, int data) {
+ npc.data().setPersistent(NPC.ITEM_ID_METADATA, material.name());
+ npc.data().setPersistent(NPC.ITEM_DATA_METADATA, data);
+ if (npc.isSpawned()) {
+ npc.despawn(DespawnReason.PENDING_RESPAWN);
+ npc.spawn(npc.getStoredLocation(), SpawnReason.RESPAWN);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ItemFrameController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ItemFrameController.java
new file mode 100644
index 000000000..fa89ac2a8
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ItemFrameController.java
@@ -0,0 +1,138 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftItemFrame;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.event.DespawnReason;
+import net.citizensnpcs.api.event.SpawnReason;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.decoration.ItemFrame;
+import net.minecraft.world.level.Level;
+
+public class ItemFrameController extends MobEntityController {
+ public ItemFrameController() {
+ super(EntityItemFrameNPC.class);
+ }
+
+ @Override
+ protected org.bukkit.entity.Entity createEntity(Location at, NPC npc) {
+ org.bukkit.entity.Entity e = super.createEntity(at, npc);
+ ItemFrame item = (ItemFrame) ((CraftEntity) e).getHandle();
+ item.setDirection(Direction.EAST);
+ item.pos = new BlockPos(at.getX(), at.getY(), at.getZ());
+ return e;
+ }
+
+ @Override
+ public org.bukkit.entity.ItemFrame getBukkitEntity() {
+ return (org.bukkit.entity.ItemFrame) super.getBukkitEntity();
+ }
+
+ public static class EntityItemFrameNPC extends ItemFrame implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityItemFrameNPC(EntityType extends ItemFrame> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityItemFrameNPC(EntityType extends ItemFrame> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ItemFrameNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public boolean survives() {
+ return npc == null || !npc.isProtected() ? super.survives() : true;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class ItemFrameNPC extends CraftItemFrame implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public ItemFrameNPC(EntityItemFrameNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ this.npc = entity.npc;
+ Material id = Material.STONE;
+ int data = npc.data().get(NPC.ITEM_DATA_METADATA, npc.data().get("falling-block-data", 0));
+ if (npc.data().has(NPC.ITEM_ID_METADATA)) {
+ id = Material.getMaterial(npc.data(). get(NPC.ITEM_ID_METADATA));
+ }
+ getItem().setType(id);
+ getItem().setDurability((short) data);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public void setType(Material material, int data) {
+ npc.data().setPersistent(NPC.ITEM_ID_METADATA, material.name());
+ npc.data().setPersistent(NPC.ITEM_DATA_METADATA, data);
+ if (npc.isSpawned()) {
+ npc.despawn(DespawnReason.PENDING_RESPAWN);
+ npc.spawn(npc.getStoredLocation(), SpawnReason.RESPAWN);
+ }
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/LargeFireballController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/LargeFireballController.java
new file mode 100644
index 000000000..e92a2bf24
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/LargeFireballController.java
@@ -0,0 +1,107 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftLargeFireball;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.LargeFireball;
+import net.minecraft.world.level.Level;
+
+public class LargeFireballController extends MobEntityController {
+ public LargeFireballController() {
+ super(EntityLargeFireballNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.LargeFireball getBukkitEntity() {
+ return (org.bukkit.entity.LargeFireball) super.getBukkitEntity();
+ }
+
+ public static class EntityLargeFireballNPC extends LargeFireball implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityLargeFireballNPC(EntityType extends LargeFireball> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityLargeFireballNPC(EntityType extends LargeFireball> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new LargeFireballNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public void refreshDimensions() {
+ if (npc == null) {
+ super.refreshDimensions();
+ } else {
+ NMSImpl.setSize(this, firstTick);
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class LargeFireballNPC extends CraftLargeFireball implements ForwardingNPCHolder {
+ public LargeFireballNPC(EntityLargeFireballNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/LeashController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/LeashController.java
new file mode 100644
index 000000000..b99002328
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/LeashController.java
@@ -0,0 +1,101 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftLeash;
+import org.bukkit.entity.LeashHitch;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
+import net.minecraft.world.level.Level;
+
+public class LeashController extends MobEntityController {
+ public LeashController() {
+ super(EntityLeashNPC.class);
+ }
+
+ @Override
+ public LeashHitch getBukkitEntity() {
+ return (LeashHitch) super.getBukkitEntity();
+ }
+
+ public static class EntityLeashNPC extends LeashFenceKnotEntity implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityLeashNPC(EntityType extends LeashFenceKnotEntity> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityLeashNPC(EntityType extends LeashFenceKnotEntity> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new LeashNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public boolean survives() {
+ return npc == null || !npc.isProtected() ? super.survives() : true;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class LeashNPC extends CraftLeash implements ForwardingNPCHolder {
+ public LeashNPC(EntityLeashNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/LlamaSpitController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/LlamaSpitController.java
new file mode 100644
index 000000000..a1b094f5e
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/LlamaSpitController.java
@@ -0,0 +1,116 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftLlamaSpit;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.AbstractEntityController;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.horse.Llama;
+import net.minecraft.world.entity.projectile.LlamaSpit;
+import net.minecraft.world.level.Level;
+
+public class LlamaSpitController extends AbstractEntityController {
+ public LlamaSpitController() {
+ super(EntityLlamaSpitNPC.class);
+ }
+
+ @Override
+ protected org.bukkit.entity.Entity createEntity(Location at, NPC npc) {
+ ServerLevel ws = ((CraftWorld) at.getWorld()).getHandle();
+ final EntityLlamaSpitNPC handle = new EntityLlamaSpitNPC(
+ NMSImpl. getEntityType(EntityLlamaSpitNPC.class), ws, npc);
+ handle.absMoveTo(at.getX(), at.getY(), at.getZ(), at.getPitch(), at.getYaw());
+ return handle.getBukkitEntity();
+ }
+
+ @Override
+ public org.bukkit.entity.LlamaSpit getBukkitEntity() {
+ return (org.bukkit.entity.LlamaSpit) super.getBukkitEntity();
+ }
+
+ public static class EntityLlamaSpitNPC extends LlamaSpit implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityLlamaSpitNPC(EntityType extends LlamaSpit> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityLlamaSpitNPC(EntityType extends LlamaSpit> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ public EntityLlamaSpitNPC(Level world, NPC npc, Llama entity) {
+ super(world, entity);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new LlamaSpitNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class LlamaSpitNPC extends CraftLlamaSpit implements ForwardingNPCHolder {
+ public LlamaSpitNPC(EntityLlamaSpitNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MarkerController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MarkerController.java
new file mode 100644
index 000000000..06ae506fc
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MarkerController.java
@@ -0,0 +1,95 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftMarker;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.Marker;
+import net.minecraft.world.level.Level;
+
+public class MarkerController extends MobEntityController {
+ public MarkerController() {
+ super(EntityMarkerNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Marker getBukkitEntity() {
+ return (org.bukkit.entity.Marker) super.getBukkitEntity();
+ }
+
+ public static class EntityMarkerNPC extends Marker implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityMarkerNPC(EntityType extends Marker> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMarkerNPC(EntityType extends Marker> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new MarkerNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class MarkerNPC extends CraftMarker implements ForwardingNPCHolder {
+ public MarkerNPC(EntityMarkerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartChestController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartChestController.java
new file mode 100644
index 000000000..707355f9a
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartChestController.java
@@ -0,0 +1,97 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftMinecartChest;
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.vehicle.MinecartChest;
+import net.minecraft.world.level.Level;
+
+public class MinecartChestController extends MobEntityController {
+ public MinecartChestController() {
+ super(EntityMinecartChestNPC.class);
+ }
+
+ @Override
+ public Minecart getBukkitEntity() {
+ return (Minecart) super.getBukkitEntity();
+ }
+
+ public static class EntityMinecartChestNPC extends MinecartChest implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityMinecartChestNPC(EntityType extends MinecartChest> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMinecartChestNPC(EntityType extends MinecartChest> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new MinecartChestNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+
+ }
+
+ public static class MinecartChestNPC extends CraftMinecartChest implements ForwardingNPCHolder {
+ public MinecartChestNPC(EntityMinecartChestNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartCommandController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartCommandController.java
new file mode 100644
index 000000000..ff4dc1cab
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartCommandController.java
@@ -0,0 +1,96 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftMinecartCommand;
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.vehicle.MinecartCommandBlock;
+import net.minecraft.world.level.Level;
+
+public class MinecartCommandController extends MobEntityController {
+ public MinecartCommandController() {
+ super(EntityMinecartCommandNPC.class);
+ }
+
+ @Override
+ public Minecart getBukkitEntity() {
+ return (Minecart) super.getBukkitEntity();
+ }
+
+ public static class EntityMinecartCommandNPC extends MinecartCommandBlock implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityMinecartCommandNPC(EntityType extends MinecartCommandBlock> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMinecartCommandNPC(EntityType extends MinecartCommandBlock> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new MinecartCommandNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+ }
+
+ public static class MinecartCommandNPC extends CraftMinecartCommand implements ForwardingNPCHolder {
+ public MinecartCommandNPC(EntityMinecartCommandNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartFurnaceController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartFurnaceController.java
new file mode 100644
index 000000000..80adf564e
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartFurnaceController.java
@@ -0,0 +1,96 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftMinecartFurnace;
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.vehicle.MinecartFurnace;
+import net.minecraft.world.level.Level;
+
+public class MinecartFurnaceController extends MobEntityController {
+ public MinecartFurnaceController() {
+ super(EntityMinecartFurnaceNPC.class);
+ }
+
+ @Override
+ public Minecart getBukkitEntity() {
+ return (Minecart) super.getBukkitEntity();
+ }
+
+ public static class EntityMinecartFurnaceNPC extends MinecartFurnace implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityMinecartFurnaceNPC(EntityType extends MinecartFurnace> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMinecartFurnaceNPC(EntityType extends MinecartFurnace> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new MinecartFurnaceNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+ }
+
+ public static class MinecartFurnaceNPC extends CraftMinecartFurnace implements ForwardingNPCHolder {
+ public MinecartFurnaceNPC(EntityMinecartFurnaceNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartHopperController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartHopperController.java
new file mode 100644
index 000000000..3993244d9
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartHopperController.java
@@ -0,0 +1,77 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.vehicle.MinecartHopper;
+import net.minecraft.world.level.Level;
+
+public class MinecartHopperController extends MobEntityController {
+ public MinecartHopperController() {
+ super(EntityMinecartHopperNPC.class);
+ }
+
+ @Override
+ public Minecart getBukkitEntity() {
+ return (Minecart) super.getBukkitEntity();
+ }
+
+ public static class EntityMinecartHopperNPC extends MinecartHopper implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityMinecartHopperNPC(EntityType extends MinecartHopper> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMinecartHopperNPC(EntityType extends MinecartHopper> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartRideableController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartRideableController.java
new file mode 100644
index 000000000..cc8fcb94f
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartRideableController.java
@@ -0,0 +1,95 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftMinecartRideable;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.vehicle.Minecart;
+import net.minecraft.world.level.Level;
+
+public class MinecartRideableController extends MobEntityController {
+ public MinecartRideableController() {
+ super(EntityMinecartRideableNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Minecart getBukkitEntity() {
+ return (org.bukkit.entity.Minecart) super.getBukkitEntity();
+ }
+
+ public static class EntityMinecartRideableNPC extends Minecart implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityMinecartRideableNPC(EntityType extends Minecart> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMinecartRideableNPC(EntityType extends Minecart> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new MinecartRideableNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+ }
+
+ public static class MinecartRideableNPC extends CraftMinecartRideable implements ForwardingNPCHolder {
+ public MinecartRideableNPC(EntityMinecartRideableNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartSpawnerController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartSpawnerController.java
new file mode 100644
index 000000000..000353944
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartSpawnerController.java
@@ -0,0 +1,77 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.vehicle.MinecartSpawner;
+import net.minecraft.world.level.Level;
+
+public class MinecartSpawnerController extends MobEntityController {
+ public MinecartSpawnerController() {
+ super(EntityMinecartSpawnerNPC.class);
+ }
+
+ @Override
+ public Minecart getBukkitEntity() {
+ return (Minecart) super.getBukkitEntity();
+ }
+
+ public static class EntityMinecartSpawnerNPC extends MinecartSpawner implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityMinecartSpawnerNPC(EntityType extends MinecartSpawner> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMinecartSpawnerNPC(EntityType extends MinecartSpawner> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartTNTController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartTNTController.java
new file mode 100644
index 000000000..e80a4f569
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/MinecartTNTController.java
@@ -0,0 +1,77 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.vehicle.MinecartTNT;
+import net.minecraft.world.level.Level;
+
+public class MinecartTNTController extends MobEntityController {
+ public MinecartTNTController() {
+ super(EntityMinecartTNTNPC.class);
+ }
+
+ @Override
+ public Minecart getBukkitEntity() {
+ return (Minecart) super.getBukkitEntity();
+ }
+
+ public static class EntityMinecartTNTNPC extends MinecartTNT implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityMinecartTNTNPC(EntityType extends MinecartTNT> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityMinecartTNTNPC(EntityType extends MinecartTNT> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/PaintingController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/PaintingController.java
new file mode 100644
index 000000000..2ab581596
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/PaintingController.java
@@ -0,0 +1,100 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPainting;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.decoration.Painting;
+import net.minecraft.world.level.Level;
+
+public class PaintingController extends MobEntityController {
+ public PaintingController() {
+ super(EntityPaintingNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Painting getBukkitEntity() {
+ return (org.bukkit.entity.Painting) super.getBukkitEntity();
+ }
+
+ public static class EntityPaintingNPC extends Painting implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityPaintingNPC(EntityType extends Painting> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityPaintingNPC(EntityType extends Painting> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new PaintingNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public boolean survives() {
+ return npc == null || !npc.isProtected() ? super.survives() : true;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class PaintingNPC extends CraftPainting implements ForwardingNPCHolder {
+ public PaintingNPC(EntityPaintingNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ShulkerBulletController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ShulkerBulletController.java
new file mode 100644
index 000000000..afd3ae40e
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ShulkerBulletController.java
@@ -0,0 +1,95 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftShulkerBullet;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.ShulkerBullet;
+import net.minecraft.world.level.Level;
+
+public class ShulkerBulletController extends MobEntityController {
+ public ShulkerBulletController() {
+ super(EntityShulkerBulletNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.ShulkerBullet getBukkitEntity() {
+ return (org.bukkit.entity.ShulkerBullet) super.getBukkitEntity();
+ }
+
+ public static class EntityShulkerBulletNPC extends ShulkerBullet implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityShulkerBulletNPC(EntityType extends ShulkerBullet> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityShulkerBulletNPC(EntityType extends ShulkerBullet> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ShulkerBulletNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class ShulkerBulletNPC extends CraftShulkerBullet implements ForwardingNPCHolder {
+ public ShulkerBulletNPC(EntityShulkerBulletNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/SmallFireballController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/SmallFireballController.java
new file mode 100644
index 000000000..08c3f576a
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/SmallFireballController.java
@@ -0,0 +1,98 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSmallFireball;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.SmallFireball;
+import net.minecraft.world.level.Level;
+
+public class SmallFireballController extends MobEntityController {
+ public SmallFireballController() {
+ super(EntitySmallFireballNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.SmallFireball getBukkitEntity() {
+ return (org.bukkit.entity.SmallFireball) super.getBukkitEntity();
+ }
+
+ public static class EntitySmallFireballNPC extends SmallFireball implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySmallFireballNPC(EntityType extends SmallFireball> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySmallFireballNPC(EntityType extends SmallFireball> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SmallFireballNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class SmallFireballNPC extends CraftSmallFireball implements ForwardingNPCHolder {
+ public SmallFireballNPC(EntitySmallFireballNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/SnowballController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/SnowballController.java
new file mode 100644
index 000000000..04bb5d29c
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/SnowballController.java
@@ -0,0 +1,95 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftSnowball;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.Snowball;
+import net.minecraft.world.level.Level;
+
+public class SnowballController extends MobEntityController {
+ public SnowballController() {
+ super(EntitySnowballNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Snowball getBukkitEntity() {
+ return (org.bukkit.entity.Snowball) super.getBukkitEntity();
+ }
+
+ public static class EntitySnowballNPC extends Snowball implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySnowballNPC(EntityType extends Snowball> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySnowballNPC(EntityType extends Snowball> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SnowballNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class SnowballNPC extends CraftSnowball implements ForwardingNPCHolder {
+ public SnowballNPC(EntitySnowballNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/SpectralArrowController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/SpectralArrowController.java
new file mode 100644
index 000000000..0ea24f251
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/SpectralArrowController.java
@@ -0,0 +1,96 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftArrow;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.entity.Arrow;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.SpectralArrow;
+import net.minecraft.world.level.Level;
+
+public class SpectralArrowController extends MobEntityController {
+ public SpectralArrowController() {
+ super(EntitySpectralArrowNPC.class);
+ }
+
+ @Override
+ public Arrow getBukkitEntity() {
+ return (Arrow) super.getBukkitEntity();
+ }
+
+ public static class EntitySpectralArrowNPC extends SpectralArrow implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySpectralArrowNPC(EntityType extends SpectralArrow> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySpectralArrowNPC(EntityType extends SpectralArrow> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SpectralArrowNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class SpectralArrowNPC extends CraftArrow implements ForwardingNPCHolder {
+ public SpectralArrowNPC(EntitySpectralArrowNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/TNTPrimedController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/TNTPrimedController.java
new file mode 100644
index 000000000..fa87704f6
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/TNTPrimedController.java
@@ -0,0 +1,96 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftTNTPrimed;
+import org.bukkit.entity.TNTPrimed;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.item.PrimedTnt;
+import net.minecraft.world.level.Level;
+
+public class TNTPrimedController extends MobEntityController {
+ public TNTPrimedController() {
+ super(EntityTNTPrimedNPC.class);
+ }
+
+ @Override
+ public TNTPrimed getBukkitEntity() {
+ return (TNTPrimed) super.getBukkitEntity();
+ }
+
+ public static class EntityTNTPrimedNPC extends PrimedTnt implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityTNTPrimedNPC(EntityType extends PrimedTnt> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityTNTPrimedNPC(EntityType extends PrimedTnt> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new TNTPrimedNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class TNTPrimedNPC extends CraftTNTPrimed implements ForwardingNPCHolder {
+ public TNTPrimedNPC(EntityTNTPrimedNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ThrownExpBottleController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ThrownExpBottleController.java
new file mode 100644
index 000000000..cf8de1471
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ThrownExpBottleController.java
@@ -0,0 +1,99 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftThrownExpBottle;
+import org.bukkit.entity.ThrownExpBottle;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.ThrownExperienceBottle;
+import net.minecraft.world.level.Level;
+
+public class ThrownExpBottleController extends MobEntityController {
+ public ThrownExpBottleController() {
+ super(EntityThrownExpBottleNPC.class);
+ }
+
+ @Override
+ public ThrownExpBottle getBukkitEntity() {
+ return (ThrownExpBottle) super.getBukkitEntity();
+ }
+
+ public static class EntityThrownExpBottleNPC extends ThrownExperienceBottle implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityThrownExpBottleNPC(EntityType extends ThrownExperienceBottle> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityThrownExpBottleNPC(EntityType extends ThrownExperienceBottle> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ThrownExpBottleNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class ThrownExpBottleNPC extends CraftThrownExpBottle implements ForwardingNPCHolder {
+ public ThrownExpBottleNPC(EntityThrownExpBottleNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ThrownPotionController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ThrownPotionController.java
new file mode 100644
index 000000000..27101604b
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ThrownPotionController.java
@@ -0,0 +1,114 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftThrownPotion;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.ThrownPotion;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.Level;
+
+public class ThrownPotionController extends MobEntityController {
+ public ThrownPotionController() {
+ super(EntityThrownPotionNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.ThrownPotion getBukkitEntity() {
+ return (org.bukkit.entity.ThrownPotion) super.getBukkitEntity();
+ }
+
+ public static class EntityThrownPotionNPC extends ThrownPotion implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityThrownPotionNPC(EntityType extends ThrownPotion> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityThrownPotionNPC(EntityType extends ThrownPotion> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ if (getItem() != null && getItem().getItem().equals(Items.LINGERING_POTION)) {
+ NMSImpl.setBukkitEntity(this, new LingeringThrownPotionNPC(this));
+ } else {
+ NMSImpl.setBukkitEntity(this, new SplashThrownPotionNPC(this));
+ }
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class LingeringThrownPotionNPC extends CraftThrownPotion implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public LingeringThrownPotionNPC(EntityThrownPotionNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ this.npc = entity.npc;
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+ }
+
+ public static class SplashThrownPotionNPC extends CraftThrownPotion implements ForwardingNPCHolder {
+ public SplashThrownPotionNPC(EntityThrownPotionNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ThrownTridentController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ThrownTridentController.java
new file mode 100644
index 000000000..cee0c8441
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/ThrownTridentController.java
@@ -0,0 +1,96 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftTrident;
+import org.bukkit.entity.Trident;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.ThrownTrident;
+import net.minecraft.world.level.Level;
+
+public class ThrownTridentController extends MobEntityController {
+ public ThrownTridentController() {
+ super(EntityThrownTridentNPC.class);
+ }
+
+ @Override
+ public Trident getBukkitEntity() {
+ return (Trident) super.getBukkitEntity();
+ }
+
+ public static class EntityThrownTridentNPC extends ThrownTrident implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityThrownTridentNPC(EntityType extends ThrownTrident> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityThrownTridentNPC(EntityType extends ThrownTrident> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new ThrownTridentNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class ThrownTridentNPC extends CraftTrident implements ForwardingNPCHolder {
+ public ThrownTridentNPC(EntityThrownTridentNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/TippedArrowController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/TippedArrowController.java
new file mode 100644
index 000000000..82f696de2
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/TippedArrowController.java
@@ -0,0 +1,95 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftTippedArrow;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.Arrow;
+import net.minecraft.world.level.Level;
+
+public class TippedArrowController extends MobEntityController {
+ public TippedArrowController() {
+ super(EntityTippedArrowNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Arrow getBukkitEntity() {
+ return (org.bukkit.entity.Arrow) super.getBukkitEntity();
+ }
+
+ public static class EntityTippedArrowNPC extends Arrow implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityTippedArrowNPC(EntityType extends Arrow> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityTippedArrowNPC(EntityType extends Arrow> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new TippedArrowNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class TippedArrowNPC extends CraftTippedArrow implements ForwardingNPCHolder {
+ public TippedArrowNPC(EntityTippedArrowNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/WitherSkullController.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/WitherSkullController.java
new file mode 100644
index 000000000..4218727de
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/entity/nonliving/WitherSkullController.java
@@ -0,0 +1,95 @@
+package net.citizensnpcs.nms.v1_17_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftWitherSkull;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_17_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_17_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.projectile.WitherSkull;
+import net.minecraft.world.level.Level;
+
+public class WitherSkullController extends MobEntityController {
+ public WitherSkullController() {
+ super(EntityWitherSkullNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.WitherSkull getBukkitEntity() {
+ return (org.bukkit.entity.WitherSkull) super.getBukkitEntity();
+ }
+
+ public static class EntityWitherSkullNPC extends WitherSkull implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityWitherSkullNPC(EntityType extends WitherSkull> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityWitherSkullNPC(EntityType extends WitherSkull> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new WitherSkullNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void push(double x, double y, double z) {
+ Vector vector = Util.callPushEvent(npc, x, y, z);
+ if (vector != null) {
+ super.push(vector.getX(), vector.getY(), vector.getZ());
+ }
+ }
+
+ @Override
+ public void push(Entity entity) {
+ // this method is called by both the entities involved - cancelling
+ // it will not stop the NPC from moving.
+ super.push(entity);
+ if (npc != null) {
+ Util.callCollisionEvent(npc, entity.getBukkitEntity());
+ }
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+ }
+
+ public static class WitherSkullNPC extends CraftWitherSkull implements ForwardingNPCHolder {
+ public WitherSkullNPC(EntityWitherSkullNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptyChannel.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptyChannel.java
new file mode 100644
index 000000000..d60a4740f
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptyChannel.java
@@ -0,0 +1,80 @@
+package net.citizensnpcs.nms.v1_17_R1.network;
+
+import java.net.SocketAddress;
+
+import io.netty.channel.AbstractChannel;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelConfig;
+import io.netty.channel.ChannelMetadata;
+import io.netty.channel.ChannelOutboundBuffer;
+import io.netty.channel.DefaultChannelConfig;
+import io.netty.channel.EventLoop;
+
+public class EmptyChannel extends AbstractChannel {
+ private final ChannelConfig config = new DefaultChannelConfig(this);
+
+ public EmptyChannel(Channel parent) {
+ super(parent);
+ }
+
+ @Override
+ public ChannelConfig config() {
+ config.setAutoRead(true);
+ return config;
+ }
+
+ @Override
+ protected void doBeginRead() throws Exception {
+ }
+
+ @Override
+ protected void doBind(SocketAddress arg0) throws Exception {
+ }
+
+ @Override
+ protected void doClose() throws Exception {
+ }
+
+ @Override
+ protected void doDisconnect() throws Exception {
+ }
+
+ @Override
+ protected void doWrite(ChannelOutboundBuffer arg0) throws Exception {
+ }
+
+ @Override
+ public boolean isActive() {
+ return false;
+ }
+
+ @Override
+ protected boolean isCompatible(EventLoop arg0) {
+ return true;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return false;
+ }
+
+ @Override
+ protected SocketAddress localAddress0() {
+ return null;
+ }
+
+ @Override
+ public ChannelMetadata metadata() {
+ return new ChannelMetadata(true);
+ }
+
+ @Override
+ protected AbstractUnsafe newUnsafe() {
+ return null;
+ }
+
+ @Override
+ protected SocketAddress remoteAddress0() {
+ return null;
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptyNetHandler.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptyNetHandler.java
new file mode 100644
index 000000000..f298e5a15
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptyNetHandler.java
@@ -0,0 +1,17 @@
+package net.citizensnpcs.nms.v1_17_R1.network;
+
+import net.minecraft.network.Connection;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerGamePacketListenerImpl;
+
+public class EmptyNetHandler extends ServerGamePacketListenerImpl {
+ public EmptyNetHandler(MinecraftServer minecraftServer, Connection networkManager, ServerPlayer entityPlayer) {
+ super(minecraftServer, networkManager, entityPlayer);
+ }
+
+ @Override
+ public void send(Packet> packet) {
+ }
+}
\ No newline at end of file
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptyNetworkManager.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptyNetworkManager.java
new file mode 100644
index 000000000..80eac78d6
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptyNetworkManager.java
@@ -0,0 +1,25 @@
+package net.citizensnpcs.nms.v1_17_R1.network;
+
+import java.io.IOException;
+
+import io.netty.util.concurrent.GenericFutureListener;
+import net.citizensnpcs.nms.v1_17_R1.util.NMSImpl;
+import net.minecraft.network.Connection;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.PacketFlow;
+
+public class EmptyNetworkManager extends Connection {
+ public EmptyNetworkManager(PacketFlow flag) throws IOException {
+ super(flag);
+ NMSImpl.initNetworkManager(this);
+ }
+
+ @Override
+ public boolean isConnected() {
+ return true;
+ }
+
+ @Override
+ public void send(Packet packet, GenericFutureListener genericfuturelistener) {
+ }
+}
\ No newline at end of file
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptySocket.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptySocket.java
new file mode 100644
index 000000000..bfc0389bc
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/network/EmptySocket.java
@@ -0,0 +1,21 @@
+package net.citizensnpcs.nms.v1_17_R1.network;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+public class EmptySocket extends Socket {
+ @Override
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream(EMPTY);
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return new ByteArrayOutputStream(10);
+ }
+
+ private static final byte[] EMPTY = new byte[50];
+}
\ No newline at end of file
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/trait/Commands.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/trait/Commands.java
new file mode 100644
index 000000000..f4e4566ad
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/trait/Commands.java
@@ -0,0 +1,561 @@
+package net.citizensnpcs.nms.v1_17_R1.trait;
+
+import java.util.List;
+
+import org.bukkit.DyeColor;
+import org.bukkit.boss.BarColor;
+import org.bukkit.boss.BarFlag;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Cat;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Fox;
+import org.bukkit.entity.Llama.Color;
+import org.bukkit.entity.MushroomCow;
+import org.bukkit.entity.Panda;
+import org.bukkit.entity.Parrot.Variant;
+import org.bukkit.entity.TropicalFish.Pattern;
+import org.bukkit.entity.Villager;
+import org.bukkit.entity.Villager.Profession;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+
+import net.citizensnpcs.api.command.Command;
+import net.citizensnpcs.api.command.CommandContext;
+import net.citizensnpcs.api.command.Requirements;
+import net.citizensnpcs.api.command.exception.CommandException;
+import net.citizensnpcs.api.command.exception.CommandUsageException;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.api.util.Colorizer;
+import net.citizensnpcs.api.util.Messaging;
+import net.citizensnpcs.trait.VillagerProfession;
+import net.citizensnpcs.trait.versioned.BeeTrait;
+import net.citizensnpcs.trait.versioned.BossBarTrait;
+import net.citizensnpcs.trait.versioned.CatTrait;
+import net.citizensnpcs.trait.versioned.FoxTrait;
+import net.citizensnpcs.trait.versioned.LlamaTrait;
+import net.citizensnpcs.trait.versioned.MushroomCowTrait;
+import net.citizensnpcs.trait.versioned.PandaTrait;
+import net.citizensnpcs.trait.versioned.ParrotTrait;
+import net.citizensnpcs.trait.versioned.PhantomTrait;
+import net.citizensnpcs.trait.versioned.PolarBearTrait;
+import net.citizensnpcs.trait.versioned.PufferFishTrait;
+import net.citizensnpcs.trait.versioned.ShulkerTrait;
+import net.citizensnpcs.trait.versioned.SnowmanTrait;
+import net.citizensnpcs.trait.versioned.TropicalFishTrait;
+import net.citizensnpcs.trait.versioned.VillagerTrait;
+import net.citizensnpcs.util.Messages;
+import net.citizensnpcs.util.Util;
+
+public class Commands {
+ @Command(
+ aliases = { "npc" },
+ usage = "bee (-s/-n) --anger anger",
+ desc = "Sets bee modifiers",
+ modifiers = { "bee" },
+ min = 1,
+ max = 1,
+ flags = "sn",
+ permission = "citizens.npc.bee")
+ @Requirements(selected = true, ownership = true, types = EntityType.BEE)
+ public void bee(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ BeeTrait trait = npc.getOrAddTrait(BeeTrait.class);
+ String output = "";
+ if (args.hasValueFlag("anger")) {
+ int anger = args.getFlagInteger("anger");
+ if (anger < 0) {
+ throw new CommandException(Messages.INVALID_BEE_ANGER);
+ }
+ trait.setAnger(anger);
+ output += ' ' + Messaging.tr(Messages.BEE_ANGER_SET, args.getFlag("anger"));
+ }
+ if (args.hasFlag('s')) {
+ trait.setStung(!trait.hasStung());
+ output += ' ' + (trait.hasStung() ? Messaging.tr(Messages.BEE_STUNG, npc.getName())
+ : Messaging.tr(Messages.BEE_NOT_STUNG, npc.getName()));
+ }
+ if (args.hasFlag('n')) {
+ trait.setNectar(!trait.hasNectar());
+ output += ' ' + (trait.hasNectar() ? Messaging.tr(Messages.BEE_HAS_NECTAR, npc.getName())
+ : Messaging.tr(Messages.BEE_NO_NECTAR, npc.getName()));
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output.trim());
+ } else {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "bossbar --color [color] --title [title] --visible [visible] --flags [flags]",
+ desc = "Edit bossbar properties",
+ modifiers = { "bossbar" },
+ min = 1,
+ max = 1)
+ @Requirements(selected = true, ownership = true, types = { EntityType.WITHER, EntityType.ENDER_DRAGON })
+ public void bossbar(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ BossBarTrait trait = npc.getOrAddTrait(BossBarTrait.class);
+ if (args.hasValueFlag("color")) {
+ BarColor color = Util.matchEnum(BarColor.values(), args.getFlag("color"));
+ trait.setColor(color);
+ }
+
+ if (args.hasValueFlag("title")) {
+ trait.setTitle(Colorizer.parseColors(args.getFlag("title")));
+ }
+ if (args.hasValueFlag("visible")) {
+ trait.setVisible(Boolean.parseBoolean(args.getFlag("visible")));
+ }
+ if (args.hasValueFlag("flags")) {
+ List flags = Lists.newArrayList();
+ for (String s : Splitter.on(',').omitEmptyStrings().trimResults().split(args.getFlag("flags"))) {
+ BarFlag flag = Util.matchEnum(BarFlag.values(), s);
+ if (flag != null) {
+ flags.add(flag);
+ }
+ }
+ trait.setFlags(flags);
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "cat (-s/-n/-l) --type type --ccolor collar color",
+ desc = "Sets cat modifiers",
+ modifiers = { "cat" },
+ min = 1,
+ max = 1,
+ flags = "snl",
+ permission = "citizens.npc.cat")
+ @Requirements(selected = true, ownership = true, types = EntityType.CAT)
+ public void cat(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ CatTrait trait = npc.getOrAddTrait(CatTrait.class);
+ String output = "";
+ if (args.hasValueFlag("type")) {
+ Cat.Type type = Util.matchEnum(Cat.Type.values(), args.getFlag("type"));
+ if (type == null) {
+ throw new CommandUsageException(Messages.INVALID_CAT_TYPE, Util.listValuesPretty(Cat.Type.values()));
+ }
+ trait.setType(type);
+ output += ' ' + Messaging.tr(Messages.CAT_TYPE_SET, args.getFlag("type"));
+ }
+ if (args.hasValueFlag("ccolor")) {
+ DyeColor color = Util.matchEnum(DyeColor.values(), args.getFlag("ccolor"));
+ if (color == null) {
+ throw new CommandUsageException(Messages.INVALID_CAT_COLLAR_COLOR,
+ Util.listValuesPretty(DyeColor.values()));
+ }
+ trait.setCollarColor(color);
+ output += ' ' + Messaging.tr(Messages.CAT_COLLAR_COLOR_SET, args.getFlag("ccolor"));
+ }
+ if (args.hasFlag('s')) {
+ trait.setSitting(true);
+ output += ' ' + Messaging.tr(Messages.CAT_STARTED_SITTING, npc.getName());
+ } else if (args.hasFlag('n')) {
+ trait.setSitting(false);
+ output += ' ' + Messaging.tr(Messages.CAT_STOPPED_SITTING, npc.getName());
+ }
+ if (args.hasFlag('l')) {
+ trait.setLyingDown(!trait.isLyingDown());
+ output += ' ' + Messaging.tr(trait.isLyingDown() ? Messages.CAT_STARTED_LYING : Messages.CAT_STOPPED_LYING,
+ npc.getName());
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output.trim());
+ } else {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "fox --type type --sleeping [true|false] --sitting [true|false] --crouching [true|false]",
+ desc = "Sets fox modifiers",
+ modifiers = { "fox" },
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.fox")
+ @Requirements(selected = true, ownership = true, types = EntityType.FOX)
+ public void fox(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ FoxTrait trait = npc.getOrAddTrait(FoxTrait.class);
+ String output = "";
+ if (args.hasValueFlag("type")) {
+ Fox.Type type = Util.matchEnum(Fox.Type.values(), args.getFlag("type"));
+ if (type == null) {
+ throw new CommandUsageException(Messages.INVALID_FOX_TYPE, Util.listValuesPretty(Fox.Type.values()));
+ }
+ trait.setType(type);
+ output += ' ' + Messaging.tr(Messages.FOX_TYPE_SET, args.getFlag("type"));
+ }
+ if (args.hasValueFlag("sleeping")) {
+ boolean sleeping = Boolean.parseBoolean(args.getFlag("sleeping"));
+ trait.setSleeping(sleeping);
+ output += ' ' + Messaging.tr(sleeping ? Messages.FOX_SLEEPING_SET : Messages.FOX_SLEEPING_UNSET);
+ }
+ if (args.hasValueFlag("sitting")) {
+ boolean sitting = Boolean.parseBoolean(args.getFlag("sitting"));
+ trait.setSitting(sitting);
+ output += ' ' + Messaging.tr(sitting ? Messages.FOX_SITTING_SET : Messages.FOX_SITTING_UNSET);
+ }
+ if (args.hasValueFlag("crouching")) {
+ boolean crouching = Boolean.parseBoolean(args.getFlag("crouching"));
+ trait.setCrouching(crouching);
+ output += ' ' + Messaging.tr(crouching ? Messages.FOX_CROUCHING_SET : Messages.FOX_CROUCHING_UNSET);
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output.trim());
+ } else {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "llama (--color color) (--strength strength)",
+ desc = "Sets llama modifiers",
+ modifiers = { "llama" },
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.llama")
+ @Requirements(selected = true, ownership = true, types = { EntityType.LLAMA, EntityType.TRADER_LLAMA })
+ public void llama(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ LlamaTrait trait = npc.getOrAddTrait(LlamaTrait.class);
+ String output = "";
+ if (args.hasValueFlag("color") || args.hasValueFlag("colour")) {
+ String colorRaw = args.getFlag("color", args.getFlag("colour"));
+ Color color = Util.matchEnum(Color.values(), colorRaw);
+ if (color == null) {
+ String valid = Util.listValuesPretty(Color.values());
+ throw new CommandException(Messages.INVALID_LLAMA_COLOR, valid);
+ }
+ trait.setColor(color);
+ output += Messaging.tr(Messages.LLAMA_COLOR_SET, Util.prettyEnum(color));
+ }
+ if (args.hasValueFlag("strength")) {
+ trait.setStrength(Math.max(1, Math.min(5, args.getFlagInteger("strength"))));
+ output += Messaging.tr(Messages.LLAMA_STRENGTH_SET, args.getFlagInteger("strength"));
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output);
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "mcow (--variant [variant])",
+ desc = "Sets mushroom cow modifiers.",
+ modifiers = { "mcow", "mushroomcow" },
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.mushroomcow")
+ @Requirements(selected = true, ownership = true, types = { EntityType.MUSHROOM_COW })
+ public void mushroomcow(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ MushroomCowTrait trait = npc.getOrAddTrait(MushroomCowTrait.class);
+ boolean hasArg = false;
+ if (args.hasValueFlag("variant")) {
+ MushroomCow.Variant variant = Util.matchEnum(MushroomCow.Variant.values(), args.getFlag("variant"));
+ if (variant == null) {
+ Messaging.sendErrorTr(sender, Messages.INVALID_MUSHROOM_COW_VARIANT,
+ Util.listValuesPretty(MushroomCow.Variant.values()));
+ return;
+ }
+ trait.setVariant(variant);
+ Messaging.sendTr(sender, Messages.MUSHROOM_COW_VARIANT_SET, npc.getName(), variant);
+ hasArg = true;
+ }
+ if (!hasArg) {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "panda --gene (main gene) --hgene (hidden gene) -s(itting)",
+ desc = "Sets panda modifiers",
+ modifiers = { "panda" },
+ flags = "s",
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.panda")
+ @Requirements(selected = true, ownership = true, types = EntityType.PANDA)
+ public void panda(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ PandaTrait trait = npc.getOrAddTrait(PandaTrait.class);
+ String output = "";
+ if (args.hasValueFlag("gene")) {
+ Panda.Gene gene = Util.matchEnum(Panda.Gene.values(), args.getFlag("gene"));
+ if (gene == null) {
+ throw new CommandUsageException(Messages.INVALID_PANDA_GENE,
+ Util.listValuesPretty(Panda.Gene.values()));
+ }
+ trait.setMainGene(gene);
+ output += ' ' + Messaging.tr(Messages.PANDA_MAIN_GENE_SET, args.getFlag("gene"));
+ }
+ if (args.hasValueFlag("hgene")) {
+ Panda.Gene gene = Util.matchEnum(Panda.Gene.values(), args.getFlag("hgene"));
+ if (gene == null) {
+ throw new CommandUsageException(Messages.INVALID_PANDA_GENE,
+ Util.listValuesPretty(Panda.Gene.values()));
+ }
+ trait.setHiddenGene(gene);
+ output += ' ' + Messaging.tr(Messages.PANDA_HIDDEN_GENE_SET, args.getFlag("hgene"));
+ }
+ if (args.hasFlag('s')) {
+ boolean isSitting = trait.toggleSitting();
+ output += ' ' + Messaging.tr(isSitting ? Messages.PANDA_SITTING : Messages.PANDA_STOPPED_SITTING);
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output.trim());
+ } else {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "parrot (--variant variant)",
+ desc = "Sets parrot modifiers",
+ modifiers = { "parrot" },
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.parrot")
+ @Requirements(selected = true, ownership = true, types = EntityType.PARROT)
+ public void parrot(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ ParrotTrait trait = npc.getOrAddTrait(ParrotTrait.class);
+ String output = "";
+ if (args.hasValueFlag("variant")) {
+ String variantRaw = args.getFlag("variant");
+ Variant variant = Util.matchEnum(Variant.values(), variantRaw);
+ if (variant == null) {
+ String valid = Util.listValuesPretty(Variant.values());
+ throw new CommandException(Messages.INVALID_PARROT_VARIANT, valid);
+ }
+ trait.setVariant(variant);
+ output += Messaging.tr(Messages.PARROT_VARIANT_SET, Util.prettyEnum(variant));
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output);
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "phantom (--size size)",
+ desc = "Sets phantom modifiers",
+ modifiers = { "phantom" },
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.phantom")
+ @Requirements(selected = true, ownership = true, types = EntityType.PHANTOM)
+ public void phantom(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ PhantomTrait trait = npc.getOrAddTrait(PhantomTrait.class);
+ String output = "";
+ if (args.hasValueFlag("size")) {
+ if (args.getFlagInteger("size") <= 0) {
+ throw new CommandUsageException();
+ }
+ trait.setSize(args.getFlagInteger("size"));
+ output += Messaging.tr(Messages.PHANTOM_STATE_SET, args.getFlagInteger("size"));
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output);
+ } else {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "polarbear (-r)",
+ desc = "Sets polarbear modifiers.",
+ modifiers = { "polarbear" },
+ min = 1,
+ max = 1,
+ flags = "r",
+ permission = "citizens.npc.polarbear")
+ @Requirements(selected = true, ownership = true, types = { EntityType.POLAR_BEAR })
+ public void polarbear(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ PolarBearTrait trait = npc.getOrAddTrait(PolarBearTrait.class);
+ String output = "";
+ if (args.hasFlag('r')) {
+ trait.setRearing(!trait.isRearing());
+ output += Messaging.tr(
+ trait.isRearing() ? Messages.POLAR_BEAR_REARING : Messages.POLAR_BEAR_STOPPED_REARING,
+ npc.getName());
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output);
+ } else {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "pufferfish (--state state)",
+ desc = "Sets pufferfish modifiers",
+ modifiers = { "pufferfish" },
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.pufferfish")
+ @Requirements(selected = true, ownership = true, types = EntityType.PUFFERFISH)
+ public void pufferfish(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ PufferFishTrait trait = npc.getOrAddTrait(PufferFishTrait.class);
+ String output = "";
+ if (args.hasValueFlag("state")) {
+ int state = Math.min(Math.max(args.getFlagInteger("state"), 0), 3);
+ trait.setPuffState(state);
+ output += Messaging.tr(Messages.PUFFERFISH_STATE_SET, state);
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output);
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "shulker (--peek [peek] --color [color])",
+ desc = "Sets shulker modifiers.",
+ modifiers = { "shulker" },
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.shulker")
+ @Requirements(selected = true, ownership = true, types = { EntityType.SHULKER })
+ public void shulker(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ ShulkerTrait trait = npc.getOrAddTrait(ShulkerTrait.class);
+ boolean hasArg = false;
+ if (args.hasValueFlag("peek")) {
+ int peek = (byte) args.getFlagInteger("peek");
+ trait.setPeek(peek);
+ Messaging.sendTr(sender, Messages.SHULKER_PEEK_SET, npc.getName(), peek);
+ hasArg = true;
+ }
+ if (args.hasValueFlag("color")) {
+ DyeColor color = Util.matchEnum(DyeColor.values(), args.getFlag("color"));
+ if (color == null) {
+ Messaging.sendErrorTr(sender, Messages.INVALID_SHULKER_COLOR, Util.listValuesPretty(DyeColor.values()));
+ return;
+ }
+ trait.setColor(color);
+ Messaging.sendTr(sender, Messages.SHULKER_COLOR_SET, npc.getName(), Util.prettyEnum(color));
+ hasArg = true;
+ }
+ if (!hasArg) {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "snowman (-d[erp])",
+ desc = "Sets snowman modifiers.",
+ modifiers = { "snowman" },
+ min = 1,
+ max = 1,
+ flags = "d",
+ permission = "citizens.npc.snowman")
+ @Requirements(selected = true, ownership = true, types = { EntityType.SNOWMAN })
+ public void snowman(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ SnowmanTrait trait = npc.getOrAddTrait(SnowmanTrait.class);
+ boolean hasArg = false;
+ if (args.hasFlag('d')) {
+ boolean isDerp = trait.toggleDerp();
+ Messaging.sendTr(sender, isDerp ? Messages.SNOWMAN_DERP_SET : Messages.SNOWMAN_DERP_STOPPED, npc.getName());
+ hasArg = true;
+ }
+ if (!hasArg) {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "tfish (--body color) (--pattern pattern) (--patterncolor color)",
+ desc = "Sets tropical fish modifiers",
+ modifiers = { "tfish" },
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.tropicalfish")
+ @Requirements(selected = true, ownership = true, types = EntityType.TROPICAL_FISH)
+ public void tropicalfish(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ TropicalFishTrait trait = npc.getOrAddTrait(TropicalFishTrait.class);
+ String output = "";
+ if (args.hasValueFlag("body")) {
+ DyeColor color = Util.matchEnum(DyeColor.values(), args.getFlag("body"));
+ if (color == null) {
+ throw new CommandException(Messages.INVALID_TROPICALFISH_COLOR,
+ Util.listValuesPretty(DyeColor.values()));
+ }
+ trait.setBodyColor(color);
+ output += Messaging.tr(Messages.TROPICALFISH_BODY_COLOR_SET, Util.prettyEnum(color));
+ }
+ if (args.hasValueFlag("patterncolor")) {
+ DyeColor color = Util.matchEnum(DyeColor.values(), args.getFlag("patterncolor"));
+ if (color == null) {
+ throw new CommandException(Messages.INVALID_TROPICALFISH_COLOR,
+ Util.listValuesPretty(DyeColor.values()));
+ }
+ trait.setPatternColor(color);
+ output += Messaging.tr(Messages.TROPICALFISH_PATTERN_COLOR_SET, Util.prettyEnum(color));
+ }
+ if (args.hasValueFlag("pattern")) {
+ Pattern pattern = Util.matchEnum(Pattern.values(), args.getFlag("pattern"));
+ if (pattern == null) {
+ throw new CommandException(Messages.INVALID_TROPICALFISH_PATTERN,
+ Util.listValuesPretty(Pattern.values()));
+ }
+ trait.setPattern(pattern);
+ output += Messaging.tr(Messages.TROPICALFISH_PATTERN_SET, Util.prettyEnum(pattern));
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output);
+ } else {
+ throw new CommandUsageException();
+ }
+ }
+
+ @Command(
+ aliases = { "npc" },
+ usage = "villager (--level level) (--type type) (--profession profession)",
+ desc = "Sets villager modifiers",
+ modifiers = { "villager" },
+ min = 1,
+ max = 1,
+ permission = "citizens.npc.villager")
+ @Requirements(selected = true, ownership = true, types = EntityType.VILLAGER)
+ public void villager(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
+ VillagerTrait trait = npc.getOrAddTrait(VillagerTrait.class);
+ String output = "";
+ if (args.hasValueFlag("level")) {
+ if (args.getFlagInteger("level") < 0) {
+ throw new CommandUsageException();
+ }
+ trait.setLevel(args.getFlagInteger("level"));
+ output += " " + Messaging.tr(Messages.VILLAGER_LEVEL_SET, args.getFlagInteger("level"));
+ }
+ if (args.hasValueFlag("type")) {
+ Villager.Type type = Util.matchEnum(Villager.Type.values(), args.getFlag("type"));
+ if (type == null) {
+ throw new CommandException(Messages.INVALID_VILLAGER_TYPE,
+ Util.listValuesPretty(Villager.Type.values()));
+ }
+ trait.setType(type);
+ output += " " + Messaging.tr(Messages.VILLAGER_TYPE_SET, args.getFlag("type"));
+ }
+ if (args.hasValueFlag("profession")) {
+ Profession parsed = Util.matchEnum(Profession.values(), args.getFlag("profession"));
+ if (parsed == null) {
+ throw new CommandException(Messages.INVALID_PROFESSION, args.getString(1),
+ Joiner.on(',').join(Profession.values()));
+ }
+ npc.getOrAddTrait(VillagerProfession.class).setProfession(parsed);
+ output += " " + Messaging.tr(Messages.PROFESSION_SET, npc.getName(), args.getFlag("profession"));
+ }
+ if (!output.isEmpty()) {
+ Messaging.send(sender, output.trim());
+ } else {
+ throw new CommandUsageException();
+ }
+ }
+}
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/util/CitizensBlockBreaker.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/util/CitizensBlockBreaker.java
new file mode 100644
index 000000000..ffffe4654
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/util/CitizensBlockBreaker.java
@@ -0,0 +1,188 @@
+package net.citizensnpcs.nms.v1_17_R1.util;
+
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+
+import net.citizensnpcs.api.ai.tree.BehaviorStatus;
+import net.citizensnpcs.api.npc.BlockBreaker;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.PlayerAnimation;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.tags.FluidTags;
+import net.minecraft.world.effect.MobEffectUtil;
+import net.minecraft.world.effect.MobEffects;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.enchantment.EnchantmentHelper;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+
+public class CitizensBlockBreaker extends BlockBreaker {
+ private final BlockBreakerConfiguration configuration;
+ private int currentDamage;
+ private int currentTick;
+ private final Entity entity;
+ private boolean isDigging = true;
+ private final Location location;
+ private boolean setTarget;
+ private int startDigTick;
+ private final int x, y, z;
+
+ public CitizensBlockBreaker(org.bukkit.entity.Entity entity, org.bukkit.block.Block target,
+ BlockBreakerConfiguration config) {
+ this.entity = ((CraftEntity) entity).getHandle();
+ this.x = target.getX();
+ this.y = target.getY();
+ this.z = target.getZ();
+ this.location = target.getLocation();
+ this.startDigTick = (int) (System.currentTimeMillis() / 50);
+ this.configuration = config;
+ }
+
+ private double distanceSquared() {
+ return Math.pow(entity.getX() - x, 2) + Math.pow(NMS.getHeight(entity.getBukkitEntity()) + entity.getY() - y, 2)
+ + Math.pow(entity.getZ() - z, 2);
+ }
+
+ private ItemStack getCurrentItem() {
+ return configuration.item() != null ? CraftItemStack.asNMSCopy(configuration.item())
+ : entity instanceof LivingEntity ? ((LivingEntity) entity).getMainHandItem() : null;
+ }
+
+ private float getStrength(BlockState block) {
+ float base = block.getDestroySpeed(null, BlockPos.ZERO);
+ return base < 0.0F ? 0.0F : (!isDestroyable(block) ? 1.0F / base / 100.0F : strengthMod(block) / base / 30.0F);
+ }
+
+ private boolean isDestroyable(BlockState block) {
+ if (block.requiresCorrectToolForDrops()) {
+ return true;
+ } else {
+ ItemStack current = getCurrentItem();
+ return current != null ? current.isCorrectToolForDrops(block) : false;
+ }
+ }
+
+ @Override
+ public void reset() {
+ if (setTarget && entity instanceof NPCHolder) {
+ NPC npc = ((NPCHolder) entity).getNPC();
+ if (npc != null && npc.getNavigator().isNavigating()) {
+ npc.getNavigator().cancelNavigation();
+ }
+ }
+ setTarget = false;
+ if (configuration.callback() != null) {
+ configuration.callback().run();
+ }
+ isDigging = false;
+ setBlockDamage(currentDamage = -1);
+ }
+
+ @Override
+ public BehaviorStatus run() {
+ if (entity.isRemoved()) {
+ return BehaviorStatus.FAILURE;
+ }
+ if (!isDigging) {
+ return BehaviorStatus.SUCCESS;
+ }
+ currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit
+ if (configuration.radiusSquared() > 0 && distanceSquared() >= configuration.radiusSquared()) {
+ startDigTick = currentTick;
+ if (entity instanceof NPCHolder) {
+ NPC npc = ((NPCHolder) entity).getNPC();
+ if (npc != null && !npc.getNavigator().isNavigating()) {
+ npc.getNavigator()
+ .setTarget(entity.level.getWorld().getBlockAt(x, y, z).getLocation().add(0, 1, 0));
+ setTarget = true;
+ }
+ }
+ return BehaviorStatus.RUNNING;
+ }
+ Util.faceLocation(entity.getBukkitEntity(), location);
+ if (entity instanceof ServerPlayer) {
+ PlayerAnimation.ARM_SWING.play((Player) entity.getBukkitEntity());
+ }
+ BlockState block = entity.level.getBlockState(new BlockPos(x, y, z));
+ if (block == null || block.getBlock() == Blocks.AIR) {
+ return BehaviorStatus.SUCCESS;
+ } else {
+ int tickDifference = currentTick - startDigTick;
+ float damage = getStrength(block) * (tickDifference + 1) * configuration.blockStrengthModifier();
+ if (damage >= 1F) {
+ entity.level.getWorld().getBlockAt(x, y, z)
+ .breakNaturally(CraftItemStack.asCraftMirror(getCurrentItem()));
+ return BehaviorStatus.SUCCESS;
+ }
+ int modifiedDamage = (int) (damage * 10.0F);
+ if (modifiedDamage != currentDamage) {
+ setBlockDamage(modifiedDamage);
+ currentDamage = modifiedDamage;
+ }
+ }
+ return BehaviorStatus.RUNNING;
+ }
+
+ private void setBlockDamage(int modifiedDamage) {
+ ((ServerLevel) entity.level).destroyBlockProgress(entity.getId(), new BlockPos(x, y, z), modifiedDamage);
+ // TODO: check this works
+ }
+
+ @Override
+ public boolean shouldExecute() {
+ return entity.level.getBlockState(new BlockPos(x, y, z)).getBlock() != Blocks.AIR;
+ }
+
+ private float strengthMod(BlockState block) {
+ ItemStack itemstack = getCurrentItem();
+ float f = itemstack.getDestroySpeed(block);
+ if (entity instanceof LivingEntity) {
+ LivingEntity handle = (LivingEntity) entity;
+ if (f > 1.0F) {
+ int i = EnchantmentHelper.getBlockEfficiency(handle);
+ if (i > 0) {
+ f += i * i + 1;
+ }
+ }
+ if (MobEffectUtil.hasDigSpeed(handle)) {
+ f *= 1.0F + (MobEffectUtil.getDigSpeedAmplification(handle) + 1) * 0.2F;
+ }
+ if (handle.hasEffect(MobEffects.DIG_SLOWDOWN)) {
+ float f1 = 1.0F;
+ switch (handle.getEffect(MobEffects.DIG_SLOWDOWN).getAmplifier()) {
+ case 0:
+ f1 = 0.3F;
+ break;
+ case 1:
+ f1 = 0.09F;
+ break;
+ case 2:
+ f1 = 0.0027F;
+ break;
+ case 3:
+ default:
+ f1 = 8.1E-4F;
+ }
+ f *= f1;
+ }
+
+ if (handle.isEyeInFluid(FluidTags.WATER) && !EnchantmentHelper.hasAquaAffinity(handle)) {
+ f /= 5.0F;
+ }
+
+ }
+ if (!entity.isOnGround()) {
+ f /= 5.0F;
+ }
+ return f;
+ }
+}
\ No newline at end of file
diff --git a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/util/CustomEntityRegistry.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/util/CustomEntityRegistry.java
new file mode 100644
index 000000000..1ae95eeb0
--- /dev/null
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/util/CustomEntityRegistry.java
@@ -0,0 +1,348 @@
+package net.citizensnpcs.nms.v1_17_R1.util;
+
+import java.lang.invoke.MethodHandle;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Set;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Maps;
+import com.mojang.serialization.Lifecycle;
+
+import net.citizensnpcs.util.NMS;
+import net.minecraft.core.DefaultedRegistry;
+import net.minecraft.core.MappedRegistry;
+import net.minecraft.core.Registry;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.entity.AreaEffectCloud;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ExperienceOrb;
+import net.minecraft.world.entity.GlowSquid;
+import net.minecraft.world.entity.LightningBolt;
+import net.minecraft.world.entity.Marker;
+import net.minecraft.world.entity.ambient.Bat;
+import net.minecraft.world.entity.animal.Bee;
+import net.minecraft.world.entity.animal.Cat;
+import net.minecraft.world.entity.animal.Chicken;
+import net.minecraft.world.entity.animal.Cod;
+import net.minecraft.world.entity.animal.Cow;
+import net.minecraft.world.entity.animal.Dolphin;
+import net.minecraft.world.entity.animal.Fox;
+import net.minecraft.world.entity.animal.IronGolem;
+import net.minecraft.world.entity.animal.MushroomCow;
+import net.minecraft.world.entity.animal.Ocelot;
+import net.minecraft.world.entity.animal.Panda;
+import net.minecraft.world.entity.animal.Parrot;
+import net.minecraft.world.entity.animal.Pig;
+import net.minecraft.world.entity.animal.PolarBear;
+import net.minecraft.world.entity.animal.Pufferfish;
+import net.minecraft.world.entity.animal.Rabbit;
+import net.minecraft.world.entity.animal.Salmon;
+import net.minecraft.world.entity.animal.Sheep;
+import net.minecraft.world.entity.animal.SnowGolem;
+import net.minecraft.world.entity.animal.Squid;
+import net.minecraft.world.entity.animal.TropicalFish;
+import net.minecraft.world.entity.animal.Turtle;
+import net.minecraft.world.entity.animal.Wolf;
+import net.minecraft.world.entity.animal.axolotl.Axolotl;
+import net.minecraft.world.entity.animal.goat.Goat;
+import net.minecraft.world.entity.animal.horse.Donkey;
+import net.minecraft.world.entity.animal.horse.Horse;
+import net.minecraft.world.entity.animal.horse.Llama;
+import net.minecraft.world.entity.animal.horse.Mule;
+import net.minecraft.world.entity.animal.horse.SkeletonHorse;
+import net.minecraft.world.entity.animal.horse.TraderLlama;
+import net.minecraft.world.entity.animal.horse.ZombieHorse;
+import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+import net.minecraft.world.entity.boss.wither.WitherBoss;
+import net.minecraft.world.entity.decoration.ArmorStand;
+import net.minecraft.world.entity.decoration.GlowItemFrame;
+import net.minecraft.world.entity.decoration.ItemFrame;
+import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
+import net.minecraft.world.entity.decoration.Painting;
+import net.minecraft.world.entity.item.FallingBlockEntity;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.entity.item.PrimedTnt;
+import net.minecraft.world.entity.monster.Blaze;
+import net.minecraft.world.entity.monster.CaveSpider;
+import net.minecraft.world.entity.monster.Creeper;
+import net.minecraft.world.entity.monster.Drowned;
+import net.minecraft.world.entity.monster.ElderGuardian;
+import net.minecraft.world.entity.monster.EnderMan;
+import net.minecraft.world.entity.monster.Endermite;
+import net.minecraft.world.entity.monster.Evoker;
+import net.minecraft.world.entity.monster.Ghast;
+import net.minecraft.world.entity.monster.Giant;
+import net.minecraft.world.entity.monster.Guardian;
+import net.minecraft.world.entity.monster.Husk;
+import net.minecraft.world.entity.monster.Illusioner;
+import net.minecraft.world.entity.monster.MagmaCube;
+import net.minecraft.world.entity.monster.Phantom;
+import net.minecraft.world.entity.monster.Pillager;
+import net.minecraft.world.entity.monster.Ravager;
+import net.minecraft.world.entity.monster.Shulker;
+import net.minecraft.world.entity.monster.Silverfish;
+import net.minecraft.world.entity.monster.Skeleton;
+import net.minecraft.world.entity.monster.Slime;
+import net.minecraft.world.entity.monster.Spider;
+import net.minecraft.world.entity.monster.Stray;
+import net.minecraft.world.entity.monster.Strider;
+import net.minecraft.world.entity.monster.Vex;
+import net.minecraft.world.entity.monster.Vindicator;
+import net.minecraft.world.entity.monster.Witch;
+import net.minecraft.world.entity.monster.WitherSkeleton;
+import net.minecraft.world.entity.monster.Zoglin;
+import net.minecraft.world.entity.monster.Zombie;
+import net.minecraft.world.entity.monster.ZombieVillager;
+import net.minecraft.world.entity.monster.ZombifiedPiglin;
+import net.minecraft.world.entity.monster.hoglin.Hoglin;
+import net.minecraft.world.entity.monster.piglin.Piglin;
+import net.minecraft.world.entity.monster.piglin.PiglinBrute;
+import net.minecraft.world.entity.npc.Villager;
+import net.minecraft.world.entity.npc.WanderingTrader;
+import net.minecraft.world.entity.projectile.Arrow;
+import net.minecraft.world.entity.projectile.DragonFireball;
+import net.minecraft.world.entity.projectile.EvokerFangs;
+import net.minecraft.world.entity.projectile.EyeOfEnder;
+import net.minecraft.world.entity.projectile.FireworkRocketEntity;
+import net.minecraft.world.entity.projectile.FishingHook;
+import net.minecraft.world.entity.projectile.LargeFireball;
+import net.minecraft.world.entity.projectile.LlamaSpit;
+import net.minecraft.world.entity.projectile.ShulkerBullet;
+import net.minecraft.world.entity.projectile.SmallFireball;
+import net.minecraft.world.entity.projectile.Snowball;
+import net.minecraft.world.entity.projectile.SpectralArrow;
+import net.minecraft.world.entity.projectile.ThrownEgg;
+import net.minecraft.world.entity.projectile.ThrownEnderpearl;
+import net.minecraft.world.entity.projectile.ThrownExperienceBottle;
+import net.minecraft.world.entity.projectile.ThrownPotion;
+import net.minecraft.world.entity.projectile.ThrownTrident;
+import net.minecraft.world.entity.projectile.WitherSkull;
+import net.minecraft.world.entity.vehicle.Boat;
+import net.minecraft.world.entity.vehicle.Minecart;
+import net.minecraft.world.entity.vehicle.MinecartChest;
+import net.minecraft.world.entity.vehicle.MinecartCommandBlock;
+import net.minecraft.world.entity.vehicle.MinecartFurnace;
+import net.minecraft.world.entity.vehicle.MinecartHopper;
+import net.minecraft.world.entity.vehicle.MinecartSpawner;
+import net.minecraft.world.entity.vehicle.MinecartTNT;
+
+@SuppressWarnings("rawtypes")
+public class CustomEntityRegistry extends DefaultedRegistry {
+ private final BiMap entities = HashBiMap.create();
+ private final BiMap entityClasses = this.entities.inverse();
+ private final Map entityIds = Maps.newHashMap();
+ private final MappedRegistry> wrapped;
+
+ @SuppressWarnings("unchecked")
+ public CustomEntityRegistry(DefaultedRegistry> original) throws Throwable {
+ super(original.getDefaultKey().getNamespace(),
+ (ResourceKey>>) IREGISTRY_RESOURCE_KEY.invoke(original),
+ (Lifecycle) IREGISTRY_LIFECYCLE.invoke(original));
+ this.wrapped = original;
+ }
+
+ @Override
+ public Object byId(int var0) {
+ return this.wrapped.byId(var0);
+ }
+
+ public EntityType findType(Class> search) {
+ return minecraftClassMap.inverse().get(search);
+ /*
+ for (Object type : wrapped) {
+ if (minecraftClassMap.get(type) == search) {
+ return (EntityTypes) type;
+ }
+ }
+ return null;
+ */
+ }
+
+ @Override
+ public EntityType get(ResourceLocation key) {
+ if (entities.containsKey(key)) {
+ return entities.get(key);
+ }
+
+ return wrapped.get(key);
+ }
+
+ @Override
+ public int getId(Object key) {
+ if (entityIds.containsKey(key)) {
+ return entityIds.get(key);
+ }
+
+ return wrapped.getId((EntityType) key);
+ }
+
+ @Override
+ public ResourceLocation getKey(Object value) {
+ if (entityClasses.containsKey(value)) {
+ return entityClasses.get(value);
+ }
+
+ return wrapped.getKey((EntityType) value);
+ }
+
+ @Override
+ public Optional getOptional(ResourceLocation var0) {
+ if (entities.containsKey(var0)) {
+ return Optional.of(entities.get(var0));
+ }
+
+ return this.wrapped.getOptional(var0);
+ }
+
+ @Override
+ public Object getRandom(Random paramRandom) {
+ return wrapped.getRandom(paramRandom);
+ }
+
+ public MappedRegistry> getWrapped() {
+ return wrapped;
+ }
+
+ @Override
+ public Iterator