diff --git a/.idea/artifacts/TamableFoxes_SNAPSHOT_jar.xml b/.idea/artifacts/TamableFoxes_SNAPSHOT_jar.xml
new file mode 100644
index 0000000..77d46d5
--- /dev/null
+++ b/.idea/artifacts/TamableFoxes_SNAPSHOT_jar.xml
@@ -0,0 +1,8 @@
+
+
+ $USER_HOME$/OneDrive/Desktop/minecraftSpigotServer/plugins
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..e106ccb
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..bee17f4
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__net_wesjd_anvilgui_1_2_1_SNAPSHOT.xml b/.idea/libraries/Maven__net_wesjd_anvilgui_1_2_1_SNAPSHOT.xml
new file mode 100644
index 0000000..08ed1c0
--- /dev/null
+++ b/.idea/libraries/Maven__net_wesjd_anvilgui_1_2_1_SNAPSHOT.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__org_spigotmc_spigot_1_14_4_R0_1_SNAPSHOT.xml b/.idea/libraries/Maven__org_spigotmc_spigot_1_14_4_R0_1_SNAPSHOT.xml
new file mode 100644
index 0000000..ce394c9
--- /dev/null
+++ b/.idea/libraries/Maven__org_spigotmc_spigot_1_14_4_R0_1_SNAPSHOT.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..6fb7c97
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..6b3344f
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..b93ac08
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..5a0ff9a
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1559787286537
+
+
+ 1559787286537
+
+
+
+
+
+
+
+
+ jar://$MAVEN_REPOSITORY$/org/spigotmc/spigot/1.14.2-R0.1-SNAPSHOT/spigot-1.14.2-R0.1-SNAPSHOT.jar!/net/minecraft/server/v1_14_R1/PathfinderGoalWrapped.class
+ 17
+
+
+
+
+
+
+
+
+
+
+
+
+ var1 == null
+ JAVA
+ EXPRESSION
+
+
+ this.a == null
+ JAVA
+ EXPRESSION
+
+
+ this.targetSelector.a == null
+ JAVA
+ EXPRESSION
+
+
+ this.a
+ JAVA
+ EXPRESSION
+
+
+ null
+ JAVA
+ EXPRESSION
+
+
+
+
+
+
+
+
+ false
+
+ TamableFoxes-SNAPSHOT:jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TamableFoxes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d73b179
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+
Tamable Foxes
+
+SpigotMC Plugin that gives you the ability to tame foxes!
+
+
+
+This code is currently only updated to work on 1.14.4. Due to the way this plugin works with NMS, it is extremely difficult to maintain for each new spigot update.
+
+Features:
+* 33% Chance of taming
+* Breeding
+* Wild foxes pick berry bushes
+* Leaping on targets
+* Tamed foxes sleep when their owner does
+* Foxes follow owner
+* You can shift + right-click to let the fox hold items
+* Right-click to make the fox sit
+* If the fox is holding a totem of undying, the fox will consume it and be reborn.
+* Foxes attack the owner's target
+* Foxes attack the thing that attacked the owner.
+* Foxes are automatically spawned inside the world. (Same areas as vanilla foxes)
+* Foxes attack chickens and rabbits.
+* Snow and red foxes.
+
+Commands:
+* /spawntamablefox [red/snow]: Spawns a tamable fox at the players location.
+Permissions:
+* tamablefoxes.spawn: Gives permission to run the command /spawntamablefox.
+
+![foxes sleeping](https://www.spigotmc.org/attachments/2019-07-18_23-19-12-png.441396/)
+![foxes sitting on bed](https://www.spigotmc.org/attachments/2019-07-18_23-20-49-png.441398/)
+![foxes with baby looking at player](https://www.spigotmc.org/attachments/2019-07-18_23-24-24-png.441399/)
+![giving fox totem](https://proxy.spigotmc.org/3c5f51d25b3fbb2e6bdacaa995c4e87538c13c62?url=https%3A%2F%2Fi.gyazo.com%2F34f94804beb17ffa68b73874c8c8d1d5.gif)
+![fox leaping towards chicken](https://proxy.spigotmc.org/4527d9b156ae8ad83f4b67a1407c67376eabcc52?url=https%3A%2F%2Fi.gyazo.com%2F367b397a86f31b0ba27fba228c295347.gif)
\ No newline at end of file
diff --git a/TamableFoxes.iml b/TamableFoxes.iml
new file mode 100644
index 0000000..2718c32
--- /dev/null
+++ b/TamableFoxes.iml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ SPIGOT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..fa38b7b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,85 @@
+
+
+ 4.0.0
+
+ net.seanomik
+ tamableFoxes
+ 1.3.2-SNAPSHOT
+ jar
+
+ TamableFoxes
+
+
+ 1.8
+ UTF-8
+
+
+
+ clean package
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.7.0
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.1.0
+
+
+ package
+
+ shade
+
+
+ C:\Users\sean_\OneDrive\Desktop\minecraftSpigotServer\plugins\${project.artifactId}-${project.version}.jar
+ false
+
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+
+ sonatype
+ https://oss.sonatype.org/content/groups/public/
+
+
+ wesjd-repo
+ https://nexus.wesjd.net/repository/thirdparty/
+
+
+
+
+
+ org.spigotmc
+ spigot
+ 1.14.4-R0.1-SNAPSHOT
+ provided
+
+
+ net.wesjd
+ anvilgui
+ 1.2.1-SNAPSHOT
+
+
+
diff --git a/src/main/java/net/seanomik/tamablefoxes/Commands/CommandSpawnTamableFox.java b/src/main/java/net/seanomik/tamablefoxes/Commands/CommandSpawnTamableFox.java
new file mode 100644
index 0000000..6386e6d
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/Commands/CommandSpawnTamableFox.java
@@ -0,0 +1,70 @@
+package net.seanomik.tamablefoxes.Commands;
+
+import net.minecraft.server.v1_14_R1.EntityFox;
+import net.seanomik.tamablefoxes.Reference;
+import net.seanomik.tamablefoxes.TamableFoxes;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+public class CommandSpawnTamableFox implements CommandExecutor {
+
+ private TamableFoxes plugin = TamableFoxes.getPlugin(TamableFoxes.class);
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
+ if (sender instanceof Player) {
+ Player player = (Player) sender;
+ TamableFoxes plugin = TamableFoxes.getPlugin(TamableFoxes.class);
+
+ if (player.hasPermission("tamableFoxes.spawntamablefox")) {
+ if (args.length != 0) {
+ if (args[0].equals("snow")) {
+ plugin.spawnTamableFox(player.getLocation(), EntityFox.Type.SNOW);
+ player.sendMessage("Spawned snow fox.");
+ } else if (args[0].equals("verbose")) {
+ player.sendMessage(TamableFoxes.foxUUIDs.toString());
+ } else if (args[0].equals("inspect")) {
+ ItemStack itemStack = new ItemStack(Material.REDSTONE_TORCH, 1);
+ ItemMeta itemMeta = itemStack.getItemMeta();
+ List lore = new LinkedList<>(Arrays.asList(
+ ChatColor.BLUE + "Tamable Fox Inspector"
+ ));
+
+ itemMeta.setLore(lore);
+ itemStack.setItemMeta(itemMeta);
+ if (player.getInventory().getItemInMainHand().getType() == Material.AIR) {
+ player.getInventory().setItemInMainHand(itemStack);
+ player.sendMessage(Reference.CHAT_PREFIX + ChatColor.GREEN + "Given item.");
+ } else {
+ if (player.getInventory().firstEmpty() == -1) {
+ player.sendMessage(Reference.CHAT_PREFIX + ChatColor.RED + "Inventory is full!");
+ } else {
+ player.sendMessage(Reference.CHAT_PREFIX + ChatColor.GREEN + "Added item to inventory.");
+ player.getInventory().addItem(itemStack);
+ }
+ }
+
+ //player.sendMessage(TamableFoxes.foxUUIDs.toString());
+ }
+ } else {
+ plugin.spawnTamableFox(player.getLocation(), EntityFox.Type.RED);
+ player.sendMessage("Spawned red fox.");
+ }
+ } else {
+ player.sendMessage(ChatColor.RED + "You do not have the permission for this command.");
+ }
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/seanomik/tamablefoxes/ConfigManager.java b/src/main/java/net/seanomik/tamablefoxes/ConfigManager.java
new file mode 100644
index 0000000..bf4589f
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/ConfigManager.java
@@ -0,0 +1,54 @@
+package net.seanomik.tamablefoxes;
+
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ConfigManager {
+ private TamableFoxes plugin = TamableFoxes.getPlugin(TamableFoxes.class);
+
+ //Files and File Configs Here
+ public FileConfiguration foxesSave;
+ public File foxesfile;
+
+ public void setup() {
+ if (!plugin.getDataFolder().exists()) {
+ plugin.getDataFolder().mkdir();
+ }
+
+ foxesfile = new File(plugin.getDataFolder(), "Foxes.yml");
+
+ if (!foxesfile.exists()) {
+ try {
+ foxesfile.createNewFile();
+ } catch (IOException e) {
+ Bukkit.getServer().getConsoleSender().sendMessage(ChatColor.RED + "Could not create the Foxes.yml file!");
+ }
+ }
+
+ foxesSave = YamlConfiguration.loadConfiguration(foxesfile);
+ Bukkit.getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "The Foxes.yml file has been created successfully!");
+ }
+
+ public FileConfiguration getFoxes() {
+ return foxesSave;
+ }
+
+ public void saveFoxes() {
+ try {
+ foxesSave.save(foxesfile);
+ Bukkit.getServer().getConsoleSender().sendMessage(ChatColor.AQUA + "The Foxes.yml file has been saved successfully!");
+ } catch (IOException e) {
+ Bukkit.getServer().getConsoleSender().sendMessage(ChatColor.RED + "Could not save the Foxes.yml file!");
+ }
+ }
+
+ public void reloadFoxes() {
+ foxesSave = YamlConfiguration.loadConfiguration(foxesfile);
+ Bukkit.getServer().getConsoleSender().sendMessage(ChatColor.BLUE + "The Foxes.yml file has been reloaded successfully!");
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBeg.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBeg.java
new file mode 100644
index 0000000..9936f61
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBeg.java
@@ -0,0 +1,72 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.TamableFox;
+
+import java.util.EnumSet;
+
+public class FoxPathfinderGoalBeg extends PathfinderGoal {
+ private final EntityFox a;
+ private final TamableFox z;
+ private EntityHuman b;
+ private final World c;
+ private final float d;
+ private int e;
+ private final PathfinderTargetCondition f;
+
+ public FoxPathfinderGoalBeg(TamableFox tamableFox, float var1) {
+ this.a = tamableFox;
+ this.c = tamableFox.world;
+ this.d = var1;
+ this.f = (new PathfinderTargetCondition()).a((double)var1).a().b().d();
+ this.a(EnumSet.of(Type.LOOK));
+ this.z = tamableFox;
+ }
+
+ public boolean a() {
+ this.b = this.c.a(this.f, this.a);
+ return this.b == null ? false : this.a(this.b);
+ }
+
+ public boolean b() {
+ if (!this.b.isAlive()) {
+ return false;
+ } else if (this.a.h(this.b) > (double)(this.d * this.d)) {
+ return false;
+ } else {
+ return this.e > 0 && this.a(this.b);
+ }
+ }
+
+ /*public void c() {
+ ((EntityFox)this.a).v(true);
+ this.e = 40 + this.a.getRandom().nextInt(40);
+ }
+
+ public void d() {
+ this.a.v(false);
+ this.b = null;
+ }*/
+
+ public void e() {
+ this.a.getControllerLook().a(this.b.locX, this.b.locY + (double)this.b.getHeadHeight(), this.b.locZ, 10.0F, (float)this.a.M());
+ --this.e;
+ }
+
+ private boolean a(EntityHuman var0) {
+ EnumHand[] var2 = EnumHand.values();
+ int var3 = var2.length;
+
+ for(int var4 = 0; var4 < var3; ++var4) {
+ if (this.z.isTamed() && var0.getItemInMainHand().getItem() == Items.SWEET_BERRIES) { //var5.getItem() == Items.SWEET_BERRIES) {
+ return true;
+ }
+
+ if (this.a.i(var0.getItemInMainHand())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBreed.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBreed.java
new file mode 100644
index 0000000..8788698
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBreed.java
@@ -0,0 +1,132 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.TamableFox;
+import net.seanomik.tamablefoxes.TamableFoxes;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_14_R1.event.CraftEventFactory;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.bukkit.event.entity.EntityBreedEvent;
+
+import javax.annotation.Nullable;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+
+public class FoxPathfinderGoalBreed extends PathfinderGoal {
+ private static final PathfinderTargetCondition d = (new PathfinderTargetCondition()).a(8.0D).a().b().c();
+ protected final TamableFox animal;
+ private final Class extends EntityAnimal> e;
+ protected final World b;
+ protected EntityAnimal partner;
+ private int f;
+ private final double g;
+
+ public FoxPathfinderGoalBreed(TamableFox entityanimal, double d0) {
+ this(entityanimal, d0, entityanimal.getClass());
+ }
+
+ public FoxPathfinderGoalBreed(TamableFox entityanimal, double d0, Class extends EntityAnimal> oclass) {
+ this.animal = entityanimal;
+ this.b = entityanimal.world;
+ this.e = oclass;
+ this.g = d0;
+ this.a(EnumSet.of(Type.MOVE, Type.LOOK));
+ }
+
+ public boolean a() {
+ if (!this.animal.isInLove()) {
+ return false;
+ } else {
+ this.partner = this.h();
+ return this.partner != null;
+ }
+ }
+
+ public boolean b() {
+ return this.partner.isAlive() && this.partner.isInLove() && this.f < 60;
+ }
+
+ public void d() {
+ this.partner = null;
+ this.f = 0;
+ }
+
+ public void e() {
+ this.animal.getControllerLook().a(this.partner, 10.0F, (float)this.animal.M());
+ this.animal.getNavigation().a(this.partner, this.g);
+ ++this.f;
+ if (this.f >= 60 && this.animal.h(this.partner) < 9.0D) {
+ this.g();
+ }
+
+ }
+
+ @Nullable
+ private EntityAnimal h() {
+ List list = this.b.a(this.e, d, this.animal, this.animal.getBoundingBox().g(8.0D));
+ double d0 = 1.7976931348623157E308D;
+ EntityAnimal entityanimal = null;
+ Iterator iterator = list.iterator();
+
+ while(iterator.hasNext()) {
+ EntityAnimal entityanimal1 = (EntityAnimal)iterator.next();
+ if (this.animal.mate(entityanimal1) && this.animal.h(entityanimal1) < d0) {
+ entityanimal = entityanimal1;
+ d0 = this.animal.h(entityanimal1);
+ }
+ }
+
+ return entityanimal;
+ }
+
+ protected void g() {
+ EntityAgeable entityageable = this.animal.createChild(animal);
+ TamableFox tamableFoxAgeable = (TamableFox) entityageable.getBukkitEntity().getHandle();
+
+ if (entityageable != null) {
+ if (entityageable instanceof EntityTameableAnimal && ((EntityTameableAnimal)entityageable).isTamed()) {
+ entityageable.persistent = true;
+ }
+
+ EntityPlayer entityplayer = this.animal.getBreedCause();
+ if (entityplayer == null && this.partner.getBreedCause() != null) {
+ entityplayer = this.partner.getBreedCause();
+ }
+
+ int experience = this.animal.getRandom().nextInt(7) + 1;
+ EntityBreedEvent entityBreedEvent = CraftEventFactory.callEntityBreedEvent(entityageable, this.animal, this.partner, entityplayer, this.animal.breedItem, experience);
+ if (entityBreedEvent.isCancelled()) {
+ return;
+ }
+
+ experience = entityBreedEvent.getExperience();
+ if (entityplayer != null) {
+ entityplayer.a(StatisticList.ANIMALS_BRED);
+ CriterionTriggers.o.a(entityplayer, this.animal, this.partner, entityageable);
+ }
+
+ this.animal.setAgeRaw(6000);
+ this.partner.setAgeRaw(6000);
+ this.animal.resetLove();
+ this.partner.resetLove();
+ entityageable.setAgeRaw(-24000);
+ entityageable.setPositionRotation(this.animal.locX, this.animal.locY, this.animal.locZ, 0.0F, 0.0F);
+ this.b.addEntity(entityageable, CreatureSpawnEvent.SpawnReason.BREEDING);
+ this.b.broadcastEntityEffect(this.animal, (byte)18);
+
+ TamableFox tamableFoxBaby = (TamableFox) entityageable;
+ tamableFoxBaby.setTamed(true);
+ tamableFoxBaby.setOwner(entityplayer);
+
+ // Add fox to foxUUIDs to get their owner and other things
+ TamableFoxes.foxUUIDs.replace(tamableFoxBaby.getUniqueID(), null, entityplayer.getUniqueID());
+
+ if (this.b.getGameRules().getBoolean(GameRules.DO_MOB_LOOT) && experience > 0) {
+ this.b.addEntity(new EntityExperienceOrb(this.b, this.animal.locX, this.animal.locY, this.animal.locZ, experience));
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFleeSun.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFleeSun.java
new file mode 100644
index 0000000..c0dd4a5
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFleeSun.java
@@ -0,0 +1,52 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.BlockPosition;
+import net.minecraft.server.v1_14_R1.PathfinderGoalFleeSun;
+import net.minecraft.server.v1_14_R1.WorldServer;
+import net.seanomik.tamablefoxes.TamableFox;
+import org.bukkit.Bukkit;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class FoxPathfinderGoalFleeSun extends PathfinderGoalFleeSun {
+ private int c = 100;
+ protected TamableFox tamableFox;
+
+ public FoxPathfinderGoalFleeSun(TamableFox tamableFox, double d0) {
+ super(tamableFox, d0);
+ this.tamableFox = tamableFox;
+ }
+
+ public boolean a() {
+ if (tamableFox.isTamed()) {
+ return false;
+ } else if (!tamableFox.isSleeping() && this.a.getGoalTarget() == null) {
+ if (tamableFox.world.U()) {
+ return true;
+ } else if (this.c > 0) {
+ --this.c;
+ return false;
+ } else {
+ this.c = 100;
+ BlockPosition blockposition = new BlockPosition(this.a);
+ return tamableFox.world.J() && tamableFox.world.f(blockposition) && !((WorldServer)tamableFox.world).b_(blockposition) && this.g();
+ }
+ } else {
+ return false;
+ }
+ }
+
+ public void c() {
+ try {
+ Class> entityFoxClass = Class.forName("net.minecraft.server.v1_14_R1.EntityFox");
+ Method method = entityFoxClass.getDeclaredMethod("en");
+ method.setAccessible(true);
+ method.invoke(tamableFox);
+ method.setAccessible(false);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ super.c();
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFloat.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFloat.java
new file mode 100644
index 0000000..5512bdf
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFloat.java
@@ -0,0 +1,33 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.EntityFox;
+import net.minecraft.server.v1_14_R1.PathfinderGoalFloat;
+import net.seanomik.tamablefoxes.TamableFox;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class FoxPathfinderGoalFloat extends PathfinderGoalFloat {
+ protected TamableFox tamableFox;
+ public FoxPathfinderGoalFloat(TamableFox tamableFox) {
+ super(tamableFox);
+ this.tamableFox = tamableFox;
+ }
+
+ public void c() {
+ try {
+ super.c();
+ Method method = tamableFox.getClass().getSuperclass().getDeclaredMethod("en");
+ method.setAccessible(true);
+ method.invoke(tamableFox);
+ method.setAccessible(false);
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ //tamableFox.en();
+ }
+
+ public boolean a() {
+ return tamableFox.isInWater() && tamableFox.cf() > 0.25D || tamableFox.aD();
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFollowOwner.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFollowOwner.java
new file mode 100644
index 0000000..03ac84b
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFollowOwner.java
@@ -0,0 +1,103 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.TamableFox;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_14_R1.entity.CraftEntity;
+import org.bukkit.event.entity.EntityTeleportEvent;
+
+import java.util.EnumSet;
+
+public class FoxPathfinderGoalFollowOwner extends PathfinderGoal {
+ protected final TamableFox a;
+ private EntityLiving c;
+ protected final IWorldReader b;
+ private final double d;
+ private final NavigationAbstract e;
+ private int f;
+ private final float g;
+ private final float h;
+ private float i;
+
+ public FoxPathfinderGoalFollowOwner(TamableFox tamableFox, double d0, float f, float f1) {
+ this.a = tamableFox;
+ this.b = tamableFox.world;
+ this.d = d0;
+ this.e = tamableFox.getNavigation();
+ this.h = f;
+ this.g = f1;
+ this.a(EnumSet.of(Type.MOVE, Type.LOOK));
+ if (!(tamableFox.getNavigation() instanceof Navigation) && !(tamableFox.getNavigation() instanceof NavigationFlying)) {
+ throw new IllegalArgumentException("Unsupported mob type for FollowOwnerGoal");
+ }
+ }
+
+ public boolean a() {
+ EntityLiving entityliving = this.a.getOwner();
+ if (entityliving == null) {
+ return false;
+ } else if (entityliving instanceof EntityHuman && ((EntityHuman)entityliving).isSpectator()) {
+ return false;
+ } else if (this.a.isSitting()) {
+ return false;
+ } else if (this.a.h(entityliving) < (double)(this.h * this.h)) {
+ return false;
+ } else {
+ this.c = entityliving;
+ return true;
+ }
+ }
+
+ public boolean b() {
+ return !this.e.n() && this.a.h(this.c) > (double)(this.g * this.g) && !this.a.isSitting();
+ }
+
+ public void c() {
+ this.f = 0;
+ this.i = this.a.a(PathType.WATER);
+ this.a.a(PathType.WATER, 0.0F);
+ }
+
+ public void d() {
+ this.c = null;
+ this.e.o();
+ this.a.a(PathType.WATER, this.i);
+ }
+
+ public void e() {
+ this.a.getControllerLook().a(this.c, 10.0F, (float)this.a.M());
+ if (!this.a.isSitting() && --this.f <= 0) {
+ this.f = 10;
+ if (!this.e.a(this.c, this.d) && !this.a.isLeashed() && !this.a.isPassenger() && this.a.h(this.c) >= 144.0D) {
+ int i = MathHelper.floor(this.c.locX) - 2;
+ int j = MathHelper.floor(this.c.locZ) - 2;
+ int k = MathHelper.floor(this.c.getBoundingBox().minY);
+
+ for(int l = 0; l <= 4; ++l) {
+ for(int i1 = 0; i1 <= 4; ++i1) {
+ if ((l < 1 || i1 < 1 || l > 3 || i1 > 3) && this.a(new BlockPosition(i + l, k - 1, j + i1))) {
+ CraftEntity entity = this.a.getBukkitEntity();
+ Location to = new Location(entity.getWorld(), (double)((float)(i + l) + 0.5F), (double)k, (double)((float)(j + i1) + 0.5F), this.a.yaw, this.a.pitch);
+ EntityTeleportEvent event = new EntityTeleportEvent(entity, entity.getLocation(), to);
+ this.a.world.getServer().getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ return;
+ }
+
+ to = event.getTo();
+ this.a.setPositionRotation(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
+ this.e.o();
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ protected boolean a(BlockPosition blockposition) {
+ IBlockData iblockdata = this.b.getType(blockposition);
+ return iblockdata.a(this.b, blockposition, this.a.getEntityType()) && this.b.isEmpty(blockposition.up()) && this.b.isEmpty(blockposition.up(2));
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalHurtByTarget.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalHurtByTarget.java
new file mode 100644
index 0000000..e3e20c4
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalHurtByTarget.java
@@ -0,0 +1,120 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.TamableFox;
+import org.bukkit.Bukkit;
+import org.bukkit.event.entity.EntityTargetEvent;
+
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+
+public class FoxPathfinderGoalHurtByTarget extends PathfinderGoalTarget {
+ private static final PathfinderTargetCondition a = (new PathfinderTargetCondition()).c().e();
+ private boolean b;
+ private int c;
+ private final Class>[] d;
+ private Class>[] i;
+
+ public FoxPathfinderGoalHurtByTarget(TamableFox entitycreature, Class>... aclass) {
+ super(entitycreature, true);
+ this.d = aclass;
+ this.a(EnumSet.of(Type.TARGET));
+ }
+
+ public boolean a() {
+ int i = this.e.ct();
+ EntityLiving entityliving = this.e.getLastDamager();
+ if (i != this.c && entityliving != null) {
+ Class[] aclass = this.d;
+ int j = aclass.length;
+
+ for(int k = 0; k < j; ++k) {
+ Class> oclass = aclass[k];
+ if (oclass.isAssignableFrom(entityliving.getClass())) {
+ return false;
+ }
+ }
+
+ return this.a(entityliving, a);
+ } else {
+ return false;
+ }
+ }
+
+ public FoxPathfinderGoalHurtByTarget a(Class>... aclass) {
+ this.b = true;
+ this.i = aclass;
+ return this;
+ }
+
+ public void c() {
+ if ( !(e instanceof TamableFox && ((TamableFox) e).getOwner() == e.getLastDamager()) ) {
+ this.e.setGoalTarget(this.e.getLastDamager(), EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true);
+ }
+
+ this.g = this.e.getGoalTarget();
+ this.c = this.e.ct();
+ this.h = 300;
+ if (this.b) {
+ this.g();
+ }
+
+ super.c();
+ }
+
+ protected void g() {
+ double d0 = this.k();
+ List list = this.e.world.b(this.e.getClass(), (new AxisAlignedBB(this.e.locX, this.e.locY, this.e.locZ, this.e.locX + 1.0D, this.e.locY + 1.0D, this.e.locZ + 1.0D)).grow(d0, 10.0D, d0));
+ Iterator iterator = list.iterator();
+
+ while(true) {
+ EntityInsentient entityinsentient;
+ boolean flag;
+ do {
+ do {
+ do {
+ do {
+ do {
+ if (!iterator.hasNext()) {
+ return;
+ }
+
+ entityinsentient = (EntityInsentient)iterator.next();
+ } while (this.e == entityinsentient);
+ } while (entityinsentient.getGoalTarget() != null);
+
+ //Bukkit.broadcastMessage(String.valueOf(this.e instanceof TamableFox && ((TamableFox)this.e).getOwner() != ((TamableFox) entityinsentient).getOwner()));
+
+ } while (this.e instanceof TamableFox && ((TamableFox)this.e).getOwner() != ((TamableFox) entityinsentient).getOwner());
+ } while (entityinsentient.r(this.e.getLastDamager()));
+
+ if (this.i == null) {
+ break;
+ }
+
+ flag = false;
+ Class[] aclass = this.i;
+ int i = aclass.length;
+
+ for(int j = 0; j < i; ++j) {
+ Class> oclass = aclass[j];
+ if (entityinsentient.getClass() == oclass) {
+ flag = true;
+ break;
+ }
+ }
+ } while(flag);
+
+ this.a(entityinsentient, this.e.getLastDamager());
+ }
+ }
+
+ protected void a(EntityInsentient entityinsentient, EntityLiving entityliving) {
+ if (entityinsentient instanceof TamableFox && ((TamableFox) entityinsentient).getOwner() == entityliving) {
+ return;
+ }
+
+ entityinsentient.setGoalTarget(entityliving, EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true);
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLunge.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLunge.java
new file mode 100644
index 0000000..237e3f0
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLunge.java
@@ -0,0 +1,125 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.TamableFox;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class FoxPathfinderGoalLunge extends PathfinderGoalWaterJumpAbstract {
+ protected TamableFox tamableFox;
+
+ public FoxPathfinderGoalLunge(TamableFox tamableFox) {
+ this.tamableFox = tamableFox;
+ }
+
+ public boolean a() {
+ if (!tamableFox.ee()) {
+ return false;
+ } else {
+ EntityLiving entityliving = tamableFox.getGoalTarget();
+ if (entityliving != null && entityliving.isAlive()) {
+ if (entityliving.getAdjustedDirection() != entityliving.getDirection()) {
+ return false;
+ } else {
+ boolean flag = EntityFox.a((EntityFox)tamableFox, (EntityLiving)entityliving);
+ if (!flag) {
+ tamableFox.getNavigation().a(entityliving, 0);
+ tamableFox.setCrouching(false);
+ tamableFox.u(false);
+ }
+
+ return flag;
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ public boolean b() {
+ EntityLiving entityliving = tamableFox.getGoalTarget();
+ if (entityliving != null && entityliving.isAlive()) {
+ double d0 = tamableFox.getMot().y;
+ return (d0 * d0 >= 0.05000000074505806D || Math.abs(tamableFox.pitch) >= 15.0F || !tamableFox.onGround) && !tamableFox.dX();
+ } else {
+ return false;
+ }
+ }
+
+ public boolean C_() {
+ return false;
+ }
+
+ public void c() {
+ tamableFox.setJumping(true);
+ tamableFox.s(true);
+ tamableFox.u(false);
+ EntityLiving entityliving = tamableFox.getGoalTarget();
+ tamableFox.getControllerLook().a(entityliving, 60.0F, 30.0F);
+ Vec3D vec3d = (new Vec3D(entityliving.locX - tamableFox.locX, entityliving.locY - tamableFox.locY, entityliving.locZ - tamableFox.locZ)).d();
+ tamableFox.setMot(tamableFox.getMot().add(vec3d.x * 0.8D, 0.9D, vec3d.z * 0.8D));
+ tamableFox.getNavigation().o();
+ }
+
+ public void d() {
+ tamableFox.setCrouching(false);
+ try {
+ Class> entityFoxClass = Class.forName("net.minecraft.server.v1_14_R1.EntityFox");
+ Field field = entityFoxClass.getDeclaredField("bN");
+ field.setAccessible(true);
+ field.set(tamableFox, 0.0F);
+ field.setAccessible(false);
+
+ field = entityFoxClass.getDeclaredField("bO");
+ field.setAccessible(true);
+ field.set(tamableFox, 0);
+ field.setAccessible(false);
+ /*tamableFox.bN = 0.0F;
+ tamableFox.bO = 0.0F;*/
+ } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ tamableFox.u(false);
+ tamableFox.s(false);
+ }
+
+ public void e() {
+ EntityLiving entityliving = tamableFox.getGoalTarget();
+ if (entityliving != null) {
+ tamableFox.getControllerLook().a(entityliving, 60.0F, 30.0F);
+ }
+
+ if (!tamableFox.dX()) {
+ Vec3D vec3d = tamableFox.getMot();
+ if (vec3d.y * vec3d.y < 0.029999999329447746D && tamableFox.pitch != 0.0F) {
+ tamableFox.pitch = this.a(tamableFox.pitch, 0.0F, 0.2F);
+ } else {
+ double d0 = Math.sqrt(Entity.b(vec3d));
+ double d1 = Math.signum(-vec3d.y) * Math.acos(d0 / vec3d.f()) * 57.2957763671875D;
+ tamableFox.pitch = (float)d1;
+ }
+ }
+
+ if (entityliving != null && tamableFox.g(entityliving) <= 2.0F) {
+ tamableFox.C(entityliving);
+ } else if (tamableFox.pitch > 0.0F && tamableFox.onGround && (float)tamableFox.getMot().y != 0.0F && tamableFox.world.getType(new BlockPosition(tamableFox)).getBlock() == Blocks.SNOW) {
+ tamableFox.pitch = 60.0F;
+ tamableFox.setGoalTarget((EntityLiving)null);
+
+ try {
+ Class> entityFoxClass = Class.forName("net.minecraft.server.v1_14_R1.EntityFox");
+ Method method = entityFoxClass.getDeclaredMethod("v");
+ method.setAccessible(true);
+ method.invoke(tamableFox, true);
+ method.setAccessible(false);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ //tamableFox.v(true);
+ }
+
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLungeUNKNOWN_USE.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLungeUNKNOWN_USE.java
new file mode 100644
index 0000000..ee84314
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLungeUNKNOWN_USE.java
@@ -0,0 +1,80 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.TamableFox;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.EnumSet;
+import java.util.function.Predicate;
+
+public class FoxPathfinderGoalLungeUNKNOWN_USE extends PathfinderGoal {
+ protected TamableFox tamableFox;
+
+ public FoxPathfinderGoalLungeUNKNOWN_USE(TamableFox tamableFox) {
+ this.a(EnumSet.of(net.minecraft.server.v1_14_R1.PathfinderGoal.Type.MOVE, net.minecraft.server.v1_14_R1.PathfinderGoal.Type.LOOK));
+ this.tamableFox = tamableFox;
+ }
+
+ public boolean a() {
+ if (tamableFox.isSleeping()) {
+ return false;
+ } else {
+ EntityLiving entityliving = tamableFox.getGoalTarget();
+
+ try {
+ Class> entityFoxClass = Class.forName("net.minecraft.server.v1_14_R1.EntityFox");
+ Field field = entityFoxClass.getDeclaredField("bG");
+ field.setAccessible(true);
+ Predicate bG = (Predicate) field.get(tamableFox);
+ field.setAccessible(false);
+
+ return entityliving != null && entityliving.isAlive() && bG.test(entityliving) && tamableFox.h(entityliving) > 36.0D && !tamableFox.isCrouching() && !tamableFox.eg() && !tamableFox.isJumping();
+ } catch (IllegalAccessException | ClassNotFoundException | NoSuchFieldException e) {
+ e.printStackTrace();
+ }
+
+ throw new NullPointerException();
+ }
+ }
+
+ public void c() {
+ tamableFox.setSitting(false);
+ try {
+ Method method = tamableFox.getClass().getSuperclass().getDeclaredMethod("v", boolean.class);
+ method.setAccessible(true);
+ method.invoke(tamableFox, false);
+ method.setAccessible(false);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void d() {
+ EntityLiving entityliving = tamableFox.getGoalTarget();
+ if (entityliving != null && EntityFox.a((EntityFox)tamableFox, (EntityLiving)entityliving)) {
+ tamableFox.u(true);
+ tamableFox.setCrouching(true);
+ tamableFox.getNavigation().o();
+ tamableFox.getControllerLook().a(entityliving, (float)tamableFox.dA(), (float)tamableFox.M());
+ } else {
+ tamableFox.u(false);
+ tamableFox.setCrouching(false);
+ }
+
+ }
+
+ public void e() {
+ EntityLiving entityliving = tamableFox.getGoalTarget();
+ tamableFox.getControllerLook().a(entityliving, (float)tamableFox.dA(), (float)tamableFox.M());
+ if (tamableFox.h(entityliving) <= 36.0D) {
+ tamableFox.u(true);
+ tamableFox.setCrouching(true);
+ tamableFox.getNavigation().o();
+ } else {
+ tamableFox.getNavigation().a(entityliving, 1.5D);
+ }
+
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalMeleeAttack.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalMeleeAttack.java
new file mode 100644
index 0000000..86597fd
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalMeleeAttack.java
@@ -0,0 +1,33 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.EntityLiving;
+import net.minecraft.server.v1_14_R1.PathfinderGoalMeleeAttack;
+import net.minecraft.server.v1_14_R1.SoundEffects;
+import net.seanomik.tamablefoxes.TamableFox;
+
+public class FoxPathfinderGoalMeleeAttack extends PathfinderGoalMeleeAttack {
+ protected TamableFox tamableFox;
+ public FoxPathfinderGoalMeleeAttack(TamableFox tamableFox, double d0, boolean flag) {
+ super(tamableFox, d0, flag);
+ this.tamableFox = tamableFox;
+ }
+
+ protected void a(EntityLiving entityliving, double d0) {
+ double d1 = this.a(entityliving);
+ if (d0 <= d1 && this.b <= 0) {
+ this.b = 20;
+ this.a.C(entityliving);
+ tamableFox.a(SoundEffects.ENTITY_FOX_BITE, 1.0F, 1.0F);
+ }
+
+ }
+
+ public void c() {
+ tamableFox.u(false);
+ super.c();
+ }
+
+ public boolean a() {
+ return !tamableFox.isSitting() && !tamableFox.isSleeping() && !tamableFox.isCrouching() && !tamableFox.dX() && super.a();
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtByTarget.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtByTarget.java
new file mode 100644
index 0000000..88700d5
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtByTarget.java
@@ -0,0 +1,45 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import java.util.EnumSet;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.minecraft.server.v1_14_R1.PathfinderGoal.Type;
+import net.seanomik.tamablefoxes.TamableFox;
+import org.bukkit.event.entity.EntityTargetEvent.TargetReason;
+
+public class FoxPathfinderGoalOwnerHurtByTarget extends PathfinderGoalTarget {
+ private final TamableFox a; // the Fox
+ private EntityLiving b;
+ private int c;
+
+ public FoxPathfinderGoalOwnerHurtByTarget(TamableFox tamableFox) {
+ super(tamableFox, false);
+ this.a = tamableFox;
+ this.a(EnumSet.of(Type.TARGET));
+ }
+
+ public boolean a() {
+ if (this.a.isTamed() && !this.a.isSitting()) {
+ EntityLiving entityliving = this.a.getOwner();
+ if (entityliving == null) {
+ return false;
+ } else {
+ this.b = entityliving.getLastDamager();
+ int i = entityliving.ct();
+ return i != this.c && this.a(this.b, PathfinderTargetCondition.a); //&& this.a.a(this.b, entityliving);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ public void c() {
+ this.e.setGoalTarget(this.b, TargetReason.TARGET_ATTACKED_OWNER, true);
+ EntityLiving entityliving = this.a.getOwner();
+ if (entityliving != null) {
+ this.c = entityliving.ct();
+ }
+
+ super.c();
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtTarget.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtTarget.java
new file mode 100644
index 0000000..a3ce86a
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtTarget.java
@@ -0,0 +1,44 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.TamableFox;
+import org.bukkit.event.entity.EntityTargetEvent;
+
+import java.util.EnumSet;
+
+public class FoxPathfinderGoalOwnerHurtTarget extends PathfinderGoalTarget {
+ private final TamableFox a;
+ private EntityLiving b;
+ private int c;
+
+ public FoxPathfinderGoalOwnerHurtTarget(TamableFox tamableFox) {
+ super(tamableFox, false);
+ this.a = tamableFox;
+ this.a(EnumSet.of(Type.TARGET));
+ }
+
+ public boolean a() {
+ if (this.a.isTamed() && !this.a.isSitting()) {
+ EntityLiving entityliving = this.a.getOwner();
+ if (entityliving == null) {
+ return false;
+ } else {
+ this.b = entityliving.cu();
+ int i = entityliving.cv();
+ return i != this.c && this.a(this.b, PathfinderTargetCondition.a);// && this.a.a(this.b, entityliving);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ public void c() {
+ this.e.setGoalTarget(this.b, EntityTargetEvent.TargetReason.OWNER_ATTACKED_TARGET, true);
+ EntityLiving entityliving = this.a.getOwner();
+ if (entityliving != null) {
+ this.c = entityliving.cv();
+ }
+
+ super.c();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalPickBushes.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalPickBushes.java
new file mode 100644
index 0000000..1ab4676
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalPickBushes.java
@@ -0,0 +1,86 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.TamableFox;
+import org.bukkit.craftbukkit.v1_14_R1.event.CraftEventFactory;
+
+import java.util.Random;
+
+public class FoxPathfinderGoalPickBushes extends PathfinderGoalGotoTarget {
+ protected int g;
+ protected TamableFox tamableFox;
+
+ public FoxPathfinderGoalPickBushes(TamableFox tamableFox, double d0, int i, int j) {
+ super(tamableFox, d0, i, j);
+ this.tamableFox = tamableFox;
+ }
+
+ public double h() {
+ return 2.0D;
+ }
+
+ public boolean j() {
+ return this.d % 100 == 0;
+ }
+
+ protected boolean a(IWorldReader iworldreader, BlockPosition blockposition) {
+ if (!tamableFox.isTamed()) {
+ IBlockData iblockdata = iworldreader.getType(blockposition);
+ return iblockdata.getBlock() == Blocks.SWEET_BERRY_BUSH && (Integer) iblockdata.get(BlockSweetBerryBush.a) >= 2;
+ }
+ return false;
+ }
+
+ public void e() {
+ if (this.k()) {
+ if (this.g >= 40) {
+ this.m();
+ } else {
+ ++this.g;
+ }
+ //} else if (!this.k() && tamableFox.getRandom().nextFloat() < 0.05F && !tamableFox.isTamed()) {
+ } else if (!this.k() && tamableFox.getRandom().nextFloat() < 0.05F && !tamableFox.isTamed()) {
+ tamableFox.a(SoundEffects.ENTITY_FOX_SNIFF, 1.0F, 1.0F);
+ }
+
+ super.e();
+ }
+
+ protected void m() {
+ if (tamableFox.world.getGameRules().getBoolean(GameRules.DO_MOB_LOOT) && !tamableFox.isTamed()) {
+ IBlockData iblockdata = tamableFox.world.getType(this.e);
+ if (iblockdata.getBlock() == Blocks.SWEET_BERRY_BUSH) {
+ int i = (Integer)iblockdata.get(BlockSweetBerryBush.a);
+ iblockdata.set(BlockSweetBerryBush.a, 1);
+ if (CraftEventFactory.callEntityChangeBlockEvent(tamableFox, this.e, (IBlockData)iblockdata.set(BlockSweetBerryBush.a, 1)).isCancelled()) {
+ return;
+ }
+
+ int j = 1 + tamableFox.world.random.nextInt(2) + (i == 3 ? 1 : 0);
+ ItemStack itemstack = tamableFox.getEquipment(EnumItemSlot.MAINHAND);
+ if (itemstack.isEmpty()) {
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.SWEET_BERRIES));
+ --j;
+ }
+
+ if (j > 0) {
+ Block.a(tamableFox.world, this.e, new ItemStack(Items.SWEET_BERRIES, j));
+ }
+
+ tamableFox.a(SoundEffects.ITEM_SWEET_BERRIES_PICK_FROM_BUSH, 1.0F, 1.0F);
+ tamableFox.world.setTypeAndData(this.e, (IBlockData)iblockdata.set(BlockSweetBerryBush.a, 1), 2);
+ }
+ }
+
+ }
+
+ public boolean a() {
+ return !tamableFox.isSleeping() && super.a();
+ }
+
+ public void c() {
+ this.g = 0;
+ tamableFox.setSitting(false);
+ super.c();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomStrollLand.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomStrollLand.java
new file mode 100644
index 0000000..30a1b05
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomStrollLand.java
@@ -0,0 +1,109 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.EntityCreature;
+import net.minecraft.server.v1_14_R1.PathfinderGoalRandomStroll;
+import net.minecraft.server.v1_14_R1.RandomPositionGenerator;
+import net.minecraft.server.v1_14_R1.Vec3D;
+import net.seanomik.tamablefoxes.TamableFox;
+import org.bukkit.Bukkit;
+
+import javax.annotation.Nullable;
+
+public class FoxPathfinderGoalRandomStrollLand extends PathfinderGoalRandomStroll {
+ protected final float h;
+ protected TamableFox tamableFox;
+ protected Vec3D vec3D;
+
+ public FoxPathfinderGoalRandomStrollLand(TamableFox var0, double var1) {
+ this(var0, var1, 0.001F);
+ this.tamableFox = var0;
+ }
+
+ public FoxPathfinderGoalRandomStrollLand(TamableFox var0, double var1, float var3) {
+ super(var0, var1);
+ this.h = var3;
+ this.tamableFox = var0;
+ }
+
+ @Override
+ public boolean a() {
+ if (this.a.isVehicle()) {
+ return false;
+ } else {
+ if (!this.g) {
+ if (this.a.cw() >= 100) {
+ return false;
+ }
+
+ if (this.a.getRandom().nextInt(this.f) != 0) {
+ return false;
+ }
+ }
+
+ if (!tamableFox.isSitting()) {
+ vec3D = this.g();
+ }
+ if (vec3D == null) {
+ return false;
+ } else {
+ this.b = vec3D.x;
+ this.c = vec3D.y;
+ this.d = vec3D.z;
+ this.g = false;
+ return true;
+ }
+ }
+ }
+
+ @Nullable
+ protected Vec3D g() {
+ if (this.a.av()) {
+ Vec3D var0 = RandomPositionGenerator.b(this.a, 15, 7);
+ return var0 == null ? super.g() : var0;
+ } else {
+ return this.a.getRandom().nextFloat() >= this.h ? RandomPositionGenerator.b(this.a, 10, 7) : super.g();
+ }
+ }
+
+ @Override
+ public boolean b() {
+ /*Bukkit.broadcastMessage("B: " + !this.a.getNavigation().n());
+ if (tamableFox.isSitting()) {
+
+ }
+
+ return false;*/
+ return !this.a.getNavigation().n();
+ }
+
+ @Override
+ public void c() {
+ this.a.getNavigation().a(this.b, this.c, this.d, this.e);
+ }
+
+ @Override
+ public void h() {
+ this.g = true;
+ }
+
+ @Override
+ public void setTimeBetweenMovement(int var0) {
+ this.f = var0;
+ }
+
+ @Override
+ public void e() {
+ if (tamableFox.isSitting()) {
+ vec3D = null;
+ }
+ }
+
+ @Override
+ public boolean C_() {
+ if (tamableFox.isSitting()) {
+ vec3D = null;
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomTargetNonTamed.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomTargetNonTamed.java
new file mode 100644
index 0000000..591de82
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomTargetNonTamed.java
@@ -0,0 +1,25 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import net.minecraft.server.v1_14_R1.EntityLiving;
+import net.minecraft.server.v1_14_R1.PathfinderGoalNearestAttackableTarget;
+import net.seanomik.tamablefoxes.TamableFox;
+
+import javax.annotation.Nullable;
+import java.util.function.Predicate;
+
+public class FoxPathfinderGoalRandomTargetNonTamed extends PathfinderGoalNearestAttackableTarget {
+ private final TamableFox tamableFox;
+
+ public FoxPathfinderGoalRandomTargetNonTamed(TamableFox tamableFox, Class var1, boolean var2, @Nullable Predicate var3) {
+ super(tamableFox, var1, 10, var2, false, var3);
+ this.tamableFox = tamableFox;
+ }
+
+ public boolean a() {
+ return !tamableFox.isTamed() && super.a();
+ }
+
+ public boolean b() {
+ return this.d != null ? this.d.a(this.e, this.c) : super.b();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalSit.java b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalSit.java
new file mode 100644
index 0000000..a392a17
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalSit.java
@@ -0,0 +1,49 @@
+package net.seanomik.tamablefoxes.CustomPathfinding;
+
+import java.util.EnumSet;
+
+import net.minecraft.server.v1_14_R1.EntityLiving;
+import net.minecraft.server.v1_14_R1.EntityTameableAnimal;
+import net.minecraft.server.v1_14_R1.PathfinderGoal;
+import net.minecraft.server.v1_14_R1.PathfinderGoal.Type;
+import net.seanomik.tamablefoxes.TamableFox;
+
+public class FoxPathfinderGoalSit extends PathfinderGoal {
+ private final TamableFox entity;
+ private boolean willSit;
+
+ public FoxPathfinderGoalSit(TamableFox tamableFox) {
+ this.entity = tamableFox;
+ this.a(EnumSet.of(Type.JUMP, Type.MOVE));
+ }
+
+ public boolean b() {
+ return this.willSit;
+ }
+
+ public boolean a() {
+ if (!this.entity.isTamed()) {
+ return this.willSit && this.entity.getGoalTarget() == null;
+ } else if (this.entity.av()) {
+ return false;
+ } else if (!this.entity.onGround) {
+ return false;
+ } else {
+ EntityLiving entityliving = this.entity.getOwner();
+ return entityliving == null ? true : (this.entity.h(entityliving) < 144.0D && entityliving.getLastDamager() != null ? false : this.willSit);
+ }
+ }
+
+ public void c() {
+ this.entity.getNavigation().o();
+ this.entity.setSitting(true);
+ }
+
+ public void d() {
+ this.entity.setSitting(false);
+ }
+
+ public void setSitting(boolean flag) {
+ this.willSit = flag;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/seanomik/tamablefoxes/NBTEditor.java b/src/main/java/net/seanomik/tamablefoxes/NBTEditor.java
new file mode 100644
index 0000000..8cf1aa7
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/NBTEditor.java
@@ -0,0 +1,1111 @@
+package net.seanomik.tamablefoxes;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.craftbukkit.libs.org.apache.commons.codec.binary.Base64;
+import org.bukkit.entity.Entity;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.properties.Property;
+
+/**
+ * Sets/Gets NBT tags from ItemStacks
+ * Supports 1.8-1.14
+ *
+ * Github: https://github.com/BananaPuncher714/NBTEditor
+ * Spigot: https://www.spigotmc.org/threads/single-class-nbt-editor-for-items-skulls-mobs-and-tile-entities-1-8-1-13.269621/
+ *
+ * @version 7.6
+ * @author BananaPuncher714
+ */
+public final class NBTEditor {
+ private static final Map< String, Class> > classCache;
+ private static final Map< String, Method > methodCache;
+ private static final Map< Class< ? >, Constructor< ? > > constructorCache;
+ private static final Map< Class< ? >, Class< ? > > NBTClasses;
+ private static final Map< Class< ? >, Field > NBTTagFieldCache;
+ private static Field NBTListData;
+ private static Field NBTCompoundMap;
+ private static final String VERSION;
+
+ static {
+ VERSION = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
+
+ classCache = new HashMap< String, Class> >();
+ try {
+ classCache.put( "NBTBase", Class.forName( "net.minecraft.server." + VERSION + "." + "NBTBase" ) );
+ classCache.put( "NBTTagCompound", Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagCompound" ) );
+ classCache.put( "NBTTagList", Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagList" ) );
+ classCache.put( "NBTBase", Class.forName( "net.minecraft.server." + VERSION + "." + "NBTBase" ) );
+
+ classCache.put( "ItemStack", Class.forName( "net.minecraft.server." + VERSION + "." + "ItemStack" ) );
+ classCache.put( "CraftItemStack", Class.forName( "org.bukkit.craftbukkit." + VERSION + ".inventory." + "CraftItemStack" ) );
+
+ classCache.put( "Entity", Class.forName( "net.minecraft.server." + VERSION + "." + "Entity" ) );
+ classCache.put( "CraftEntity", Class.forName( "org.bukkit.craftbukkit." + VERSION + ".entity." + "CraftEntity" ) );
+ classCache.put( "EntityLiving", Class.forName( "net.minecraft.server." + VERSION + "." + "EntityLiving" ) );
+
+ classCache.put( "CraftWorld", Class.forName( "org.bukkit.craftbukkit." + VERSION + "." + "CraftWorld" ) );
+ classCache.put( "CraftBlockState", Class.forName( "org.bukkit.craftbukkit." + VERSION + ".block." + "CraftBlockState" ) );
+ classCache.put( "BlockPosition", Class.forName( "net.minecraft.server." + VERSION + "." + "BlockPosition" ) );
+ classCache.put( "TileEntity", Class.forName( "net.minecraft.server." + VERSION + "." + "TileEntity" ) );
+ classCache.put( "World", Class.forName( "net.minecraft.server." + VERSION + "." + "World" ) );
+
+ classCache.put( "TileEntitySkull", Class.forName( "net.minecraft.server." + VERSION + "." + "TileEntitySkull" ) );
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ NBTClasses = new HashMap< Class< ? >, Class< ? > >();
+ try {
+ NBTClasses.put( Byte.class, Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagByte" ) );
+ NBTClasses.put( String.class, Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagString" ) );
+ NBTClasses.put( Double.class, Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagDouble" ) );
+ NBTClasses.put( Integer.class, Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagInt" ) );
+ NBTClasses.put( Long.class, Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagLong" ) );
+ NBTClasses.put( Short.class, Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagShort" ) );
+ NBTClasses.put( Float.class, Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagFloat" ) );
+ NBTClasses.put( Class.forName( "[B" ), Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagByteArray" ) );
+ NBTClasses.put( Class.forName( "[I" ), Class.forName( "net.minecraft.server." + VERSION + "." + "NBTTagIntArray" ) );
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ methodCache = new HashMap< String, Method >();
+ try {
+ methodCache.put( "get", getNMSClass( "NBTTagCompound" ).getMethod( "get", String.class ) );
+ methodCache.put( "set", getNMSClass( "NBTTagCompound" ).getMethod( "set", String.class, getNMSClass( "NBTBase" ) ) );
+ methodCache.put( "hasKey", getNMSClass( "NBTTagCompound" ).getMethod( "hasKey", String.class ) );
+ methodCache.put( "setIndex", getNMSClass( "NBTTagList" ).getMethod( "a", int.class, getNMSClass( "NBTBase" ) ) );
+ if ( VERSION.contains( "1_14" ) ) {
+ methodCache.put( "getTypeId", getNMSClass( "NBTBase" ).getMethod( "getTypeId" ) );
+ methodCache.put( "add", getNMSClass( "NBTTagList" ).getMethod( "add", int.class, getNMSClass( "NBTBase" ) ) );
+ } else {
+ methodCache.put( "add", getNMSClass( "NBTTagList" ).getMethod( "add", getNMSClass( "NBTBase" ) ) );
+ }
+
+ if ( VERSION.contains( "1_8" ) ) {
+ methodCache.put( "listRemove", getNMSClass( "NBTTagList" ).getMethod( "a", int.class ) );
+ } else {
+ methodCache.put( "listRemove", getNMSClass( "NBTTagList" ).getMethod( "remove", int.class ) );
+ }
+ methodCache.put( "remove", getNMSClass( "NBTTagCompound" ).getMethod( "remove", String.class ) );
+
+ methodCache.put( "hasTag", getNMSClass( "ItemStack" ).getMethod( "hasTag" ) );
+ methodCache.put( "getTag", getNMSClass( "ItemStack" ).getMethod( "getTag" ) );
+ methodCache.put( "setTag", getNMSClass( "ItemStack" ).getMethod( "setTag", getNMSClass( "NBTTagCompound" ) ) );
+ methodCache.put( "asNMSCopy", getNMSClass( "CraftItemStack" ).getMethod( "asNMSCopy", ItemStack.class ) );
+ methodCache.put( "asBukkitCopy", getNMSClass( "CraftItemStack" ).getMethod( "asBukkitCopy", getNMSClass( "ItemStack" ) ) );
+
+ methodCache.put( "getEntityHandle", getNMSClass( "CraftEntity" ).getMethod( "getHandle" ) );
+ methodCache.put( "getEntityTag", getNMSClass( "Entity" ).getMethod( "c", getNMSClass( "NBTTagCompound" ) ) );
+ methodCache.put( "setEntityTag", getNMSClass( "Entity" ).getMethod( "f", getNMSClass( "NBTTagCompound" ) ) );
+
+ if ( VERSION.contains( "1_12" ) || VERSION.contains( "1_13" ) || VERSION.contains( "1_14" ) ) {
+ methodCache.put( "setTileTag", getNMSClass( "TileEntity" ).getMethod( "load", getNMSClass( "NBTTagCompound" ) ) );
+ } else {
+ methodCache.put( "setTileTag", getNMSClass( "TileEntity" ).getMethod( "a", getNMSClass( "NBTTagCompound" ) ) );
+ }
+ methodCache.put( "getTileEntity", getNMSClass( "World" ).getMethod( "getTileEntity", getNMSClass( "BlockPosition" ) ) );
+ methodCache.put( "getWorldHandle", getNMSClass( "CraftWorld" ).getMethod( "getHandle" ) );
+
+ methodCache.put( "setGameProfile", getNMSClass( "TileEntitySkull" ).getMethod( "setGameProfile", GameProfile.class ) );
+ } catch( Exception e ) {
+ e.printStackTrace();
+ }
+
+ try {
+ methodCache.put( "getTileTag", getNMSClass( "TileEntity" ).getMethod( "save", getNMSClass( "NBTTagCompound" ) ) );
+ } catch( NoSuchMethodException exception ) {
+ try {
+ methodCache.put( "getTileTag", getNMSClass( "TileEntity" ).getMethod( "b", getNMSClass( "NBTTagCompound" ) ) );
+ } catch ( Exception exception2 ) {
+ exception2.printStackTrace();
+ }
+ } catch( Exception exception ) {
+ exception.printStackTrace();
+ }
+
+ constructorCache = new HashMap< Class< ? >, Constructor< ? > >();
+ try {
+ constructorCache.put( getNBTTag( Byte.class ), getNBTTag( Byte.class ).getConstructor( byte.class ) );
+ constructorCache.put( getNBTTag( String.class ), getNBTTag( String.class ).getConstructor( String.class ) );
+ constructorCache.put( getNBTTag( Double.class ), getNBTTag( Double.class ).getConstructor( double.class ) );
+ constructorCache.put( getNBTTag( Integer.class ), getNBTTag( Integer.class ).getConstructor( int.class ) );
+ constructorCache.put( getNBTTag( Long.class ), getNBTTag( Long.class ).getConstructor( long.class ) );
+ constructorCache.put( getNBTTag( Float.class ), getNBTTag( Float.class ).getConstructor( float.class ) );
+ constructorCache.put( getNBTTag( Short.class ), getNBTTag( Short.class ).getConstructor( short.class ) );
+ constructorCache.put( getNBTTag( Class.forName( "[B" ) ), getNBTTag( Class.forName( "[B" ) ).getConstructor( Class.forName( "[B" ) ) );
+ constructorCache.put( getNBTTag( Class.forName( "[I" ) ), getNBTTag( Class.forName( "[I" ) ).getConstructor( Class.forName( "[I" ) ) );
+
+ constructorCache.put( getNMSClass( "BlockPosition" ), getNMSClass( "BlockPosition" ).getConstructor( int.class, int.class, int.class ) );
+ } catch( Exception e ) {
+ e.printStackTrace();
+ }
+
+ NBTTagFieldCache = new HashMap< Class< ? >, Field >();
+ try {
+ for ( Class< ? > clazz : NBTClasses.values() ) {
+ Field data = clazz.getDeclaredField( "data" );
+ data.setAccessible( true );
+ NBTTagFieldCache.put( clazz, data );
+ }
+ } catch( Exception e ) {
+ e.printStackTrace();
+ }
+
+ try {
+ NBTListData = getNMSClass( "NBTTagList" ).getDeclaredField( "list" );
+ NBTListData.setAccessible( true );
+ NBTCompoundMap = getNMSClass( "NBTTagCompound" ).getDeclaredField( "map" );
+ NBTCompoundMap.setAccessible( true );
+ } catch( Exception e ) {
+ e.printStackTrace();
+ }
+ }
+
+ private static Class< ? > getNBTTag( Class< ? > primitiveType ) {
+ if ( NBTClasses.containsKey( primitiveType ) )
+ return NBTClasses.get( primitiveType );
+ return primitiveType;
+ }
+
+ private static Object getNBTVar( Object object ) {
+ if ( object == null ) {
+ return null;
+ }
+ Class< ? > clazz = object.getClass();
+ try {
+ if ( NBTTagFieldCache.containsKey( clazz ) ) {
+ return NBTTagFieldCache.get( clazz ).get( object );
+ }
+ } catch ( Exception exception ) {
+ exception.printStackTrace();
+ }
+ return null;
+ }
+
+ private static Method getMethod( String name ) {
+ return methodCache.containsKey( name ) ? methodCache.get( name ) : null;
+ }
+
+ private static Constructor< ? > getConstructor( Class< ? > clazz ) {
+ return constructorCache.containsKey( clazz ) ? constructorCache.get( clazz ) : null;
+ }
+
+ private static Class> getNMSClass(String name) {
+ if ( classCache.containsKey( name ) ) {
+ return classCache.get( name );
+ }
+
+ try {
+ return Class.forName("net.minecraft.server." + VERSION + "." + name);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static String getMatch( String string, String regex ) {
+ Pattern pattern = Pattern.compile( regex );
+ Matcher matcher = pattern.matcher( string );
+ if ( matcher.find() ) {
+ return matcher.group( 1 );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the Bukkit version
+ *
+ * @return
+ * The Bukkit version in standard package format
+ */
+ public final static String getVersion() {
+ return VERSION;
+ }
+
+ /**
+ * Creates a skull with the given url as the skin
+ *
+ * @param skinURL
+ * The URL of the skin, must be from mojang
+ * @return
+ * An item stack with count of 1
+ */
+ public final static ItemStack getHead( String skinURL ) {
+ Material material = Material.getMaterial( "SKULL_ITEM" );
+ if ( material == null ) {
+ // Most likely 1.13 materials
+ material = Material.getMaterial( "PLAYER_HEAD" );
+ }
+ ItemStack head = new ItemStack( material, 1, ( short ) 3 );
+ if ( skinURL == null || skinURL.isEmpty() ) {
+ return head;
+ }
+ ItemMeta headMeta = head.getItemMeta();
+ GameProfile profile = new GameProfile( UUID.randomUUID(), null);
+ byte[] encodedData = Base64.encodeBase64( String.format( "{textures:{SKIN:{\"url\":\"%s\"}}}", skinURL ).getBytes() );
+ profile.getProperties().put("textures", new Property("textures", new String(encodedData)));
+ Field profileField = null;
+ try {
+ profileField = headMeta.getClass().getDeclaredField("profile");
+ } catch ( NoSuchFieldException | SecurityException e ) {
+ e.printStackTrace();
+ }
+ profileField.setAccessible(true);
+ try {
+ profileField.set(headMeta, profile);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ head.setItemMeta(headMeta);
+ return head;
+ }
+
+ /**
+ * Fetches the texture of a skull
+ *
+ * @param head
+ * The item stack itself
+ * @return
+ * The URL of the texture
+ */
+ public final static String getTexture( ItemStack head ) {
+ ItemMeta meta = head.getItemMeta();
+ Field profileField = null;
+ try {
+ profileField = meta.getClass().getDeclaredField("profile");
+ } catch ( NoSuchFieldException | SecurityException e ) {
+ e.printStackTrace();
+ throw new IllegalArgumentException( "Item is not a player skull!" );
+ }
+ profileField.setAccessible(true);
+ try {
+ GameProfile profile = ( GameProfile ) profileField.get( meta );
+ if ( profile == null ) {
+ return null;
+ }
+
+ for ( Property prop : profile.getProperties().values() ) {
+ if ( prop.getName().equals( "textures" ) ) {
+ String texture = new String( Base64.decodeBase64( prop.getValue() ) );
+ return getMatch( texture, "\\{\"url\":\"(.*?)\"\\}" );
+ }
+ }
+ return null;
+ } catch ( IllegalArgumentException | IllegalAccessException | SecurityException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * @deprecated
+ *
+ * Gets an NBT tag in a given item with the specified keys
+ *
+ * @param item
+ * The itemstack to get the keys from
+ * @param keys
+ * The keys to fetch; an integer after a key value indicates that it should get the nth place of
+ * the previous compound because it is a list;
+ * @return
+ * The item represented by the keys, and an integer if it is showing how long a list is.
+ */
+ public final static Object getItemTag( ItemStack item, Object... keys ) {
+ if ( item == null ) {
+ return null;
+ }
+ try {
+ Object stack = null;
+ stack = getMethod( "asNMSCopy" ).invoke( null, item );
+
+ Object tag = null;
+
+ if ( getMethod( "hasTag" ).invoke( stack ).equals( true ) ) {
+ tag = getMethod( "getTag" ).invoke( stack );
+ } else {
+ tag = getNMSClass( "NBTTagCompound" ).newInstance();
+ }
+
+ return getTag( tag, keys );
+ } catch ( Exception exception ) {
+ exception.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Gets an NBTCompound from the item provided
+ *
+ * @param item
+ * Itemstack
+ * @param keys
+ * Keys in descending order
+ * @return
+ * An NBTCompound
+ */
+ public final static NBTCompound getItemNBTTag( ItemStack item, Object... keys ) {
+ if ( item == null ) {
+ return null;
+ }
+ try {
+ Object stack = null;
+ stack = getMethod( "asNMSCopy" ).invoke( null, item );
+
+ Object tag = null;
+
+ if ( getMethod( "hasTag" ).invoke( stack ).equals( true ) ) {
+ tag = getMethod( "getTag" ).invoke( stack );
+ } else {
+ tag = getNMSClass( "NBTTagCompound" ).newInstance();
+ Object count = getConstructor( getNBTTag( Integer.class ) ).newInstance( item.getAmount() );
+ getMethod( "set" ).invoke( tag, "Count", count );
+ Object id = getConstructor( getNBTTag( String.class ) ).newInstance( item.getType().name().toLowerCase() );
+ getMethod( "set" ).invoke( tag, "id", id );
+ }
+
+ return getNBTTag( tag, keys );
+ } catch ( Exception exception ) {
+ exception.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * @deprecated
+ *
+ * Sets an NBT tag in an item with the provided keys and value
+ *
+ * @param item
+ * The itemstack to set
+ * @param keys
+ * The keys to set, String for NBTCompound, int or null for an NBTTagList
+ * @param value
+ * The value to set
+ * @return
+ * A new ItemStack with the updated NBT tags
+ */
+ public final static ItemStack setItemTag( ItemStack item, Object value, Object... keys ) {
+ if ( item == null ) {
+ return null;
+ }
+ try {
+ Object stack = getMethod( "asNMSCopy" ).invoke( null, item );
+
+ Object tag = null;
+
+ if ( getMethod( "hasTag" ).invoke( stack ).equals( true ) ) {
+ tag = getMethod( "getTag" ).invoke( stack );
+ } else {
+ tag = getNMSClass( "NBTTagCompound" ).newInstance();
+ }
+
+ setTag( tag, value, keys );
+ getMethod( "setTag" ).invoke( stack, tag );
+ return ( ItemStack ) getMethod( "asBukkitCopy" ).invoke( null, stack );
+ } catch ( Exception exception ) {
+ exception.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Constructs an ItemStack from a given NBTCompound
+ *
+ * @param compound
+ * An NBTCompound following an ItemStack structure
+ * @return
+ * A new ItemStack
+ */
+ public final static ItemStack getItemFromTag( NBTCompound compound ) {
+ if ( compound == null ) {
+ return null;
+ }
+ try {
+ Object tag = compound.tag;
+ Object count = getTag( tag, "Count" );
+ Object id = getTag( tag, "id" );
+ if ( count == null || id == null ) {
+ return null;
+ }
+ if ( count instanceof Byte && id instanceof String ) {
+ int amount = ( byte ) count;
+ String material = ( String ) id;
+ Material type = Material.valueOf( material.substring( material.indexOf( ":" ) + 1 ).toUpperCase() );
+ return NBTEditor.setItemTag( new ItemStack( type, amount ), tag );
+ }
+ return null;
+ } catch ( Exception exception ) {
+ exception.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * @deprecated
+ *
+ * Gets an NBT tag in a given entity with the specified keys
+ *
+ * @param entity
+ * The entity to get the keys from
+ * @param keys
+ * The keys to fetch; an integer after a key value indicates that it should get the nth place of
+ * the previous compound because it is a list;
+ * @return
+ * The item represented by the keys, and an integer if it is showing how long a list is.
+ */
+ public final static Object getEntityTag( Entity entity, Object... keys ) {
+ if ( entity == null ) {
+ return entity;
+ }
+ try {
+ Object NMSEntity = getMethod( "getEntityHandle" ).invoke( entity );
+
+ Object tag = getNMSClass( "NBTTagCompound" ).newInstance();
+
+ getMethod( "getEntityTag" ).invoke( NMSEntity, tag );
+
+ return getTag( tag, keys );
+ } catch ( Exception exception ) {
+ exception.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Gets an NBTCompound from the entity provided
+ *
+ * @param entity
+ * The Bukkit entity provided
+ * @param keys
+ * Keys in descending order
+ * @return
+ * An NBTCompound
+ */
+ public final static NBTCompound getEntityNBTTag( Entity entity, Object...keys ) {
+ if ( entity == null ) {
+ return null;
+ }
+ try {
+ Object NMSEntity = getMethod( "getEntityHandle" ).invoke( entity );
+
+ Object tag = getNMSClass( "NBTTagCompound" ).newInstance();
+
+ getMethod( "getEntityTag" ).invoke( NMSEntity, tag );
+
+ return getNBTTag( tag, keys );
+ } catch ( Exception exception ) {
+ exception.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * @deprecated
+ *
+ * Sets an NBT tag in an entity with the provided keys and value
+ *
+ * @param entity
+ * The entity to set
+ * @param keys
+ * The keys to set, String for NBTCompound, int or null for an NBTTagList
+ * @param value
+ * The value to set
+ * @return
+ * A new ItemStack with the updated NBT tags
+ */
+ public final static void setEntityTag( Entity entity, Object value, Object... keys ) {
+ if ( entity == null ) {
+ return;
+ }
+ try {
+ Object NMSEntity = getMethod( "getEntityHandle" ).invoke( entity );
+
+ Object tag = getNMSClass( "NBTTagCompound" ).newInstance() ;
+
+ getMethod( "getEntityTag" ).invoke( NMSEntity, tag );
+
+ setTag( tag, value, keys );
+
+ getMethod( "setEntityTag" ).invoke( NMSEntity, tag );
+ } catch ( Exception exception ) {
+ exception.printStackTrace();
+ return;
+ }
+ }
+
+ /**
+ * @deprecated
+ *
+ * Gets an NBT tag in a given block with the specified keys
+ *
+ * @param block
+ * The block to get the keys from
+ * @param keys
+ * The keys to fetch; an integer after a key value indicates that it should get the nth place of
+ * the previous compound because it is a list;
+ * @return
+ * The item represented by the keys, and an integer if it is showing how long a list is.
+ */
+ public final static Object getBlockTag( Block block, Object... keys ) {
+ try {
+ if ( block == null || !getNMSClass( "CraftBlockState" ).isInstance( block.getState() ) ) {
+ return null;
+ }
+ Location location = block.getLocation();
+
+ Object blockPosition = getConstructor( getNMSClass( "BlockPosition" ) ).newInstance( location.getBlockX(), location.getBlockY(), location.getBlockZ() );
+
+ Object nmsWorld = getMethod( "getWorldHandle" ).invoke( location.getWorld() );
+
+ Object tileEntity = getMethod( "getTileEntity" ).invoke( nmsWorld, blockPosition );
+
+ Object tag = getNMSClass( "NBTTagCompound" ).newInstance();
+
+ getMethod( "getTileTag" ).invoke( tileEntity, tag );
+
+ return getTag( tag, keys );
+ } catch( Exception exception ) {
+ exception.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Gets an NBTCompound from the block provided
+ *
+ * @param block
+ * The block provided
+ * @param keys
+ * Keys in descending order
+ * @return
+ * An NBTCompound
+ */
+ public final static Object getBlockNBTTag( Block block, Object... keys ) {
+ try {
+ if ( block == null || !getNMSClass( "CraftBlockState" ).isInstance( block.getState() ) ) {
+ return null;
+ }
+ Location location = block.getLocation();
+
+ Object blockPosition = getConstructor( getNMSClass( "BlockPosition" ) ).newInstance( location.getBlockX(), location.getBlockY(), location.getBlockZ() );
+
+ Object nmsWorld = getMethod( "getWorldHandle" ).invoke( location.getWorld() );
+
+ Object tileEntity = getMethod( "getTileEntity" ).invoke( nmsWorld, blockPosition );
+
+ Object tag = getNMSClass( "NBTTagCompound" ).newInstance();
+
+ getMethod( "getTileTag" ).invoke( tileEntity, tag );
+
+ return getNBTTag( tag, keys );
+ } catch( Exception exception ) {
+ exception.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * @deprecated
+ *
+ * Sets an NBT tag in an block with the provided keys and value
+ *
+ * @param block
+ * The block to set
+ * @param keys
+ * The keys to set, String for NBTCompound, int or null for an NBTTagList
+ * @param value
+ * The value to set
+ * @return
+ * A new ItemStack with the updated NBT tags
+ */
+ public final static void setBlockTag( Block block, Object value, Object... keys ) {
+ try {
+ if ( block == null || !getNMSClass( "CraftBlockState" ).isInstance( block.getState() ) ) {
+ return;
+ }
+ Location location = block.getLocation();
+
+ Object blockPosition = getConstructor( getNMSClass( "BlockPosition" ) ).newInstance( location.getBlockX(), location.getBlockY(), location.getBlockZ() );
+
+ Object nmsWorld = getMethod( "getWorldHandle" ).invoke( location.getWorld() );
+
+ Object tileEntity = getMethod( "getTileEntity" ).invoke( nmsWorld, blockPosition );
+
+ Object tag = getNMSClass( "NBTTagCompound" ).newInstance();
+
+ getMethod( "getTileTag" ).invoke( tileEntity, tag );
+
+ setTag( tag, value, keys );
+
+ getMethod( "setTileTag" ).invoke( tileEntity, tag );
+ } catch( Exception exception ) {
+ exception.printStackTrace();
+ return;
+ }
+ }
+
+ /**
+ * Sets the texture of a skull block
+ *
+ * @param block
+ * The block, must be a skull
+ * @param texture
+ * The URL of the skin
+ */
+ public final static void setSkullTexture( Block block, String texture ) {
+ GameProfile profile = new GameProfile( UUID.randomUUID(), null );
+ profile.getProperties().put( "textures", new com.mojang.authlib.properties.Property( "textures", new String( Base64.encodeBase64( String.format( "{textures:{SKIN:{\"url\":\"%s\"}}}", texture ).getBytes() ) ) ) );
+
+ try {
+ Location location = block.getLocation();
+
+ Object blockPosition = getConstructor( getNMSClass( "BlockPosition" ) ).newInstance( location.getBlockX(), location.getBlockY(), location.getBlockZ() );
+
+ Object nmsWorld = getMethod( "getWorldHandle" ).invoke( location.getWorld() );
+
+ Object tileEntity = getMethod( "getTileEntity" ).invoke( nmsWorld, blockPosition );
+
+ getMethod( "setGameProfile" ).invoke( tileEntity, profile );
+ } catch( Exception exception ) {
+ exception.printStackTrace();
+ }
+ }
+
+ /**
+ * Gets a string from an object
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param keys
+ * Keys in descending order
+ * @return
+ * A string, or null if none is stored at the provided location
+ */
+ public final static String getString( Object object, Object... keys ) {
+ Object result;
+ if ( object instanceof ItemStack ) {
+ result = getItemTag( ( ItemStack ) object, keys );
+ } else if ( object instanceof Entity ) {
+ result = getEntityTag( ( Entity ) object, keys );
+ } else if ( object instanceof Block ) {
+ result = getBlockTag( ( Block ) object, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return result instanceof String ? ( String ) result : null;
+ }
+
+ /**
+ * Gets an int from an object
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param keys
+ * Keys in descending order
+ * @return
+ * An integer, or 0 if none is stored at the provided location
+ */
+ public final static int getInt( Object object, Object... keys ) {
+ Object result;
+ if ( object instanceof ItemStack ) {
+ result = getItemTag( ( ItemStack ) object, keys );
+ } else if ( object instanceof Entity ) {
+ result = getEntityTag( ( Entity ) object, keys );
+ } else if ( object instanceof Block ) {
+ result = getBlockTag( ( Block ) object, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return result instanceof Integer ? ( int ) result : 0;
+ }
+
+ /**
+ * Gets a long from an object
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param keys
+ * Keys in descending order
+ * @return
+ * A long, or 0 if none is stored at the provided location
+ */
+ public final static long getLong( Object object, Object... keys ) {
+ Object result;
+ if ( object instanceof ItemStack ) {
+ result = getItemTag( ( ItemStack ) object, keys );
+ } else if ( object instanceof Entity ) {
+ result = getEntityTag( ( Entity ) object, keys );
+ } else if ( object instanceof Block ) {
+ result = getBlockTag( ( Block ) object, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return result instanceof Long ? ( long ) result : 0;
+ }
+
+ /**
+ * Gets a float from an object
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param keys
+ * Keys in descending order
+ * @return
+ * A float, or 0 if none is stored at the provided location
+ */
+ public final static float getFloat( Object object, Object... keys ) {
+ Object result;
+ if ( object instanceof ItemStack ) {
+ result = getItemTag( ( ItemStack ) object, keys );
+ } else if ( object instanceof Entity ) {
+ result = getEntityTag( ( Entity ) object, keys );
+ } else if ( object instanceof Block ) {
+ result = getBlockTag( ( Block ) object, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return result instanceof Float ? ( float ) result : 0;
+ }
+
+ /**
+ * Gets a short from an object
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param keys
+ * Keys in descending order
+ * @return
+ * A short, or 0 if none is stored at the provided location
+ */
+ public final static short getShort( Object object, Object... keys ) {
+ Object result;
+ if ( object instanceof ItemStack ) {
+ result = getItemTag( ( ItemStack ) object, keys );
+ } else if ( object instanceof Entity ) {
+ result = getEntityTag( ( Entity ) object, keys );
+ } else if ( object instanceof Block ) {
+ result = getBlockTag( ( Block ) object, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return result instanceof Short ? ( short ) result : 0;
+ }
+
+ /**
+ * Gets a byte from an object
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param keys
+ * Keys in descending order
+ * @return
+ * A byte, or 0 if none is stored at the provided location
+ */
+ public final static byte getByte( Object object, Object... keys ) {
+ Object result;
+ if ( object instanceof ItemStack ) {
+ result = getItemTag( ( ItemStack ) object, keys );
+ } else if ( object instanceof Entity ) {
+ result = getEntityTag( ( Entity ) object, keys );
+ } else if ( object instanceof Block ) {
+ result = getBlockTag( ( Block ) object, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return result instanceof Byte ? ( byte ) result : 0;
+ }
+
+ /**
+ * Gets a byte array from an object
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param keys
+ * Keys in descending order
+ * @return
+ * A byte array, or null if none is stored at the provided location
+ */
+ public final static byte[] getByteArray( Object object, Object... keys ) {
+ Object result;
+ if ( object instanceof ItemStack ) {
+ result = getItemTag( ( ItemStack ) object, keys );
+ } else if ( object instanceof Entity ) {
+ result = getEntityTag( ( Entity ) object, keys );
+ } else if ( object instanceof Block ) {
+ result = getBlockTag( ( Block ) object, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return result instanceof byte[] ? ( byte[] ) result : null;
+ }
+
+ /**
+ * Gets an int array from an object
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param keys
+ * Keys in descending order
+ * @return
+ * An int array, or null if none is stored at the provided location
+ */
+ public final static int[] getIntArray( Object object, Object... keys ) {
+ Object result;
+ if ( object instanceof ItemStack ) {
+ result = getItemTag( ( ItemStack ) object, keys );
+ } else if ( object instanceof Entity ) {
+ result = getEntityTag( ( Entity ) object, keys );
+ } else if ( object instanceof Block ) {
+ result = getBlockTag( ( Block ) object, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return result instanceof int[] ? ( int[] ) result : null;
+ }
+
+ /**
+ * Checks if the object contains the given key
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param keys
+ * Keys in descending order
+ * @return
+ * Whether or not the particular tag exists, may not be a primitive
+ */
+ public final static boolean contains( Object object, Object... keys ) {
+ Object result;
+ if ( object instanceof ItemStack ) {
+ result = getItemTag( ( ItemStack ) object, keys );
+ } else if ( object instanceof Entity ) {
+ result = getEntityTag( ( Entity ) object, keys );
+ } else if ( object instanceof Block ) {
+ result = getBlockTag( ( Block ) object, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return result != null;
+ }
+
+ /**
+ * Sets the value in the object with the given keys
+ *
+ * @param object
+ * Must be an ItemStack, Entity, or Block
+ * @param value
+ * The value to set, can be an NBTCompound
+ * @param keys
+ * The keys in descending order
+ * @return
+ * The new item stack if the object provided is an item, else original object
+ */
+ public final static < T > T set( T object, Object value, Object... keys ) {
+ if ( object instanceof ItemStack ) {
+ return ( T ) setItemTag( ( ItemStack ) object, value, keys );
+ } else if ( object instanceof Entity ) {
+ setEntityTag( ( Entity ) object, value, keys );
+ } else if ( object instanceof Block ) {
+ setBlockTag( ( Block ) object, value, keys );
+ } else {
+ throw new IllegalArgumentException( "Object provided must be of type ItemStack, Entity, or Block!" );
+ }
+ return object;
+ }
+
+ private static void setTag( Object tag, Object value, Object... keys ) throws Exception {
+ Object notCompound;
+ if ( value != null ) {
+ if ( getNMSClass( "NBTTagList" ).isInstance( value ) || getNMSClass( "NBTTagCompound" ).isInstance( value ) ) {
+ notCompound = value;
+ } else {
+ notCompound = getConstructor( getNBTTag( value.getClass() ) ).newInstance( value );
+ }
+ } else {
+ notCompound = null;
+ }
+
+ Object compound = tag;
+ for ( int index = 0; index < keys.length; index++ ) {
+ Object key = keys[ index ];
+ if ( index + 1 == keys.length ) {
+ if ( key == null ) {
+ if ( VERSION.contains( "1_14" ) ) {
+ int type = ( int ) getMethod( "getTypeId" ).invoke( notCompound );
+ getMethod( "add" ).invoke( compound, type, notCompound );
+ } else {
+ getMethod( "add" ).invoke( compound, notCompound );
+ }
+ } else if ( key instanceof Integer ) {
+ if ( notCompound == null ) {
+ getMethod( "listRemove" ).invoke( compound, ( int ) key );
+ } else {
+ getMethod( "setIndex" ).invoke( compound, ( int ) key, notCompound );
+ }
+ } else {
+ if ( notCompound == null ) {
+ getMethod( "remove" ).invoke( compound, ( String ) key );
+ } else {
+ getMethod( "set" ).invoke( compound, ( String ) key, notCompound );
+ }
+ }
+ break;
+ }
+ Object oldCompound = compound;
+ if ( key instanceof Integer ) {
+ compound = ( ( List< ? > ) NBTListData.get( compound ) ).get( ( int ) key );
+ } else if ( key != null ) {
+ compound = getMethod( "get" ).invoke( compound, ( String ) key );
+ }
+ if ( compound == null || key == null ) {
+ if ( keys[ index + 1 ] == null || keys[ index + 1 ] instanceof Integer ) {
+ compound = getNMSClass( "NBTTagList" ).newInstance();
+ } else {
+ compound = getNMSClass( "NBTTagCompound" ).newInstance();
+ }
+ if ( oldCompound.getClass().getSimpleName().equals( "NBTTagList" ) ) {
+ getMethod( "add" ).invoke( oldCompound, compound );
+ } else {
+ if ( notCompound == null ) {
+ getMethod( "remove" ).invoke( oldCompound, ( String ) key );
+ } else {
+ getMethod( "set" ).invoke( oldCompound, ( String ) key, compound );
+ }
+ }
+ }
+ }
+ }
+
+ private static NBTCompound getNBTTag( Object tag, Object...keys ) throws Exception {
+ Object compound = tag;
+
+ for ( Object key : keys ) {
+ if ( compound == null ) {
+ return null;
+ } else if ( getNMSClass( "NBTTagCompound" ).isInstance( compound ) ) {
+ compound = getMethod( "get" ).invoke( compound, ( String ) key );
+ } else if ( getNMSClass( "NBTTagList" ).isInstance( compound ) ) {
+ compound = ( ( List< ? > ) NBTListData.get( compound ) ).get( ( int ) key );
+ }
+ }
+ return new NBTCompound( compound );
+ }
+
+ private static Object getTag( Object tag, Object... keys ) throws Exception {
+ if ( keys.length == 0 ) {
+ return getTags( tag );
+ }
+
+ Object notCompound = tag;
+
+ for ( Object key : keys ) {
+ if ( notCompound == null ) {
+ return null;
+ } else if ( getNMSClass( "NBTTagCompound" ).isInstance( notCompound ) ) {
+ notCompound = getMethod( "get" ).invoke( notCompound, ( String ) key );
+ } else if ( getNMSClass( "NBTTagList" ).isInstance( notCompound ) ) {
+ notCompound = ( ( List< ? > ) NBTListData.get( notCompound ) ).get( ( int ) key );
+ } else {
+ return getNBTVar( notCompound );
+ }
+ }
+ if ( notCompound == null ) {
+ return null;
+ } else if ( getNMSClass( "NBTTagList" ).isInstance( notCompound ) ) {
+ return getTags( notCompound );
+ } else if ( getNMSClass( "NBTTagCompound" ).isInstance( notCompound ) ) {
+ return getTags( notCompound );
+ } else {
+ return getNBTVar( notCompound );
+ }
+ }
+
+ @SuppressWarnings( "unchecked" )
+ private static Object getTags( Object tag ) {
+ Map< Object, Object > tags = new HashMap< Object, Object >();
+ try {
+ if ( getNMSClass( "NBTTagCompound" ).isInstance( tag ) ) {
+ Map< String, Object > tagCompound = ( Map< String, Object > ) NBTCompoundMap.get( tag );
+ for ( String key : tagCompound.keySet() ) {
+ Object value = tagCompound.get( key );
+ if ( getNMSClass( "NBTTagEnd" ).isInstance( value ) ) {
+ continue;
+ }
+ tags.put( key, getTag( value ) );
+ }
+ } else if ( getNMSClass( "NBTTagList" ).isInstance( tag ) ) {
+ List< Object > tagList = ( List< Object > ) NBTListData.get( tag );
+ for ( int index = 0; index < tagList.size(); index++ ) {
+ Object value = tagList.get( index );
+ if ( getNMSClass( "NBTTagEnd" ).isInstance( value ) ) {
+ continue;
+ }
+ tags.put( index, getTag( value ) );
+ }
+ } else {
+ return getNBTVar( tag );
+ }
+ return tags;
+ } catch ( Exception e ) {
+ e.printStackTrace();
+ return tags;
+ }
+ }
+
+ /**
+ * A class for holding NBTTagCompounds
+ */
+ public static final class NBTCompound {
+ protected final Object tag;
+
+ protected NBTCompound( @Nonnull Object tag ) {
+ this.tag = tag;
+ }
+
+ @Override
+ public String toString() {
+ return tag.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return tag.hashCode();
+ }
+
+ @Override
+ public boolean equals( Object obj ) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ NBTCompound other = (NBTCompound) obj;
+ if (tag == null) {
+ if (other.tag != null)
+ return false;
+ } else if (!tag.equals(other.tag))
+ return false;
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/Reference.java b/src/main/java/net/seanomik/tamablefoxes/Reference.java
new file mode 100644
index 0000000..782900d
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/Reference.java
@@ -0,0 +1,9 @@
+package net.seanomik.tamablefoxes;
+
+import org.bukkit.ChatColor;
+
+public class Reference {
+ public static final String CHAT_PREFIX = ChatColor.RED + "[Tamable Foxes] ";
+ public static final String CUSTOM_FOX_REGISTER_NAME = "tameablefox";
+}
+
diff --git a/src/main/java/net/seanomik/tamablefoxes/TamableFox.java b/src/main/java/net/seanomik/tamablefoxes/TamableFox.java
new file mode 100644
index 0000000..a06ebdf
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/TamableFox.java
@@ -0,0 +1,241 @@
+package net.seanomik.tamablefoxes;
+
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.CustomPathfinding.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_14_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_14_R1.event.CraftEventFactory;
+import org.bukkit.entity.Fox;
+import org.bukkit.event.entity.EntityTargetEvent;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.scheduler.BukkitTask;
+import org.bukkit.util.Vector;
+
+import javax.annotation.Nullable;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.UUID;
+
+import static net.seanomik.tamablefoxes.TamableFoxes.plugin;
+import static net.seanomik.tamablefoxes.TamableFoxes.fileManager;
+
+public class TamableFox extends EntityFox {
+ private boolean isTamed;
+ private EntityLiving owner;
+ private UUID ownerUUID;
+ private boolean sit = false;
+ private static Fox thisFox;
+ private BukkitTask sittingRunnable;
+
+ protected FoxPathfinderGoalSit goalSit;
+
+ @Override
+ protected void initPathfinder() {
+ this.goalSit = new FoxPathfinderGoalSit(this);
+
+ this.goalSelector.a(1, new FoxPathfinderGoalFloat(this));
+
+ this.goalSelector.a(2, this.goalSit);
+
+ this.goalSelector.a(3, new FoxPathfinderGoalMeleeAttack(this, 1.2000000476837158D, true)); // l | Lunging
+ this.goalSelector.a(3, new PathfinderGoalAvoidTarget(this, EntityWolf.class, 8.0F, 1.6D, 1.4D, (entityliving) -> {
+ return !((EntityWolf)entityliving).isTamed();
+ }));
+ this.goalSelector.a(4, new FoxPathfinderGoalFollowOwner(this, 1.35D, 10.0F, 2.0F));
+
+ this.goalSelector.a(4, new FoxPathfinderGoalLungeUNKNOWN_USE(this)); // u | Lunging
+ this.goalSelector.a(5, new FoxPathfinderGoalLunge(this)); // o | Lunging
+
+ this.goalSelector.a(5, new FoxPathfinderGoalFleeSun(this, 1.25D));
+ this.goalSelector.a(5, new FoxPathfinderGoalBreed(this, 1.0D));
+
+ this.goalSelector.a(7, new PathfinderGoalFollowParent(this, 1.1D));
+ this.goalSelector.a(7, new FoxPathfinderGoalBeg(this, 8.0F));
+ this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
+ this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
+ this.goalSelector.a(9, new FoxPathfinderGoalPickBushes(this, 1.2000000476837158D, 12, 2));
+ this.goalSelector.a(10, new FoxPathfinderGoalRandomStrollLand(this, 1D));
+
+ this.targetSelector.a(1, new FoxPathfinderGoalOwnerHurtByTarget(this));
+ this.targetSelector.a(2, new FoxPathfinderGoalOwnerHurtTarget(this));
+
+ PathfinderGoal targetsGoal = new PathfinderGoalNearestAttackableTarget(this, EntityLiving.class, 10, false, false, (entityliving) -> {
+ return entityliving instanceof EntityChicken || entityliving instanceof EntityRabbit;
+ });
+
+ this.targetSelector.a(4, targetsGoal);
+ this.targetSelector.a(5, (new FoxPathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0]));
+ }
+
+ public TamableFox(EntityTypes entitytypes, World world) {
+ super(EntityTypes.FOX, world);
+
+ thisFox = (Fox) this.getBukkitEntity();
+ TamableFoxes.foxUUIDs.put(this.getBukkitEntity().getUniqueId(), null);
+ }
+
+ //@Override
+ //protected void a(DifficultyDamageScaler difficultydamagescaler) { } // Doesn't spawn with any items in its mouth
+
+ @Override
+ protected void initAttributes() {
+ this.getAttributeMap().b(GenericAttributes.MAX_HEALTH);
+ this.getAttributeMap().b(GenericAttributes.KNOCKBACK_RESISTANCE);
+ this.getAttributeMap().b(GenericAttributes.MOVEMENT_SPEED);
+ this.getAttributeMap().b(GenericAttributes.ARMOR);
+ this.getAttributeMap().b(GenericAttributes.ARMOR_TOUGHNESS);
+
+ this.getAttributeMap().b(GenericAttributes.FOLLOW_RANGE).setValue(2.0D);
+ this.getAttributeMap().b(GenericAttributes.ATTACK_KNOCKBACK);
+
+ this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.30000001192092896D);
+ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(24.0D);
+ this.getAttributeInstance(GenericAttributes.FOLLOW_RANGE).setValue(32.0D);
+ this.getAttributeMap().b(GenericAttributes.ATTACK_DAMAGE).setValue(3.0D);
+ }
+
+ @Override
+ public EntityFox createChild(EntityAgeable entityageable) {
+ WorldServer world = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle();
+ Location location = entityageable.getBukkitEntity().getLocation();
+ net.minecraft.server.v1_14_R1.Entity b = TamableFoxes.customType.b(world,
+ null,
+ null,
+ null,
+ new BlockPosition(location.getX(), location.getY(), location.getZ()),
+ null, false, false);
+
+ EntityFox entityfox = (EntityFox) b;
+ entityfox.setFoxType(this.random.nextBoolean() ? this.getFoxType() : ((EntityFox)entityageable).getFoxType());
+
+ return entityfox;
+ }
+
+ // Pick up items
+ @Override
+ protected void a(EntityItem entityitem) {
+ ItemStack itemstack = entityitem.getItemStack();
+ if (!isTamed() && !CraftEventFactory.callEntityPickupItemEvent(this, entityitem, itemstack.getCount() - 1, !this.g(itemstack)).isCancelled()) {
+ try {
+ int i = itemstack.getCount();
+ if (i > 1) {
+ Method method = this.getClass().getSuperclass().getDeclaredMethod("l", ItemStack.class);
+ method.setAccessible(true);
+ method.invoke(this, itemstack.cloneAndSubtract(i - 1));
+ method.setAccessible(false);
+ }
+ Method method = this.getClass().getSuperclass().getDeclaredMethod("k", ItemStack.class);
+ method.setAccessible(true);
+ method.invoke(this, this.getEquipment(EnumItemSlot.MAINHAND));
+ method.setAccessible(false);
+
+ this.setSlot(EnumItemSlot.MAINHAND, itemstack.cloneAndSubtract(1));
+ this.dropChanceHand[EnumItemSlot.MAINHAND.b()] = 2.0F;
+ this.receive(entityitem, itemstack.getCount());
+ entityitem.die();
+
+ Field field = this.getClass().getSuperclass().getDeclaredField("bO");
+ field.setAccessible(true);
+ field.set(this, 0);
+ field.setAccessible(false);
+ } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public boolean isTamed() {
+ return isTamed;
+ }
+
+ public void setOwner(EntityLiving owner) {
+ this.owner = owner;
+ fileManager.getConfig("foxes.yml").set("Foxes." + thisFox.getUniqueId() + ".owner", owner.getUniqueIDString());
+ fileManager.saveConfig("foxes.yml");
+ sittingRunnable = new UpdateFoxRunnable(plugin).runTask(plugin);
+ }
+
+ public EntityLiving getOwner() {
+ return owner;
+ }
+
+ public void setTamed(Boolean tamed) {
+ this.isTamed = tamed;
+ }
+
+ public boolean toggleSitting() {
+ sit = !sit;
+ sittingRunnable = new UpdateFoxRunnable(plugin).runTask(plugin);
+ return sit;
+ }
+
+ public void updateFox() {
+ sittingRunnable = new UpdateFoxRunnable(plugin).runTask(plugin);
+ }
+
+ @Nullable
+ @Override
+ public GroupDataEntity prepare(GeneratorAccess generatoraccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) {
+ BiomeBase biomebase = generatoraccess.getBiome(new BlockPosition(this));
+ EntityFox.Type entityfox_type = EntityFox.Type.a(biomebase);
+ boolean flag = false;
+ if (groupdataentity instanceof EntityFox.i) {
+ entityfox_type = ((EntityFox.i)groupdataentity).a;
+ if (((EntityFox.i)groupdataentity).b >= 2) {
+ flag = true;
+ } else {
+ ++((EntityFox.i)groupdataentity).b;
+ }
+ } else {
+ groupdataentity = new EntityFox.i(entityfox_type);
+ ++((EntityFox.i)groupdataentity).b;
+ }
+
+ this.setFoxType(entityfox_type);
+ if (flag) {
+ this.setAgeRaw(-24000);
+ }
+
+ this.initPathfinder();
+ this.a(difficultydamagescaler);
+
+ // From EntityInsentient
+ this.getAttributeInstance(GenericAttributes.FOLLOW_RANGE).addModifier(new AttributeModifier("Random spawn bonus", this.random.nextGaussian() * 0.05D, AttributeModifier.Operation.MULTIPLY_BASE));
+ if (this.random.nextFloat() < 0.05F) {
+ this.p(true);
+ } else {
+ this.p(false);
+ }
+
+ return groupdataentity;
+ }
+
+ private class UpdateFoxRunnable extends BukkitRunnable {
+ private final JavaPlugin plugin;
+
+ public UpdateFoxRunnable(JavaPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void run() {
+ TamableFox.this.goalSit.setSitting(TamableFox.this.sit);
+ TamableFox.thisFox.setVelocity(new Vector(0,0,0));
+
+ TamableFox.this.setGoalTarget(null, EntityTargetEvent.TargetReason.CUSTOM, true);
+
+ // Set custom name
+ if (TamableFox.this.owner != null && fileManager.getConfig("config.yml").get().getBoolean("show-owner-in-fox-name")) {
+ getBukkitEntity().setCustomName("Tamed by: " + TamableFox.this.owner.getName());
+ getBukkitEntity().setCustomNameVisible(true);
+ }
+ }
+ }
+
+ public boolean isJumping() {
+ return jumping;
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/TamableFoxes.java b/src/main/java/net/seanomik/tamablefoxes/TamableFoxes.java
new file mode 100644
index 0000000..3a1aeb7
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/TamableFoxes.java
@@ -0,0 +1,464 @@
+package net.seanomik.tamablefoxes;
+
+import com.mojang.datafixers.DataFixUtils;
+import com.mojang.datafixers.types.Type;
+import com.sun.istack.internal.NotNull;
+import net.minecraft.server.v1_14_R1.*;
+import net.seanomik.tamablefoxes.Commands.CommandSpawnTamableFox;
+import net.seanomik.tamablefoxes.Utils.FileManager;
+import org.bukkit.*;
+import org.bukkit.Chunk;
+import org.bukkit.Material;
+import org.bukkit.Particle;
+import org.bukkit.World;
+import org.bukkit.craftbukkit.v1_14_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_14_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_14_R1.entity.CraftFox;
+import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack;
+import org.bukkit.entity.*;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.event.player.*;
+import org.bukkit.event.world.ChunkLoadEvent;
+import org.bukkit.inventory.EquipmentSlot;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.io.*;
+import java.util.*;
+import java.util.logging.Level;
+
+public final class TamableFoxes extends JavaPlugin implements Listener {
+
+ public static Map foxUUIDs = new HashMap<>(); // FoxUUID, OwnerUUID
+ public static EntityTypes customType;
+ public static JavaPlugin plugin;
+ private boolean isOnLoad = true;
+ private File dataFolder = null;
+
+ public static FileManager fileManager;
+
+ // TODO:
+ // Fix the fox moving when you make it sit while it was moving.
+
+ @Override
+ public void onEnable() {
+ if (!Bukkit.getVersion().contains("1.14.4")) {
+ Bukkit.getConsoleSender().sendMessage(Reference.CHAT_PREFIX + ChatColor.RED + "THIS PLUGIN WILL ONLY RUN ON MC SPIGOT 1.14.4!");
+ getServer().getPluginManager().disablePlugin(this);
+ return;
+ }
+
+ fileManager = new FileManager(this);
+ fileManager.getConfig("foxes.yml").copyDefaults(true).save();
+ fileManager.getConfig("config.yml").copyDefaults(true).save();
+
+ getServer().getPluginManager().registerEvents(this, this);
+ getCommand("spawntamablefox").setExecutor(new CommandSpawnTamableFox());
+
+ plugin = getPlugin(this.getClass());
+ dataFolder = getDataFolder();
+
+ // Registering Fox
+ Map> types = (Map>) DataConverterRegistry.a().getSchema(DataFixUtils.makeKey(SharedConstants.a().getWorldVersion())).findChoiceType(DataConverterTypes.ENTITY).types();
+ types.put("minecraft:" + Reference.CUSTOM_FOX_REGISTER_NAME, types.get("minecraft:fox"));
+ EntityTypes.a a = EntityTypes.a.a(TamableFox::new, EnumCreatureType.AMBIENT);
+ customType = IRegistry.a(IRegistry.ENTITY_TYPE, Reference.CUSTOM_FOX_REGISTER_NAME, a.a(Reference.CUSTOM_FOX_REGISTER_NAME));
+
+ // Spawn all foxes
+ replaceFoxesOnLoad();
+ }
+
+ private void replaceFoxesOnLoad() {
+ int amountReplaced = 0;
+ World world = Bukkit.getWorlds().get(0);
+ for (Chunk chunk : world.getLoadedChunks()) {
+ for (Entity entity : chunk.getEntities()) {
+ if(entity instanceof Fox) {
+ if (isTamableFox(entity)) {
+ continue;
+ }
+ TamableFox tamableFox = (TamableFox) spawnTamableFox(entity.getLocation(), ((CraftFox) entity).getHandle().getFoxType());
+
+ if (fileManager.getConfig("foxes.yml").get().getString("Foxes." + entity.getUniqueId()) != null) {
+ Bukkit.broadcastMessage("NOT NULL");
+ String owner = fileManager.getConfig("foxes.yml").get().getString("Foxes." + entity.getUniqueId() + ".owner");
+
+ fileManager.getConfig("foxes.yml").get().set("Foxes." + entity.getUniqueId(), null);
+ if (owner.equals("none")) {
+ foxUUIDs.replace(tamableFox.getUniqueID(), null);
+ fileManager.getConfig("foxes.yml").get().set("Foxes." + tamableFox.getUniqueID() + ".owner", "none");
+ } else {
+ foxUUIDs.replace(tamableFox.getUniqueID(), UUID.fromString(owner));
+ tamableFox.setTamed(true);
+ fileManager.getConfig("foxes.yml").get().set("Foxes." + tamableFox.getUniqueID() + ".owner", owner);
+ }
+
+ tamableFox.setSitting(((EntityFox) ((CraftEntity) entity).getHandle()).isSitting());
+ tamableFox.updateFox();
+
+ tamableFox.setAge(((CraftFox) entity).getAge());
+
+ org.bukkit.inventory.ItemStack entityMouthItem = ((CraftFox) entity).getEquipment().getItemInMainHand();
+ if (entityMouthItem.getType() != Material.AIR) {
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, CraftItemStack.asNMSCopy(new org.bukkit.inventory.ItemStack(entityMouthItem.getType(), 1)));
+ } else {
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.AIR));
+ }
+ } else {
+ fileManager.getConfig("foxes.yml").get().set("Foxes." + tamableFox.getUniqueID() + ".owner", "none");
+ tamableFox.setAge(((CraftFox) entity).getAge());
+
+ org.bukkit.inventory.ItemStack entityMouthItem = ((CraftFox) entity).getEquipment().getItemInMainHand();
+ if (entityMouthItem.getType() != Material.AIR) {
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, CraftItemStack.asNMSCopy(new org.bukkit.inventory.ItemStack(entityMouthItem.getType(), 1)));
+ } else {
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.AIR));
+ }
+ }
+
+ entity.remove();
+ amountReplaced++;
+ }
+ }
+ }
+
+ //Bukkit.getConsoleSender().sendMessage(Reference.CHAT_PREFIX + "Respawned " + amountReplaced + " foxes.");
+ fileManager.saveConfig("foxes.yml");
+ isOnLoad = false;
+ }
+
+ public static net.minecraft.server.v1_14_R1.Entity spawnTamableFox(Location location, EntityFox.Type type) {
+ WorldServer world = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle();
+
+ net.minecraft.server.v1_14_R1.Entity spawnedEntity = customType.b(world,
+ null,
+ null,
+ null,
+ new BlockPosition(location.getX(), location.getY(), location.getZ()),
+ null, false, false);
+ world.addEntity(spawnedEntity);
+
+ EntityFox fox = (EntityFox) spawnedEntity;
+ fox.setFoxType(type);
+ fileManager.getConfig("foxes.yml").get().set("Foxes." + spawnedEntity.getUniqueID() + ".owner", "none");
+ fileManager.saveConfig("foxes.yml");
+ return fox;
+ }
+
+ @EventHandler
+ public void onChunkLoad (ChunkLoadEvent event) {
+ if (!isOnLoad) {
+ Chunk chunk = event.getChunk();
+ for (Entity entity : chunk.getEntities()) {
+ if (entity instanceof Fox) {
+ if (isTamableFox(entity)) {
+ continue;
+ }
+ TamableFox tamableFox = (TamableFox) spawnTamableFox(entity.getLocation(), ((CraftFox) entity).getHandle().getFoxType());
+
+ if (fileManager.getConfig("foxes.yml").get().getString("Foxes." + entity.getUniqueId()) != null) {
+ String owner = fileManager.getConfig("foxes.yml").get().getString("Foxes." + entity.getUniqueId() + ".owner");
+ if (!owner.equals("none")) {
+ foxUUIDs.replace(tamableFox.getUniqueID(), UUID.fromString(owner));
+ fileManager.getConfig("foxes.yml").get().set("Foxes." + tamableFox.getUniqueID() + ".owner", owner);
+ fileManager.getConfig("foxes.yml").get().set("Foxes." + entity.getUniqueId(), null);
+ tamableFox.setTamed(true);
+ tamableFox.setSitting(((EntityFox) ((CraftEntity) entity).getHandle()).isSitting());
+ tamableFox.updateFox();
+
+ tamableFox.setAge(((CraftFox) entity).getAge());
+
+ org.bukkit.inventory.ItemStack entityMouthItem = ((CraftFox) entity).getEquipment().getItemInMainHand();
+ if (entityMouthItem.getType() != Material.AIR) {
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, CraftItemStack.asNMSCopy(new org.bukkit.inventory.ItemStack(entityMouthItem.getType(), 1)));
+ } else {
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.AIR));
+ }
+ }
+ } else {
+ fileManager.getConfig("foxes.yml").get().set("Foxes." + tamableFox.getUniqueID() + ".owner", "none");
+ tamableFox.setAge(((CraftFox) entity).getAge());
+
+ org.bukkit.inventory.ItemStack entityMouthItem = ((CraftFox) entity).getEquipment().getItemInMainHand();
+ if (entityMouthItem.getType() != Material.AIR) {
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, CraftItemStack.asNMSCopy(new org.bukkit.inventory.ItemStack(entityMouthItem.getType(), 1)));
+ } else {
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.AIR));
+ }
+ }
+
+ entity.remove();
+ }
+ }
+ }
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ Player player = event.getPlayer();
+ if (foxUUIDs.containsValue(player.getUniqueId())) {
+ for (Map.Entry entry : foxUUIDs.entrySet()) {
+ if (entry.getValue() != null && entry.getValue().equals(player.getUniqueId())) {
+ TamableFox tamableFox = (TamableFox) ((CraftEntity) getEntityByUniqueId(entry.getKey())).getHandle();
+ tamableFox.setOwner(((CraftPlayer) player).getHandle());
+ tamableFox.setTamed(true);
+ foxUUIDs.replace(tamableFox.getUniqueID(), player.getUniqueId());
+ }
+ }
+ }
+ }
+
+ @EventHandler
+ public void onPlayerInteractEntityEvent(PlayerInteractEntityEvent event) {
+ Entity entity = event.getRightClicked();
+ Player player = event.getPlayer();
+
+ if (event.getHand().equals(EquipmentSlot.HAND)) {
+ ItemMeta itemMeta = player.getInventory().getItemInMainHand().getItemMeta();
+ if (player.getInventory().getItemInMainHand().getType() == Material.REDSTONE_TORCH && !isTamableFox(entity) && itemMeta.getLore().contains(ChatColor.BLUE + "Tamable Fox Inspector")) {
+ org.bukkit.inventory.ItemStack item = player.getInventory().getItemInMainHand();
+ ItemMeta itemMeta1 = item.getItemMeta();
+ List lore = new LinkedList<>(Arrays.asList(
+ ChatColor.BLUE + "Tamable Fox Inspector",
+ "UUID: " + entity.getUniqueId(),
+ "Entity ID: " + entity.getEntityId()
+ ));
+ itemMeta1.setLore(lore);
+ item.setItemMeta(itemMeta1);
+ player.sendMessage("Inspected Entity.");
+ }
+
+ if (isTamableFox(entity)) {
+ TamableFox tamableFox = (TamableFox) ((CraftEntity)entity).getHandle();
+ if (player.getInventory().getItemInMainHand().getType() == Material.REDSTONE_TORCH && itemMeta.getLore().contains(ChatColor.BLUE + "Tamable Fox Inspector")) {
+ org.bukkit.inventory.ItemStack item = player.getInventory().getItemInMainHand();
+ List lore = new LinkedList<>();
+ if (tamableFox.getOwner() == null) {
+ lore = new LinkedList<>(Arrays.asList(
+ ChatColor.BLUE + "Tamable Fox Inspector",
+ "UUID: " + entity.getUniqueId(),
+ "Entity ID: " + entity.getEntityId(),
+ "Tamable",
+ "Owner: None"
+ ));
+ } else {
+ lore = new LinkedList<>(Arrays.asList(
+ ChatColor.BLUE + "Tamable Fox Inspector",
+ "UUID: " + entity.getUniqueId(),
+ "Entity ID: " + entity.getEntityId(),
+ "Tamable",
+ "Owner: " + tamableFox.getOwner().getName()
+ ));
+ }
+
+ itemMeta.setLore(lore);
+ item.setItemMeta(itemMeta);
+
+ event.setCancelled(true);
+ return;
+ }
+
+ if (tamableFox.isTamed() && tamableFox.getOwner() != null && player.getInventory().getItemInMainHand().getType() != Material.SWEET_BERRIES) {
+ if (player.getUniqueId() == foxUUIDs.get(entity.getUniqueId())) {
+ if (player.isSneaking()) { // Shift right click to add items
+ ItemStack itemstack = tamableFox.getEquipment(EnumItemSlot.MAINHAND);
+ if (itemstack.isEmpty()) {
+ if (player.getInventory().getItemInMainHand().getType() == Material.AIR) {
+ return;
+ }
+ net.minecraft.server.v1_14_R1.ItemStack playerItemInHandNMS = CraftItemStack.asNMSCopy(player.getInventory().getItemInMainHand());
+
+ playerItemInHandNMS.setCount(1);
+
+ // Set foxes mouth
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, playerItemInHandNMS);
+
+ // Take item from player
+ org.bukkit.inventory.ItemStack playerItemInHand = player.getInventory().getItemInMainHand();
+ playerItemInHand.setAmount(playerItemInHand.getAmount() - 1);
+ player.getInventory().setItemInMainHand(playerItemInHand);
+ } else {
+ if (player.getInventory().getItemInMainHand().getType() == Material.AIR) { //
+ // Drop the item in the foxes mouth on the floor
+ World world = Bukkit.getWorlds().get(0);
+ world.dropItem(tamableFox.getBukkitEntity().getLocation().add(0D, 0.2D, 0D), CraftItemStack.asBukkitCopy(itemstack));
+
+ // Remove the item from mouth
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.AIR));
+ } else { // Replace items
+ net.minecraft.server.v1_14_R1.ItemStack playerItemInHandNMS = CraftItemStack.asNMSCopy(player.getInventory().getItemInMainHand());
+
+ // Drop the item in the foxes mouth on the floor
+ World world = Bukkit.getWorlds().get(0);
+ world.dropItem(tamableFox.getBukkitEntity().getLocation().add(0D, 0.2D, 0D), CraftItemStack.asBukkitCopy(itemstack));
+
+ playerItemInHandNMS.setCount(1);
+
+ // Set foxes mouth
+ tamableFox.setSlot(EnumItemSlot.MAINHAND, playerItemInHandNMS);
+
+ // Take item from player
+ org.bukkit.inventory.ItemStack playerItemInHand = player.getInventory().getItemInMainHand();
+ playerItemInHand.setAmount(playerItemInHand.getAmount() - 1);
+ player.getInventory().setItemInMainHand(playerItemInHand);
+ }
+ }
+ } else {
+ tamableFox.toggleSitting();
+ }
+ event.setCancelled(true);
+ }
+ } else if (player.getInventory().getItemInMainHand().getType() == Material.CHICKEN) {
+ if (Math.random() < 0.33) {
+ tamableFox.setTamed(true);
+ tamableFox.setOwner(((CraftPlayer) player).getHandle());
+
+ // Add fox to foxUUIDs to get their owner and other things
+ TamableFoxes.foxUUIDs.replace(tamableFox.getUniqueID(), null, player.getUniqueId());
+
+ // Indicate that it was tamed
+ player.getWorld().spawnParticle(Particle.HEART, entity.getLocation(), 6, 0.5D, 0.5D, 0.5D);
+ } else {
+ player.getWorld().spawnParticle(Particle.SMOKE_NORMAL, entity.getLocation(), 10, 0.3D, 0.3D, 0.3D, 0.15D);
+ }
+
+ if (!player.getGameMode().equals(GameMode.CREATIVE)) { // Remove chicken from inventory unless in creative
+ player.getInventory().getItemInMainHand().setAmount(player.getInventory().getItemInMainHand().getAmount() - 1);
+ }
+ event.setCancelled(true);
+ }
+ }
+ }
+ }
+
+ // Make it so when player sleeps, fox does too
+ @EventHandler
+ public void onPlayerBedEnterEvent(PlayerBedEnterEvent event) {
+ Player player = event.getPlayer();
+ if (foxUUIDs.containsValue(player.getUniqueId())) {
+ List listOfUUIDs = new ArrayList<>();
+
+ for (Map.Entry entry : foxUUIDs.entrySet()) {
+ if (entry.getValue().equals(player.getUniqueId())) {
+ listOfUUIDs.add(entry.getKey());
+ }
+ }
+
+ for (UUID uuid : listOfUUIDs) {
+ TamableFox tamableFox = ((TamableFox)((CraftFox)getEntityByUniqueId(uuid)).getHandle());
+ if (player.getWorld().getTime() > 12541 && player.getWorld().getTime() < 23460) {
+ tamableFox.setSleeping(true);
+ }
+ }
+ }
+ }
+
+ // Wake the fox up when the player wakes up
+ @EventHandler
+ public void onPlayerBedLeaveEvent(PlayerBedLeaveEvent event) {
+ Player player = event.getPlayer();
+ if (foxUUIDs.containsValue(player.getUniqueId())) {
+ List listOfUUIDs = new ArrayList<>();
+
+ for (Map.Entry entry : foxUUIDs.entrySet()) {
+ if (entry.getValue().equals(event.getPlayer().getUniqueId())) {
+ listOfUUIDs.add(entry.getKey());
+ }
+ }
+
+ for (UUID uuid : listOfUUIDs) {
+ TamableFox tamableFox = ((TamableFox)((CraftFox)getEntityByUniqueId(uuid)).getHandle());
+ tamableFox.setSleeping(false);
+ }
+ }
+ }
+
+ // Replace all spawned foxes with the TamableFox
+ @EventHandler
+ public void onCreatureSpawnEvent(CreatureSpawnEvent event) {
+ Entity entity = event.getEntity();
+ if(entity instanceof Fox && !(isTamableFox(entity))) {
+ EntityFox.Type foxType = ((EntityFox) ((CraftEntity)entity).getHandle()).getFoxType();
+ spawnTamableFox(entity.getLocation(), foxType);
+ //Bukkit.getConsoleSender().sendMessage(Reference.CHAT_PREFIX + "Replaced vanilla fox");
+ event.setCancelled(true);
+ }
+ }
+
+ public Entity getEntityByUniqueId(UUID uniqueId){
+ for (org.bukkit.World world : Bukkit.getWorlds()) {
+ for (org.bukkit.Chunk chunk : world.getLoadedChunks()) {
+ for (Entity entity : chunk.getEntities()) {
+ if (entity.getUniqueId().equals(uniqueId)) {
+ return entity;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @EventHandler
+ public void onEntityDeathEvent (EntityDeathEvent event) {
+ Entity entity = event.getEntity();
+ if (isTamableFox(entity)) {
+ foxUUIDs.remove(entity.getUniqueId());
+ if (fileManager.getConfig("foxes.yml").get().getConfigurationSection("Foxes").contains(entity.getUniqueId() + "")) {
+ fileManager.getConfig("foxes.yml").get().set("Foxes." + entity.getUniqueId(), null);
+ fileManager.saveConfig("foxes.yml");
+ }
+ }
+ }
+
+ public boolean isTamableFox(Entity entity) {
+ return ((CraftEntity)entity).getHandle().getClass().getName().contains("TamableFox") || ((CraftEntity)entity).getHandle() instanceof TamableFox;
+ }
+
+ @Override
+ public void saveResource(@NotNull String resourcePath, boolean replace) {
+ File file = getFile();
+ if (resourcePath != null && !resourcePath.equals("")) {
+ resourcePath = resourcePath.replace('\\', '/');
+ InputStream in = this.getResource(resourcePath);
+ if (in == null) {
+ throw new IllegalArgumentException("The embedded resource '" + resourcePath + "' cannot be found in " + file);
+ } else {
+ File outFile = new File(this.dataFolder, resourcePath);
+ int lastIndex = resourcePath.lastIndexOf(47);
+ File outDir = new File(this.dataFolder, resourcePath.substring(0, lastIndex >= 0 ? lastIndex : 0));
+ if (!outDir.exists()) {
+ outDir.mkdirs();
+ }
+
+ try {
+ //if (outFile.exists() && !replace) {
+ if (!outFile.exists() && replace) {
+ //this.logger.log(Level.WARNING, "Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists.");
+ OutputStream out = new FileOutputStream(outFile);
+ byte[] buf = new byte[1024];
+
+ int len;
+ while((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+
+ out.close();
+ in.close();
+ } /*else {
+
+ }*/
+ } catch (IOException var10) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + outFile.getName() + " to " + outFile, var10);
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("ResourcePath cannot be null or empty");
+ }
+ }
+}
diff --git a/src/main/java/net/seanomik/tamablefoxes/Utils/FileManager.java b/src/main/java/net/seanomik/tamablefoxes/Utils/FileManager.java
new file mode 100644
index 0000000..2cceca5
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/Utils/FileManager.java
@@ -0,0 +1,112 @@
+package net.seanomik.tamablefoxes.Utils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class FileManager {
+
+ private final JavaPlugin plugin;
+ private HashMap configs = new HashMap();
+
+ public FileManager(JavaPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public Config getConfig(String name) {
+ if (!configs.containsKey(name))
+ configs.put(name, new Config(name));
+
+ return configs.get(name);
+ }
+
+ public Config saveConfig(String name) {
+ return getConfig(name).save();
+ }
+
+ public Config reloadConfig(String name) {
+ return getConfig(name).reload();
+ }
+
+ public class Config {
+ private String name;
+ private File file;
+ private YamlConfiguration config;
+
+ public Config(String name) {
+ this.name = name;
+ }
+
+ public Config save() {
+ if ((this.config == null) || (this.file == null))
+ return this;
+ try
+ {
+ if (config.getConfigurationSection("").getKeys(true).size() != 0)
+ config.save(this.file);
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ }
+ return this;
+ }
+
+ public YamlConfiguration get() {
+ if (this.config == null)
+ reload();
+
+ return this.config;
+ }
+
+ public Config saveDefaultConfig() {
+ file = new File(plugin.getDataFolder(), this.name);
+
+ plugin.saveResource(this.name, false);
+
+ return this;
+ }
+
+ public Config reload() {
+ if (file == null)
+ this.file = new File(plugin.getDataFolder(), this.name);
+
+ this.config = YamlConfiguration.loadConfiguration(file);
+
+ Reader defConfigStream;
+ try {
+ defConfigStream = new InputStreamReader(plugin.getResource(this.name), "UTF8");
+
+ if (defConfigStream != null)
+ {
+ YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream);
+ this.config.setDefaults(defConfig);
+ }
+ }
+ catch (UnsupportedEncodingException | NullPointerException e) {
+
+ }
+ return this;
+ }
+
+ public Config copyDefaults(boolean force) {
+ get().options().copyDefaults(force);
+ return this;
+ }
+
+ public Config set(String key, Object value) {
+ get().set(key, value);
+ return this;
+ }
+
+ public Object get(String key) {
+ return get().get(key);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/seanomik/tamablefoxes/Utils/Utils.java b/src/main/java/net/seanomik/tamablefoxes/Utils/Utils.java
new file mode 100644
index 0000000..099305f
--- /dev/null
+++ b/src/main/java/net/seanomik/tamablefoxes/Utils/Utils.java
@@ -0,0 +1,27 @@
+package net.seanomik.tamablefoxes.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class Utils {
+ public static List getAllKeysForValue(Map mapOfWords, V value) {
+ List listOfKeys = null;
+
+ if(mapOfWords.containsValue(value)) {
+ listOfKeys = new ArrayList<>();
+
+ // Iterate over each entry of map using entrySet
+ for (Map.Entry entry : mapOfWords.entrySet()) {
+ // Check if value matches with given value
+ if (entry.getValue().equals(value))
+ {
+ // Store the key from entry to the list
+ listOfKeys.add(entry.getKey());
+ }
+ }
+ }
+ // Return the list of keys whose value matches with given value.
+ return listOfKeys;
+ }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..514f71e
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1 @@
+show-owner-in-fox-name: true
\ No newline at end of file
diff --git a/src/main/resources/foxes.yml b/src/main/resources/foxes.yml
new file mode 100644
index 0000000..0803ad5
--- /dev/null
+++ b/src/main/resources/foxes.yml
@@ -0,0 +1,2 @@
+# Foxes are saved here:
+Foxes: {}
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..4e5b274
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,14 @@
+name: TamableFoxes
+version: ${project.version}
+main: net.seanomik.tamablefoxes.TamableFoxes
+api-version: 1.14
+load: POSTWORLD
+commands:
+ spawntamablefox:
+ aliases: [stf, spawntf]
+ usage: /spawntamablefox [type]
+ description: Spawn a tamable fox at the standing location. Type can be snow or red, or left empty for a red.
+permissions:
+ tamablefoxes.spawn:
+ description: "Gives the player the ability to spawn tamable foxes."
+ default: false
diff --git a/target/classes/META-INF/TamableFoxes.kotlin_module b/target/classes/META-INF/TamableFoxes.kotlin_module
new file mode 100644
index 0000000..2983af7
Binary files /dev/null and b/target/classes/META-INF/TamableFoxes.kotlin_module differ
diff --git a/target/classes/config.yml b/target/classes/config.yml
new file mode 100644
index 0000000..514f71e
--- /dev/null
+++ b/target/classes/config.yml
@@ -0,0 +1 @@
+show-owner-in-fox-name: true
\ No newline at end of file
diff --git a/target/classes/foxes.yml b/target/classes/foxes.yml
new file mode 100644
index 0000000..0803ad5
--- /dev/null
+++ b/target/classes/foxes.yml
@@ -0,0 +1,2 @@
+# Foxes are saved here:
+Foxes: {}
\ No newline at end of file
diff --git a/target/classes/net/seanomik/tamablefoxes/Commands/CommandSpawnTamableFox.class b/target/classes/net/seanomik/tamablefoxes/Commands/CommandSpawnTamableFox.class
new file mode 100644
index 0000000..0ff0590
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/Commands/CommandSpawnTamableFox.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/ConfigManager.class b/target/classes/net/seanomik/tamablefoxes/ConfigManager.class
new file mode 100644
index 0000000..d8d1b6b
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/ConfigManager.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBeg.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBeg.class
new file mode 100644
index 0000000..401d6ed
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBeg.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBreed.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBreed.class
new file mode 100644
index 0000000..9e0134e
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalBreed.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFleeSun.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFleeSun.class
new file mode 100644
index 0000000..c5643b8
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFleeSun.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFloat.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFloat.class
new file mode 100644
index 0000000..6b01fa7
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFloat.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFollowOwner.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFollowOwner.class
new file mode 100644
index 0000000..a4b8631
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalFollowOwner.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalHurtByTarget.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalHurtByTarget.class
new file mode 100644
index 0000000..cd10698
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalHurtByTarget.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLunge.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLunge.class
new file mode 100644
index 0000000..809f0a4
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLunge.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLungeUNKNOWN_USE.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLungeUNKNOWN_USE.class
new file mode 100644
index 0000000..2b7325a
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalLungeUNKNOWN_USE.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalMeleeAttack.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalMeleeAttack.class
new file mode 100644
index 0000000..011b9e5
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalMeleeAttack.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtByTarget.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtByTarget.class
new file mode 100644
index 0000000..58319a6
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtByTarget.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtTarget.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtTarget.class
new file mode 100644
index 0000000..906cde6
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalOwnerHurtTarget.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalPickBushes.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalPickBushes.class
new file mode 100644
index 0000000..3631239
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalPickBushes.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomStrollLand.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomStrollLand.class
new file mode 100644
index 0000000..4023eed
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomStrollLand.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomTargetNonTamed.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomTargetNonTamed.class
new file mode 100644
index 0000000..56f4e79
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalRandomTargetNonTamed.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalSit.class b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalSit.class
new file mode 100644
index 0000000..ed7e6d9
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/CustomPathfinding/FoxPathfinderGoalSit.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/NBTEditor$NBTCompound.class b/target/classes/net/seanomik/tamablefoxes/NBTEditor$NBTCompound.class
new file mode 100644
index 0000000..6798af4
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/NBTEditor$NBTCompound.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/NBTEditor.class b/target/classes/net/seanomik/tamablefoxes/NBTEditor.class
new file mode 100644
index 0000000..99ceeef
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/NBTEditor.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/Reference.class b/target/classes/net/seanomik/tamablefoxes/Reference.class
new file mode 100644
index 0000000..627c70f
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/Reference.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/TamableFox$UpdateFoxRunnable.class b/target/classes/net/seanomik/tamablefoxes/TamableFox$UpdateFoxRunnable.class
new file mode 100644
index 0000000..16a4168
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/TamableFox$UpdateFoxRunnable.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/TamableFox.class b/target/classes/net/seanomik/tamablefoxes/TamableFox.class
new file mode 100644
index 0000000..322c741
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/TamableFox.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/TamableFoxes.class b/target/classes/net/seanomik/tamablefoxes/TamableFoxes.class
new file mode 100644
index 0000000..d4b0c3f
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/TamableFoxes.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/Utils/FileManager$Config.class b/target/classes/net/seanomik/tamablefoxes/Utils/FileManager$Config.class
new file mode 100644
index 0000000..a08447b
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/Utils/FileManager$Config.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/Utils/FileManager.class b/target/classes/net/seanomik/tamablefoxes/Utils/FileManager.class
new file mode 100644
index 0000000..704e3b5
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/Utils/FileManager.class differ
diff --git a/target/classes/net/seanomik/tamablefoxes/Utils/Utils.class b/target/classes/net/seanomik/tamablefoxes/Utils/Utils.class
new file mode 100644
index 0000000..6ad9311
Binary files /dev/null and b/target/classes/net/seanomik/tamablefoxes/Utils/Utils.class differ
diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml
new file mode 100644
index 0000000..08d86ad
--- /dev/null
+++ b/target/classes/plugin.yml
@@ -0,0 +1,14 @@
+name: TamableFoxes
+version: 1.3.2-SNAPSHOT
+main: net.seanomik.tamablefoxes.TamableFoxes
+api-version: 1.14
+load: POSTWORLD
+commands:
+ spawntamablefox:
+ aliases: [stf, spawntf]
+ usage: /spawntamablefox [type]
+ description: Spawn a tamable fox at the standing location. Type can be snow or red, or left empty for a red.
+permissions:
+ tamablefoxes.spawn:
+ description: "Gives the player the ability to spawn tamable foxes."
+ default: false
diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties
new file mode 100644
index 0000000..4c65d67
--- /dev/null
+++ b/target/maven-archiver/pom.properties
@@ -0,0 +1,5 @@
+#Generated by Maven
+#Fri Jun 21 00:51:53 CDT 2019
+version=0.8-SNAPSHOT
+groupId=net.seanomik
+artifactId=tamableFoxes
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 0000000..f8545b7
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,13 @@
+net\seanomik\tamablefoxes\Reference.class
+net\seanomik\tamablefoxes\TamableFox.class
+net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalOwnerHurtByTarget.class
+net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalOwnerHurtTarget.class
+net\seanomik\tamablefoxes\Commands\CommandSpawnTamableFox.class
+net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalBeg.class
+net\seanomik\tamablefoxes\TamableFoxes.class
+net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalSit.class
+net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalFollowOwner.class
+net\seanomik\tamablefoxes\NBTEditor.class
+net\seanomik\tamablefoxes\ConfigManager.class
+net\seanomik\tamablefoxes\NBTEditor$NBTCompound.class
+net\seanomik\tamablefoxes\TamableFox$UpdateFoxRunnable.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 0000000..95bf521
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,11 @@
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\ConfigManager.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\NBTEditor.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\Commands\CommandSpawnTamableFox.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalSit.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\Reference.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\TamableFox.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalFollowOwner.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\TamableFoxes.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalBeg.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalOwnerHurtByTarget.java
+D:\Code\java\myPlugins\TamableFoxes\src\main\java\net\seanomik\tamablefoxes\CustomPathfinding\FoxPathfinderGoalOwnerHurtTarget.java
diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
new file mode 100644
index 0000000..e69de29
diff --git a/target/original-tamableFoxes-0.8-SNAPSHOT.jar b/target/original-tamableFoxes-0.8-SNAPSHOT.jar
new file mode 100644
index 0000000..9622032
Binary files /dev/null and b/target/original-tamableFoxes-0.8-SNAPSHOT.jar differ
diff --git a/target/tamableFoxes-0.8-SNAPSHOT-shaded.jar b/target/tamableFoxes-0.8-SNAPSHOT-shaded.jar
new file mode 100644
index 0000000..29e0bad
Binary files /dev/null and b/target/tamableFoxes-0.8-SNAPSHOT-shaded.jar differ
diff --git a/target/tamableFoxes-0.8-SNAPSHOT.jar b/target/tamableFoxes-0.8-SNAPSHOT.jar
new file mode 100644
index 0000000..db33650
Binary files /dev/null and b/target/tamableFoxes-0.8-SNAPSHOT.jar differ