EntityTamableFox recode and a few changes

- Recoded EntityTamableFox to clean the pathfinder bloat
- Removed sleeping and chosenName as this data is already handled by vanilla
- Improved some logic around interact event
- Added a config option to enable and disable the anvil name GUI
- Fixed a bug where item counts would go to zero if just one was present
This commit is contained in:
Checkium Folf 2020-01-28 18:14:44 +00:00
parent a6408840b2
commit afef958991
9 changed files with 203 additions and 467 deletions

View File

@ -1,11 +1,12 @@
package net.seanomik.tamablefoxes; package net.seanomik.tamablefoxes;
import com.mojang.datafixers.Dynamic;
import net.minecraft.server.v1_15_R1.*; import net.minecraft.server.v1_15_R1.*;
import net.seanomik.tamablefoxes.io.Config; import net.seanomik.tamablefoxes.io.Config;
import net.seanomik.tamablefoxes.io.LanguageConfig;
import net.seanomik.tamablefoxes.versions.version_1_15.pathfinding.*; import net.seanomik.tamablefoxes.versions.version_1_15.pathfinding.*;
import org.bukkit.ChatColor;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.OfflinePlayer;
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_15_R1.persistence.CraftPersistentDataContainer; import org.bukkit.craftbukkit.v1_15_R1.persistence.CraftPersistentDataContainer;
import org.bukkit.entity.Item; import org.bukkit.entity.Item;
@ -13,29 +14,37 @@ import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import javax.annotation.Nullable; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.*;
import java.util.List;
import java.util.UUID;
public class EntityTamableFox extends EntityFox { public class EntityTamableFox extends EntityFox {
List<PathfinderGoal> untamedGoals = new ArrayList<>();
private boolean tamed; private boolean tamed;
private boolean sitting; private boolean sitting;
private boolean sleeping;
private String chosenName;
private EntityLiving owner; private EntityLiving owner;
private UUID ownerUUID; private UUID ownerUUID;
private FoxPathfinderGoalSit goalSit; private FoxPathfinderGoalSit goalSit;
private PathfinderGoal goalRandomSitting;
private PathfinderGoal goalBerryPicking;
private PathfinderGoal goalFleeSun;
private PathfinderGoal goalNearestVillage;
public EntityTamableFox(EntityTypes<? extends EntityFox> entitytypes, World world) { public EntityTamableFox(EntityTypes<? extends EntityFox> entitytypes, World world) {
super(entitytypes, world); super(entitytypes, world);
clearPathFinderGoals();
initPathfinderGoals();
}
public static Object getPrivateField(String fieldName, Class clazz, Object object) {
Field field;
Object o = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
o = field.get(object);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return o;
} }
private PathfinderGoal getFoxInnerPathfinderGoal(String innerName, List<Object> args, List<Class<?>> argTypes) { private PathfinderGoal getFoxInnerPathfinderGoal(String innerName, List<Object> args, List<Class<?>> argTypes) {
@ -47,18 +56,37 @@ public class EntityTamableFox extends EntityFox {
} }
@Override @Override
protected void initPathfinder() { 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);
// Default value is 32, might want to make this configurable in the future
this.getAttributeMap().b(GenericAttributes.FOLLOW_RANGE).setValue(16.0D);
this.getAttributeMap().b(GenericAttributes.ATTACK_KNOCKBACK);
this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.30000001192092896D);
// Default value is 10, might want to make this configurable in the future
this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(24.0D);
// Default value is 2, might want to make this configurable in the future
this.getAttributeMap().b(GenericAttributes.ATTACK_DAMAGE).setValue(3.0D);
}
private void initPathfinderGoals() {
try { try {
this.goalSelector.a(0, getFoxInnerPathfinderGoal("g")); // FloatGoal
this.goalSit = new FoxPathfinderGoalSit(this); this.goalSit = new FoxPathfinderGoalSit(this);
this.goalSelector.a(0, getFoxInnerPathfinderGoal("g")); // Swim this.goalSelector.a(1, goalSit);
this.goalSelector.a(1, this.goalSit);
this.goalSelector.a(1, getFoxInnerPathfinderGoal("b")); // Unknown
// Panic this.goalSelector.a(1, getFoxInnerPathfinderGoal("b")); // FaceplantGoal
this.goalSelector.a(2, new FoxPathfinderGoalPanic(this, 2.2D)); this.goalSelector.a(2, new FoxPathfinderGoalPanic(this, 2.2D)); // PanicGoal
this.goalSelector.a(3, getFoxInnerPathfinderGoal("e", Arrays.asList(1.0D), Arrays.asList(double.class))); // BreedGoal
// Breed
this.goalSelector.a(3, getFoxInnerPathfinderGoal("n", Arrays.asList(1.0D), Arrays.asList(double.class)));
// Avoid human only if not tamed // Avoid human only if not tamed
this.goalSelector.a(4, new PathfinderGoalAvoidTarget(this, EntityHuman.class, 16.0F, 1.6D, 1.4D, (entityliving) -> !tamed)); this.goalSelector.a(4, new PathfinderGoalAvoidTarget(this, EntityHuman.class, 16.0F, 1.6D, 1.4D, (entityliving) -> !tamed));
@ -76,35 +104,38 @@ public class EntityTamableFox extends EntityFox {
return !((EntityWolf) entityliving).isTamed(); return !((EntityWolf) entityliving).isTamed();
} }
})); }));
this.goalSelector.a(4, new FoxPathfinderGoalMeleeAttack(this, 1.2000000476837158D, true)); this.goalSelector.a(4, new FoxPathfinderGoalMeleeAttack(this, 1.2000000476837158D, true));
this.goalSelector.a(5, new FoxPathfinderGoalFollowOwner(this, 1.3D, 10.0F, 2.0F, false)); this.goalSelector.a(5, new FoxPathfinderGoalFollowOwner(this, 1.3D, 10.0F, 2.0F, false));
this.goalSelector.a(6, getFoxInnerPathfinderGoal("u")); // Lunge shake this.goalSelector.a(6, getFoxInnerPathfinderGoal("u")); // StalkPrey
this.goalSelector.a(7, new EntityFox.o()); // Lunge this.goalSelector.a(7, new EntityFox.o()); // Pounce
// Flee sun PathfinderGoal seekShelter = getFoxInnerPathfinderGoal("s", Arrays.asList(1.25D), Arrays.asList(double.class));
goalFleeSun = getFoxInnerPathfinderGoal("s", Arrays.asList(1.25D), Arrays.asList(double.class)); this.goalSelector.a(7, seekShelter); // SeekShelter
this.goalSelector.a(7, goalFleeSun); untamedGoals.add(seekShelter);
this.goalSelector.a(8, getFoxInnerPathfinderGoal("t")); // Sleeping under trees this.goalSelector.a(8, getFoxInnerPathfinderGoal("t")); // Sleep
this.goalSelector.a(9, getFoxInnerPathfinderGoal("h", Arrays.asList(this, 1.25D), Arrays.asList(EntityFox.class, double.class))); // Follow parent this.goalSelector.a(9, getFoxInnerPathfinderGoal("h", Arrays.asList(this, 1.25D), Arrays.asList(EntityFox.class, double.class))); // FollowParent
// Nearest village PathfinderGoal strollThroughVillage = getFoxInnerPathfinderGoal("q", Arrays.asList(32, 200), Arrays.asList(int.class, int.class));
goalNearestVillage = getFoxInnerPathfinderGoal("q", Arrays.asList(32, 200), Arrays.asList(int.class, int.class)); this.goalSelector.a(9, strollThroughVillage); // StrollThroughVillage
this.goalSelector.a(9, goalNearestVillage); untamedGoals.add(strollThroughVillage);
// Pick berry bushes // EatBerries (Pick berry bushes)
goalBerryPicking = new EntityFox.f(1.2000000476837158D, 12, 2); PathfinderGoal eatBerries = new EntityFox.f(1.2000000476837158D, 12, 2);
this.goalSelector.a(10, goalBerryPicking); this.goalSelector.a(10, eatBerries);
untamedGoals.add(eatBerries); // Maybe this should be configurable too?
this.goalSelector.a(10, new PathfinderGoalLeapAtTarget(this, 0.4F)); this.goalSelector.a(10, new PathfinderGoalLeapAtTarget(this, 0.4F));
this.goalSelector.a(11, new PathfinderGoalRandomStrollLand(this, 1.15D)); this.goalSelector.a(11, new PathfinderGoalRandomStrollLand(this, 1.15D));
this.goalSelector.a(11, getFoxInnerPathfinderGoal("p")); // If a item is on the ground, go to it and take it this.goalSelector.a(11, getFoxInnerPathfinderGoal("p")); // SearchForItems
this.goalSelector.a(12, getFoxInnerPathfinderGoal("j", Arrays.asList(this, EntityHuman.class, 24.0f), Arrays.asList(EntityInsentient.class, Class.class, float.class))); // Look at player this.goalSelector.a(12, getFoxInnerPathfinderGoal("j", Arrays.asList(this, EntityHuman.class, 24.0f), Arrays.asList(EntityInsentient.class, Class.class, float.class))); // LookAtPlayer
// The random sitting(?) // PerchAndSearch (Random sitting?)
this.goalRandomSitting = getFoxInnerPathfinderGoal("r"); PathfinderGoal perchAndSearch = getFoxInnerPathfinderGoal("r");
this.goalSelector.a(13, goalRandomSitting); this.goalSelector.a(13, perchAndSearch);
untamedGoals.add(perchAndSearch);
this.targetSelector.a(1, new FoxPathfinderGoalOwnerHurtByTarget(this)); this.targetSelector.a(1, new FoxPathfinderGoalOwnerHurtByTarget(this));
this.targetSelector.a(2, new FoxPathfinderGoalOwnerHurtTarget(this)); this.targetSelector.a(2, new FoxPathfinderGoalOwnerHurtTarget(this));
@ -122,118 +153,85 @@ public class EntityTamableFox extends EntityFox {
} }
} }
@Override private void clearPathFinderGoals() {
protected void initAttributes() { Set<?> goalSet = (Set<?>) getPrivateField("d", PathfinderGoalSelector.class, goalSelector);
this.getAttributeMap().b(GenericAttributes.MAX_HEALTH); Set<?> targetSet = (Set<?>) getPrivateField("d", PathfinderGoalSelector.class, targetSelector);
this.getAttributeMap().b(GenericAttributes.KNOCKBACK_RESISTANCE); goalSet.clear();
this.getAttributeMap().b(GenericAttributes.MOVEMENT_SPEED); targetSet.clear();
this.getAttributeMap().b(GenericAttributes.ARMOR);
this.getAttributeMap().b(GenericAttributes.ARMOR_TOUGHNESS);
this.getAttributeMap().b(GenericAttributes.FOLLOW_RANGE).setValue(16.0D);
this.getAttributeMap().b(GenericAttributes.ATTACK_KNOCKBACK);
this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.30000001192092896D); Map<?, ?> goalMap = (Map<?, ?>) getPrivateField("c", PathfinderGoalSelector.class, goalSelector);
this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(24.0D); Map<?, ?> targetMap = (Map<?, ?>) getPrivateField("c", PathfinderGoalSelector.class, targetSelector);
goalMap.clear();
targetMap.clear();
this.getAttributeMap().b(GenericAttributes.ATTACK_DAMAGE).setValue(3.0D); EnumSet<?> goalEnumSet = (EnumSet<?>) getPrivateField("f", PathfinderGoalSelector.class, goalSelector);
} EnumSet<?> targetEnumSet = (EnumSet<?>) getPrivateField("f", PathfinderGoalSelector.class, targetSelector);
goalEnumSet.clear();
public boolean isOtherFoxFamily(EntityLiving living) { targetEnumSet.clear();
if (living instanceof EntityTamableFox) {
EntityTamableFox tamableFox = (EntityTamableFox) living;
return (tamableFox.isTamed() && tamableFox.getOwner().getUniqueID() == this.getOwner().getUniqueID());
} else {
return false;
}
}
public void setTamed(boolean tamed) {
this.tamed = tamed;
// Remove goals that are not needed when named, or defeats the purpose of taming
this.goalSelector.a(goalRandomSitting);
this.goalSelector.a(goalBerryPicking);
this.goalSelector.a(goalFleeSun);
this.goalSelector.a(goalNearestVillage);
} }
public boolean isTamed() { public boolean isTamed() {
return tamed; return tamed;
} }
public void setOwner(EntityLiving entityLiving) { public void setTamed(boolean tamed) {
this.owner = entityLiving; this.tamed = tamed;
updateFoxVisual();
// Remove goals that are not needed when named, or defeats the purpose of taming
untamedGoals.forEach(goal -> goalSelector.a(goal));
} }
public EntityLiving getOwner() { public EntityLiving getOwner() {
if (Objects.isNull(owner)) {
if (ownerUUID == null) return null;
OfflinePlayer opOwner = TamableFoxes.getPlugin().getServer().getOfflinePlayer(UUID.fromString(ownerUUID.toString()));
if (opOwner.isOnline()) this.owner = (EntityLiving) ((CraftEntity) opOwner).getHandle();
}
return owner; return owner;
} }
public void setOwnerUUID(UUID uuid) { public void setOwner(EntityLiving entityLiving) {
this.ownerUUID = uuid; this.owner = entityLiving;
} this.ownerUUID = entityLiving.getUniqueID();
public UUID getOwnerUUID() {
return ownerUUID;
}
public void setChosenName(String name) {
this.chosenName = name;
updateFoxVisual(); updateFoxVisual();
} }
public String getChosenName() { public void setOwnerUUID(UUID ownerUUID) {
return chosenName; this.ownerUUID = ownerUUID;
} }
public void setMouthItem(ItemStack item) { public boolean isOtherFoxFamily(EntityLiving living) {
item.setCount(1); if (living instanceof EntityTamableFox) {
setSlot(EnumItemSlot.MAINHAND, item); EntityTamableFox tamableFox = (EntityTamableFox) living;
save(); return (tamableFox.isTamed() && tamableFox.getOwner() != null && tamableFox.getOwner().getUniqueID() == this.getOwner().getUniqueID());
} } else {
return false;
public void setMouthItem(org.bukkit.inventory.ItemStack item) { }
ItemStack itemNMS = CraftItemStack.asNMSCopy(item);
setMouthItem(itemNMS);
}
public ItemStack getMouthItem() {
return getEquipment(EnumItemSlot.MAINHAND);
}
public Item dropMouthItem() {
Item droppedItem = getBukkitEntity().getWorld().dropItem(getBukkitEntity().getLocation().add(0, 0, 0), CraftItemStack.asBukkitCopy(getMouthItem()));
setSlot(EnumItemSlot.MAINHAND, new net.minecraft.server.v1_15_R1.ItemStack(Items.AIR));
return droppedItem;
} }
public void updateFoxVisual() {
new BukkitRunnable() {
@Override @Override
public void setSitting(boolean sit) { public void run() {
super.setSitting(sit); goalSit.setSitting(sitting);
if (sleeping) { if (tamed && owner != null && !hasCustomName() && Config.doesShowOwnerFoxName()) {
sleeping = false; getBukkitEntity().setCustomName(LanguageConfig.getOwnerInFoxNameFormat().replaceAll("%player%", owner.getName()));
super.setSleeping(false);
} }
}
}.runTask(TamableFoxes.getPlugin());
}
public void setHardSitting(boolean hardSitting) {
super.setSitting(hardSitting);
this.sitting = hardSitting;
if (super.isSleeping()) super.setSleeping(false);
updateFoxVisual(); updateFoxVisual();
} }
public void setHardSitting(boolean sit) {
super.setSitting(sit);
this.sitting = sit;
if (sleeping) {
sleeping = false;
super.setSleeping(false);
}
updateFoxVisual();
}
public boolean toggleSitting() { public boolean toggleSitting() {
this.sitting = !this.sitting; this.sitting = !this.sitting;
@ -242,31 +240,29 @@ public class EntityTamableFox extends EntityFox {
return this.sitting; return this.sitting;
} }
public boolean isJumping() { public ItemStack getMouthItem() {
return this.jumping; return getEquipment(EnumItemSlot.MAINHAND);
} }
public void updateFoxVisual() { public void setMouthItem(ItemStack item) {
new UpdateFoxRunnable().runTask(TamableFoxes.getPlugin()); item.setCount(1);
setSlot(EnumItemSlot.MAINHAND, item);
saveNbt();
} }
private class UpdateFoxRunnable extends BukkitRunnable { public void setMouthItem(org.bukkit.inventory.ItemStack item) {
UpdateFoxRunnable() { ItemStack itemNMS = CraftItemStack.asNMSCopy(item);
setMouthItem(itemNMS);
} }
public void run() { public org.bukkit.entity.Item dropMouthItem() {
goalSit.setSitting(sitting); Item droppedItem = getBukkitEntity().getWorld().dropItem(getBukkitEntity().getLocation(), CraftItemStack.asBukkitCopy(getMouthItem()));
setSlot(EnumItemSlot.MAINHAND, new net.minecraft.server.v1_15_R1.ItemStack(Items.AIR));
if (tamed) { return droppedItem;
getBukkitEntity().setCustomName((chosenName != null ? chosenName : "")
+ (owner != null && Config.doesShowOwnerFoxName() ? ChatColor.RESET + " (" + owner.getName() + ")" : ""));
getBukkitEntity().setCustomNameVisible(Config.doesShowNameTags());
}
}
} }
public void save() { public void saveNbt() {
NamespacedKey rootKey = new NamespacedKey(TamableFoxes.getPlugin(), "tamableFoxes"); NamespacedKey rootKey = new NamespacedKey(TamableFoxes.getPlugin(), "tamableFoxes");
CraftPersistentDataContainer persistentDataContainer = getBukkitEntity().getPersistentDataContainer(); CraftPersistentDataContainer persistentDataContainer = getBukkitEntity().getPersistentDataContainer();
PersistentDataContainer tamableFoxesData; PersistentDataContainer tamableFoxesData;
@ -277,218 +273,10 @@ public class EntityTamableFox extends EntityFox {
} }
NamespacedKey ownerKey = new NamespacedKey(TamableFoxes.getPlugin(), "owner"); NamespacedKey ownerKey = new NamespacedKey(TamableFoxes.getPlugin(), "owner");
NamespacedKey chosenNameKey = new NamespacedKey(TamableFoxes.getPlugin(), "chosenName");
NamespacedKey sittingKey = new NamespacedKey(TamableFoxes.getPlugin(), "sitting"); NamespacedKey sittingKey = new NamespacedKey(TamableFoxes.getPlugin(), "sitting");
NamespacedKey sleepingKey = new NamespacedKey(TamableFoxes.getPlugin(), "sleeping");
tamableFoxesData.set(ownerKey, PersistentDataType.STRING, getOwner() == null ? "none" : getOwner().getUniqueID().toString()); tamableFoxesData.set(ownerKey, PersistentDataType.STRING, getOwner() == null ? "none" : getOwner().getUniqueID().toString());
if (getChosenName() != null && !getChosenName().isEmpty()) {
tamableFoxesData.set(chosenNameKey, PersistentDataType.STRING, getChosenName());
}
tamableFoxesData.set(sittingKey, PersistentDataType.BYTE, (byte) (isSitting() ? 1 : 0)); tamableFoxesData.set(sittingKey, PersistentDataType.BYTE, (byte) (isSitting() ? 1 : 0));
tamableFoxesData.set(sleepingKey, PersistentDataType.BYTE, (byte) (isSleeping() ? 1 : 0));
persistentDataContainer.set(rootKey, PersistentDataType.TAG_CONTAINER, tamableFoxesData); persistentDataContainer.set(rootKey, PersistentDataType.TAG_CONTAINER, tamableFoxesData);
} }
// Used for all the nasty stuff below.
private static boolean isLevelAtLeast(NBTTagCompound tag, int level) {
return tag.hasKey("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level;
}
// To remove a call to initializePathFinderGoals()
// This is all from every super class that has a method like this.
// This was needed because you cant call a "super.super.method()"
@Override
public void a(NBTTagCompound nbttagcompound) {
try {
// EntityLiving
this.setAbsorptionHearts(nbttagcompound.getFloat("AbsorptionAmount"));
if (nbttagcompound.hasKeyOfType("Attributes", 9) && this.world != null && !this.world.isClientSide) {
GenericAttributes.a(this.getAttributeMap(), nbttagcompound.getList("Attributes", 10));
}
if (nbttagcompound.hasKeyOfType("ActiveEffects", 9)) {
NBTTagList nbttaglist = nbttagcompound.getList("ActiveEffects", 10);
for(int i = 0; i < nbttaglist.size(); ++i) {
NBTTagCompound nbttagcompound1 = nbttaglist.getCompound(i);
MobEffect mobeffect = MobEffect.b(nbttagcompound1);
if (mobeffect != null) {
this.effects.put(mobeffect.getMobEffect(), mobeffect);
}
}
}
if (nbttagcompound.hasKey("Bukkit.MaxHealth")) {
NBTBase nbtbase = nbttagcompound.get("Bukkit.MaxHealth");
if (nbtbase.getTypeId() == 5) {
this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(((NBTTagFloat)nbtbase).asDouble());
} else if (nbtbase.getTypeId() == 3) {
this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(((NBTTagInt)nbtbase).asDouble());
}
}
if (nbttagcompound.hasKeyOfType("Health", 99)) {
this.setHealth(nbttagcompound.getFloat("Health"));
}
this.hurtTicks = nbttagcompound.getShort("HurtTime");
this.deathTicks = nbttagcompound.getShort("DeathTime");
this.hurtTimestamp = nbttagcompound.getInt("HurtByTimestamp");
if (nbttagcompound.hasKeyOfType("Team", 8)) {
String s = nbttagcompound.getString("Team");
ScoreboardTeam scoreboardteam = this.world.getScoreboard().getTeam(s);
boolean flag = scoreboardteam != null && this.world.getScoreboard().addPlayerToTeam(this.getUniqueIDString(), scoreboardteam);
if (!flag) {
LOGGER.warn("Unable to add mob to team \"{}\" (that team probably doesn't exist)", s);
}
}
if (nbttagcompound.getBoolean("FallFlying")) {
this.setFlag(7, true);
}
if (nbttagcompound.hasKeyOfType("SleepingX", 99) && nbttagcompound.hasKeyOfType("SleepingY", 99) && nbttagcompound.hasKeyOfType("SleepingZ", 99)) {
BlockPosition blockposition = new BlockPosition(nbttagcompound.getInt("SleepingX"), nbttagcompound.getInt("SleepingY"), nbttagcompound.getInt("SleepingZ"));
this.d(blockposition);
this.datawatcher.set(POSE, EntityPose.SLEEPING);
if (!this.justCreated) {
this.a(blockposition);
}
}
if (nbttagcompound.hasKeyOfType("Brain", 10)) {
this.bo = this.a(new Dynamic(DynamicOpsNBT.a, nbttagcompound.get("Brain")));
}
// EntityInsentient
NonNullList<ItemStack> by = (NonNullList<ItemStack>) Utils.getPrivateFieldValue(EntityInsentient.class, "by", this);
NonNullList<ItemStack> bx = (NonNullList<ItemStack>) Utils.getPrivateFieldValue(EntityInsentient.class, "bx", this);
boolean data;
if (nbttagcompound.hasKeyOfType("CanPickUpLoot", 1)) {
data = nbttagcompound.getBoolean("CanPickUpLoot");
if (isLevelAtLeast(nbttagcompound, 1) || data) {
this.setCanPickupLoot(data);
}
}
data = nbttagcompound.getBoolean("PersistenceRequired");
if (isLevelAtLeast(nbttagcompound, 1) || data) {
this.persistent = data;
}
NBTTagList nbttaglist;
int i;
if (nbttagcompound.hasKeyOfType("ArmorItems", 9)) {
nbttaglist = nbttagcompound.getList("ArmorItems", 10);
for(i = 0; i < by.size(); ++i) {
by.set(i, ItemStack.a(nbttaglist.getCompound(i)));
}
}
if (nbttagcompound.hasKeyOfType("HandItems", 9)) {
nbttaglist = nbttagcompound.getList("HandItems", 10);
for(i = 0; i < bx.size(); ++i) {
bx.set(i, ItemStack.a(nbttaglist.getCompound(i)));
}
}
if (nbttagcompound.hasKeyOfType("ArmorDropChances", 9)) {
nbttaglist = nbttagcompound.getList("ArmorDropChances", 5);
for(i = 0; i < nbttaglist.size(); ++i) {
this.dropChanceArmor[i] = nbttaglist.i(i);
}
}
if (nbttagcompound.hasKeyOfType("HandDropChances", 9)) {
nbttaglist = nbttagcompound.getList("HandDropChances", 5);
for(i = 0; i < nbttaglist.size(); ++i) {
this.dropChanceHand[i] = nbttaglist.i(i);
}
}
if (nbttagcompound.hasKeyOfType("Leash", 10)) {
//this.bG = nbttagcompound.getCompound("Leash");
Utils.setPrivateFieldValue(EntityInsentient.class, "bG", this, nbttagcompound.getCompound("Leash"));
}
this.p(nbttagcompound.getBoolean("LeftHanded"));
if (nbttagcompound.hasKeyOfType("DeathLootTable", 8)) {
this.lootTableKey = new MinecraftKey(nbttagcompound.getString("DeathLootTable"));
this.lootTableSeed = nbttagcompound.getLong("DeathLootTableSeed");
}
this.setNoAI(nbttagcompound.getBoolean("NoAI"));
// EntityAgeable
this.setAgeRaw(nbttagcompound.getInt("Age"));
this.c = nbttagcompound.getInt("ForcedAge");
this.ageLocked = nbttagcompound.getBoolean("AgeLocked");
// EntityAnimal
this.loveTicks = nbttagcompound.getInt("InLove");
this.breedCause = nbttagcompound.b("LoveCause") ? nbttagcompound.a("LoveCause") : null;
NBTTagList foxNBTTagList = nbttagcompound.getList("TrustedUUIDs", 10);
Method method = this.getClass().getSuperclass().getDeclaredMethod("b", UUID.class);
method.setAccessible(true);
for (int index = 0; index < foxNBTTagList.size(); ++index) {
//this.b(GameProfileSerializer.b(nbttaglist.getCompound(i)));
method.invoke(this, GameProfileSerializer.b(foxNBTTagList.getCompound(index)));
}
method.setAccessible(false);
this.setSleeping(nbttagcompound.getBoolean("Sleeping"));
this.setFoxType(EntityFox.Type.a(nbttagcompound.getString("Type")));
// Use super class due to the new set sitting causing errors
super.setSitting(nbttagcompound.getBoolean("Sitting"));
this.setCrouching(nbttagcompound.getBoolean("Crouching"));
} catch (Exception e) {
e.printStackTrace();
}
}
// To remove the last call to initializePathFinderGoals()
// Cant just override because its a private method
@Override
@Nullable
public GroupDataEntity prepare(GeneratorAccess generatoraccess, DifficultyDamageScaler difficultydamagescaler,
EnumMobSpawn enummobspawn, GroupDataEntity groupdataentity, NBTTagCompound nbttagcompound) {
BiomeBase biomebase = generatoraccess.getBiome(new BlockPosition(this));
Type entityfox_type = Type.a(biomebase);
boolean flag = false;
if (groupdataentity instanceof i) {
entityfox_type = ((i) groupdataentity).a;
if (((i) groupdataentity).a() >= 2) {
flag = true;
} else {
((i) groupdataentity).b();
}
} else {
groupdataentity = new i(entityfox_type);
((i) groupdataentity).b();
}
this.setFoxType(entityfox_type);
if (flag) {
this.setAgeRaw(-24000);
}
this.initPathfinder();
this.a(difficultydamagescaler);
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;
}
} }

