777 lines
33 KiB
Java
777 lines
33 KiB
Java
package me.libraryaddict.disguise.disguisetypes;
|
|
|
|
import com.comphenix.protocol.PacketType;
|
|
import com.comphenix.protocol.ProtocolLibrary;
|
|
import com.comphenix.protocol.events.PacketContainer;
|
|
import com.comphenix.protocol.reflect.StructureModifier;
|
|
import me.libraryaddict.disguise.DisguiseAPI;
|
|
import me.libraryaddict.disguise.DisguiseConfig;
|
|
import me.libraryaddict.disguise.LibsDisguises;
|
|
import me.libraryaddict.disguise.disguisetypes.TargetedDisguise.TargetType;
|
|
import me.libraryaddict.disguise.disguisetypes.watchers.AgeableWatcher;
|
|
import me.libraryaddict.disguise.disguisetypes.watchers.BatWatcher;
|
|
import me.libraryaddict.disguise.disguisetypes.watchers.HorseWatcher;
|
|
import me.libraryaddict.disguise.disguisetypes.watchers.ZombieWatcher;
|
|
import me.libraryaddict.disguise.events.DisguiseEvent;
|
|
import me.libraryaddict.disguise.events.UndisguiseEvent;
|
|
import me.libraryaddict.disguise.utilities.DisguiseUtilities;
|
|
import me.libraryaddict.disguise.utilities.DisguiseValues;
|
|
import me.libraryaddict.disguise.utilities.PacketsManager;
|
|
import me.libraryaddict.disguise.utilities.ReflectionManager;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.entity.Entity;
|
|
import org.bukkit.entity.Horse.Variant;
|
|
import org.bukkit.entity.LivingEntity;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.scheduler.BukkitTask;
|
|
import org.bukkit.util.Vector;
|
|
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.UUID;
|
|
|
|
public abstract class Disguise {
|
|
|
|
private boolean disguiseInUse;
|
|
private DisguiseType disguiseType;
|
|
private Entity entity;
|
|
private boolean hearSelfDisguise = DisguiseConfig.isSelfDisguisesSoundsReplaced();
|
|
private boolean hideArmorFromSelf = DisguiseConfig.isHidingArmorFromSelf();
|
|
private boolean hideHeldItemFromSelf = DisguiseConfig.isHidingHeldItemFromSelf();
|
|
private boolean keepDisguiseEntityDespawn = DisguiseConfig.isKeepDisguiseOnEntityDespawn();
|
|
private boolean keepDisguisePlayerDeath = DisguiseConfig.isKeepDisguiseOnPlayerDeath();
|
|
private boolean keepDisguisePlayerLogout = DisguiseConfig.isKeepDisguiseOnPlayerLogout();
|
|
private boolean modifyBoundingBox = DisguiseConfig.isModifyBoundingBox();
|
|
private boolean replaceSounds = DisguiseConfig.isSoundEnabled();
|
|
private BukkitTask task = null;
|
|
private Runnable velocityRunnable;
|
|
private boolean velocitySent = DisguiseConfig.isVelocitySent();
|
|
private boolean viewSelfDisguise = DisguiseConfig.isViewDisguises();
|
|
private FlagWatcher watcher;
|
|
private boolean showName = false;
|
|
|
|
private static List<UUID> viewSelf = new ArrayList<>();
|
|
|
|
@Override
|
|
public abstract Disguise clone();
|
|
|
|
/**
|
|
* Seems I do this method so I can make cleaner constructors on disguises..
|
|
*
|
|
* @param newType
|
|
*/
|
|
protected void createDisguise(DisguiseType newType) {
|
|
if (getWatcher() != null) {
|
|
return;
|
|
}
|
|
if (newType.getEntityType() == null) {
|
|
throw new RuntimeException(
|
|
"DisguiseType "
|
|
+ newType
|
|
+ " was used in a futile attempt to construct a disguise, but this version of craftbukkit does not have that entity");
|
|
}
|
|
// Set the disguise type
|
|
disguiseType = newType;
|
|
// Get if they are a adult now..
|
|
boolean isAdult = true;
|
|
if (isMobDisguise()) {
|
|
isAdult = ((MobDisguise) this).isAdult();
|
|
}
|
|
try {
|
|
// Construct the FlagWatcher from the stored class
|
|
setWatcher((FlagWatcher) getType().getWatcherClass().getConstructor(Disguise.class).newInstance(this));
|
|
} catch (Exception e) {
|
|
e.printStackTrace(System.out);
|
|
}
|
|
// Set the disguise if its a baby or not
|
|
if (!isAdult) {
|
|
if (getWatcher() instanceof AgeableWatcher) {
|
|
((AgeableWatcher) getWatcher()).setBaby(true);
|
|
} else if (getWatcher() instanceof ZombieWatcher) {
|
|
((ZombieWatcher) getWatcher()).setBaby(true);
|
|
}
|
|
}
|
|
// If the disguise type is a wither, set the flagwatcher value for the skeleton to a wither skeleton
|
|
if (getType() == DisguiseType.WITHER_SKELETON) {
|
|
getWatcher().setValue(13, (byte) 1);
|
|
} // Else if its a zombie, but the disguise type is a zombie villager. Set the value.
|
|
else if (getType() == DisguiseType.ZOMBIE_VILLAGER) {
|
|
getWatcher().setValue(13, (byte) 1);
|
|
} else if (getType() == DisguiseType.ELDER_GUARDIAN) {
|
|
getWatcher().setValue(16, 0 | 4);
|
|
} // Else if its a horse. Set the horse watcher type
|
|
else if (getWatcher() instanceof HorseWatcher) {
|
|
try {
|
|
// Don't mess with this because Varient is something like ZombieHorse and so on.
|
|
// Not something that a watcher needs to access.
|
|
Variant horseType = Variant.valueOf(getType().name());
|
|
getWatcher().setValue(19, (byte) horseType.ordinal());
|
|
} catch (Exception ex) {
|
|
// Ok.. So it aint a horse
|
|
}
|
|
}
|
|
final boolean alwaysSendVelocity;
|
|
switch (getType()) {
|
|
case EGG:
|
|
case ENDER_PEARL:
|
|
case BAT:
|
|
case EXPERIENCE_ORB:
|
|
case FIREBALL:
|
|
case SMALL_FIREBALL:
|
|
case SNOWBALL:
|
|
case SPLASH_POTION:
|
|
case THROWN_EXP_BOTTLE:
|
|
case WITHER_SKULL:
|
|
case FIREWORK:
|
|
alwaysSendVelocity = true;
|
|
break;
|
|
default:
|
|
alwaysSendVelocity = false;
|
|
break;
|
|
}
|
|
double velocitySpeed = 0.0005;
|
|
switch (getType()) {
|
|
case FIREWORK:
|
|
velocitySpeed = -0.040;
|
|
break;
|
|
case WITHER_SKULL:
|
|
velocitySpeed = 0.000001D;
|
|
break;
|
|
case ARROW:
|
|
case BOAT:
|
|
case ENDER_CRYSTAL:
|
|
case ENDER_DRAGON:
|
|
case GHAST:
|
|
case ITEM_FRAME:
|
|
case MINECART:
|
|
case MINECART_CHEST:
|
|
case MINECART_COMMAND:
|
|
case MINECART_FURNACE:
|
|
case MINECART_HOPPER:
|
|
case MINECART_MOB_SPAWNER:
|
|
case MINECART_TNT:
|
|
case PAINTING:
|
|
case PLAYER:
|
|
case SQUID:
|
|
velocitySpeed = 0;
|
|
break;
|
|
case DROPPED_ITEM:
|
|
case PRIMED_TNT:
|
|
case WITHER:
|
|
case FALLING_BLOCK:
|
|
velocitySpeed = 0.04;
|
|
break;
|
|
case EXPERIENCE_ORB:
|
|
velocitySpeed = 0.0221;
|
|
break;
|
|
case SPIDER:
|
|
case BAT:
|
|
case CAVE_SPIDER:
|
|
velocitySpeed = 0.004;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
final double vectorY = velocitySpeed;
|
|
final TargetedDisguise disguise = (TargetedDisguise) this;
|
|
// A scheduler to clean up any unused disguises.
|
|
velocityRunnable = new Runnable() {
|
|
private int blockX, blockY, blockZ, facing;
|
|
private int deadTicks = 0;
|
|
private int refreshDisguise = 0;
|
|
|
|
@Override
|
|
public void run() {
|
|
// If entity is no longer valid. Remove it.
|
|
if (!getEntity().isValid()) {
|
|
// If it has been dead for 30+ ticks
|
|
// This is to ensure that this disguise isn't removed while clients think its the real entity
|
|
// The delay is because if it sends the destroy entity packets straight away, then it means no death animation
|
|
// This is probably still a problem for wither and enderdragon deaths.
|
|
if (deadTicks++ > (getType() == DisguiseType.ENDER_DRAGON ? 200 : 20)) {
|
|
deadTicks = 0;
|
|
if (isRemoveDisguiseOnDeath()) {
|
|
removeDisguise();
|
|
} else {
|
|
entity = null;
|
|
watcher = getWatcher().clone(disguise);
|
|
task.cancel();
|
|
task = null;
|
|
}
|
|
}
|
|
} else {
|
|
deadTicks = 0;
|
|
// If the disguise type is tnt, we need to resend the entity packet else it will turn invisible
|
|
if (getType() == DisguiseType.PRIMED_TNT || getType() == DisguiseType.FIREWORK) {
|
|
refreshDisguise++;
|
|
if (refreshDisguise % 40 == 0) {
|
|
refreshDisguise = 0;
|
|
DisguiseUtilities.refreshTrackers(disguise);
|
|
}
|
|
}
|
|
if (getType() == DisguiseType.ITEM_FRAME) {
|
|
Location loc = getEntity().getLocation();
|
|
int newFacing = (((int) loc.getYaw() + 720 + 45) / 90) % 4;
|
|
if (loc.getBlockX() != blockX || loc.getBlockY() != blockY || loc.getBlockZ() != blockZ
|
|
|| newFacing != facing) {
|
|
blockX = loc.getBlockX();
|
|
blockY = loc.getBlockY();
|
|
blockZ = loc.getBlockZ();
|
|
facing = newFacing;
|
|
DisguiseUtilities.refreshTrackers(disguise);
|
|
}
|
|
}
|
|
if (isModifyBoundingBox()) {
|
|
DisguiseUtilities.doBoundingBox(disguise);
|
|
}
|
|
if (getType() == DisguiseType.BAT && !((BatWatcher) getWatcher()).isFlying()) {
|
|
return;
|
|
}
|
|
// If the vectorY isn't 0. Cos if it is. Then it doesn't want to send any vectors.
|
|
// If this disguise has velocity sending enabled and the entity is flying.
|
|
if (isVelocitySent() && vectorY != 0 && (alwaysSendVelocity || !getEntity().isOnGround())) {
|
|
Vector vector = getEntity().getVelocity();
|
|
// If the entity doesn't have velocity changes already - You know. I really can't wrap my head about the
|
|
// if statement.
|
|
// But it doesn't seem to do anything wrong..
|
|
if (vector.getY() != 0 && !(vector.getY() < 0 && alwaysSendVelocity && getEntity().isOnGround())) {
|
|
return;
|
|
}
|
|
// If disguise isn't a experience orb, or the entity isn't standing on the ground
|
|
if (getType() != DisguiseType.EXPERIENCE_ORB || !getEntity().isOnGround()) {
|
|
PacketContainer lookPacket = null;
|
|
if (getType() == DisguiseType.WITHER_SKULL && DisguiseConfig.isWitherSkullPacketsEnabled()) {
|
|
lookPacket = new PacketContainer(PacketType.Play.Server.ENTITY_LOOK);
|
|
StructureModifier<Object> mods = lookPacket.getModifier();
|
|
lookPacket.getIntegers().write(0, getEntity().getEntityId());
|
|
Location loc = getEntity().getLocation();
|
|
mods.write(
|
|
4,
|
|
PacketsManager.getYaw(getType(), getEntity().getType(),
|
|
(byte) Math.floor(loc.getYaw() * 256.0F / 360.0F)));
|
|
mods.write(5, PacketsManager.getPitch(getType(), DisguiseType.getType(getEntity().getType()),
|
|
(byte) Math.floor(loc.getPitch() * 256.0F / 360.0F)));
|
|
if (isSelfDisguiseVisible() && getEntity() instanceof Player) {
|
|
PacketContainer selfLookPacket = lookPacket.shallowClone();
|
|
selfLookPacket.getIntegers().write(0, DisguiseAPI.getSelfDisguiseId());
|
|
try {
|
|
ProtocolLibrary.getProtocolManager().sendServerPacket((Player) getEntity(),
|
|
selfLookPacket, false);
|
|
} catch (InvocationTargetException e) {
|
|
e.printStackTrace(System.out);
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
PacketContainer velocityPacket = new PacketContainer(PacketType.Play.Server.ENTITY_VELOCITY);
|
|
StructureModifier<Integer> mods = velocityPacket.getIntegers();
|
|
mods.write(1, (int) (vector.getX() * 8000));
|
|
mods.write(3, (int) (vector.getZ() * 8000));
|
|
for (Player player : DisguiseUtilities.getPerverts(disguise)) {
|
|
if (getEntity() == player) {
|
|
if (!isSelfDisguiseVisible()) {
|
|
continue;
|
|
}
|
|
mods.write(0, DisguiseAPI.getSelfDisguiseId());
|
|
} else {
|
|
mods.write(0, getEntity().getEntityId());
|
|
}
|
|
mods.write(2, (int) (8000D * (vectorY * ReflectionManager.getPing(player)) * 0.069D));
|
|
if (lookPacket != null && player != getEntity()) {
|
|
ProtocolLibrary.getProtocolManager().sendServerPacket(player, lookPacket, false);
|
|
}
|
|
ProtocolLibrary.getProtocolManager().sendServerPacket(player, velocityPacket.shallowClone(),
|
|
false);
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace(System.out);
|
|
}
|
|
}
|
|
// If we need to send a packet to update the exp position as it likes to gravitate client sided to
|
|
// players.
|
|
}
|
|
if (getType() == DisguiseType.EXPERIENCE_ORB) {
|
|
PacketContainer packet = new PacketContainer(PacketType.Play.Server.REL_ENTITY_MOVE);
|
|
packet.getIntegers().write(0, getEntity().getEntityId());
|
|
try {
|
|
for (Player player : DisguiseUtilities.getPerverts(disguise)) {
|
|
if (getEntity() != player) {
|
|
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet, false);
|
|
} else if (isSelfDisguiseVisible()) {
|
|
PacketContainer selfPacket = packet.shallowClone();
|
|
selfPacket.getModifier().write(0, DisguiseAPI.getSelfDisguiseId());
|
|
try {
|
|
ProtocolLibrary.getProtocolManager().sendServerPacket((Player) getEntity(), selfPacket,
|
|
false);
|
|
} catch (InvocationTargetException e) {
|
|
e.printStackTrace(System.out);
|
|
}
|
|
}
|
|
}
|
|
} catch (InvocationTargetException e) {
|
|
e.printStackTrace(System.out);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the disguised entity
|
|
*
|
|
* @return
|
|
*/
|
|
public Entity getEntity() {
|
|
return entity;
|
|
}
|
|
|
|
/**
|
|
* Get the disguise type
|
|
*
|
|
* @return
|
|
*/
|
|
public DisguiseType getType() {
|
|
return disguiseType;
|
|
}
|
|
|
|
/**
|
|
* Get the flag watcher
|
|
*
|
|
* @return
|
|
*/
|
|
public FlagWatcher getWatcher() {
|
|
return watcher;
|
|
}
|
|
|
|
/**
|
|
* In use doesn't mean that this disguise is active. It means that Lib's Disguises still stores a reference to the disguise. getEntity() can still return null if this disguise is active after despawn, logout, etc.
|
|
*
|
|
* @return
|
|
*/
|
|
public boolean isDisguiseInUse() {
|
|
return disguiseInUse;
|
|
}
|
|
|
|
public boolean isHidingArmorFromSelf() {
|
|
return hideArmorFromSelf;
|
|
}
|
|
|
|
public boolean isHidingHeldItemFromSelf() {
|
|
return hideHeldItemFromSelf;
|
|
}
|
|
|
|
public boolean isKeepDisguiseOnEntityDespawn() {
|
|
return this.keepDisguiseEntityDespawn;
|
|
}
|
|
|
|
public boolean isKeepDisguiseOnPlayerDeath() {
|
|
return this.keepDisguisePlayerDeath;
|
|
}
|
|
|
|
public boolean isKeepDisguiseOnPlayerLogout() {
|
|
return this.keepDisguisePlayerLogout;
|
|
}
|
|
|
|
public boolean isMiscDisguise() {
|
|
return false;
|
|
}
|
|
|
|
public boolean isMobDisguise() {
|
|
return false;
|
|
}
|
|
|
|
public boolean isModifyBoundingBox() {
|
|
return modifyBoundingBox;
|
|
}
|
|
|
|
public boolean isPlayerDisguise() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Internal use
|
|
*
|
|
* @return
|
|
*/
|
|
public boolean isRemoveDisguiseOnDeath() {
|
|
if (getEntity() == null) {
|
|
return true;
|
|
}
|
|
return getEntity() instanceof Player
|
|
? (!((Player) getEntity()).isOnline() ? !isKeepDisguiseOnPlayerLogout() : !isKeepDisguiseOnPlayerDeath())
|
|
: (!isKeepDisguiseOnEntityDespawn() || getEntity().isDead());
|
|
}
|
|
|
|
public boolean isSelfDisguiseSoundsReplaced() {
|
|
return hearSelfDisguise;
|
|
}
|
|
|
|
/**
|
|
* Can the disguised view himself as the disguise
|
|
*
|
|
* @return
|
|
*/
|
|
public boolean isSelfDisguiseVisible() {
|
|
return viewSelfDisguise;
|
|
}
|
|
|
|
public boolean isSoundsReplaced() {
|
|
return replaceSounds;
|
|
}
|
|
|
|
public boolean isVelocitySent() {
|
|
return velocitySent;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the entity's name is showing through the disguise
|
|
*
|
|
* @return
|
|
*/
|
|
public boolean isShowName() {
|
|
return showName;
|
|
}
|
|
|
|
/**
|
|
* Removes the disguise and undisguises the entity if its using this disguise.
|
|
*
|
|
* @return
|
|
*/
|
|
public boolean removeDisguise() {
|
|
if (disguiseInUse) {
|
|
UndisguiseEvent event = new UndisguiseEvent(entity, this);
|
|
Bukkit.getPluginManager().callEvent(event);
|
|
if (!event.isCancelled()) {
|
|
disguiseInUse = false;
|
|
if (task != null) {
|
|
task.cancel();
|
|
task = null;
|
|
}
|
|
HashMap<UUID, HashSet<TargetedDisguise>> disguises = DisguiseUtilities.getDisguises();
|
|
// If this disguise has a entity set
|
|
if (getEntity() != null) {
|
|
// If this disguise is active
|
|
// Remove the disguise from the current disguises.
|
|
if (DisguiseUtilities.removeDisguise((TargetedDisguise) this)) {
|
|
if (getEntity() instanceof Player) {
|
|
DisguiseUtilities.removeSelfDisguise((Player) getEntity());
|
|
}
|
|
|
|
// Better refresh the entity to undisguise it
|
|
if (getEntity().isValid()) {
|
|
DisguiseUtilities.refreshTrackers((TargetedDisguise) this);
|
|
} else {
|
|
DisguiseUtilities.destroyEntity((TargetedDisguise) this);
|
|
}
|
|
}
|
|
} else {
|
|
// Loop through the disguises because it could be used with a unknown entity id.
|
|
HashMap<Integer, HashSet<TargetedDisguise>> future = DisguiseUtilities.getFutureDisguises();
|
|
Iterator<Integer> itel = DisguiseUtilities.getFutureDisguises().keySet().iterator();
|
|
while (itel.hasNext()) {
|
|
int id = itel.next();
|
|
if (future.get(id).remove(this) && future.get(id).isEmpty()) {
|
|
itel.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isPlayerDisguise()) {
|
|
String name = ((PlayerDisguise) this).getName();
|
|
if (!DisguiseUtilities.getAddedByPlugins().contains(name.toLowerCase())) {
|
|
for (HashSet<TargetedDisguise> disguise : disguises.values()) {
|
|
for (Disguise d : disguise) {
|
|
if (d.isPlayerDisguise() && ((PlayerDisguise) d).getName().equals(name)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
DisguiseUtilities.getGameProfiles().remove(name.toLowerCase());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Set the entity of the disguise. Only used for internal things.
|
|
*
|
|
* @param entity
|
|
* @return
|
|
*/
|
|
public Disguise setEntity(Entity entity) {
|
|
if (this.getEntity() != null) {
|
|
if (getEntity() == entity) {
|
|
return this;
|
|
}
|
|
throw new RuntimeException("This disguise is already in use! Try .clone()");
|
|
}
|
|
if (isMiscDisguise() && !DisguiseConfig.isMiscDisguisesForLivingEnabled() && entity instanceof LivingEntity) {
|
|
throw new RuntimeException(
|
|
"Cannot disguise a living entity with a misc disguise. Renable MiscDisguisesForLiving in the config to do this");
|
|
}
|
|
this.entity = entity;
|
|
setupWatcher();
|
|
return this;
|
|
}
|
|
|
|
public Disguise setShowName(boolean showName) {
|
|
this.showName = showName;
|
|
return this;
|
|
}
|
|
|
|
public Disguise setHearSelfDisguise(boolean hearSelfDisguise) {
|
|
this.hearSelfDisguise = hearSelfDisguise;
|
|
return this;
|
|
}
|
|
|
|
public Disguise setHideArmorFromSelf(boolean hideArmor) {
|
|
this.hideArmorFromSelf = hideArmor;
|
|
if (getEntity() instanceof Player) {
|
|
((Player) getEntity()).updateInventory();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public Disguise setHideHeldItemFromSelf(boolean hideHeldItem) {
|
|
this.hideHeldItemFromSelf = hideHeldItem;
|
|
if (getEntity() instanceof Player) {
|
|
((Player) getEntity()).updateInventory();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public Disguise setKeepDisguiseOnEntityDespawn(boolean keepDisguise) {
|
|
this.keepDisguiseEntityDespawn = keepDisguise;
|
|
return this;
|
|
}
|
|
|
|
public Disguise setKeepDisguiseOnPlayerDeath(boolean keepDisguise) {
|
|
this.keepDisguisePlayerDeath = keepDisguise;
|
|
return this;
|
|
}
|
|
|
|
public Disguise setKeepDisguiseOnPlayerLogout(boolean keepDisguise) {
|
|
this.keepDisguisePlayerLogout = keepDisguise;
|
|
return this;
|
|
}
|
|
|
|
public Disguise setModifyBoundingBox(boolean modifyBox) {
|
|
if (((TargetedDisguise) this).getDisguiseTarget() != TargetType.SHOW_TO_EVERYONE_BUT_THESE_PLAYERS) {
|
|
throw new RuntimeException(
|
|
"Cannot modify the bounding box of a disguise which is not TargetType.SHOW_TO_EVERYONE_BUT_THESE_PLAYERS");
|
|
}
|
|
if (isModifyBoundingBox() != modifyBox) {
|
|
this.modifyBoundingBox = modifyBox;
|
|
if (DisguiseUtilities.isDisguiseInUse(this)) {
|
|
DisguiseUtilities.doBoundingBox((TargetedDisguise) this);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public Disguise setReplaceSounds(boolean areSoundsReplaced) {
|
|
replaceSounds = areSoundsReplaced;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets up the FlagWatcher with the entityclass, it creates all the data it needs to prevent conflicts when sending the datawatcher.
|
|
*/
|
|
private void setupWatcher() {
|
|
HashMap<Integer, Object> disguiseValues = DisguiseValues.getMetaValues(getType());
|
|
HashMap<Integer, Object> entityValues = DisguiseValues.getMetaValues(DisguiseType.getType(getEntity().getType()));
|
|
// Start from 2 as they ALL share 0 and 1
|
|
for (int dataNo = 0; dataNo <= 31; dataNo++) {
|
|
// STEP 1. Find out if the watcher has set data on it.
|
|
// If the watcher already set a metadata on this
|
|
if (getWatcher().hasValue(dataNo)) {
|
|
// Better check that the value is stable.
|
|
// To check this, I'm going to check if there exists a default value
|
|
// Then I'm going to check if the watcher value is the same as the default value.
|
|
if (disguiseValues.containsKey(dataNo)) {
|
|
// Now check if they are the same class, or both null.
|
|
if (disguiseValues.get(dataNo) == null || getWatcher().getValue(dataNo, null) == null) {
|
|
if (disguiseValues.get(dataNo) == null && getWatcher().getValue(dataNo, null) == null) {
|
|
// They are both null. Idk what this means really.
|
|
continue;
|
|
}
|
|
} else if (getWatcher().getValue(dataNo, null).getClass() == disguiseValues.get(dataNo).getClass()) {
|
|
// The classes are the same. The client "shouldn't" crash.
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
// STEP 2. As the watcher has not set data on it, check if I need to set the default data.
|
|
// If neither of them touch it
|
|
if (!entityValues.containsKey(dataNo) && !disguiseValues.containsKey(dataNo)) {
|
|
continue;
|
|
}
|
|
// If the disguise has this, but not the entity. Then better set it!
|
|
if (!entityValues.containsKey(dataNo) && disguiseValues.containsKey(dataNo)) {
|
|
getWatcher().setBackupValue(dataNo, disguiseValues.get(dataNo));
|
|
continue;
|
|
}
|
|
// Else if the disguise doesn't have it. But the entity does. Better remove it!
|
|
if (entityValues.containsKey(dataNo) && !disguiseValues.containsKey(dataNo)) {
|
|
getWatcher().setBackupValue(dataNo, null);
|
|
continue;
|
|
}
|
|
Object eObj = entityValues.get(dataNo);
|
|
Object dObj = disguiseValues.get(dataNo);
|
|
if (eObj == null || dObj == null) {
|
|
if (eObj == null && dObj == null) {
|
|
continue;
|
|
} else {
|
|
getWatcher().setBackupValue(dataNo, dObj);
|
|
continue;
|
|
}
|
|
}
|
|
if (eObj.getClass() != dObj.getClass()) {
|
|
getWatcher().setBackupValue(dataNo, dObj);
|
|
continue;
|
|
}
|
|
|
|
// Since they both share it. With the same classes. Time to check if its from something they extend.
|
|
// Better make this clear before I compare the values because some default values are different!
|
|
// Entity is 0 & 1 - But we aint gonna be checking that
|
|
// EntityAgeable is 16
|
|
// EntityInsentient is 10 & 11
|
|
// EntityZombie is 12 & 13 & 14 - But it overrides other values and another check already does this.
|
|
// EntityLiving is 6 & 7 & 8 & 9
|
|
// Lets use switch
|
|
Class baseClass = null;
|
|
switch (dataNo) {
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
case 9:
|
|
baseClass = ReflectionManager.getNmsClass("EntityLiving");
|
|
break;
|
|
case 10:
|
|
case 11:
|
|
baseClass = ReflectionManager.getNmsClass("EntityInsentient");
|
|
break;
|
|
case 16:
|
|
baseClass = ReflectionManager.getNmsClass("EntityAgeable");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
Class nmsEntityClass = ReflectionManager.getNmsEntity(getEntity()).getClass();
|
|
Class nmsDisguiseClass = DisguiseValues.getNmsEntityClass(getType());
|
|
if (nmsDisguiseClass != null) {
|
|
// If they both extend the same base class. They OBVIOUSLY share the same datavalue. Right..?
|
|
if (baseClass != null && baseClass.isAssignableFrom(nmsDisguiseClass)
|
|
&& baseClass.isAssignableFrom(nmsEntityClass)) {
|
|
continue;
|
|
}
|
|
|
|
// So they don't extend a basic class.
|
|
// Maybe if I check that they extend each other..
|
|
// Seeing as I only store the finished forms of entitys. This should raise no problems and allow for more shared
|
|
// datawatchers.
|
|
if (nmsEntityClass.isAssignableFrom(nmsDisguiseClass) || nmsDisguiseClass.isAssignableFrom(nmsEntityClass)) {
|
|
continue;
|
|
}
|
|
}
|
|
// Well I can't find a reason I should leave it alone. They will probably conflict.
|
|
// Time to set the value to the disguises value so no conflicts!
|
|
getWatcher().setBackupValue(dataNo, disguiseValues.get(dataNo));
|
|
}
|
|
}
|
|
|
|
public Disguise setVelocitySent(boolean sendVelocity) {
|
|
this.velocitySent = sendVelocity;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Can the disguised view himself as the disguise
|
|
*
|
|
* @param viewSelfDisguise
|
|
* @return
|
|
*/
|
|
public Disguise setViewSelfDisguise(boolean viewSelfDisguise) {
|
|
if (isSelfDisguiseVisible() != viewSelfDisguise) {
|
|
this.viewSelfDisguise = viewSelfDisguise;
|
|
if (getEntity() != null && getEntity() instanceof Player) {
|
|
if (DisguiseAPI.getDisguise((Player) getEntity(), getEntity()) == this) {
|
|
if (isSelfDisguiseVisible()) {
|
|
DisguiseUtilities.setupFakeDisguise(this);
|
|
} else {
|
|
DisguiseUtilities.removeSelfDisguise((Player) getEntity());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public Disguise setWatcher(FlagWatcher newWatcher) {
|
|
if (!getType().getWatcherClass().isInstance(newWatcher)) {
|
|
throw new IllegalArgumentException(newWatcher.getClass().getSimpleName() + " is not a instance of "
|
|
+ getType().getWatcherClass().getSimpleName() + " for DisguiseType " + getType().name());
|
|
}
|
|
watcher = newWatcher;
|
|
if (getEntity() != null) {
|
|
setupWatcher();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public boolean startDisguise() {
|
|
if (!isDisguiseInUse()) {
|
|
if (getEntity() == null) {
|
|
throw new RuntimeException("No entity is assigned to this disguise!");
|
|
}
|
|
// Fire a disguise event
|
|
DisguiseEvent event = new DisguiseEvent(entity, this);
|
|
Bukkit.getPluginManager().callEvent(event);
|
|
// If they cancelled this disguise event. No idea why.
|
|
// Just return.
|
|
if (!event.isCancelled()) {
|
|
disguiseInUse = true;
|
|
task = Bukkit.getScheduler().runTaskTimer(LibsDisguises.getInstance(), velocityRunnable, 1, 1);
|
|
// Stick the disguise in the disguises bin
|
|
DisguiseUtilities.addDisguise(entity.getUniqueId(), (TargetedDisguise) this);
|
|
if (isSelfDisguiseVisible() && getEntity() instanceof Player) {
|
|
DisguiseUtilities.removeSelfDisguise((Player) getEntity());
|
|
}
|
|
// Resend the disguised entity's packet
|
|
DisguiseUtilities.refreshTrackers((TargetedDisguise) this);
|
|
// If he is a player, then self disguise himself
|
|
Bukkit.getScheduler().scheduleSyncDelayedTask(LibsDisguises.getInstance(), new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
DisguiseUtilities.setupFakeDisguise(Disguise.this);
|
|
}
|
|
}, 2);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean stopDisguise() {
|
|
return removeDisguise();
|
|
}
|
|
|
|
/**
|
|
* Returns the list of people who have /disguiseViewSelf toggled on
|
|
*
|
|
* @return
|
|
*/
|
|
public static List<UUID> getViewSelf() {
|
|
return viewSelf;
|
|
}
|
|
}
|