diff --git a/dist/pom.xml b/dist/pom.xml
index 7e3e0d3f1..11ecd4fd2 100644
--- a/dist/pom.xml
+++ b/dist/pom.xml
@@ -5,7 +5,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens
pom
@@ -92,5 +92,12 @@
jar
compile
+
+ ${project.groupId}
+ citizens-v1_20_R1
+ ${project.version}
+ jar
+ compile
+
\ No newline at end of file
diff --git a/main/pom.xml b/main/pom.xml
index d03c0f563..44d2dfc82 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -4,7 +4,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-main
@@ -131,13 +131,13 @@
net.kyori
adventure-text-minimessage
- 4.11.0
+ 4.14.0
provided
net.kyori
adventure-platform-bukkit
- 4.1.2
+ 4.3.0
provided
diff --git a/main/src/main/java/net/citizensnpcs/Citizens.java b/main/src/main/java/net/citizensnpcs/Citizens.java
index 7170e9653..ae787b6b6 100644
--- a/main/src/main/java/net/citizensnpcs/Citizens.java
+++ b/main/src/main/java/net/citizensnpcs/Citizens.java
@@ -305,28 +305,28 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
lib.loadLibrary(Library.builder().groupId("net{}sf{}trove4j").artifactId("trove4j").version("3.0.3")
.relocate("gnu{}trove", "clib{}trove").build());
lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-text-minimessage")
- .version("4.12.0").relocate("net{}kyori", "clib{}net{}kyori").build());
- lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-platform-bukkit").version("4.2.0")
+ .version("4.14.0").relocate("net{}kyori", "clib{}net{}kyori").build());
+ lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-platform-bukkit").version("4.3.0")
.relocate("net{}kyori", "clib{}net{}kyori").build());
- lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-platform-api").version("4.2.0")
+ lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-platform-api").version("4.3.0")
.relocate("net{}kyori", "clib{}net{}kyori").build());
- lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-platform-facet").version("4.2.0")
+ lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-platform-facet").version("4.3.0")
.relocate("net{}kyori", "clib{}net{}kyori").build());
lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-platform-viaversion")
- .version("4.2.0").relocate("net{}kyori", "clib{}net{}kyori").build());
- lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-api").version("4.12.0")
+ .version("4.3.0").relocate("net{}kyori", "clib{}net{}kyori").build());
+ lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-api").version("4.14.0")
.relocate("net{}kyori", "clib{}net{}kyori").build());
lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-text-serializer-bungeecord")
- .version("4.2.0").relocate("net{}kyori", "clib{}net{}kyori").build());
+ .version("4.3.0").relocate("net{}kyori", "clib{}net{}kyori").build());
lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-text-serializer-legacy")
- .version("4.12.0").relocate("net{}kyori", "clib{}net{}kyori").build());
+ .version("4.13.0").relocate("net{}kyori", "clib{}net{}kyori").build());
lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-text-serializer-gson")
- .version("4.12.0").relocate("net{}kyori", "clib{}net{}kyori").build());
+ .version("4.13.0").relocate("net{}kyori", "clib{}net{}kyori").build());
lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-text-serializer-gson-legacy-impl")
- .version("4.12.0").relocate("net{}kyori", "clib{}net{}kyori").build());
- lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-nbt").version("4.12.0")
+ .version("4.13.0").relocate("net{}kyori", "clib{}net{}kyori").build());
+ lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-nbt").version("4.13.0")
.relocate("net{}kyori", "clib{}net{}kyori").build());
- lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-key").version("4.12.0")
+ lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-key").version("4.14.0")
.relocate("net{}kyori", "clib{}net{}kyori").build());
lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("examination-api").version("1.3.0")
.relocate("net{}kyori", "clib{}net{}kyori").build());
diff --git a/main/src/main/java/net/citizensnpcs/Settings.java b/main/src/main/java/net/citizensnpcs/Settings.java
index e5e7a3b01..c2266acd0 100644
--- a/main/src/main/java/net/citizensnpcs/Settings.java
+++ b/main/src/main/java/net/citizensnpcs/Settings.java
@@ -141,7 +141,7 @@ public class Settings {
"npc.pathfinding.straight-line-targeting-distance", 5),
DEFAULT_STUCK_ACTION(
"The default action to perform when NPCs are unable to find a path or are stuck in the same block for too long. Supported options are: 'teleport to destination' or 'none'",
- "npc.pathfinding.default-stuck-action", "teleport to destination"),
+ "npc.pathfinding.default-stuck-action", "none"),
DEFAULT_TALK_CLOSE("npc.default.talk-close.enabled", false),
DEFAULT_TALK_CLOSE_RANGE("Default talk close range in blocks", "npc.default.talk-close.range", 5),
DEFAULT_TEXT("npc.default.talk-close.text.0", "Hi, I'm !") {
diff --git a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java
index 01861832f..4a1683f94 100644
--- a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java
+++ b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java
@@ -133,6 +133,7 @@ import net.citizensnpcs.trait.ScriptTrait;
import net.citizensnpcs.trait.SheepTrait;
import net.citizensnpcs.trait.ShopTrait;
import net.citizensnpcs.trait.ShopTrait.NPCShop;
+import net.citizensnpcs.trait.SitTrait;
import net.citizensnpcs.trait.SkinLayers;
import net.citizensnpcs.trait.SkinLayers.Layer;
import net.citizensnpcs.trait.SkinTrait;
@@ -2647,6 +2648,31 @@ public class NPCCommands {
}
}
+ @Command(
+ aliases = { "npc" },
+ usage = "sitting (--explicit [true|false]) (--at [at])",
+ desc = "Sets the NPC sitting",
+ modifiers = { "sitting" },
+ min = 1,
+ max = 2,
+ permission = "citizens.npc.sitting")
+ @Requirements(selected = true, ownership = true)
+ public void sitting(CommandContext args, CommandSender sender, NPC npc, @Flag("explicit") Boolean explicit,
+ @Flag("at") Location at) {
+ SitTrait trait = npc.getOrAddTrait(SitTrait.class);
+ boolean toSit = explicit != null ? explicit : !trait.isSitting();
+ if (!toSit) {
+ trait.setSitting(null);
+ Messaging.sendTr(sender, Messages.SITTING_UNSET, npc.getName());
+ return;
+ }
+ if (at == null) {
+ at = npc.getStoredLocation();
+ }
+ trait.setSitting(at);
+ Messaging.sendTr(sender, Messages.SITTING_SET, npc.getName(), at);
+ }
+
@Command(
aliases = { "npc" },
usage = "skin (-c(lear) -l(atest)) [name] (or --url [url] --file [file] (-s(lim)) or -t [uuid/name] [data] [signature])",
diff --git a/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java b/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java
index 7afce364d..99b798651 100644
--- a/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java
+++ b/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java
@@ -301,6 +301,7 @@ public class CitizensNPC extends AbstractNPC {
getOrAddTrait(CurrentLocation.class).setLocation(at);
entityController.create(at.clone(), this);
getEntity().setMetadata("NPC", new FixedMetadataValue(CitizensAPI.getPlugin(), true));
+ getEntity().setMetadata("NPC-ID", new FixedMetadataValue(CitizensAPI.getPlugin(), getId()));
if (getEntity() instanceof SkinnableEntity && !hasTrait(SkinLayers.class)) {
((SkinnableEntity) getEntity()).setSkinFlags(EnumSet.allOf(SkinLayers.Layer.class));
diff --git a/main/src/main/java/net/citizensnpcs/trait/FollowTrait.java b/main/src/main/java/net/citizensnpcs/trait/FollowTrait.java
index 987188912..07275636b 100644
--- a/main/src/main/java/net/citizensnpcs/trait/FollowTrait.java
+++ b/main/src/main/java/net/citizensnpcs/trait/FollowTrait.java
@@ -97,7 +97,6 @@ public class FollowTrait extends Trait {
if (entity == null)
return;
}
-
if (!isActive())
return;
@@ -107,6 +106,7 @@ public class FollowTrait extends Trait {
}
return;
}
+
if (!npc.getNavigator().isNavigating()) {
npc.getNavigator().setTarget(entity, false);
} else {
diff --git a/main/src/main/java/net/citizensnpcs/trait/HologramTrait.java b/main/src/main/java/net/citizensnpcs/trait/HologramTrait.java
index 6d9201197..eea97c430 100644
--- a/main/src/main/java/net/citizensnpcs/trait/HologramTrait.java
+++ b/main/src/main/java/net/citizensnpcs/trait/HologramTrait.java
@@ -44,6 +44,8 @@ public class HologramTrait extends Trait {
private BiFunction customHologramSupplier;
@Persist
private HologramDirection direction = HologramDirection.BOTTOM_UP;
+ @Persist(reify = true)
+ private HologramFilter filter;
private double lastEntityHeight = 0;
private boolean lastNameplateVisible;
@Persist
@@ -417,6 +419,17 @@ public class HologramTrait extends Trait {
TOP_DOWN;
}
+ public static class HologramFilter {
+ private HologramFilter() {
+ }
+
+ public static class Builder {
+ public HologramFilter build() {
+ return new HologramFilter();
+ }
+ }
+ }
+
private class HologramLine implements Function {
NPC hologram;
double mb, mt;
diff --git a/main/src/main/java/net/citizensnpcs/util/Messages.java b/main/src/main/java/net/citizensnpcs/util/Messages.java
index f8b49bd90..a4caced69 100644
--- a/main/src/main/java/net/citizensnpcs/util/Messages.java
+++ b/main/src/main/java/net/citizensnpcs/util/Messages.java
@@ -361,6 +361,8 @@ public class Messages {
public static final String SHEEP_COLOR_SET = "citizens.commands.npc.sheep.color-set";
public static final String SHULKER_COLOR_SET = "citizens.commands.npc.shulker.color-set";
public static final String SHULKER_PEEK_SET = "citizens.commands.npc.shulker.peek-set";
+ public static final String SITTING_SET = "citizens.commands.npc.sitting.set";
+ public static final String SITTING_UNSET = "citizens.commands.npc.sitting.unset";
public static final String SIZE_DESCRIPTION = "citizens.commands.npc.size.description";
public static final String SIZE_SET = "citizens.commands.npc.size.set";
public static final String SKELETON_TYPE_SET = "citizens.commands.npc.skeletontype.set";
diff --git a/main/src/main/java/net/citizensnpcs/util/Util.java b/main/src/main/java/net/citizensnpcs/util/Util.java
index 9eb0a4c7a..ddd14f47a 100644
--- a/main/src/main/java/net/citizensnpcs/util/Util.java
+++ b/main/src/main/java/net/citizensnpcs/util/Util.java
@@ -9,7 +9,7 @@ import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@@ -60,20 +60,26 @@ public class Util {
}
}
- public static void callPossiblyAsyncEvent(Event event, boolean sync) {
+ public static boolean callEventPossiblySync(Event event, boolean sync) {
+ if (!sync) {
+ try {
+ Bukkit.getPluginManager().callEvent(event);
+ return false;
+ } catch (IllegalStateException ex) {
+ // sync method called
+ }
+ }
try {
- Callable callable = () -> {
+ Bukkit.getScheduler().callSyncMethod(CitizensAPI.getPlugin(), () -> {
Bukkit.getPluginManager().callEvent(event);
return null;
- };
- if (sync) {
- Bukkit.getScheduler().callSyncMethod(CitizensAPI.getPlugin(), callable).get();
- } else {
- callable.call();
- }
- } catch (Exception e) {
+ }).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
e.printStackTrace();
}
+ return true;
}
public static Vector callPushEvent(NPC npc, double x, double y, double z) {
diff --git a/main/src/main/resources/messages_en.properties b/main/src/main/resources/messages_en.properties
index 926bec78f..c1a3844b8 100644
--- a/main/src/main/resources/messages_en.properties
+++ b/main/src/main/resources/messages_en.properties
@@ -278,12 +278,14 @@ citizens.commands.npc.script.current-scripts=[[{0}]]''s current scripts are [[{1
citizens.commands.npc.shulker.peek-set=[[{0}]]''s peek amount set to [[{1}]].
citizens.commands.npc.shulker.color-set=[[{0}]]''s color set to [[{1}]].
citizens.commands.npc.shulker.invalid-color=Invalid shulker color given. Valid colors are: [[{0}]].
+citizens.commands.npc.sitting.set=[[{0}]] is now sitting at [[{1}]].
+citizens.commands.npc.sitting.unset=[[{0}]] is no longer sitting.
citizens.commands.npc.skin.error-setting-url=Error downloading skin texture from [[{0}]]. Are you sure the URL is valid?
citizens.commands.npc.skin.skin-url-set=Downloaded [[{0}]]''s skin from [[{1}]].
citizens.commands.npc.skin.set=[[{0}]]''s skin name set to [[{1}]].
citizens.commands.npc.skin.missing-skin=A skin name is required.
citizens.commands.npc.skin.fetching=Attempting to generate skin using https://mineskin.org
-citizens.commands.npc.skin.invalid-file=Skin file [[{0}]] not found. Must be a file under plugins/Citizens2/skins/
+citizens.commands.npc.skin.invalid-file=Skin file [[{0}]] not found. Must be a file under plugins/Citizens/skins/
citizens.commands.npc.skin.cleared=[[{0}]]''s skin name was cleared.
citizens.commands.npc.skin.layers-set=[[{0}]]''s skin layers: cape - [[{1}]], hat - [[{2}]], jacket - [[{3}]], sleeves - [[{4}]], pants - [[{5}]].
citizens.commands.npc.size.description=[[{0}]]''s size is [[{1}]].
diff --git a/pom.xml b/pom.xml
index 3cef1f8a5..833f27202 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,10 +4,10 @@
pom
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
Unknown
- 2.0.31
+ 2.0.32
3.5.0
3.5.0
3.1.1
@@ -37,6 +37,7 @@
v1_16_R3
v1_18_R2
v1_19_R3
+ v1_20_R1
dist
\ No newline at end of file
diff --git a/v1_10_R1/pom.xml b/v1_10_R1/pom.xml
index e59367f66..d6ec0e482 100644
--- a/v1_10_R1/pom.xml
+++ b/v1_10_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_10_R1
diff --git a/v1_11_R1/pom.xml b/v1_11_R1/pom.xml
index 0a01a3e52..d6827c7bf 100644
--- a/v1_11_R1/pom.xml
+++ b/v1_11_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_11_R1
diff --git a/v1_12_R1/pom.xml b/v1_12_R1/pom.xml
index 94fcb9db3..4b45b6c3c 100644
--- a/v1_12_R1/pom.xml
+++ b/v1_12_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_12_R1
diff --git a/v1_13_R2/pom.xml b/v1_13_R2/pom.xml
index 0c87e946e..a2bac48d0 100644
--- a/v1_13_R2/pom.xml
+++ b/v1_13_R2/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_13_R2
diff --git a/v1_14_R1/pom.xml b/v1_14_R1/pom.xml
index 480ecd6f7..b60821802 100644
--- a/v1_14_R1/pom.xml
+++ b/v1_14_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_14_R1
diff --git a/v1_15_R1/pom.xml b/v1_15_R1/pom.xml
index 158a96ebc..3136f8f6f 100644
--- a/v1_15_R1/pom.xml
+++ b/v1_15_R1/pom.xml
@@ -4,7 +4,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_15_R1
diff --git a/v1_16_R3/pom.xml b/v1_16_R3/pom.xml
index efc50a1a5..b37bc939e 100644
--- a/v1_16_R3/pom.xml
+++ b/v1_16_R3/pom.xml
@@ -4,7 +4,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_16_R3
diff --git a/v1_17_R1/pom.xml b/v1_17_R1/pom.xml
index b983368a5..c226712a3 100644
--- a/v1_17_R1/pom.xml
+++ b/v1_17_R1/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_17_R1
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
index d98079430..e6cb4796d 100644
--- 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
@@ -480,6 +480,5 @@ public class EntityHumanNPC extends ServerPlayer implements NPCHolder, Skinnable
private static final float EPSILON = 0.003F;
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/util/PlayerlistTracker.java b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/util/PlayerlistTracker.java
index 3ff33e155..a2e6cd951 100644
--- a/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/util/PlayerlistTracker.java
+++ b/v1_17_R1/src/main/java/net/citizensnpcs/nms/v1_17_R1/util/PlayerlistTracker.java
@@ -54,11 +54,8 @@ public class PlayerlistTracker extends ChunkMap.TrackedEntity {
public void updatePlayer(final ServerPlayer entityplayer) {
if (tracker instanceof NPCHolder) {
NPC npc = ((NPCHolder) tracker).getNPC();
- if (REQUIRES_SYNC == null) {
- REQUIRES_SYNC = !Bukkit.isPrimaryThread();
- }
NPCSeenByPlayerEvent event = new NPCSeenByPlayerEvent(npc, entityplayer.getBukkitEntity());
- Util.callPossiblyAsyncEvent(event, REQUIRES_SYNC);
+ REQUIRES_SYNC = Util.callEventPossiblySync(event, REQUIRES_SYNC);
if (event.isCancelled())
return;
Integer trackingRange = npc.data(). get(NPC.Metadata.TRACKING_RANGE);
@@ -117,9 +114,8 @@ public class PlayerlistTracker extends ChunkMap.TrackedEntity {
}
private static final MethodHandle E = NMS.getGetter(ServerEntity.class, "e");
-
private static final MethodHandle F = NMS.getGetter(ServerEntity.class, "f");
- private static Boolean REQUIRES_SYNC;
+ private static boolean REQUIRES_SYNC;
private static final MethodHandle TRACKER = NMS.getFirstGetter(TrackedEntity.class, Entity.class);
private static final MethodHandle TRACKER_ENTRY = NMS.getFirstGetter(TrackedEntity.class, ServerEntity.class);
private static final MethodHandle TRACKING_RANGE = NMS.getFirstGetter(TrackedEntity.class, int.class);
diff --git a/v1_18_R2/pom.xml b/v1_18_R2/pom.xml
index 57c24e737..532de4afe 100644
--- a/v1_18_R2/pom.xml
+++ b/v1_18_R2/pom.xml
@@ -6,7 +6,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_18_R2
diff --git a/v1_18_R2/src/main/java/net/citizensnpcs/nms/v1_18_R2/util/PlayerlistTracker.java b/v1_18_R2/src/main/java/net/citizensnpcs/nms/v1_18_R2/util/PlayerlistTracker.java
index ebb4ac902..04c52f381 100644
--- a/v1_18_R2/src/main/java/net/citizensnpcs/nms/v1_18_R2/util/PlayerlistTracker.java
+++ b/v1_18_R2/src/main/java/net/citizensnpcs/nms/v1_18_R2/util/PlayerlistTracker.java
@@ -54,11 +54,8 @@ public class PlayerlistTracker extends ChunkMap.TrackedEntity {
public void updatePlayer(final ServerPlayer entityplayer) {
if (tracker instanceof NPCHolder) {
NPC npc = ((NPCHolder) tracker).getNPC();
- if (REQUIRES_SYNC == null) {
- REQUIRES_SYNC = !Bukkit.isPrimaryThread();
- }
NPCSeenByPlayerEvent event = new NPCSeenByPlayerEvent(npc, entityplayer.getBukkitEntity());
- Util.callPossiblyAsyncEvent(event, REQUIRES_SYNC);
+ REQUIRES_SYNC = Util.callEventPossiblySync(event, REQUIRES_SYNC);
if (event.isCancelled())
return;
Integer trackingRange = npc.data(). get(NPC.Metadata.TRACKING_RANGE);
@@ -117,9 +114,8 @@ public class PlayerlistTracker extends ChunkMap.TrackedEntity {
}
private static final MethodHandle E = NMS.getGetter(ServerEntity.class, "e");
-
private static final MethodHandle F = NMS.getGetter(ServerEntity.class, "f");
- private static Boolean REQUIRES_SYNC;
+ private static boolean REQUIRES_SYNC;
private static final MethodHandle TRACKER = NMS.getFirstGetter(TrackedEntity.class, Entity.class);
private static final MethodHandle TRACKER_ENTRY = NMS.getFirstGetter(TrackedEntity.class, ServerEntity.class);
private static final MethodHandle TRACKING_RANGE = NMS.getFirstGetter(TrackedEntity.class, int.class);
diff --git a/v1_19_R3/pom.xml b/v1_19_R3/pom.xml
index 1fe20cc5a..6b1f7e746 100644
--- a/v1_19_R3/pom.xml
+++ b/v1_19_R3/pom.xml
@@ -4,7 +4,7 @@
net.citizensnpcs
citizens-parent
- 2.0.31-SNAPSHOT
+ 2.0.32-SNAPSHOT
citizens-v1_19_R3
diff --git a/v1_19_R3/src/main/java/net/citizensnpcs/nms/v1_19_R3/util/CitizensEntityTracker.java b/v1_19_R3/src/main/java/net/citizensnpcs/nms/v1_19_R3/util/CitizensEntityTracker.java
index d49edcd5b..22c4207f7 100644
--- a/v1_19_R3/src/main/java/net/citizensnpcs/nms/v1_19_R3/util/CitizensEntityTracker.java
+++ b/v1_19_R3/src/main/java/net/citizensnpcs/nms/v1_19_R3/util/CitizensEntityTracker.java
@@ -81,11 +81,8 @@ public class CitizensEntityTracker extends ChunkMap.TrackedEntity {
if (tracker instanceof NPCHolder) {
NPC npc = ((NPCHolder) tracker).getNPC();
- if (REQUIRES_SYNC == null) {
- REQUIRES_SYNC = !Bukkit.isPrimaryThread();
- }
NPCSeenByPlayerEvent event = new NPCSeenByPlayerEvent(npc, entityplayer.getBukkitEntity());
- Util.callPossiblyAsyncEvent(event, REQUIRES_SYNC);
+ REQUIRES_SYNC = Util.callEventPossiblySync(event, REQUIRES_SYNC);
if (event.isCancelled())
return;
Integer trackingRange = npc.data(). get(NPC.Metadata.TRACKING_RANGE);
@@ -141,7 +138,7 @@ public class CitizensEntityTracker extends ChunkMap.TrackedEntity {
private static final MethodHandle E = NMS.getGetter(ServerEntity.class, "e");
private static final MethodHandle F = NMS.getGetter(ServerEntity.class, "f");
- private static Boolean REQUIRES_SYNC;
+ private static boolean REQUIRES_SYNC = false;
private static final MethodHandle TRACKER = NMS.getFirstGetter(TrackedEntity.class, Entity.class);
private static final MethodHandle TRACKER_ENTRY = NMS.getFirstGetter(TrackedEntity.class, ServerEntity.class);
private static final MethodHandle TRACKING_RANGE = NMS.getFirstGetter(TrackedEntity.class, int.class);
diff --git a/v1_20_R1/pom.xml b/v1_20_R1/pom.xml
new file mode 100644
index 000000000..d3aad712b
--- /dev/null
+++ b/v1_20_R1/pom.xml
@@ -0,0 +1,112 @@
+
+
+ 4.0.0
+
+ net.citizensnpcs
+ citizens-parent
+ 2.0.32-SNAPSHOT
+
+ citizens-v1_20_R1
+
+ UTF-8
+ 1.20-R0.1-SNAPSHOT
+
+
+
+ viaversion-repo
+ https://repo.viaversion.com
+
+
+
+
+ ${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
+ ${maven-compiler-plugin.version}
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ ${maven-deploy-plugin.version}
+
+
+ default-deploy
+ none
+
+
+
+
+ net.md-5
+ specialsource-maven-plugin
+ 1.2.4
+
+
+ package
+
+ remap
+
+ remap-obf
+
+ org.spigotmc:minecraft-server:${craftbukkit.version}:txt:maps-mojang
+ true
+ org.spigotmc:spigot:${craftbukkit.version}:jar:remapped-mojang
+ true
+ remapped-obf
+
+
+
+ package
+
+ remap
+
+ remap-spigot
+
+ ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
+ org.spigotmc:minecraft-server:${craftbukkit.version}:csrg:maps-spigot
+ org.spigotmc:spigot:${craftbukkit.version}:jar:remapped-obf
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven-jar-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${maven-shade-plugin.version}
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/AllayController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/AllayController.java
new file mode 100644
index 000000000..6613aa6bb
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/AllayController.java
@@ -0,0 +1,242 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftAllay;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import com.google.common.collect.Lists;
+import com.mojang.datafixers.util.Pair;
+
+import net.citizensnpcs.api.CitizensAPI;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.EquipmentSlot;
+import net.minecraft.world.entity.animal.allay.Allay;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class AllayController extends MobEntityController {
+ public AllayController() {
+ super(EntityAllayNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Allay getBukkitEntity() {
+ return (org.bukkit.entity.Allay) super.getBukkitEntity();
+ }
+
+ public static class AllayNPC extends CraftAllay implements ForwardingNPCHolder {
+ public AllayNPC(EntityAllayNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityAllayNPC extends Allay implements NPCHolder {
+ private final CitizensNPC npc;
+ private int taskId = -1;
+
+ public EntityAllayNPC(EntityType extends Allay> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityAllayNPC(EntityType extends Allay> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new AllayNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ protected InteractionResult mobInteract(Player var0, InteractionHand var1) {
+ if (npc != null && npc.isProtected()) {
+ // prevent clientside prediction
+ if (taskId == -1) {
+ taskId = Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), () -> {
+ NMSImpl.sendPacket((org.bukkit.entity.Player) var0.getBukkitEntity(),
+ new ClientboundSetEquipmentPacket(getId(),
+ Lists.newArrayList(
+ Pair.of(EquipmentSlot.OFFHAND,
+ this.getItemInHand(InteractionHand.OFF_HAND)),
+ Pair.of(EquipmentSlot.MAINHAND,
+ this.getItemInHand(InteractionHand.MAIN_HAND)))));
+ ((org.bukkit.entity.Player) var0.getBukkitEntity()).updateInventory();
+ taskId = -1;
+ }, 2);
+ }
+ return InteractionResult.FAIL;
+ }
+ return super.mobInteract(var0, var1);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ArmorStandController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ArmorStandController.java
new file mode 100644
index 000000000..eebb2cf4c
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ArmorStandController.java
@@ -0,0 +1,150 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftArmorStand;
+import org.bukkit.craftbukkit.v1_20_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_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.MobAI;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_20_R1.util.MobAI.ForwardingMobAI;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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, ForwardingMobAI {
+ private MobAI ai;
+ 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;
+ if (npc != null) {
+ ai = new BasicMobAI(this);
+ }
+ }
+
+ @Override
+ public MobAI getAI() {
+ return ai;
+ }
+
+ @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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ ai.tickAI();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/AxolotlController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/AxolotlController.java
new file mode 100644
index 000000000..cb601e037
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/AxolotlController.java
@@ -0,0 +1,256 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftAxolotl;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import com.mojang.serialization.Dynamic;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.Brain;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.animal.axolotl.Axolotl;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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) {
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new MoveControl(this);
+ this.getAttribute(Attributes.MOVEMENT_SPEED)
+ .setBaseValue(this.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue() / 10);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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 SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ protected Brain> makeBrain(Dynamic> dynamic) {
+ if (npc == null || npc.useMinecraftAI()) {
+ return super.makeBrain(dynamic);
+ }
+ return brainProvider().makeBrain(dynamic);
+ }
+
+ @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 || itemstack.getItem() == Items.WATER_BUCKET) {
+ 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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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 MoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ if (!NMSImpl.moveFish(npc, this, vec3d)) {
+ super.travel(vec3d);
+ }
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/BatController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/BatController.java
new file mode 100644
index 000000000..ad1c180c6
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/BatController.java
@@ -0,0 +1,185 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftBat;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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) {
+ setFlying(false);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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()) {
+ super.customServerAiStep();
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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);
+ }
+
+ @Override
+ public Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/BeeController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/BeeController.java
new file mode 100644
index 000000000..22c17b0bd
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/BeeController.java
@@ -0,0 +1,180 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftBee;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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()) {
+ super.customServerAiStep();
+ }
+ npc.update();
+ }
+ }
+
+
+
+ @Override
+ public SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/BlazeController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/BlazeController.java
new file mode 100644
index 000000000..546275207
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/BlazeController.java
@@ -0,0 +1,175 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftBlaze;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+public class BlazeController extends MobEntityController {
+ public BlazeController() {
+ super(EntityBlazeNPC.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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CamelController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CamelController.java
new file mode 100644
index 000000000..28d6a606f
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CamelController.java
@@ -0,0 +1,239 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftCamel;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.camel.Camel;
+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.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class CamelController extends MobEntityController {
+ public CamelController() {
+ super(EntityCamelNPC.class);
+ }
+
+ @Override
+ public void create(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.create(at, npc);
+ }
+
+ @Override
+ public org.bukkit.entity.Camel getBukkitEntity() {
+ return (org.bukkit.entity.Camel) super.getBukkitEntity();
+ }
+
+ public static class CamelNPC extends CraftCamel implements ForwardingNPCHolder {
+ public CamelNPC(EntityCamelNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityCamelNPC extends Camel implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityCamelNPC(EntityType extends Camel> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityCamelNPC(EntityType extends Camel> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ ((org.bukkit.entity.Camel) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.Camel) getBukkitEntity()).getMaxDomestication());
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new CamelNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CatController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CatController.java
new file mode 100644
index 000000000..5ea7dbbc2
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CatController.java
@@ -0,0 +1,222 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftCat;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CaveSpiderController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CaveSpiderController.java
new file mode 100644
index 000000000..188a3e757
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CaveSpiderController.java
@@ -0,0 +1,221 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftCaveSpider;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ChickenController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ChickenController.java
new file mode 100644
index 000000000..a263ce207
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ChickenController.java
@@ -0,0 +1,230 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftChicken;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @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.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CodController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CodController.java
new file mode 100644
index 000000000..bb07a854c
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CodController.java
@@ -0,0 +1,261 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftCod;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.EntityMoveControl;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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) {
+ 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.isProtected();
+ }
+ 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 EntityMoveControl(this);
+ }
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ if (!NMSImpl.moveFish(npc, this, vec3d)) {
+ super.travel(vec3d);
+ }
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CowController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CowController.java
new file mode 100644
index 000000000..02a8713f7
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CowController.java
@@ -0,0 +1,238 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftCow;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CreeperController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CreeperController.java
new file mode 100644
index 000000000..62201cfcd
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/CreeperController.java
@@ -0,0 +1,241 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftCreeper;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public void ignite() {
+ if (npc == null || !npc.isProtected()) {
+ super.ignite();
+ }
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/DolphinController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/DolphinController.java
new file mode 100644
index 000000000..e29d7aea9
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/DolphinController.java
@@ -0,0 +1,240 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDolphin;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.ai.control.MoveControl;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 boolean inProtectedTick;
+
+ 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) {
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new MoveControl(this);
+ this.getAttribute(Attributes.MOVEMENT_SPEED)
+ .setBaseValue(this.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue() / 10);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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 SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isInWaterRainOrBubble() {
+ return inProtectedTick ? true : super.isInWaterRainOrBubble();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null && npc.isProtected()) {
+ inProtectedTick = true;
+ }
+ super.tick();
+ inProtectedTick = false;
+ 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 MoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ if (!NMSImpl.moveFish(npc, this, vec3d)) {
+ super.travel(vec3d);
+ }
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/DrownedController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/DrownedController.java
new file mode 100644
index 000000000..f4ab376dd
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/DrownedController.java
@@ -0,0 +1,211 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDrowned;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EnderDragonController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EnderDragonController.java
new file mode 100644
index 000000000..2038b4adc
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EnderDragonController.java
@@ -0,0 +1,274 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import java.lang.invoke.MethodHandle;
+import java.util.List;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEnderDragon;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.versioned.EnderDragonTrait;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntitySelector;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+import net.minecraft.world.entity.boss.enderdragon.phases.EnderDragonPhase;
+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.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ public void aiStep() {
+ if (npc != null) {
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+
+ }
+ if (npc != null && !npc.useMinecraftAI()) {
+ if (isDeadOrDying()) {
+ setHealth(0F);
+ return;
+ }
+ if (this.posPointer < 0) {
+ for (int i = 0; i < this.positions.length; ++i) {
+ this.positions[i][0] = this.getYRot();
+ this.positions[i][1] = this.getY();
+ }
+ }
+
+ if (++this.posPointer == this.positions.length) {
+ this.posPointer = 0;
+ }
+
+ this.positions[this.posPointer][0] = this.getYRot();
+ this.positions[this.posPointer][1] = this.getY();
+
+ float[][] pos = NMS.calculateDragonPositions(getYRot(),
+ new double[][] { getLatencyPos(0, 1F), getLatencyPos(5, 1F), getLatencyPos(10, 1F),
+ getLatencyPos(12, 1F), getLatencyPos(14, 1F), getLatencyPos(16, 1F) });
+ for (int j = 0; j < subEntities.length; ++j) {
+ Vec3 vec3 = new Vec3(this.subEntities[j].getX(), this.subEntities[j].getY(),
+ this.subEntities[j].getZ());
+ subEntities[j].setPos(this.getX() + pos[j][0], this.getY() + pos[j][1], this.getZ() + pos[j][2]);
+ subEntities[j].xo = subEntities[j].xOld = vec3.x;
+ subEntities[j].yo = subEntities[j].yOld = vec3.y;
+ subEntities[j].zo = subEntities[j].zOld = vec3.z;
+ }
+
+ if (getFirstPassenger() != null) {
+ setYRot(getFirstPassenger().getBukkitYaw() - 180);
+ }
+
+ Vec3 mot = getDeltaMovement();
+ if (mot.x != 0 || mot.y != 0 || mot.z != 0) {
+ mot = mot.multiply(0.98, 0.91, 0.98);
+ if (getFirstPassenger() == null) {
+ setYRot(Util.getDragonYaw(getBukkitEntity(), mot.x, mot.z));
+ }
+ setPos(getX() + mot.x, getY() + mot.y, getZ() + mot.z);
+ setDeltaMovement(mot);
+ }
+
+ if (npc.hasTrait(EnderDragonTrait.class) && npc.getOrAddTrait(EnderDragonTrait.class).isDestroyWalls()
+ && NMSImpl.ENDERDRAGON_CHECK_WALLS != null) {
+ for (int i = 0; i < 3; i++) {
+ try {
+ this.inWall |= (boolean) NMSImpl.ENDERDRAGON_CHECK_WALLS.invoke(this,
+ subEntities[i].getBoundingBox());
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ if (npc.data().get(NPC.Metadata.COLLIDABLE, false)) {
+ try {
+ KNOCKBACK.invoke(this,
+ this.level().getEntities(this,
+ subEntities[6].getBoundingBox().inflate(4.0, 2.0, 4.0).move(0.0, -2.0, 0.0),
+ EntitySelector.NO_CREATIVE_OR_SPECTATOR));
+ KNOCKBACK.invoke(this,
+ this.level().getEntities(this,
+ subEntities[7].getBoundingBox().inflate(4.0, 2.0, 4.0).move(0.0, -2.0, 0.0),
+ EntitySelector.NO_CREATIVE_OR_SPECTATOR));
+ HURT.invoke(this, this.level().getEntities(this, subEntities[0].getBoundingBox().inflate(1.0),
+ EntitySelector.NO_CREATIVE_OR_SPECTATOR));
+ HURT.invoke(this, this.level().getEntities(this, subEntities[1].getBoundingBox().inflate(1.0),
+ EntitySelector.NO_CREATIVE_OR_SPECTATOR));
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ } else {
+ super.aiStep();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ return super.canRide(entity);
+ }
+
+ @Override
+ public void checkDespawn() {
+ if (npc == null) {
+ super.checkDespawn();
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new EnderDragonNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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
+ protected boolean reallyHurt(DamageSource source, float f) {
+ if (npc == null)
+ return super.reallyHurt(source, f);
+
+ Vec3 old = getDeltaMovement();
+ boolean res = super.reallyHurt(source, f);
+ if (getPhaseManager().getCurrentPhase().getPhase() == EnderDragonPhase.HOVERING) {
+ setDeltaMovement(old);
+ }
+ return res;
+ }
+
+ @Override
+ public boolean save(CompoundTag save) {
+ return npc == null ? super.save(save) : false;
+ }
+
+ @Override
+ public Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+
+ private static final MethodHandle HURT = NMS.getMethodHandle(EnderDragon.class, "c", true, List.class);
+
+ private static final MethodHandle KNOCKBACK = NMS.getMethodHandle(EnderDragon.class, "b", true, List.class);
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EndermanController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EndermanController.java
new file mode 100644
index 000000000..d9e23cc66
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EndermanController.java
@@ -0,0 +1,232 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import java.util.Optional;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEnderman;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Optional randomTeleport(double d0, double d1, double d2, boolean flag,
+ PlayerTeleportEvent.TeleportCause cause) {
+ if (npc == null) {
+ return super.randomTeleport(d0, d1, d2, flag, cause);
+ }
+ return Optional.of(false);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EndermiteController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EndermiteController.java
new file mode 100644
index 000000000..0149f1f19
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EndermiteController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEndermite;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EntityHumanNPC.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EntityHumanNPC.java
new file mode 100644
index 000000000..ecf3f37d3
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EntityHumanNPC.java
@@ -0,0 +1,494 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.net.Socket;
+import java.util.List;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_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.mojang.authlib.GameProfile;
+
+import net.citizensnpcs.Settings.Setting;
+import net.citizensnpcs.api.CitizensAPI;
+import net.citizensnpcs.api.event.NPCKnockbackEvent;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.api.npc.NPC.NPCUpdate;
+import net.citizensnpcs.api.trait.trait.Inventory;
+import net.citizensnpcs.api.util.SpigotUtil;
+import net.citizensnpcs.nms.v1_20_R1.network.EmptyNetHandler;
+import net.citizensnpcs.nms.v1_20_R1.network.EmptyNetworkManager;
+import net.citizensnpcs.nms.v1_20_R1.util.EmptyAdvancementDataPlayer;
+import net.citizensnpcs.nms.v1_20_R1.util.EmptyServerStatsCounter;
+import net.citizensnpcs.nms.v1_20_R1.util.MobAI;
+import net.citizensnpcs.nms.v1_20_R1.util.MobAI.ForwardingMobAI;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+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.EmptySocket;
+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.MutableComponent;
+import net.minecraft.network.chat.contents.LiteralContents;
+import net.minecraft.network.protocol.PacketFlow;
+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.sounds.SoundEvent;
+import net.minecraft.stats.ServerStatsCounter;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.GameType;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class EntityHumanNPC extends ServerPlayer implements NPCHolder, SkinnableEntity, ForwardingMobAI {
+ private MobAI ai;
+ private int jumpTicks = 0;
+ private final CitizensNPC npc;
+ private boolean setBukkitEntity;
+ private final SkinPacketTracker skinTracker;
+ private EmptyServerStatsCounter statsCache;
+
+ public EntityHumanNPC(MinecraftServer minecraftServer, ServerLevel world, GameProfile gameProfile, NPC npc) {
+ super(minecraftServer, world, gameProfile);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ ai = new BasicMobAI(this);
+ 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 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(), () -> {
+ ((ServerLevel) level()).removePlayerImmediately(EntityHumanNPC.this, RemovalReason.KILLED);
+ ((ServerLevel) level()).getChunkSource().removeEntity(EntityHumanNPC.this);
+ }, 15); // give enough time for death and smoke animation
+ }
+
+ @Override
+ public void doTick() {
+ if (npc == null) {
+ super.doTick();
+ return;
+ }
+ super.baseTick();
+ boolean navigating = npc.getNavigator().isNavigating() || ai.getMoveControl().hasWanted();
+ if (!navigating && getBukkitEntity() != null
+ && (!npc.hasTrait(Gravity.class) || npc.getOrAddTrait(Gravity.class).hasGravity())
+ && Util.isLoaded(getBukkitEntity().getLocation(LOADED_LOCATION))
+ && SpigotUtil.checkYSafe(getY(), getBukkitEntity().getWorld())) {
+ moveWithFallDamage(Vec3.ZERO);
+ }
+ Vec3 mot = getDeltaMovement();
+ if (Math.abs(mot.x) < EPSILON && Math.abs(mot.y) < EPSILON && Math.abs(mot.z) < EPSILON) {
+ setDeltaMovement(Vec3.ZERO);
+ }
+ if (navigating) {
+ if (!ai.getNavigation().isDone()) {
+ ai.getNavigation().tick();
+ }
+ moveOnCurrentHeading();
+ }
+ tickAI();
+ detectEquipmentUpdates();
+ noPhysics = isSpectator();
+ if (isSpectator()) {
+ this.onGround = false;
+ }
+
+ pushEntities();
+
+ if (npc.data().get(NPC.Metadata.PICKUP_ITEMS, false)) {
+ AABB axisalignedbb;
+ if (this.isPassenger() && !this.getVehicle().isRemoved()) {
+ axisalignedbb = this.getBoundingBox().minmax(this.getVehicle().getBoundingBox()).inflate(1.0, 0.0, 1.0);
+ } else {
+ axisalignedbb = this.getBoundingBox().inflate(1.0, 0.5, 1.0);
+ }
+ for (Entity entity : level().getEntities(this, axisalignedbb)) {
+ entity.playerTouch(this);
+ }
+ }
+ }
+
+ @Override
+ public MobAI getAI() {
+ return ai;
+ }
+
+ @Override
+ public CraftPlayer getBukkitEntity() {
+ if (npc != null && !setBukkitEntity) {
+ NMSImpl.setBukkitEntity(this, new PlayerNPC(this));
+ setBukkitEntity = true;
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @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 ServerStatsCounter getStats() {
+ return this.statsCache == null ? statsCache = new EmptyServerStatsCounter() : statsCache;
+ }
+
+ @Override
+ public Component getTabListDisplayName() {
+ if (Setting.DISABLE_TABLIST.asBoolean()) {
+ return MutableComponent.create(new LiteralContents(""));
+ }
+ 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
+ }
+ 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());
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable() : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NPCKnockbackEvent event = new NPCKnockbackEvent(npc, strength, dx, dz);
+ Bukkit.getPluginManager().callEvent(event);
+ Vector kb = event.getKnockbackVector();
+ if (!event.isCancelled()) {
+ super.knockback(event.getStrength(), kb.getX(), kb.getZ());
+ }
+ }
+
+ private void moveOnCurrentHeading() {
+ if (jumping) {
+ if (onGround && jumpTicks == 0) {
+ jumpFromGround();
+ jumpTicks = 10;
+ }
+ } else {
+ jumpTicks = 0;
+ }
+ xxa *= 0.98F;
+ zza *= 0.98F;
+ moveWithFallDamage(new Vec3(this.xxa, this.yya, this.zza));
+ NMS.setHeadYaw(getBukkitEntity(), getYRot());
+ if (jumpTicks > 0) {
+ jumpTicks--;
+ }
+ }
+
+ private void moveWithFallDamage(Vec3 vec) {
+ double x = getX();
+ double y = getY();
+ double z = getZ();
+ travel(vec);
+ if (!npc.isProtected()) {
+ doCheckFallDamage(getX() - x, getY() - y, getZ() - z, onGround);
+ }
+ }
+
+ @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();
+ }
+
+ @Override
+ public void setSkinFlags(byte flags) {
+ 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);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc == null)
+ return;
+ Bukkit.getServer().getPluginManager().unsubscribeFromPermission("bukkit.broadcast.user", getBukkitEntity());
+ updatePackets(npc.getNavigator().isNavigating());
+ npc.update();
+ }
+
+ @Override
+ public void tickAI() {
+ ai.getMoveControl().tick();
+ ai.getJumpControl().tick();
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ Vec3 old = getDeltaMovement().add(0, 0, 0);
+ boolean res = super.updateFluidHeightAndDoFluidPushing(tagkey, d0);
+ if (!npc.isPushableByFluids()) {
+ setDeltaMovement(old);
+ }
+ return res;
+ }
+
+ private void updatePackets(boolean navigating) {
+ if (!npc.isUpdating(NPCUpdate.PACKET))
+ return;
+
+ effectsDirty = true;
+ }
+
+ public static class PlayerNPC extends CraftPlayer implements NPCHolder, SkinnableEntity {
+ private final CitizensNPC npc;
+
+ private PlayerNPC(EntityHumanNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ this.npc = entity.npc;
+ npc.getOrAddTrait(Inventory.class);
+ }
+
+ @Override
+ public boolean canSee(org.bukkit.entity.Entity entity) {
+ if (entity != null && entity.getType().name().contains("ITEM_FRAME")) {
+ return false; // optimise for large maps in item frames
+ }
+ return super.canSee(entity);
+ }
+
+ @Override
+ public Player getBukkitEntity() {
+ return this;
+ }
+
+ @Override
+ public EntityHumanNPC getHandle() {
+ return (EntityHumanNPC) this.entity;
+ }
+
+ @Override
+ public List getMetadata(String metadataKey) {
+ return ((CraftServer) Bukkit.getServer()).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 ((CraftServer) Bukkit.getServer()).getEntityMetadata().hasMetadata(this, metadataKey);
+ }
+
+ @Override
+ public void removeMetadata(String metadataKey, Plugin owningPlugin) {
+ ((CraftServer) Bukkit.getServer()).getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin);
+ }
+
+ @Override
+ public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {
+ ((CraftServer) Bukkit.getServer()).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 float EPSILON = 0.003F;
+ 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_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EvokerController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EvokerController.java
new file mode 100644
index 000000000..82a02678c
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/EvokerController.java
@@ -0,0 +1,212 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEvoker;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class EvokerNPC extends CraftEvoker implements ForwardingNPCHolder {
+ public EvokerNPC(EntityEvokerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/FoxController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/FoxController.java
new file mode 100644
index 000000000..3869ead2b
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/FoxController.java
@@ -0,0 +1,222 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftFox;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class FoxNPC extends CraftFox implements ForwardingNPCHolder {
+ public FoxNPC(EntityFoxNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/FrogController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/FrogController.java
new file mode 100644
index 000000000..ff8293f1f
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/FrogController.java
@@ -0,0 +1,225 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftFrog;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.frog.Frog;
+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.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class FrogController extends MobEntityController {
+ public FrogController() {
+ super(EntityFrogNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Frog getBukkitEntity() {
+ return (org.bukkit.entity.Frog) super.getBukkitEntity();
+ }
+
+ public static class EntityFrogNPC extends Frog implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityFrogNPC(EntityType extends Frog> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityFrogNPC(EntityType extends Frog> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ croakAnimationState.start(1);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new FrogNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class FrogNPC extends CraftFrog implements ForwardingNPCHolder {
+ public FrogNPC(EntityFrogNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GhastController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GhastController.java
new file mode 100644
index 000000000..5e5d1dcb2
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GhastController.java
@@ -0,0 +1,181 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftGhast;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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);
+ }
+ super.customServerAiStep();
+ }
+
+
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isAutoSpinAttack() {
+ return npc != null;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class GhastNPC extends CraftGhast implements ForwardingNPCHolder {
+ public GhastNPC(EntityGhastNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GiantController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GiantController.java
new file mode 100644
index 000000000..706fae85a
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GiantController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftGiant;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class GiantNPC extends CraftGiant implements ForwardingNPCHolder {
+ public GiantNPC(EntityGiantNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GlowSquidController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GlowSquidController.java
new file mode 100644
index 000000000..0b131084a
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GlowSquidController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftGlowSquid;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class GlowSquidNPC extends CraftGlowSquid implements ForwardingNPCHolder {
+ public GlowSquidNPC(EntityGlowSquidNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GoatController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GoatController.java
new file mode 100644
index 000000000..958223c3e
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GoatController.java
@@ -0,0 +1,222 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftGoat;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class GoatNPC extends CraftGoat implements ForwardingNPCHolder {
+ public GoatNPC(EntityGoatNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GuardianController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GuardianController.java
new file mode 100644
index 000000000..b70d0b473
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GuardianController.java
@@ -0,0 +1,227 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftGuardian;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @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.isProtected();
+ }
+ 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 SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class GuardianNPC extends CraftGuardian implements ForwardingNPCHolder {
+ public GuardianNPC(EntityGuardianNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GuardianElderController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GuardianElderController.java
new file mode 100644
index 000000000..60eeea193
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/GuardianElderController.java
@@ -0,0 +1,227 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftElderGuardian;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @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.isProtected();
+ }
+ 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 SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class GuardianElderNPC extends CraftElderGuardian implements ForwardingNPCHolder {
+ public GuardianElderNPC(EntityGuardianElderNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HoglinController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HoglinController.java
new file mode 100644
index 000000000..9812dd068
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HoglinController.java
@@ -0,0 +1,215 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftHoglin;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class HoglinNPC extends CraftHoglin implements ForwardingNPCHolder {
+ public HoglinNPC(EntityHoglinNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseController.java
new file mode 100644
index 000000000..c20c11cec
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseController.java
@@ -0,0 +1,268 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftHorse;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseController extends MobEntityController {
+ public HorseController() {
+ super(EntityHorseNPC.class);
+ }
+
+ @Override
+ public void create(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.create(at, npc);
+ }
+
+ @Override
+ public org.bukkit.entity.Horse getBukkitEntity() {
+ return (org.bukkit.entity.Horse) super.getBukkitEntity();
+ }
+
+ public static class EntityHorseNPC extends Horse implements NPCHolder {
+ private double baseMovementSpeed;
+
+ 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) {
+ org.bukkit.entity.Horse horse = (org.bukkit.entity.Horse) getBukkitEntity();
+ horse.setDomestication(horse.getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isVehicle() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.isVehicle();
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class HorseNPC extends CraftHorse implements ForwardingNPCHolder {
+ public HorseNPC(EntityHorseNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseDonkeyController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseDonkeyController.java
new file mode 100644
index 000000000..edeef6e44
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseDonkeyController.java
@@ -0,0 +1,268 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDonkey;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseDonkeyController extends MobEntityController {
+ public HorseDonkeyController() {
+ super(EntityHorseDonkeyNPC.class);
+ }
+
+ @Override
+ public void create(Location at, NPC npc) {
+ npc.addTrait(HorseModifiers.class);
+ super.create(at, npc);
+ }
+
+ @Override
+ public org.bukkit.entity.Donkey getBukkitEntity() {
+ return (org.bukkit.entity.Donkey) super.getBukkitEntity();
+ }
+
+ public static class EntityHorseDonkeyNPC extends Donkey implements NPCHolder {
+ private double baseMovementSpeed;
+
+ 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) {
+ ((org.bukkit.entity.Donkey) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.Donkey) getBukkitEntity()).getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isVehicle() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.isVehicle();
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class HorseDonkeyNPC extends CraftDonkey implements ForwardingNPCHolder {
+ public HorseDonkeyNPC(EntityHorseDonkeyNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseMuleController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseMuleController.java
new file mode 100644
index 000000000..fa6abc3af
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseMuleController.java
@@ -0,0 +1,268 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMule;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseMuleController extends MobEntityController {
+ public HorseMuleController() {
+ super(EntityHorseMuleNPC.class);
+ }
+
+ @Override
+ public void create(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.create(at, npc);
+ }
+
+ @Override
+ public org.bukkit.entity.Mule getBukkitEntity() {
+ return (org.bukkit.entity.Mule) super.getBukkitEntity();
+ }
+
+ public static class EntityHorseMuleNPC extends Mule implements NPCHolder {
+ private double baseMovementSpeed;
+
+ 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) {
+ ((org.bukkit.entity.Mule) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.Mule) getBukkitEntity()).getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isVehicle() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.isVehicle();
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class HorseMuleNPC extends CraftMule implements ForwardingNPCHolder {
+ public HorseMuleNPC(EntityHorseMuleNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseSkeletonController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseSkeletonController.java
new file mode 100644
index 000000000..b23902359
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseSkeletonController.java
@@ -0,0 +1,268 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSkeletonHorse;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseSkeletonController extends MobEntityController {
+ public HorseSkeletonController() {
+ super(EntityHorseSkeletonNPC.class);
+ }
+
+ @Override
+ public void create(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.create(at, npc);
+ }
+
+ @Override
+ public org.bukkit.entity.SkeletonHorse getBukkitEntity() {
+ return (org.bukkit.entity.SkeletonHorse) super.getBukkitEntity();
+ }
+
+ public static class EntityHorseSkeletonNPC extends SkeletonHorse implements NPCHolder {
+ private double baseMovementSpeed;
+
+ 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) {
+ ((org.bukkit.entity.SkeletonHorse) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.SkeletonHorse) getBukkitEntity()).getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isVehicle() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.isVehicle();
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class HorseSkeletonNPC extends CraftSkeletonHorse implements ForwardingNPCHolder {
+ public HorseSkeletonNPC(EntityHorseSkeletonNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseZombieController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseZombieController.java
new file mode 100644
index 000000000..731ab2711
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HorseZombieController.java
@@ -0,0 +1,268 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftZombieHorse;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class HorseZombieController extends MobEntityController {
+ public HorseZombieController() {
+ super(EntityHorseZombieNPC.class);
+ }
+
+ @Override
+ public void create(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.create(at, npc);
+ }
+
+ @Override
+ public org.bukkit.entity.ZombieHorse getBukkitEntity() {
+ return (org.bukkit.entity.ZombieHorse) super.getBukkitEntity();
+ }
+
+ public static class EntityHorseZombieNPC extends ZombieHorse implements NPCHolder {
+ private double baseMovementSpeed;
+
+ 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) {
+ ((org.bukkit.entity.ZombieHorse) getBukkitEntity())
+ .setDomestication(((org.bukkit.entity.ZombieHorse) getBukkitEntity()).getMaxDomestication());
+ baseMovementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED).getValue();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isControlledByLocalInstance() {
+ if (npc != null && riding) {
+ return true;
+ }
+ return super.isControlledByLocalInstance();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isVehicle() {
+ return npc != null && npc.getNavigator().isNavigating() ? false : super.isVehicle();
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class HorseZombieNPC extends CraftZombieHorse implements ForwardingNPCHolder {
+ public HorseZombieNPC(EntityHorseZombieNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HumanController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HumanController.java
new file mode 100644
index 000000000..ea00736ab
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/HumanController.java
@@ -0,0 +1,70 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_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.trait.ScoreboardTrait;
+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) { // set version to 2
+ 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()) {
+ npc.getOrAddTrait(ScoreboardTrait.class).createTeam(name);
+ }
+ 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(), () -> {
+ if (getBukkitEntity() == null || !getBukkitEntity().isValid()
+ || getBukkitEntity() != handle.getBukkitEntity())
+ return;
+ boolean removeFromPlayerList = npc.data().get(NPC.Metadata.REMOVE_FROM_PLAYERLIST,
+ 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();
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/IllusionerController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/IllusionerController.java
new file mode 100644
index 000000000..856c56624
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/IllusionerController.java
@@ -0,0 +1,212 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftIllusioner;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class IllusionerNPC extends CraftIllusioner implements ForwardingNPCHolder {
+ public IllusionerNPC(EntityIllusionerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/IronGolemController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/IronGolemController.java
new file mode 100644
index 000000000..55d16d68d
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/IronGolemController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftIronGolem;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class IronGolemNPC extends CraftIronGolem implements ForwardingNPCHolder {
+ public IronGolemNPC(EntityIronGolemNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/LlamaController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/LlamaController.java
new file mode 100644
index 000000000..f4f56f89b
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/LlamaController.java
@@ -0,0 +1,239 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftLlama;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class LlamaController extends MobEntityController {
+ public LlamaController() {
+ super(EntityLlamaNPC.class);
+ }
+
+ @Override
+ public void create(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.create(at, npc);
+ }
+
+ @Override
+ public org.bukkit.entity.Llama getBukkitEntity() {
+ return (org.bukkit.entity.Llama) super.getBukkitEntity();
+ }
+
+ public static class EntityLlamaNPC extends Llama implements NPCHolder {
+ 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) {
+ ((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.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class LlamaNPC extends CraftLlama implements ForwardingNPCHolder {
+ public LlamaNPC(EntityLlamaNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/MagmaCubeController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/MagmaCubeController.java
new file mode 100644
index 000000000..c7cbede63
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/MagmaCubeController.java
@@ -0,0 +1,243 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMagmaCube;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.EntityMoveControl;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new EntityMoveControl(this);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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 SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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 EntityMoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class MagmaCubeNPC extends CraftMagmaCube implements ForwardingNPCHolder {
+ public MagmaCubeNPC(EntityMagmaCubeNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/MobEntityController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/MobEntityController.java
new file mode 100644
index 000000000..8e3c8b5d5
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/MobEntityController.java
@@ -0,0 +1,82 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import java.lang.reflect.Constructor;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.bukkit.Location;
+import org.bukkit.block.BlockFace;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.entity.Entity;
+
+import net.citizensnpcs.Settings.Setting;
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.nms.v1_20_R1.util.PitchableLookControl;
+import net.citizensnpcs.npc.AbstractEntityController;
+import net.citizensnpcs.trait.ScoreboardTrait;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.control.LookControl;
+import net.minecraft.world.level.Level;
+
+public abstract class MobEntityController extends AbstractEntityController {
+ private final Class> clazz;
+
+ protected MobEntityController(Class> clazz) {
+ super(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);
+ if (entity instanceof Mob) {
+ NMSImpl.clearGoals(npc, ((Mob) entity).goalSelector, ((Mob) entity).targetSelector);
+ Mob mob = (Mob) entity;
+ if (mob.getLookControl().getClass() == LookControl.class) {
+ NMSImpl.setLookControl(mob, new PitchableLookControl(mob));
+ }
+ }
+ entity.absMoveTo(at.getX(), at.getY(), at.getZ(), at.getYaw(), at.getPitch());
+ if (npc != null) {
+ // 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);
+ }
+ entity.setUUID(npc.getUniqueId());
+ if (Setting.USE_SCOREBOARD_TEAMS.asBoolean()) {
+ npc.getOrAddTrait(ScoreboardTrait.class).createTeam(npc.getUniqueId().toString());
+ }
+ }
+
+ return entity.getBukkitEntity();
+ }
+
+ private net.minecraft.world.entity.Entity createEntityFromClass(Object... args) {
+ try {
+ return (net.minecraft.world.entity.Entity) getConstructor(clazz).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 {
+ CONSTRUCTOR_CACHE.put(clazz, constructor = clazz.getConstructor(EntityType.class, Level.class, NPC.class));
+ return constructor;
+ } catch (Exception ex) {
+ throw new IllegalStateException("unable to find an entity constructor");
+ }
+ }
+
+ private static final Map, Constructor>> CONSTRUCTOR_CACHE = new WeakHashMap, Constructor>>();
+}
\ No newline at end of file
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/MushroomCowController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/MushroomCowController.java
new file mode 100644
index 000000000..c9f68ae54
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/MushroomCowController.java
@@ -0,0 +1,231 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMushroomCow;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class MushroomCowNPC extends CraftMushroomCow implements ForwardingNPCHolder {
+ public MushroomCowNPC(EntityMushroomCowNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/OcelotController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/OcelotController.java
new file mode 100644
index 000000000..89ebe0ddb
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/OcelotController.java
@@ -0,0 +1,228 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftOcelot;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class OcelotNPC extends CraftOcelot implements ForwardingNPCHolder {
+ public OcelotNPC(EntityOcelotNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PandaController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PandaController.java
new file mode 100644
index 000000000..5d1a45cce
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PandaController.java
@@ -0,0 +1,222 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPanda;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class PandaNPC extends CraftPanda implements ForwardingNPCHolder {
+ public PandaNPC(EntityPandaNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ParrotController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ParrotController.java
new file mode 100644
index 000000000..43363090d
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ParrotController.java
@@ -0,0 +1,190 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftParrot;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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()) {
+ super.customServerAiStep();
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ // block feeding
+ if (npc == null || !npc.isProtected()) {
+ 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;
+ }
+
+ @Override
+ public Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class ParrotNPC extends CraftParrot implements ForwardingNPCHolder {
+ public ParrotNPC(EntityParrotNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PhantomController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PhantomController.java
new file mode 100644
index 000000000..12dac8ccc
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PhantomController.java
@@ -0,0 +1,257 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPhantom;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.EntityMoveControl;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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) {
+ 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;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new EntityMoveControl(this);
+ this.lookControl = new LookControl(this);
+ }
+ if (npc.isProtected()) {
+ setSecondsOnFire(0);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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 SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isSunBurnTick() {
+ if (npc == null || !npc.isProtected())
+ return super.isSunBurnTick();
+ return false;
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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
+ protected boolean shouldDespawnInPeaceful() {
+ return npc != null ? false : super.shouldDespawnInPeaceful();
+ }
+
+ @Override
+ public Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class PhantomNPC extends CraftPhantom implements ForwardingNPCHolder {
+ public PhantomNPC(EntityPhantomNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PigController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PigController.java
new file mode 100644
index 000000000..877285346
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PigController.java
@@ -0,0 +1,236 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPig;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isVehicle() {
+ // block carrot-on-a-stick behaviour
+ return npc == null ? super.isVehicle() : false;
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class PigNPC extends CraftPig implements ForwardingNPCHolder {
+ public PigNPC(EntityPigNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PigZombieController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PigZombieController.java
new file mode 100644
index 000000000..9718d6915
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PigZombieController.java
@@ -0,0 +1,211 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPigZombie;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class PigZombieNPC extends CraftPigZombie implements ForwardingNPCHolder {
+ public PigZombieNPC(EntityPigZombieNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PiglinBruteController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PiglinBruteController.java
new file mode 100644
index 000000000..0c7a22145
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PiglinBruteController.java
@@ -0,0 +1,214 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPiglinBrute;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class PiglinBruteNPC extends CraftPiglinBrute implements ForwardingNPCHolder {
+ public PiglinBruteNPC(EntityPiglinBruteNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PiglinController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PiglinController.java
new file mode 100644
index 000000000..0bbf3a27d
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PiglinController.java
@@ -0,0 +1,214 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPiglin;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class PiglinNPC extends CraftPiglin implements ForwardingNPCHolder {
+ public PiglinNPC(EntityPiglinNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PillagerController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PillagerController.java
new file mode 100644
index 000000000..14ec3d035
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PillagerController.java
@@ -0,0 +1,222 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPillager;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class PillagerNPC extends CraftPillager implements ForwardingNPCHolder {
+ public PillagerNPC(EntityPillagerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PolarBearController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PolarBearController.java
new file mode 100644
index 000000000..b786878eb
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PolarBearController.java
@@ -0,0 +1,186 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPolarBear;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+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.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class PolarBearNPC extends CraftPolarBear implements ForwardingNPCHolder {
+ public PolarBearNPC(EntityPolarBearNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PufferFishController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PufferFishController.java
new file mode 100644
index 000000000..f30106f53
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/PufferFishController.java
@@ -0,0 +1,293 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPufferFish;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.EntityMoveControl;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.trait.versioned.PufferFishTrait;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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) {
+ 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.isProtected();
+ }
+ 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 EntityMoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @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.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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()) {
+ if (!NMSImpl.moveFish(npc, this, vec3d)) {
+ super.travel(vec3d);
+ }
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+
+ 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_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/RabbitController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/RabbitController.java
new file mode 100644
index 000000000..25724e034
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/RabbitController.java
@@ -0,0 +1,242 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftRabbit;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public LivingEntity getTarget() {
+ return npc != null ? null : super.getTarget();
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 setVariant(Variant variant) {
+ if (npc != null) {
+ if (NMSImpl.getRabbitTypeField() == null)
+ return;
+ this.entityData.set(NMSImpl.getRabbitTypeField(), variant.id());
+ return;
+ }
+ super.setVariant(variant);
+ }
+
+ @Override
+ public Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class RabbitNPC extends CraftRabbit implements ForwardingNPCHolder {
+ public RabbitNPC(EntityRabbitNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/RavagerController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/RavagerController.java
new file mode 100644
index 000000000..1ec7c7372
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/RavagerController.java
@@ -0,0 +1,227 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftRavager;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isVehicle() {
+ return (npc == null || npc.useMinecraftAI()) ? super.isVehicle() : false;
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class RavagerNPC extends CraftRavager implements ForwardingNPCHolder {
+ public RavagerNPC(EntityRavagerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SalmonController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SalmonController.java
new file mode 100644
index 000000000..c38243a83
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SalmonController.java
@@ -0,0 +1,258 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSalmon;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.EntityMoveControl;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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) {
+ 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.isProtected();
+ }
+ 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 EntityMoveControl(this);
+ }
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ if (!NMSImpl.moveFish(npc, this, vec3d)) {
+ super.travel(vec3d);
+ }
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SalmonNPC extends CraftSalmon implements ForwardingNPCHolder {
+ public SalmonNPC(EntitySalmonNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SheepController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SheepController.java
new file mode 100644
index 000000000..cb7c212d1
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SheepController.java
@@ -0,0 +1,221 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSheep;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SheepNPC extends CraftSheep implements ForwardingNPCHolder {
+ public SheepNPC(EntitySheepNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ShulkerController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ShulkerController.java
new file mode 100644
index 000000000..c707e15e1
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ShulkerController.java
@@ -0,0 +1,237 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftShulker;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @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.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class ShulkerNPC extends CraftShulker implements ForwardingNPCHolder {
+ public ShulkerNPC(EntityShulkerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SilverfishController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SilverfishController.java
new file mode 100644
index 000000000..b1b1b3551
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SilverfishController.java
@@ -0,0 +1,221 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSilverfish;
+import org.bukkit.util.Vector;
+
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SilverfishNPC extends CraftSilverfish implements ForwardingNPCHolder {
+ public SilverfishNPC(EntitySilverfishNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SkeletonController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SkeletonController.java
new file mode 100644
index 000000000..17696075a
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SkeletonController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSkeleton;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SkeletonNPC extends CraftSkeleton implements ForwardingNPCHolder {
+ public SkeletonNPC(EntitySkeletonNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SkeletonStrayController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SkeletonStrayController.java
new file mode 100644
index 000000000..f148f3594
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SkeletonStrayController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftStray;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class StrayNPC extends CraftStray implements ForwardingNPCHolder {
+ public StrayNPC(EntityStrayNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SkeletonWitherController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SkeletonWitherController.java
new file mode 100644
index 000000000..56d623589
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SkeletonWitherController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftWitherSkeleton;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SkeletonWitherNPC extends CraftWitherSkeleton implements ForwardingNPCHolder {
+ public SkeletonWitherNPC(EntitySkeletonWitherNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SlimeController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SlimeController.java
new file mode 100644
index 000000000..00acee619
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SlimeController.java
@@ -0,0 +1,244 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSlime;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.EntityMoveControl;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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);
+ this.oldMoveController = this.moveControl;
+ this.moveControl = new EntityMoveControl(this);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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 SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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 EntityMoveControl(this);
+ }
+ npc.update();
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SlimeNPC extends CraftSlime implements ForwardingNPCHolder {
+ public SlimeNPC(EntitySlimeNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SnifferController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SnifferController.java
new file mode 100644
index 000000000..8cebfdd4a
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SnifferController.java
@@ -0,0 +1,236 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSniffer;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.sniffer.Sniffer;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class SnifferController extends MobEntityController {
+ public SnifferController() {
+ super(EntitySnifferNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Sniffer getBukkitEntity() {
+ return (org.bukkit.entity.Sniffer) super.getBukkitEntity();
+ }
+
+ public static class EntitySnifferNPC extends Sniffer implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntitySnifferNPC(EntityType extends Sniffer> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntitySnifferNPC(EntityType extends Sniffer> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new SnifferNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SnifferNPC extends CraftSniffer implements ForwardingNPCHolder {
+ public SnifferNPC(EntitySnifferNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SnowmanController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SnowmanController.java
new file mode 100644
index 000000000..9766e1910
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SnowmanController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSnowman;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SnowmanNPC extends CraftSnowman implements ForwardingNPCHolder {
+ public SnowmanNPC(EntitySnowmanNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SpiderController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SpiderController.java
new file mode 100644
index 000000000..834f4ea11
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SpiderController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSpider;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SpiderNPC extends CraftSpider implements ForwardingNPCHolder {
+ public SpiderNPC(EntitySpiderNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SquidController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SquidController.java
new file mode 100644
index 000000000..5bdeaded6
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/SquidController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSquid;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SquidNPC extends CraftSquid implements ForwardingNPCHolder {
+ public SquidNPC(EntitySquidNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/StriderController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/StriderController.java
new file mode 100644
index 000000000..a3382ece2
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/StriderController.java
@@ -0,0 +1,211 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftStrider;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class StriderNPC extends CraftStrider implements ForwardingNPCHolder {
+ public StriderNPC(EntityStriderNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TadpoleController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TadpoleController.java
new file mode 100644
index 000000000..8c6f83810
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TadpoleController.java
@@ -0,0 +1,258 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftTadpole;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.EntityMoveControl;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.frog.Frog;
+import net.minecraft.world.entity.animal.frog.Tadpole;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class TadpoleController extends MobEntityController {
+ public TadpoleController() {
+ super(EntityTadpoleNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Tadpole getBukkitEntity() {
+ return (org.bukkit.entity.Tadpole) super.getBukkitEntity();
+ }
+
+ public static class EntityTadpoleNPC extends Tadpole implements NPCHolder {
+ private final CitizensNPC npc;
+
+ private MoveControl oldMoveController;
+
+ public EntityTadpoleNPC(EntityType extends Tadpole> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityTadpoleNPC(EntityType extends Tadpole> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ if (npc != null) {
+ 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.isProtected();
+ }
+ 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);
+ if (npc.useMinecraftAI() && this.moveControl != this.oldMoveController) {
+ this.moveControl = this.oldMoveController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new EntityMoveControl(this);
+ }
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new TadpoleNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc == null || !npc.isProtected())
+ return super.mobInteract(entityhuman, enumhand);
+ ItemStack itemstack = entityhuman.getItemInHand(enumhand);
+ if ((Frog.TEMPTATION_ITEM.test(itemstack) || 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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ if (!NMSImpl.moveFish(npc, this, vec3d)) {
+ super.travel(vec3d);
+ }
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class TadpoleNPC extends CraftTadpole implements ForwardingNPCHolder {
+ public TadpoleNPC(EntityTadpoleNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TraderLlamaController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TraderLlamaController.java
new file mode 100644
index 000000000..a8cb412f9
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TraderLlamaController.java
@@ -0,0 +1,240 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftTraderLlama;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class TraderLlamaController extends MobEntityController {
+ public TraderLlamaController() {
+ super(EntityTraderLlamaNPC.class);
+ }
+
+ @Override
+ public void create(Location at, NPC npc) {
+ npc.getOrAddTrait(HorseModifiers.class);
+ super.create(at, npc);
+ }
+
+ @Override
+ public org.bukkit.entity.TraderLlama getBukkitEntity() {
+ return (org.bukkit.entity.TraderLlama) super.getBukkitEntity();
+ }
+
+ public static class EntityTraderLlamaNPC extends TraderLlama implements NPCHolder {
+ 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) {
+ ((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.isProtected();
+ }
+ 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();
+ }
+ setDespawnDelay(10);
+ NMS.setStepHeight(getBukkitEntity(), 1);
+ npc.update();
+ }
+ }
+
+
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class TraderLlamaNPC extends CraftTraderLlama implements ForwardingNPCHolder {
+ public TraderLlamaNPC(EntityTraderLlamaNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TropicalFishController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TropicalFishController.java
new file mode 100644
index 000000000..be8527f9e
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TropicalFishController.java
@@ -0,0 +1,260 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftTropicalFish;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.EntityMoveControl;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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) {
+ 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.isProtected();
+ }
+ 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 EntityMoveControl(this);
+ }
+ }
+ super.customServerAiStep();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ if (!NMSImpl.moveFish(npc, this, vec3d)) {
+ super.travel(vec3d);
+ }
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class TropicalFishNPC extends CraftTropicalFish implements ForwardingNPCHolder {
+ public TropicalFishNPC(EntityTropicalFishNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TurtleController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TurtleController.java
new file mode 100644
index 000000000..38b6cd08c
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/TurtleController.java
@@ -0,0 +1,246 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftTurtle;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.EntityMoveControl;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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) {
+ 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.isProtected();
+ }
+ 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;
+ this.jumpControl = this.oldJumpController;
+ }
+ if (!npc.useMinecraftAI() && this.moveControl == this.oldMoveController) {
+ this.moveControl = new EntityMoveControl(this);
+ this.jumpControl = new EmptyControllerJump(this);
+ }
+ npc.update();
+ }
+ }
+
+
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ if (!NMSImpl.moveFish(npc, this, vec3d)) {
+ super.travel(vec3d);
+ }
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+
+ 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_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/VexController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/VexController.java
new file mode 100644
index 000000000..f468b5b85
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/VexController.java
@@ -0,0 +1,179 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftVex;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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) {
+ setNoGravity(true);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class VexNPC extends CraftVex implements ForwardingNPCHolder {
+ public VexNPC(EntityVexNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/VillagerController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/VillagerController.java
new file mode 100644
index 000000000..91b919bfc
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/VillagerController.java
@@ -0,0 +1,263 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftVillager;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 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) {
+ getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.3);
+ }
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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();
+ if (npc.data().get(NPC.Metadata.RESET_PITCH_ON_TICK, true)) {
+ NMS.setPitch(getBukkitEntity(), 0);
+ }
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isTrading() {
+ if (blockingATrade) {
+ blockingATrade = false;
+ return true;
+ }
+ return super.isTrading();
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public InteractionResult mobInteract(Player entityhuman, InteractionHand enumhand) {
+ if (npc != null && npc.data().get(NPC.Metadata.VILLAGER_BLOCK_TRADES, true)) {
+ 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) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class VillagerNPC extends CraftVillager implements ForwardingNPCHolder {
+ public VillagerNPC(EntityVillagerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/VindicatorController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/VindicatorController.java
new file mode 100644
index 000000000..238ebdd2e
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/VindicatorController.java
@@ -0,0 +1,222 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftVindicator;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class VindicatorNPC extends CraftVindicator implements ForwardingNPCHolder {
+ public VindicatorNPC(EntityVindicatorNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WanderingTraderController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WanderingTraderController.java
new file mode 100644
index 000000000..fa982e3ba
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WanderingTraderController.java
@@ -0,0 +1,269 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftWanderingTrader;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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) {
+ setDespawnDelay(10);
+ npc.update();
+ }
+ }
+
+
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public boolean isBlockingTrades() {
+ return blockTrades;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public boolean isTrading() {
+ if (blockingATrade) {
+ blockingATrade = false;
+ return true;
+ }
+ return super.isTrading();
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class WanderingTraderNPC extends CraftWanderingTrader implements ForwardingNPCHolder {
+ public WanderingTraderNPC(EntityWanderingTraderNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WardenController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WardenController.java
new file mode 100644
index 000000000..7b16a0cb2
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WardenController.java
@@ -0,0 +1,217 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftWarden;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+import net.minecraft.util.Unit;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ai.memory.MemoryModuleType;
+import net.minecraft.world.entity.monster.warden.Warden;
+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.material.Fluid;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class WardenController extends MobEntityController {
+ public WardenController() {
+ super(EntityWardenNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Warden getBukkitEntity() {
+ return (org.bukkit.entity.Warden) super.getBukkitEntity();
+ }
+
+ public static class EntityWardenNPC extends Warden implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityWardenNPC(EntityType extends Warden> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityWardenNPC(EntityType extends Warden> types, Level level, NPC npc) {
+ super(types, level);
+ this.npc = (CitizensNPC) npc;
+ this.getBrain().setMemoryWithExpiry(MemoryModuleType.DIG_COOLDOWN, Unit.INSTANCE, 1200L);
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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();
+ return;
+ }
+ NMSImpl.updateMinecraftAIState(npc, this);
+ npc.update();
+ if (npc.useMinecraftAI()) {
+ super.customServerAiStep();
+ }
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @Override
+ public CraftEntity getBukkitEntity() {
+ if (npc != null && !(super.getBukkitEntity() instanceof NPCHolder)) {
+ NMSImpl.setBukkitEntity(this, new WardenNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ protected SoundEvent getDeathSound() {
+ return NMSImpl.getSoundEffect(npc, super.getDeathSound(), NPC.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class WardenNPC extends CraftWarden implements ForwardingNPCHolder {
+ public WardenNPC(EntityWardenNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WitchController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WitchController.java
new file mode 100644
index 000000000..5f43822ea
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WitchController.java
@@ -0,0 +1,220 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftWitch;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class WitchNPC extends CraftWitch implements ForwardingNPCHolder {
+ public WitchNPC(EntityWitchNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WitherController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WitherController.java
new file mode 100644
index 000000000..cb5ce6c7e
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WitherController.java
@@ -0,0 +1,190 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftWither;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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 int getAlternativeTarget(int i) {
+ return npc == null ? super.getAlternativeTarget(i) : 0;
+ }
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPowered() {
+ return npc == null || !npc.data().has("wither-arrow-shield") ? super.isPowered()
+ : npc.data().get("wither-arrow-shield");
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class WitherNPC extends CraftWither implements ForwardingNPCHolder {
+ public WitherNPC(EntityWitherNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WolfController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WolfController.java
new file mode 100644
index 000000000..5f5927d25
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/WolfController.java
@@ -0,0 +1,234 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftWolf;
+import org.bukkit.event.entity.EntityTargetEvent;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+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.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 {
+ 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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @Override
+ public boolean onClimbable() {
+ if (npc == null || !npc.isFlyable()) {
+ return super.onClimbable();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onSyncedDataUpdated(EntityDataAccessor> datawatcherobject) {
+ if (npc == null) {
+ super.onSyncedDataUpdated(datawatcherobject);
+ return;
+ }
+ NMSImpl.checkAndUpdateHeight(this, datawatcherobject, super::onSyncedDataUpdated);
+ }
+
+ @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 setTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fire) {
+ return npc == null || this.equals(entityliving) ? super.setTarget(entityliving, reason, fire) : false;
+ }
+
+ @Override
+ public Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ 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_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZoglinController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZoglinController.java
new file mode 100644
index 000000000..02dfd251b
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZoglinController.java
@@ -0,0 +1,213 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftZoglin;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class ZoglinNPC extends CraftZoglin implements ForwardingNPCHolder {
+ public ZoglinNPC(EntityZoglinNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZombieController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZombieController.java
new file mode 100644
index 000000000..52f10b7d5
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZombieController.java
@@ -0,0 +1,211 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftZombie;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class ZombieNPC extends CraftZombie implements ForwardingNPCHolder {
+ public ZombieNPC(EntityZombieNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZombieHuskController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZombieHuskController.java
new file mode 100644
index 000000000..2c9eed96b
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZombieHuskController.java
@@ -0,0 +1,211 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftHusk;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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
+ protected SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ protected SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class ZombieHuskNPC extends CraftHusk implements ForwardingNPCHolder {
+ public ZombieHuskNPC(EntityZombieHuskNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZombieVillagerController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZombieVillagerController.java
new file mode 100644
index 000000000..5a534593f
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/ZombieVillagerController.java
@@ -0,0 +1,212 @@
+package net.citizensnpcs.nms.v1_20_R1.entity;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftVillagerZombie;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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;
+ }
+
+ @Override
+ protected boolean canRide(Entity entity) {
+ if (npc != null && (entity instanceof Boat || entity instanceof AbstractMinecart)) {
+ return !npc.isProtected();
+ }
+ 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 SoundEvent getAmbientSound() {
+ return NMSImpl.getSoundEffect(npc, super.getAmbientSound(), NPC.Metadata.AMBIENT_SOUND);
+ }
+
+ @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.Metadata.DEATH_SOUND);
+ }
+
+ @Override
+ public SoundEvent getHurtSound(DamageSource damagesource) {
+ return NMSImpl.getSoundEffect(npc, super.getHurtSound(damagesource), NPC.Metadata.HURT_SOUND);
+ }
+
+ @Override
+ public int getMaxFallDistance() {
+ return NMS.getFallDistance(npc, super.getMaxFallDistance());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isLeashed() {
+ return NMSImpl.isLeashed(npc, super::isLeashed, this);
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ public void knockback(double strength, double dx, double dz) {
+ NMS.callKnockbackEvent(npc, (float) strength, dx, dz, (evt) -> super.knockback((float) evt.getStrength(),
+ evt.getKnockbackVector().getX(), evt.getKnockbackVector().getZ()));
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void travel(Vec3 vec3d) {
+ if (npc == null || !npc.isFlyable()) {
+ super.travel(vec3d);
+ } else {
+ NMSImpl.flyingMoveLogic(this, vec3d);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class ZombieVillagerNPC extends CraftVillagerZombie implements ForwardingNPCHolder {
+ public ZombieVillagerNPC(EntityZombieVillagerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/AreaEffectCloudController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/AreaEffectCloudController.java
new file mode 100644
index 000000000..82c4ee291
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/AreaEffectCloudController.java
@@ -0,0 +1,124 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftAreaEffectCloud;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.entity.AreaEffectCloud;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/BlockDisplayController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/BlockDisplayController.java
new file mode 100644
index 000000000..76d3011a0
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/BlockDisplayController.java
@@ -0,0 +1,123 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftBlockDisplay;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.entity.Display.BlockDisplay;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+public class BlockDisplayController extends MobEntityController {
+ public BlockDisplayController() {
+ super(EntityBlockDisplayNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.BlockDisplay getBukkitEntity() {
+ return (org.bukkit.entity.BlockDisplay) super.getBukkitEntity();
+ }
+
+ public static class BlockDisplayNPC extends CraftBlockDisplay implements ForwardingNPCHolder {
+ public BlockDisplayNPC(EntityBlockDisplayNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityBlockDisplayNPC extends BlockDisplay implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityBlockDisplayNPC(EntityType extends BlockDisplay> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityBlockDisplayNPC(EntityType extends BlockDisplay> 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 BlockDisplayNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/BoatController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/BoatController.java
new file mode 100644
index 000000000..824056e43
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/BoatController.java
@@ -0,0 +1,258 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftBoat;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.tags.FluidTags;
+import net.minecraft.tags.TagKey;
+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.Fluid;
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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;
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ChestBoatController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ChestBoatController.java
new file mode 100644
index 000000000..f9f752418
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ChestBoatController.java
@@ -0,0 +1,259 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftChestBoat;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.tags.FluidTags;
+import net.minecraft.tags.TagKey;
+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.entity.vehicle.ChestBoat;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.level.material.FluidState;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public class ChestBoatController extends MobEntityController {
+ public ChestBoatController() {
+ super(EntityChestBoatNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.ChestBoat getBukkitEntity() {
+ return (org.bukkit.entity.ChestBoat) super.getBukkitEntity();
+ }
+
+ public static class ChestBoatNPC extends CraftChestBoat implements ForwardingNPCHolder {
+ public ChestBoatNPC(EntityChestBoatNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+
+ public static class EntityChestBoatNPC extends ChestBoat 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 EntityChestBoatNPC(EntityType extends Boat> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityChestBoatNPC(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 ChestBoatNPC(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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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;
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/DragonFireballController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/DragonFireballController.java
new file mode 100644
index 000000000..ab31a22ee
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/DragonFireballController.java
@@ -0,0 +1,136 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDragonFireball;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.isProtected()) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EggController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EggController.java
new file mode 100644
index 000000000..3b303503b
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EggController.java
@@ -0,0 +1,141 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEgg;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.isProtected()) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EnderCrystalController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EnderCrystalController.java
new file mode 100644
index 000000000..94f8d3b6f
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EnderCrystalController.java
@@ -0,0 +1,124 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEnderCrystal;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EnderPearlController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EnderPearlController.java
new file mode 100644
index 000000000..2523d8476
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EnderPearlController.java
@@ -0,0 +1,128 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEnderPearl;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.entity.EnderPearl;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.isProtected()) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EnderSignalController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EnderSignalController.java
new file mode 100644
index 000000000..b71d28dce
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EnderSignalController.java
@@ -0,0 +1,125 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEnderSignal;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.entity.EnderSignal;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EvokerFangsController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EvokerFangsController.java
new file mode 100644
index 000000000..7bf880682
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/EvokerFangsController.java
@@ -0,0 +1,139 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_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_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class EvokerFangsNPC extends CraftEvokerFangs implements ForwardingNPCHolder {
+ public EvokerFangsNPC(EntityEvokerFangsNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ExperienceOrbController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ExperienceOrbController.java
new file mode 100644
index 000000000..716188a14
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ExperienceOrbController.java
@@ -0,0 +1,124 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftExperienceOrb;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.ExperienceOrb;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class ExperienceOrbNPC extends CraftExperienceOrb implements ForwardingNPCHolder {
+ public ExperienceOrbNPC(EntityExperienceOrbNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/FallingBlockController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/FallingBlockController.java
new file mode 100644
index 000000000..6ff4dbedc
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/FallingBlockController.java
@@ -0,0 +1,166 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftFallingBlock;
+import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers;
+import org.bukkit.entity.FallingBlock;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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.material.Fluid;
+import net.minecraft.world.phys.AABB;
+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 = CraftMagicNumbers.getBlock(npc.getItemProvider().get().getType());
+ final EntityFallingBlockNPC handle = new EntityFallingBlockNPC(EntityType.FALLING_BLOCK, ws, npc);
+ handle.setPos(at.getX(), at.getY(), at.getZ());
+ handle.setDeltaMovement(Vec3.ZERO);
+ NMSImpl.setFallingBlockState(handle, 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;
+ }
+
+ @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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @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();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/FireworkController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/FireworkController.java
new file mode 100644
index 000000000..35d26b5b1
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/FireworkController.java
@@ -0,0 +1,125 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftFirework;
+import org.bukkit.entity.Firework;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class FireworkNPC extends CraftFirework implements ForwardingNPCHolder {
+ public FireworkNPC(EntityFireworkNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/FishingHookController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/FishingHookController.java
new file mode 100644
index 000000000..fbe84d012
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/FishingHookController.java
@@ -0,0 +1,159 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_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_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+public class FishingHookController extends MobEntityController {
+ public FishingHookController() {
+ super(EntityFishingHookNPC.class);
+ }
+
+ @Override
+ protected org.bukkit.entity.Entity createEntity(Location at, NPC npc) {
+ ServerLevel level = ((CraftWorld) at.getWorld()).getHandle();
+ ServerPlayer sp = new ServerPlayer(level.getServer(), level,
+ new GameProfile(UUID.randomUUID(), "dummyfishhook")) {
+ };
+ sp.setPos(at.getX(), at.getY(), at.getZ());
+ sp.setYRot(at.getYaw());
+ sp.setXRot(at.getPitch());
+ sp.setHealth(20F);
+ sp.getInventory().items.set(sp.getInventory().selected, new ItemStack(Items.FISHING_ROD, 1));
+ final EntityFishingHookNPC handle = new EntityFishingHookNPC(EntityType.FISHING_BOBBER, level, npc, sp);
+ return handle.getBukkitEntity();
+ }
+
+ @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, null);
+ }
+
+ public EntityFishingHookNPC(EntityType extends FishingHook> types, Level level, NPC npc, ServerPlayer sp) {
+ super(sp, 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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ getPlayerOwner().unsetRemoved();
+ NMSImpl.setLife(this, 0);
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class FishingHookNPC extends CraftFishHook implements ForwardingNPCHolder {
+ public FishingHookNPC(EntityFishingHookNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/GlowItemFrameController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/GlowItemFrameController.java
new file mode 100644
index 000000000..4c004c361
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/GlowItemFrameController.java
@@ -0,0 +1,150 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftGlowItemFrame;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.entity.nonliving.ItemFrameController.EntityItemFrameNPC;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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.getBlockX(), at.getBlockY(), at.getBlockZ());
+ 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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ 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;
+ setItem(npc.getItemProvider().get());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/InteractionController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/InteractionController.java
new file mode 100644
index 000000000..a1ff41806
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/InteractionController.java
@@ -0,0 +1,124 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftInteraction;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.Interaction;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+public class InteractionController extends MobEntityController {
+ public InteractionController() {
+ super(EntityInteractionNPC.class);
+ }
+
+ @Override
+ public org.bukkit.entity.Interaction getBukkitEntity() {
+ return (org.bukkit.entity.Interaction) super.getBukkitEntity();
+ }
+
+ public static class EntityInteractionNPC extends Interaction implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityInteractionNPC(EntityType extends Interaction> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityInteractionNPC(EntityType extends Interaction> 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 InteractionNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class InteractionNPC extends CraftInteraction implements ForwardingNPCHolder {
+ public InteractionNPC(EntityInteractionNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ItemController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ItemController.java
new file mode 100644
index 000000000..cdc2fdefd
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ItemController.java
@@ -0,0 +1,152 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftItem;
+import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
+import org.bukkit.entity.Item;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+public class ItemController extends AbstractEntityController {
+ public ItemController() {
+ super(EntityItemNPC.class);
+ }
+
+ @Override
+ protected org.bukkit.entity.Entity createEntity(Location at, NPC npc) {
+ final EntityItemNPC handle = new EntityItemNPC(((CraftWorld) at.getWorld()).getHandle(), npc, at.getX(),
+ at.getY(), at.getZ(), CraftItemStack.asNMSCopy(npc.getItemProvider().get()));
+ 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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ItemDisplayController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ItemDisplayController.java
new file mode 100644
index 000000000..728bb7ba1
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ItemDisplayController.java
@@ -0,0 +1,136 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftItemDisplay;
+import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.entity.Display.ItemDisplay;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+public class ItemDisplayController extends MobEntityController {
+ public ItemDisplayController() {
+ super(EntityItemDisplayNPC.class);
+ }
+
+ @Override
+ protected org.bukkit.entity.Entity createEntity(Location at, NPC npc) {
+ final EntityItemDisplayNPC handle = new EntityItemDisplayNPC(EntityType.ITEM_DISPLAY,
+ ((CraftWorld) at.getWorld()).getHandle(), npc);
+ if (npc != null) {
+ handle.setItemStack(CraftItemStack.asNMSCopy(npc.getItemProvider().get()));
+ }
+ return handle.getBukkitEntity();
+ }
+
+ @Override
+ public org.bukkit.entity.ItemDisplay getBukkitEntity() {
+ return (org.bukkit.entity.ItemDisplay) super.getBukkitEntity();
+ }
+
+ public static class EntityItemDisplayNPC extends ItemDisplay implements NPCHolder {
+ private final CitizensNPC npc;
+
+ public EntityItemDisplayNPC(EntityType extends ItemDisplay> types, Level level) {
+ this(types, level, null);
+ }
+
+ public EntityItemDisplayNPC(EntityType extends ItemDisplay> 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 ItemDisplayNPC(this));
+ }
+ return super.getBukkitEntity();
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+
+ @Override
+ public boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class ItemDisplayNPC extends CraftItemDisplay implements ForwardingNPCHolder {
+ public ItemDisplayNPC(EntityItemDisplayNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ItemFrameController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ItemFrameController.java
new file mode 100644
index 000000000..eacfb11e7
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ItemFrameController.java
@@ -0,0 +1,149 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftItemFrame;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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.getBlockX(), at.getBlockY(), at.getBlockZ());
+ 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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ 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;
+ setItem(npc.getItemProvider().get());
+ }
+
+ @Override
+ public NPC getNPC() {
+ return npc;
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/LargeFireballController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/LargeFireballController.java
new file mode 100644
index 000000000..eb42f4b6a
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/LargeFireballController.java
@@ -0,0 +1,136 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftLargeFireball;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.isProtected()) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class LargeFireballNPC extends CraftLargeFireball implements ForwardingNPCHolder {
+ public LargeFireballNPC(EntityLargeFireballNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/LeashController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/LeashController.java
new file mode 100644
index 000000000..5bf5b2b45
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/LeashController.java
@@ -0,0 +1,130 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftLeash;
+import org.bukkit.entity.LeashHitch;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class LeashNPC extends CraftLeash implements ForwardingNPCHolder {
+ public LeashNPC(EntityLeashNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/LlamaSpitController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/LlamaSpitController.java
new file mode 100644
index 000000000..685e68c7e
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/LlamaSpitController.java
@@ -0,0 +1,144 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftLlamaSpit;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_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.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.isProtected()) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class LlamaSpitNPC extends CraftLlamaSpit implements ForwardingNPCHolder {
+ public LlamaSpitNPC(EntityLlamaSpitNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MarkerController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MarkerController.java
new file mode 100644
index 000000000..cc2f0d972
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MarkerController.java
@@ -0,0 +1,124 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMarker;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.Marker;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class MarkerNPC extends CraftMarker implements ForwardingNPCHolder {
+ public MarkerNPC(EntityMarkerNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartChestController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartChestController.java
new file mode 100644
index 000000000..cf355ff8a
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartChestController.java
@@ -0,0 +1,125 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMinecartChest;
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class MinecartChestNPC extends CraftMinecartChest implements ForwardingNPCHolder {
+ public MinecartChestNPC(EntityMinecartChestNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartCommandController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartCommandController.java
new file mode 100644
index 000000000..fe6935948
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartCommandController.java
@@ -0,0 +1,125 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMinecartCommand;
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class MinecartCommandNPC extends CraftMinecartCommand implements ForwardingNPCHolder {
+ public MinecartCommandNPC(EntityMinecartCommandNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartFurnaceController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartFurnaceController.java
new file mode 100644
index 000000000..a495a307b
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartFurnaceController.java
@@ -0,0 +1,125 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMinecartFurnace;
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class MinecartFurnaceNPC extends CraftMinecartFurnace implements ForwardingNPCHolder {
+ public MinecartFurnaceNPC(EntityMinecartFurnaceNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartHopperController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartHopperController.java
new file mode 100644
index 000000000..517af4a52
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartHopperController.java
@@ -0,0 +1,106 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
\ No newline at end of file
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartRideableController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartRideableController.java
new file mode 100644
index 000000000..73ec3d2ed
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartRideableController.java
@@ -0,0 +1,124 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMinecartRideable;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class MinecartRideableNPC extends CraftMinecartRideable implements ForwardingNPCHolder {
+ public MinecartRideableNPC(EntityMinecartRideableNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartSpawnerController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartSpawnerController.java
new file mode 100644
index 000000000..0a0784aa9
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartSpawnerController.java
@@ -0,0 +1,106 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
\ No newline at end of file
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartTNTController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartTNTController.java
new file mode 100644
index 000000000..a5eb7be1a
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/MinecartTNTController.java
@@ -0,0 +1,106 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.entity.Minecart;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ if (npc != null) {
+ npc.update();
+ NMSImpl.minecartItemLogic(this);
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+}
\ No newline at end of file
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/PaintingController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/PaintingController.java
new file mode 100644
index 000000000..1817f4b2e
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/PaintingController.java
@@ -0,0 +1,129 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPainting;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class PaintingNPC extends CraftPainting implements ForwardingNPCHolder {
+ public PaintingNPC(EntityPaintingNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ShulkerBulletController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ShulkerBulletController.java
new file mode 100644
index 000000000..f99c56bc4
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/ShulkerBulletController.java
@@ -0,0 +1,124 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftShulkerBullet;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class ShulkerBulletNPC extends CraftShulkerBullet implements ForwardingNPCHolder {
+ public ShulkerBulletNPC(EntityShulkerBulletNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/SmallFireballController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/SmallFireballController.java
new file mode 100644
index 000000000..f646bbd02
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/SmallFireballController.java
@@ -0,0 +1,127 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSmallFireball;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data(). get(NPC.Metadata.COLLIDABLE, !npc.isProtected());
+ }
+
+ @Override
+ protected AABB makeBoundingBox() {
+ return NMSBoundingBox.makeBB(npc, super.makeBoundingBox());
+ }
+
+ @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 Entity teleportTo(ServerLevel worldserver, PositionImpl location) {
+ if (npc == null)
+ return super.teleportTo(worldserver, location);
+ return NMSImpl.teleportAcrossWorld(this, worldserver, location);
+ }
+
+ @Override
+ public void tick() {
+ if (npc != null) {
+ npc.update();
+ if (!npc.isProtected()) {
+ super.tick();
+ }
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean updateFluidHeightAndDoFluidPushing(TagKey tagkey, double d0) {
+ return NMSImpl.fluidPush(npc, this, () -> super.updateFluidHeightAndDoFluidPushing(tagkey, d0));
+ }
+ }
+
+ public static class SmallFireballNPC extends CraftSmallFireball implements ForwardingNPCHolder {
+ public SmallFireballNPC(EntitySmallFireballNPC entity) {
+ super((CraftServer) Bukkit.getServer(), entity);
+ }
+ }
+}
diff --git a/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/SnowballController.java b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/SnowballController.java
new file mode 100644
index 000000000..11a9772bb
--- /dev/null
+++ b/v1_20_R1/src/main/java/net/citizensnpcs/nms/v1_20_R1/entity/nonliving/SnowballController.java
@@ -0,0 +1,124 @@
+package net.citizensnpcs.nms.v1_20_R1.entity.nonliving;
+
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftSnowball;
+import org.bukkit.util.Vector;
+
+import net.citizensnpcs.api.npc.NPC;
+import net.citizensnpcs.nms.v1_20_R1.entity.MobEntityController;
+import net.citizensnpcs.nms.v1_20_R1.util.ForwardingNPCHolder;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSBoundingBox;
+import net.citizensnpcs.nms.v1_20_R1.util.NMSImpl;
+import net.citizensnpcs.npc.CitizensNPC;
+import net.citizensnpcs.npc.ai.NPCHolder;
+import net.citizensnpcs.util.Util;
+import net.minecraft.core.PositionImpl;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.TagKey;
+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;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.phys.AABB;
+
+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 boolean isPushable() {
+ return npc == null ? super.isPushable()
+ : npc.data().