View File

@ -27,13 +27,11 @@ import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
// @TODO: // @TODO:
@ -101,12 +99,12 @@ public final class TamableFoxes extends JavaPlugin implements Listener {
@Override @Override
public void onDisable() { public void onDisable() {
getServer().getConsoleSender().sendMessage(Utils.getPrefix() + ChatColor.YELLOW + LanguageConfig.getSavingFoxMessage()); getServer().getConsoleSender().sendMessage(Utils.getPrefix() + ChatColor.YELLOW + LanguageConfig.getSavingFoxMessage());
spawnedFoxes.forEach(EntityTamableFox::save); spawnedFoxes.forEach(EntityTamableFox::saveNbt);
} }
@EventHandler @EventHandler
public void onWorldSaveEvent(WorldSaveEvent event) { public void onWorldSaveEvent(WorldSaveEvent event) {
spawnedFoxes.forEach(EntityTamableFox::save); spawnedFoxes.forEach(EntityTamableFox::saveNbt);
} }
@EventHandler @EventHandler
@ -150,54 +148,41 @@ public final class TamableFoxes extends JavaPlugin implements Listener {
if (Utils.isTamableFox(entity)) { if (Utils.isTamableFox(entity)) {
EntityTamableFox tamableFox = (EntityTamableFox) ((CraftEntity) entity).getHandle(); EntityTamableFox tamableFox = (EntityTamableFox) ((CraftEntity) entity).getHandle();
// Check if its tamed but ignore it if the player is holding sweet berries for breeding // Check if its tamed but ignore it if the player is holding sweet berries for breeding or nametag for renaming
if (tamableFox.isTamed() && tamableFox.getOwner() != null && itemHand.getType() != Material.SWEET_BERRIES) { if (tamableFox.isTamed() && tamableFox.getOwner() != null && itemHand.getType() != Material.SWEET_BERRIES && itemHand.getType() != Material.NAME_TAG) {
if (tamableFox.getOwner().getUniqueID() == player.getUniqueId()) { if (tamableFox.getOwner().getUniqueID() == player.getUniqueId()) {
event.setCancelled(true);
if (player.isSneaking()) { if (player.isSneaking()) {
net.minecraft.server.v1_15_R1.ItemStack foxMouth = tamableFox.getEquipment(EnumItemSlot.MAINHAND); net.minecraft.server.v1_15_R1.ItemStack foxMouth = tamableFox.getEquipment(EnumItemSlot.MAINHAND);
if (!foxMouth.isEmpty()) tamableFox.dropMouthItem();
if (foxMouth.isEmpty() && itemHand.getType() != Material.AIR) { // Giving an item if (itemHand.getType() != Material.AIR) {
tamableFox.setMouthItem(itemHand); tamableFox.setMouthItem(itemHand);
itemHand.setAmount(itemHand.getAmount() - 1); if (itemHand.getAmount() == 1) player.getInventory().removeItem(itemHand);
} else if (!foxMouth.isEmpty() && itemHand.getType() == Material.AIR) { // Taking the item else itemHand.setAmount(itemHand.getAmount() - 1);
tamableFox.dropMouthItem();
} else if (!foxMouth.isEmpty() && itemHand.getType() != Material.AIR){ // Swapping items
// Drop item
tamableFox.dropMouthItem();
// Give item and take one away from player
tamableFox.setMouthItem(itemHand);
itemHand.setAmount(itemHand.getAmount() - 1);
} }
} else if (itemHand.getType() == Material.NAME_TAG) {
tamableFox.setChosenName(handMeta.getDisplayName());
} else { } else {
tamableFox.toggleSitting(); tamableFox.toggleSitting();
} }
event.setCancelled(true);
} }
} else if (itemHand.getType() == Material.CHICKEN && Config.canPlayerTameFox(player)) { } else if (itemHand.getType() == Material.CHICKEN && Config.canPlayerTameFox(player)) {
if (Math.random() < 0.33D) { // tamed if (Math.random() < 0.33D) { // tamed
tamableFox.setTamed(true); tamableFox.setTamed(true);
tamableFox.setOwner(((CraftPlayer) player).getHandle()); tamableFox.setOwner(((CraftPlayer) player).getHandle());
// store uuid
player.getWorld().spawnParticle(Particle.HEART, entity.getLocation(), 6, 0.5D, 0.5D, 0.5D); player.getWorld().spawnParticle(Particle.HEART, entity.getLocation(), 6, 0.5D, 0.5D, 0.5D);
// Name fox
player.sendMessage(ChatColor.RED + ChatColor.BOLD.toString() + LanguageConfig.getTamedMessage()); player.sendMessage(ChatColor.RED + ChatColor.BOLD.toString() + LanguageConfig.getTamedMessage());
if (Config.askForNameAfterTaming()) {
player.sendMessage(ChatColor.RED + LanguageConfig.getTamingAskingName()); player.sendMessage(ChatColor.RED + LanguageConfig.getTamingAskingName());
tamableFox.setChosenName("???");
//TamableFoxes.getPlugin().sqLiteSetterGetter.saveFox(tamableFox);
event.setCancelled(true);
new AnvilGUI.Builder() new AnvilGUI.Builder()
.onComplete((plr, text) -> { // Called when the inventory output slot is clicked .onComplete((plr, text) -> { // Called when the inventory output slot is clicked
if(!text.equals("")) { if (!text.equals("")) {
tamableFox.setChosenName(text); tamableFox.getBukkitEntity().setCustomName(text);
tamableFox.setCustomNameVisible(true);
plr.sendMessage(Utils.getPrefix() + ChatColor.GREEN + LanguageConfig.getTamingChosenPerfect(text)); plr.sendMessage(Utils.getPrefix() + ChatColor.GREEN + LanguageConfig.getTamingChosenPerfect(text));
tamableFox.save(); tamableFox.saveNbt();
} }
return AnvilGUI.Response.close(); return AnvilGUI.Response.close();
@ -206,12 +191,14 @@ public final class TamableFoxes extends JavaPlugin implements Listener {
.text("Fox name") // Sets the text the GUI should start with .text("Fox name") // Sets the text the GUI should start with
.plugin(this) // Set the plugin instance .plugin(this) // Set the plugin instance
.open(player); // Opens the GUI for the player provided .open(player); // Opens the GUI for the player provided
}
} else { // Tame failed } else { // Tame failed
player.getWorld().spawnParticle(Particle.SMOKE_NORMAL, entity.getLocation(), 10, 0.3D, 0.3D, 0.3D, 0.15D); player.getWorld().spawnParticle(Particle.SMOKE_NORMAL, entity.getLocation(), 10, 0.3D, 0.3D, 0.3D, 0.15D);
} }
if (!player.getGameMode().equals(GameMode.CREATIVE)) { if (!player.getGameMode().equals(GameMode.CREATIVE)) {
itemHand.setAmount(itemHand.getAmount() - 1); if (itemHand.getAmount() == 1) player.getInventory().removeItem(itemHand);
else itemHand.setAmount(itemHand.getAmount() - 1);
} }
event.setCancelled(true); event.setCancelled(true);
@ -248,19 +235,14 @@ public final class TamableFoxes extends JavaPlugin implements Listener {
public void onEntityDeathEvent(EntityDeathEvent event) { public void onEntityDeathEvent(EntityDeathEvent event) {
Entity entity = event.getEntity(); Entity entity = event.getEntity();
if (!Utils.isTamableFox(entity)) return; // Is the entity a tamable fox? if (!Utils.isTamableFox(entity)) return; // Is the entity a tamable fox?
// Remove the fox from storage // Remove the fox from storage
spawnedFoxes.remove(entity); spawnedFoxes.remove(entity);
// Notify the owner // Notify the owner
EntityTamableFox tamableFox = (EntityTamableFox) ((CraftEntity) entity).getHandle(); EntityTamableFox tamableFox = (EntityTamableFox) ((CraftEntity) entity).getHandle();
if (tamableFox.getOwner() != null) { if (tamableFox.getOwner() != null) {
Player owner = ((EntityPlayer) tamableFox.getOwner()).getBukkitEntity(); Player owner = ((EntityPlayer) tamableFox.getOwner()).getBukkitEntity();
owner.sendMessage(Utils.getPrefix() + ChatColor.RED + tamableFox.getChosenName() + " was killed!"); owner.sendMessage(Utils.getPrefix() + ChatColor.RED + (tamableFox.hasCustomName() ? tamableFox.getBukkitEntity().getCustomName() : "Your fox") + " was killed!");
} }
// Remove the fox from database
//sqLiteSetterGetter.removeFox(tamableFox);
} }
public EntityTamableFox spawnTamableFox(Location loc, EntityFox.Type type) { public EntityTamableFox spawnTamableFox(Location loc, EntityFox.Type type) {

View File

@ -1,15 +1,16 @@
package net.seanomik.tamablefoxes; package net.seanomik.tamablefoxes;
import net.minecraft.server.v1_15_R1.EntityLiving; import net.minecraft.server.v1_15_R1.EntityLiving;
import org.bukkit.*; import org.bukkit.ChatColor;
import org.bukkit.Chunk;
import org.bukkit.NamespacedKey;
import org.bukkit.OfflinePlayer;
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftEntity; import org.bukkit.craftbukkit.v1_15_R1.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_15_R1.persistence.CraftPersistentDataContainer; import org.bukkit.craftbukkit.v1_15_R1.persistence.CraftPersistentDataContainer;
import org.bukkit.entity.Entity;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -25,35 +26,6 @@ public class Utils {
return ChatColor.RED + "[Tamable Foxes] "; return ChatColor.RED + "[Tamable Foxes] ";
} }
public static Object getPrivateFieldValue(Class c, String field, Object instance) {
Object value = null;
try {
Field f = c.getDeclaredField(field);
f.setAccessible(true);
value = f.get(instance);
f.setAccessible(false);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
public static void setPrivateFieldValue(Class c, String field, Object instance, Object value) {
try {
Field f = c.getDeclaredField(field);
f.setAccessible(true);
f.set(instance, value);
f.setAccessible(false);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void sendConsoleMessage(String message) {
TamableFoxes.getPlugin().getServer().getConsoleSender().sendMessage(message);
}
public static Class<?> getPrivateInnerClass(Class outer, String innerName) { public static Class<?> getPrivateInnerClass(Class outer, String innerName) {
for (Class<?> declaredClass : outer.getDeclaredClasses()) { for (Class<?> declaredClass : outer.getDeclaredClasses()) {
if (declaredClass.getSimpleName().equals(innerName)) return declaredClass; if (declaredClass.getSimpleName().equals(innerName)) return declaredClass;
@ -101,35 +73,27 @@ public class Utils {
if (persistentDataContainer.has(rootKey, PersistentDataType.TAG_CONTAINER)) { if (persistentDataContainer.has(rootKey, PersistentDataType.TAG_CONTAINER)) {
PersistentDataContainer tamableFoxesData = persistentDataContainer.get(rootKey, PersistentDataType.TAG_CONTAINER); PersistentDataContainer tamableFoxesData = persistentDataContainer.get(rootKey, PersistentDataType.TAG_CONTAINER);
NamespacedKey ownerKey = new NamespacedKey(TamableFoxes.getPlugin(), "owner"); NamespacedKey ownerKey = new NamespacedKey(TamableFoxes.getPlugin(), "owner");
NamespacedKey chosenNameKey = new NamespacedKey(TamableFoxes.getPlugin(), "chosenName");
NamespacedKey sittingKey = new NamespacedKey(TamableFoxes.getPlugin(), "sitting"); NamespacedKey sittingKey = new NamespacedKey(TamableFoxes.getPlugin(), "sitting");
NamespacedKey sleepingKey = new NamespacedKey(TamableFoxes.getPlugin(), "sleeping");
String ownerUUIDString = tamableFoxesData.get(ownerKey, PersistentDataType.STRING); String ownerUUIDString = tamableFoxesData.get(ownerKey, PersistentDataType.STRING);
String chosenName = tamableFoxesData.get(chosenNameKey, PersistentDataType.STRING);
boolean sitting = ((byte) 1) == tamableFoxesData.get(sittingKey, PersistentDataType.BYTE); boolean sitting = ((byte) 1) == tamableFoxesData.get(sittingKey, PersistentDataType.BYTE);
boolean sleeping = ((byte) 1) == tamableFoxesData.get(sleepingKey, PersistentDataType.BYTE);
boolean tamed = false; boolean tamed = false;
if (!ownerUUIDString.equals("none")) { if (!ownerUUIDString.equals("none")) {
tamed = true; tamed = true;
OfflinePlayer owner = TamableFoxes.getPlugin().getServer().getOfflinePlayer(UUID.fromString(ownerUUIDString)); OfflinePlayer owner = TamableFoxes.getPlugin().getServer().getOfflinePlayer(UUID.fromString(ownerUUIDString));
if (owner.isOnline()) { if (owner.isOnline()) {
EntityLiving livingOwner = (EntityLiving) ((CraftEntity) owner).getHandle(); EntityLiving livingOwner = (EntityLiving) ((CraftEntity) owner).getHandle();
tamableFox.setOwner(livingOwner); tamableFox.setOwner(livingOwner);
} else {
tamableFox.setOwnerUUID(UUID.fromString(ownerUUIDString));
} }
tamableFox.setOwnerUUID(owner.getUniqueId());
tamableFox.setTamed(true); tamableFox.setTamed(true);
tamableFox.setChosenName(chosenName);
} }
if (sitting && tamed) { if (sitting && tamed) {
tamableFox.setHardSitting(true); tamableFox.setHardSitting(true);
} else if (sleeping) { } else {
tamableFox.setSleeping(true);
} else { // Avoid the foxes getting stuck sitting down.
tamableFox.setSitting(false); tamableFox.setSitting(false);
tamableFox.setSleeping(false); tamableFox.setSleeping(false);
} }

View File

@ -10,10 +10,6 @@ public class Config {
return plugin.getConfig().getBoolean("show-owner-in-fox-name"); return plugin.getConfig().getBoolean("show-owner-in-fox-name");
} }
public static boolean doesShowNameTags() {
return plugin.getConfig().getBoolean("show-nametags");
}
public static boolean doesTamedAttackWildAnimals() { public static boolean doesTamedAttackWildAnimals() {
return plugin.getConfig().getBoolean("tamed-behavior.attack-wild-animals"); return plugin.getConfig().getBoolean("tamed-behavior.attack-wild-animals");
} }
@ -22,4 +18,8 @@ public class Config {
return !plugin.getConfig().getBoolean("enable-taming-permission") || (plugin.getConfig().getBoolean("enable-taming-permission") && (player.hasPermission("tamablefoxes.tame") || player.isOp())); return !plugin.getConfig().getBoolean("enable-taming-permission") || (plugin.getConfig().getBoolean("enable-taming-permission") && (player.hasPermission("tamablefoxes.tame") || player.isOp()));
} }
public static boolean askForNameAfterTaming() {
return plugin.getConfig().getBoolean("ask-for-name-after-taming");
}
} }

View File

@ -107,6 +107,10 @@ public class LanguageConfig extends YamlConfiguration {
return getConfig().getString("taming-chosen-name-perfect").replaceAll("%NAME%", chosen); return getConfig().getString("taming-chosen-name-perfect").replaceAll("%NAME%", chosen);
} }
public static String getOwnerInFoxNameFormat() {
return getConfig().getString("owner-in-fox-name-format");
}
public static String getNoPermMessage() { public static String getNoPermMessage() {
return getConfig().getString("no-permission"); return getConfig().getString("no-permission");
} }

View File

@ -41,7 +41,7 @@ public class CommandSpawnTamableFox implements TabExecutor {
case "red": case "red":
try { try {
EntityTamableFox fox = plugin.spawnTamableFox(player.getLocation(), EntityFox.Type.RED); EntityTamableFox fox = plugin.spawnTamableFox(player.getLocation(), EntityFox.Type.RED);
fox.save(); fox.saveNbt();
player.sendMessage(Utils.getPrefix() + ChatColor.RESET + LanguageConfig.getSpawnedFoxMessage(EntityFox.Type.RED)); player.sendMessage(Utils.getPrefix() + ChatColor.RESET + LanguageConfig.getSpawnedFoxMessage(EntityFox.Type.RED));
} catch (Exception e) { } catch (Exception e) {
@ -52,7 +52,7 @@ public class CommandSpawnTamableFox implements TabExecutor {
case "snow": case "snow":
try { try {
EntityTamableFox spawnedFox = plugin.spawnTamableFox(player.getLocation(), EntityFox.Type.SNOW); EntityTamableFox spawnedFox = plugin.spawnTamableFox(player.getLocation(), EntityFox.Type.SNOW);
spawnedFox.save(); spawnedFox.saveNbt();
player.sendMessage(Utils.getPrefix() + ChatColor.RESET + LanguageConfig.getSpawnedFoxMessage(EntityFox.Type.SNOW)); player.sendMessage(Utils.getPrefix() + ChatColor.RESET + LanguageConfig.getSpawnedFoxMessage(EntityFox.Type.SNOW));
} catch (Exception e) { } catch (Exception e) {

View File

@ -16,12 +16,12 @@ public class FoxPathfinderGoalPanic extends PathfinderGoalPanic {
public boolean a() { public boolean a() {
try { try {
Method eFMethod = EntityFox.class.getDeclaredMethod("eF"); Method isDefendingMethod = EntityFox.class.getDeclaredMethod("eF");
eFMethod.setAccessible(true); isDefendingMethod.setAccessible(true);
boolean eF = (boolean) eFMethod.invoke(tamableFox); boolean isDefending = (boolean) isDefendingMethod.invoke(tamableFox);
eFMethod.setAccessible(false); isDefendingMethod.setAccessible(false);
return !tamableFox.isTamed() && !eF && super.a(); return !tamableFox.isTamed() && !isDefending && super.a();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@ -1,7 +1,7 @@
# Config for Tamable Foxes # Config for Tamable Foxes
show-owner-in-fox-name: true show-owner-in-fox-name: true
show-nametags: true
enable-taming-permission: true enable-taming-permission: true
ask-for-name-after-taming: false
tamed-behavior: tamed-behavior:
attack-wild-animals: true attack-wild-animals: true

View File

@ -2,17 +2,15 @@ unsupported-mc-version-not-registering: "ERROR: This plugin version only support
unsupported-mc-version-disabling: "This plugin version only supports Spigot 1.15.X! Disabling plugin!" unsupported-mc-version-disabling: "This plugin version only supports Spigot 1.15.X! Disabling plugin!"
success-replaced-entity: "Replaced tamable fox entity!" success-replaced-entity: "Replaced tamable fox entity!"
error-to-replaced-entity: "Failed to replace tamable fox entity!" error-to-replaced-entity: "Failed to replace tamable fox entity!"
saving-foxes-message: "Saving foxes." saving-foxes-message: "Saving foxes."
taming-tamed-message: "You just tamed a wild fox!" taming-tamed-message: "You just tamed a wild fox!"
taming-asking-for-name-message: "What do you want to call it?" taming-asking-for-name-message: "What do you want to call it?"
taming-chosen-name-perfect: "%NAME% is perfect!" taming-chosen-name-perfect: "%NAME% is perfect!"
owner-in-fox-name-format: "%player%'s Fox"
no-permission: "You do not have the permission for this command." no-permission: "You do not have the permission for this command."
only-run-by-player: "Command can only be run from player state!" only-run-by-player: "Command can only be run from player state!"
spawned-fox-message: "Spawned a %TYPE% fox." spawned-fox-message: "Spawned a %TYPE% fox."
failed-to-spawn-message: "Failed to spawn fox!" failed-to-spawn-message: "Failed to spawn fox!"
reloaded-message: "Reloaded" reloaded-message: "Reloaded"
created-sql-foxes-database: "Created foxes SQLite database!"