/* * This file is part of MyPet * * Copyright © 2011-2016 Keyle * MyPet is licensed under the GNU Lesser General Public License. * * MyPet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MyPet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.Keyle.MyPet.entity; import com.google.common.base.Optional; import de.Keyle.MyPet.MyPetApi; import de.Keyle.MyPet.api.Configuration; import de.Keyle.MyPet.api.Util; import de.Keyle.MyPet.api.WorldGroup; import de.Keyle.MyPet.api.entity.EntitySize; import de.Keyle.MyPet.api.entity.MyPetBukkitEntity; import de.Keyle.MyPet.api.entity.MyPetMinecraftEntity; import de.Keyle.MyPet.api.entity.MyPetType; import de.Keyle.MyPet.api.event.MyPetCallEvent; import de.Keyle.MyPet.api.event.MyPetLevelUpEvent; import de.Keyle.MyPet.api.player.MyPetPlayer; import de.Keyle.MyPet.api.player.Permissions; import de.Keyle.MyPet.api.skill.MyPetExperience; import de.Keyle.MyPet.api.skill.SkillInstance; import de.Keyle.MyPet.api.skill.Skills; import de.Keyle.MyPet.api.skill.experience.Experience; import de.Keyle.MyPet.api.skill.skilltree.SkillTree; import de.Keyle.MyPet.api.skill.skilltree.SkillTreeMobType; import de.Keyle.MyPet.api.util.NBTStorage; import de.Keyle.MyPet.api.util.NameFilter; import de.Keyle.MyPet.api.util.Scheduler; import de.Keyle.MyPet.api.util.Since; import de.Keyle.MyPet.api.util.locale.Translation; import de.Keyle.MyPet.api.util.service.types.RepositoryMyPetConverterService; import de.Keyle.MyPet.skill.experience.Default; import de.Keyle.MyPet.skill.experience.JavaScript; import de.Keyle.MyPet.skill.skills.Damage; import de.Keyle.MyPet.skill.skills.Inventory; import de.Keyle.MyPet.skill.skills.Life; import de.Keyle.MyPet.skill.skills.Ranged; import de.Keyle.MyPet.util.hooks.VaultHook; import de.keyle.knbt.*; import org.apache.commons.lang.RandomStringUtils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.scoreboard.Team; import java.io.File; import java.util.*; import static org.bukkit.Bukkit.getServer; public abstract class MyPet implements de.Keyle.MyPet.api.entity.MyPet, NBTStorage { protected final MyPetPlayer petOwner; protected MyPetBukkitEntity bukkitEntity; protected String petName = "Pet"; protected double health; protected int respawnTime = 0; protected int hungerTime = 0; protected double saturation = 100; protected UUID uuid = null; protected String worldGroup = ""; @Override public void setExp(double exp) { getExperience().setExp(exp); } @Override public TagCompound getInfo() { return writeExtendedInfo(); } @Override public void setInfo(TagCompound info) { readExtendedInfo(info); } @Override public void setOwner(MyPetPlayer owner) { throw new UnsupportedOperationException("You can't change the owner for an active MyPet!"); } @Override public void setPetType(MyPetType petType) { throw new UnsupportedOperationException("You can't change the type for an active MyPet!"); } @Override public void setSkills(TagCompound skills) { } protected PetState status = PetState.Despawned; protected boolean wantsToRespawn = false; protected SkillTree skillTree = null; protected Skills skills; protected MyPetExperience experience; protected long lastUsed = -1; protected MyPet(MyPetPlayer petOwner) { if (petOwner == null) { throw new IllegalArgumentException("Owner must not be null."); } this.petOwner = petOwner; skills = new Skills(this); Experience expMode = null; if (Configuration.LevelSystem.CALCULATION_MODE.equalsIgnoreCase("JS") || Configuration.LevelSystem.CALCULATION_MODE.equalsIgnoreCase("JavaScript")) { if (!new File(MyPetApi.getPlugin().getDataFolder(), "rhino.jar").exists()) { MyPetApi.getLogger().warning("rhino.jar is missing. Please download it here (https://github.com/mozilla/rhino/releases) and put it into the MyPet folder."); } else { expMode = new JavaScript(this); } } if (expMode == null || !expMode.isUsable()) { expMode = new Default(this); Configuration.LevelSystem.CALCULATION_MODE = "Default"; } experience = new MyPetExperience(this, expMode); hungerTime = Configuration.HungerSystem.HUNGER_SYSTEM_TIME; petName = Translation.getString("Name." + getPetType().name(), this.petOwner); } public Optional<MyPetBukkitEntity> getEntity() { if (getStatus() == PetState.Here) { return Optional.of(bukkitEntity); } return Optional.absent(); } public double getYSpawnOffset() { return 0; } public Optional<Location> getLocation() { if (status == PetState.Here) { return Optional.of(bukkitEntity.getLocation()); } else if (petOwner.isOnline()) { return Optional.of(petOwner.getPlayer().getLocation()); } else { return Optional.absent(); } } public void setLocation(Location loc) { if (status == PetState.Here && MyPetApi.getPlatformHelper().canSpawn(loc, this.bukkitEntity.getHandle())) { bukkitEntity.teleport(loc); } } public double getDamage() { return getSkills().hasSkill(Damage.class) ? getSkills().getSkill(Damage.class).get().getDamage() : 0; } public double getRangedDamage() { return getSkills().hasSkill(Ranged.class) ? getSkills().getSkill(Ranged.class).get().getDamage() : 0; } public boolean isPassiv() { return getDamage() == 0 && getRangedDamage() == 0; } public boolean hasTarget() { return this.getStatus() == PetState.Here && bukkitEntity.getHandle().hasTarget(); } public double getExp() { return getExperience().getExp(); } public MyPetExperience getExperience() { return experience; } public TagCompound writeExtendedInfo() { TagCompound newTag = new TagCompound(); newTag.put("Version", new TagInt(RepositoryMyPetConverterService.Version.valueOf(MyPetApi.getCompatUtil().getInternalVersion()).ordinal())); return newTag; } public void readExtendedInfo(TagCompound info) { } public double getMaxHealth() { return MyPetApi.getMyPetInfo().getStartHP(getPetType()) + (skills.isSkillActive(Life.class) ? skills.getSkill(Life.class).get().getHpIncrease() : 0); } public double getHealth() { if (status == PetState.Here) { return bukkitEntity.getHealth(); } else { return health; } } public void setHealth(double d) { if (d > getMaxHealth()) { health = getMaxHealth(); } else { health = d; } if (status == PetState.Here) { bukkitEntity.setHealth(health); } } @Deprecated @Since("24.11.2016") public double getHungerValue() { return getSaturation(); } public double getSaturation() { if (Configuration.HungerSystem.USE_HUNGER_SYSTEM) { return saturation; } else { return 100; } } @Deprecated @Since("24.11.2016") public void setHungerValue(double value) { setSaturation(value); } public void setSaturation(double value) { if (!Double.isNaN(value) && !Double.isInfinite(value)) { saturation = Math.max(1, Math.min(100, value)); hungerTime = Configuration.HungerSystem.HUNGER_SYSTEM_TIME; } else { MyPetApi.getLogger().warning("Saturation was set to an invalid number!\n" + Util.stackTraceToString()); } } @Deprecated @Since("24.11.2016") public void decreaseHunger(double value) { decreaseSaturation(value); } public void decreaseSaturation(double value) { if (!Double.isNaN(value) && !Double.isInfinite(value)) { saturation = Math.max(1, Math.min(100, saturation - value)); } else { MyPetApi.getLogger().warning("Saturation was decreased by an invalid number!\n" + Util.stackTraceToString()); } } public String getPetName() { return this.petName; } public void setPetName(String newName) { if (!NameFilter.isClean(newName)) { newName = Translation.getString("Name." + getPetType().name(), getOwner().getLanguage()); } this.petName = newName; if (status == PetState.Here) { if (Configuration.Name.OVERHEAD_NAME) { getEntity().get().getHandle().updateNameTag(); } } } public abstract MyPetType getPetType(); public int getRespawnTime() { return respawnTime; } public void setRespawnTime(int time) { respawnTime = time > 0 ? time : 0; if (respawnTime > 0) { status = PetState.Dead; } } public boolean autoAssignSkilltree() { if (skillTree == null && this.petOwner.isOnline()) { if (Configuration.Skilltree.AUTOMATIC_SKILLTREE_ASSIGNMENT) { if (SkillTreeMobType.getSkillTreeNames(this.getPetType()).size() > 0) { List<SkillTree> skilltrees = SkillTreeMobType.getSkillTrees(this.getPetType()); if (Configuration.Skilltree.RANDOM_SKILLTREE_ASSIGNMENT) { Collections.shuffle(skilltrees); } for (SkillTree skillTree : skilltrees) { if (Permissions.hasLegacy(this.petOwner.getPlayer(), "MyPet.skilltree.", skillTree.getPermission())) { return setSkilltree(skillTree); } } } } else { for (SkillTree skillTree : SkillTreeMobType.getSkillTrees(this.getPetType())) { if (Permissions.hasLegacy(this.petOwner.getPlayer(), "MyPet.skilltree.", skillTree.getPermission())) { getOwner().sendMessage(Util.formatText(Translation.getString("Message.Skilltree.SelectionPrompt", getOwner()), getPetName())); break; } } return false; } } return true; } public SkillTree getSkilltree() { return skillTree; } public TagCompound getSkillInfo() { TagCompound skillsNBT = new TagCompound(); Collection<SkillInstance> skillList = this.getSkills().getSkills(); if (skillList.size() > 0) { for (SkillInstance skill : skillList) { if (skill instanceof NBTStorage) { NBTStorage storageSkill = (NBTStorage) skill; TagCompound s = storageSkill.save(); if (s != null) { skillsNBT.getCompoundData().put(skill.getName(), s); } } } } return skillsNBT; } public Skills getSkills() { return skills; } public PetState getStatus() { if (status == PetState.Here) { if (bukkitEntity == null || bukkitEntity.getHandle() == null) { status = PetState.Despawned; } else if (bukkitEntity.getHealth() <= 0 || bukkitEntity.isDead()) { status = PetState.Dead; } } return status; } public void setStatus(PetState status) { if (status == PetState.Here) { if (this.status == PetState.Dead) { respawnPet(); } else if (this.status == PetState.Despawned) { createEntity(); } } else if (status == PetState.Dead) { this.status = PetState.Dead; } else { if (this.status == PetState.Here) { removePet(); } } } public UUID getUUID() { if (this.uuid == null) { this.uuid = UUID.randomUUID(); } return this.uuid; } public void setUUID(UUID uuid) { this.uuid = uuid; } public void setLastUsed(long date) { this.lastUsed = date; } @Override public long getLastUsed() { return lastUsed; } @Override public String getWorldGroup() { return this.worldGroup; } public void setWorldGroup(String worldGroup) { if (worldGroup == null) { return; } if (WorldGroup.getGroupByName(worldGroup) == null) { worldGroup = "default"; } this.worldGroup = worldGroup; } public SpawnFlags createEntity() { lastUsed = System.currentTimeMillis(); if (status != PetState.Here && getOwner().isOnline()) { Player owner = getOwner().getPlayer(); if (owner.isDead()) { status = PetState.Despawned; return SpawnFlags.OwnerDead; } if (owner.getGameMode().name().equals("SPECTATOR")) { return SpawnFlags.Spectator; } if (respawnTime <= 0) { Location loc = petOwner.getPlayer().getLocation(); if (!WorldGroup.getGroupByWorld(loc.getWorld().getName()).getName().equals(getWorldGroup())) { return SpawnFlags.WrongWorldGroup; } if (owner.isFlying()) { boolean groundFound = false; for (int i = 10; i >= 0; i--) { Block b = loc.getBlock(); if (b.getRelative(BlockFace.DOWN).getType().isSolid()) { groundFound = true; break; } loc = loc.subtract(0, 1, 0); } if (!groundFound) { return SpawnFlags.Flying; } } MyPetCallEvent event = new MyPetCallEvent(this); Bukkit.getServer().getPluginManager().callEvent(event); if (event.isCancelled()) { return SpawnFlags.NotAllowed; } MyPetMinecraftEntity minecraftEntity = MyPetApi.getEntityRegistry().createMinecraftEntity(this, loc.getWorld()); if (minecraftEntity == null) { status = PetState.Despawned; return SpawnFlags.Canceled; } bukkitEntity = minecraftEntity.getBukkitEntity(); if (MyPetApi.getCompatUtil().compareWithMinecraftVersion("1.9") >= 0) { Random r = new Random(petOwner.getInternalUUID().toString().hashCode()); String random = RandomStringUtils.random(10, 0, 0, true, true, null, r); Team t; if (owner.getScoreboard().getTeam("MyPet-" + random) != null) { t = owner.getScoreboard().getTeam("MyPet-" + random); } else { t = owner.getScoreboard().registerNewTeam("MyPet-" + random); t.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER); } for (String entry : t.getEntries()) { t.removeEntry(entry); } t.addEntry(minecraftEntity.getUniqueID().toString()); } if (getYSpawnOffset() > 0) { loc = loc.add(0, getYSpawnOffset(), 0); } loc.setPitch(0); loc.setYaw(0); Location origin = loc.clone(); boolean positionFound = false; loc.subtract(1, 0, 1); for (double x = 0; x <= 2; x += 0.5) { for (double z = 0; z <= 2; z += 0.5) { if (x != 1 && z != 1) { minecraftEntity.setLocation(loc); if (MyPetApi.getPlatformHelper().canSpawn(loc, minecraftEntity)) { Block b = loc.getBlock(); if (b.getRelative(BlockFace.DOWN).getType().isSolid()) { positionFound = true; break; } } } loc.add(0, 0, 0.5); } if (positionFound) { break; } loc.subtract(0, 0, 2); loc.add(0.5, 0, 0); } if (!positionFound) { minecraftEntity.setLocation(origin); if (!MyPetApi.getPlatformHelper().canSpawn(origin, minecraftEntity)) { status = PetState.Despawned; return SpawnFlags.NoSpace; } } if (MyPetApi.getEntityRegistry().spawnMinecraftEntity(minecraftEntity, loc.getWorld())) { bukkitEntity.setMetadata("MyPet", new FixedMetadataValue(MyPetApi.getPlugin(), this)); status = PetState.Here; if (worldGroup == null || worldGroup.equals("")) { setWorldGroup(WorldGroup.getGroupByWorld(loc.getWorld().getName()).getName()); } autoAssignSkilltree(); wantsToRespawn = false; return SpawnFlags.Success; } return SpawnFlags.Canceled; } } if (status == PetState.Dead) { return SpawnFlags.Dead; } else { return SpawnFlags.AlreadyHere; } } public void removePet() { if (status == PetState.Here) { health = bukkitEntity.getHealth(); status = PetState.Despawned; bukkitEntity.removeEntity(); bukkitEntity = null; Optional<Inventory> invSkill = getSkills().getSkill(Inventory.class); if (invSkill.isPresent()) { invSkill.get().closeInventory(); } } } public void removePet(boolean wantToRespawn) { this.wantsToRespawn = wantToRespawn; removePet(); } public void respawnPet() { if (status != PetState.Here && getOwner().isOnline()) { this.status = PetState.Despawned; respawnTime = 0; switch (createEntity()) { case Success: getOwner().sendMessage(Util.formatText(Translation.getString("Message.Spawn.Respawn", petOwner), petName)); break; case Canceled: getOwner().sendMessage(Util.formatText(Translation.getString("Message.Spawn.Prevent", petOwner), petName)); break; case NoSpace: getOwner().sendMessage(Util.formatText(Translation.getString("Message.Spawn.NoSpace", petOwner), petName)); break; case Flying: getOwner().sendMessage(Util.formatText(Translation.getString("Message.Spawn.Flying", petOwner), petName)); break; } if (Configuration.HungerSystem.USE_HUNGER_SYSTEM) { setHealth((int) Math.ceil(getMaxHealth() / 100. * (saturation + 1 - (saturation % 10)))); } else { setHealth(getMaxHealth()); } } } public MyPetPlayer getOwner() { return petOwner; } public void setWantsToRespawn(boolean wantsToRespawn) { this.wantsToRespawn = wantsToRespawn; } public boolean wantsToRespawn() { return wantsToRespawn; } public void schedule() { if (status != PetState.Despawned && getOwner().isOnline()) { for (SkillInstance skill : skills.getSkills()) { if (skill instanceof Scheduler) { ((Scheduler) skill).schedule(); } } if (status == PetState.Dead) { respawnTime--; if (respawnTime <= 0) { respawnPet(); } else if (MyPetApi.getPluginHookManager().isHookActive(VaultHook.class) && getOwner().hasAutoRespawnEnabled() && respawnTime >= getOwner().getAutoRespawnMin() && Permissions.has(getOwner().getPlayer(), "MyPet.user.respawn")) { double cost = respawnTime * Configuration.Respawn.COSTS_FACTOR + Configuration.Respawn.COSTS_FIXED; VaultHook vaultHook = MyPetApi.getPluginHookManager().getHook(VaultHook.class); if (vaultHook.canPay(getOwner().getPlayer(), cost)) { vaultHook.pay(getOwner().getPlayer(), cost); getOwner().sendMessage(Util.formatText(Translation.getString("Message.Command.Respawn.Paid", petOwner.getLanguage()), petName, cost + " " + vaultHook.currencyNameSingular())); respawnTime = 1; } } } if (status == PetState.Here) { if (Configuration.HungerSystem.USE_HUNGER_SYSTEM) { if (saturation > 1 && --hungerTime <= 0) { saturation--; hungerTime = Configuration.HungerSystem.HUNGER_SYSTEM_TIME; if (saturation == 66) { getOwner().sendMessage(Util.formatText(Translation.getString("Message.Hunger.Rumbling", getOwner()), getPetName())); } else if (saturation == 33) { getOwner().sendMessage(Util.formatText(Translation.getString("Message.Hunger.Hungry", getOwner()), getPetName())); } else if (saturation == 1) { getOwner().sendMessage(Util.formatText(Translation.getString("Message.Hunger.Starving", getOwner()), getPetName())); } } if (saturation == 1 && getHealth() >= 2) { getEntity().get().damage(1.); } } } } } @Override public void load(TagCompound myPetNBT) { } @Override public TagCompound save() { TagCompound petNBT = new TagCompound(); petNBT.getCompoundData().put("UUID", new TagString(getUUID().toString())); petNBT.getCompoundData().put("Type", new TagString(this.getPetType().name())); petNBT.getCompoundData().put("Health", new TagDouble(this.health)); petNBT.getCompoundData().put("Respawntime", new TagInt(this.respawnTime)); petNBT.getCompoundData().put("Hunger", new TagDouble(this.saturation)); petNBT.getCompoundData().put("Name", new TagString(this.petName)); petNBT.getCompoundData().put("WorldGroup", new TagString(this.worldGroup)); petNBT.getCompoundData().put("Exp", new TagDouble(this.getExp())); petNBT.getCompoundData().put("LastUsed", new TagLong(this.lastUsed)); petNBT.getCompoundData().put("Info", writeExtendedInfo()); petNBT.getCompoundData().put("Internal-Owner-UUID", new TagString(this.petOwner.getInternalUUID().toString())); petNBT.getCompoundData().put("Wants-To-Respawn", new TagByte(wantsToRespawn)); if (this.skillTree != null) { petNBT.getCompoundData().put("Skilltree", new TagString(skillTree.getName())); } TagCompound skillsNBT = new TagCompound(); Collection<SkillInstance> skillList = this.getSkills().getSkills(); if (skillList.size() > 0) { for (SkillInstance skill : skillList) { if (skill instanceof NBTStorage) { NBTStorage storageSkill = (NBTStorage) skill; TagCompound s = storageSkill.save(); if (s != null) { skillsNBT.getCompoundData().put(skill.getName(), s); } } } } petNBT.getCompoundData().put("Skills", skillsNBT); return petNBT; } @Override public String toString() { return "MyPet{owner=" + getOwner().getName() + ", name=" + ChatColor.stripColor(petName) + ", exp=" + experience.getExp() + "/" + experience.getRequiredExp() + ", lv=" + experience.getLevel() + ", status=" + status.name() + ", skilltree=" + skillTree.getName() + ", worldgroup=" + worldGroup + "}"; } public static float[] getEntitySize(Class<? extends MyPetMinecraftEntity> entityMyPetClass) { EntitySize es = entityMyPetClass.getAnnotation(EntitySize.class); if (es != null) { return new float[]{es.height(), es.width()}; } return new float[]{0, 0}; } public boolean setSkilltree(SkillTree skillTree) { if (skillTree == null || this.skillTree == skillTree) { return false; } if (skillTree.getRequiredLevel() > 1 && getExperience().getLevel() < skillTree.getRequiredLevel()) { return false; } skills.reset(); this.skillTree = skillTree; getServer().getPluginManager().callEvent(new MyPetLevelUpEvent(this, experience.getLevel(), 0, true)); return true; } }