2020-04-13 19:57:20 +02:00
|
|
|
package net.citizensnpcs.trait;
|
|
|
|
|
2020-04-15 21:04:42 +02:00
|
|
|
import java.util.Random;
|
2020-04-13 19:57:20 +02:00
|
|
|
|
2021-01-20 15:10:47 +01:00
|
|
|
import org.bukkit.Bukkit;
|
2020-04-13 19:57:20 +02:00
|
|
|
import org.bukkit.GameMode;
|
|
|
|
import org.bukkit.Location;
|
|
|
|
import org.bukkit.entity.Entity;
|
|
|
|
import org.bukkit.entity.LivingEntity;
|
|
|
|
import org.bukkit.entity.Player;
|
|
|
|
import org.bukkit.metadata.MetadataValue;
|
|
|
|
import org.bukkit.potion.PotionEffectType;
|
|
|
|
|
|
|
|
import net.citizensnpcs.Settings.Setting;
|
|
|
|
import net.citizensnpcs.api.CitizensAPI;
|
|
|
|
import net.citizensnpcs.api.command.CommandConfigurable;
|
|
|
|
import net.citizensnpcs.api.command.CommandContext;
|
2021-02-04 02:51:02 +01:00
|
|
|
import net.citizensnpcs.api.command.CommandMessages;
|
|
|
|
import net.citizensnpcs.api.command.exception.CommandException;
|
2021-01-20 15:10:47 +01:00
|
|
|
import net.citizensnpcs.api.event.NPCLookCloseChangeTargetEvent;
|
2020-04-13 19:57:20 +02:00
|
|
|
import net.citizensnpcs.api.persistence.Persist;
|
|
|
|
import net.citizensnpcs.api.trait.Trait;
|
|
|
|
import net.citizensnpcs.api.trait.TraitName;
|
|
|
|
import net.citizensnpcs.api.util.DataKey;
|
|
|
|
import net.citizensnpcs.util.NMS;
|
|
|
|
import net.citizensnpcs.util.Util;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Persists the /npc lookclose metadata
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
@TraitName("lookclose")
|
|
|
|
public class LookClose extends Trait implements Toggleable, CommandConfigurable {
|
2021-02-04 02:51:02 +01:00
|
|
|
@Persist("disablewhilenavigating")
|
|
|
|
private boolean disableWhileNavigating = Setting.DISABLE_LOOKCLOSE_WHILE_NAVIGATING.asBoolean();
|
2020-04-13 19:57:20 +02:00
|
|
|
@Persist("enabled")
|
|
|
|
private boolean enabled = Setting.DEFAULT_LOOK_CLOSE.asBoolean();
|
|
|
|
@Persist
|
|
|
|
private boolean enableRandomLook = Setting.DEFAULT_RANDOM_LOOK_CLOSE.asBoolean();
|
|
|
|
private Player lookingAt;
|
|
|
|
@Persist
|
|
|
|
private int randomLookDelay = Setting.DEFAULT_RANDOM_LOOK_DELAY.asInt();
|
|
|
|
@Persist
|
2022-04-13 05:14:37 +02:00
|
|
|
private float[] randomPitchRange = { 0, 0 };
|
2020-04-13 19:57:20 +02:00
|
|
|
@Persist
|
|
|
|
private float[] randomYawRange = { 0, 360 };
|
|
|
|
private double range = Setting.DEFAULT_LOOK_CLOSE_RANGE.asDouble();
|
|
|
|
@Persist("realisticlooking")
|
|
|
|
private boolean realisticLooking = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean();
|
|
|
|
private int t;
|
|
|
|
|
|
|
|
public LookClose() {
|
|
|
|
super("lookclose");
|
|
|
|
}
|
|
|
|
|
2021-01-25 14:30:07 +01:00
|
|
|
private boolean canSee(Player player) {
|
2021-04-04 05:31:43 +02:00
|
|
|
if (player == null || !player.isValid())
|
|
|
|
return false;
|
2021-01-25 14:30:07 +01:00
|
|
|
return realisticLooking && npc.getEntity() instanceof LivingEntity
|
|
|
|
? ((LivingEntity) npc.getEntity()).hasLineOfSight(player)
|
2021-04-07 08:26:07 +02:00
|
|
|
: true;
|
2021-01-25 14:30:07 +01:00
|
|
|
}
|
|
|
|
|
2020-04-13 19:57:20 +02:00
|
|
|
/**
|
2020-04-15 21:04:42 +02:00
|
|
|
* Returns whether the target can be seen. Will use realistic line of sight if {@link #setRealisticLooking(boolean)}
|
|
|
|
* is true.
|
2020-04-13 19:57:20 +02:00
|
|
|
*/
|
|
|
|
public boolean canSeeTarget() {
|
2021-01-25 14:30:07 +01:00
|
|
|
return canSee(lookingAt);
|
2020-04-13 19:57:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-02-04 02:51:02 +01:00
|
|
|
public void configure(CommandContext args) throws CommandException {
|
|
|
|
try {
|
|
|
|
range = args.getFlagDouble("range", args.getFlagDouble("r", range));
|
|
|
|
} catch (NumberFormatException ex) {
|
|
|
|
throw new CommandException(CommandMessages.INVALID_NUMBER);
|
|
|
|
}
|
2020-04-13 19:57:20 +02:00
|
|
|
realisticLooking = args.hasFlag('r');
|
|
|
|
}
|
|
|
|
|
2021-02-04 02:51:02 +01:00
|
|
|
public boolean disableWhileNavigating() {
|
|
|
|
return disableWhileNavigating;
|
|
|
|
}
|
|
|
|
|
2020-04-13 19:57:20 +02:00
|
|
|
/**
|
|
|
|
* Finds a new look-close target
|
|
|
|
*/
|
|
|
|
public void findNewTarget() {
|
2021-01-20 13:27:46 +01:00
|
|
|
double min = range * range;
|
2021-01-20 15:10:47 +01:00
|
|
|
Player old = lookingAt;
|
2020-04-13 19:57:20 +02:00
|
|
|
for (Entity entity : npc.getEntity().getNearbyEntities(range, range, range)) {
|
2020-04-15 21:04:42 +02:00
|
|
|
if (!(entity instanceof Player))
|
|
|
|
continue;
|
2020-04-13 19:57:20 +02:00
|
|
|
Player player = (Player) entity;
|
2021-01-20 10:34:24 +01:00
|
|
|
Location location = player.getLocation(CACHE_LOCATION);
|
|
|
|
if (location.getWorld() != NPC_LOCATION.getWorld())
|
|
|
|
continue;
|
|
|
|
double dist = location.distanceSquared(NPC_LOCATION);
|
2021-01-20 13:27:46 +01:00
|
|
|
if (dist > min || CitizensAPI.getNPCRegistry().getNPC(entity) != null || isInvisible(player))
|
2020-04-13 19:57:20 +02:00
|
|
|
continue;
|
2021-01-20 13:27:46 +01:00
|
|
|
min = dist;
|
|
|
|
lookingAt = player;
|
2020-04-13 19:57:20 +02:00
|
|
|
}
|
2021-01-20 15:10:47 +01:00
|
|
|
if (old != lookingAt) {
|
|
|
|
NPCLookCloseChangeTargetEvent event = new NPCLookCloseChangeTargetEvent(npc, old, lookingAt);
|
|
|
|
Bukkit.getPluginManager().callEvent(event);
|
2021-01-23 03:52:53 +01:00
|
|
|
if (lookingAt != event.getNewTarget() && event.getNewTarget() != null && !isValid(event.getNewTarget())) {
|
|
|
|
return;
|
|
|
|
}
|
2021-01-20 15:10:47 +01:00
|
|
|
lookingAt = event.getNewTarget();
|
|
|
|
}
|
2020-04-13 19:57:20 +02:00
|
|
|
}
|
|
|
|
|
2020-10-07 13:16:41 +02:00
|
|
|
public int getRandomLookDelay() {
|
|
|
|
return randomLookDelay;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float[] getRandomLookPitchRange() {
|
|
|
|
return randomPitchRange;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float[] getRandomLookYawRange() {
|
|
|
|
return randomYawRange;
|
|
|
|
}
|
|
|
|
|
|
|
|
public double getRange() {
|
|
|
|
return range;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Player getTarget() {
|
|
|
|
return lookingAt;
|
|
|
|
}
|
|
|
|
|
2021-01-20 10:34:24 +01:00
|
|
|
private boolean isInvisible(Player player) {
|
|
|
|
return player.getGameMode() == GameMode.SPECTATOR || player.hasPotionEffect(PotionEffectType.INVISIBILITY)
|
2021-01-25 14:30:07 +01:00
|
|
|
|| isPluginVanished(player) || !canSee(player);
|
2021-01-20 10:34:24 +01:00
|
|
|
}
|
|
|
|
|
2020-04-13 19:57:20 +02:00
|
|
|
private boolean isPluginVanished(Player player) {
|
|
|
|
for (MetadataValue meta : player.getMetadata("vanished")) {
|
|
|
|
if (meta.asBoolean()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-10-07 13:16:41 +02:00
|
|
|
public boolean isRandomLook() {
|
|
|
|
return enableRandomLook;
|
|
|
|
}
|
|
|
|
|
2021-01-23 03:52:53 +01:00
|
|
|
private boolean isValid(Player entity) {
|
|
|
|
return entity.isOnline() && entity.isValid() && entity.getWorld() == npc.getEntity().getWorld()
|
2021-01-25 14:30:07 +01:00
|
|
|
&& entity.getLocation(PLAYER_LOCATION).distanceSquared(NPC_LOCATION) < range * range
|
|
|
|
&& !isInvisible(entity);
|
2021-01-23 03:52:53 +01:00
|
|
|
}
|
|
|
|
|
2020-04-13 19:57:20 +02:00
|
|
|
@Override
|
|
|
|
public void load(DataKey key) {
|
|
|
|
range = key.getDouble("range");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables/disables the trait
|
|
|
|
*/
|
|
|
|
public void lookClose(boolean lookClose) {
|
|
|
|
enabled = lookClose;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDespawn() {
|
2021-01-20 15:10:47 +01:00
|
|
|
NPCLookCloseChangeTargetEvent event = new NPCLookCloseChangeTargetEvent(npc, lookingAt, null);
|
|
|
|
Bukkit.getPluginManager().callEvent(event);
|
2021-01-23 03:52:53 +01:00
|
|
|
if (event.getNewTarget() != null && isValid(event.getNewTarget())) {
|
|
|
|
lookingAt = event.getNewTarget();
|
|
|
|
} else {
|
|
|
|
lookingAt = null;
|
|
|
|
}
|
2020-04-13 19:57:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private void randomLook() {
|
|
|
|
Random rand = new Random();
|
2020-06-12 18:18:59 +02:00
|
|
|
float pitch = isEqual(randomPitchRange) ? randomPitchRange[0]
|
|
|
|
: rand.doubles(randomPitchRange[0], randomPitchRange[1]).iterator().next().floatValue();
|
|
|
|
float yaw = isEqual(randomYawRange) ? randomYawRange[0]
|
|
|
|
: rand.doubles(randomYawRange[0], randomYawRange[1]).iterator().next().floatValue();
|
2022-04-19 09:20:59 +02:00
|
|
|
Util.face(npc.getEntity(), yaw, pitch);
|
2020-04-13 19:57:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
2022-04-19 09:20:59 +02:00
|
|
|
if (!npc.isSpawned())
|
2020-04-13 19:57:20 +02:00
|
|
|
return;
|
2022-04-19 09:20:59 +02:00
|
|
|
|
|
|
|
if (enableRandomLook) {
|
|
|
|
if (!npc.getNavigator().isNavigating() && lookingAt == null && t <= 0) {
|
|
|
|
randomLook();
|
|
|
|
t = randomLookDelay;
|
|
|
|
} else {
|
|
|
|
t--;
|
|
|
|
}
|
2020-04-13 19:57:20 +02:00
|
|
|
}
|
2022-04-19 09:20:59 +02:00
|
|
|
if (!enabled)
|
2020-04-13 19:57:20 +02:00
|
|
|
return;
|
2022-04-19 09:20:59 +02:00
|
|
|
|
|
|
|
if (npc.getNavigator().isNavigating() && disableWhileNavigating())
|
|
|
|
return;
|
|
|
|
|
2020-04-13 19:57:20 +02:00
|
|
|
npc.getEntity().getLocation(NPC_LOCATION);
|
2021-01-20 15:10:47 +01:00
|
|
|
if (tryInvalidateTarget()) {
|
2020-04-13 19:57:20 +02:00
|
|
|
findNewTarget();
|
|
|
|
}
|
2022-04-19 09:20:59 +02:00
|
|
|
|
2020-04-13 19:57:20 +02:00
|
|
|
if (npc.getNavigator().isNavigating()) {
|
|
|
|
npc.getNavigator().setPaused(lookingAt != null);
|
|
|
|
}
|
2022-04-19 09:20:59 +02:00
|
|
|
|
2021-01-25 14:30:48 +01:00
|
|
|
if (lookingAt == null)
|
|
|
|
return;
|
|
|
|
Util.faceEntity(npc.getEntity(), lookingAt);
|
|
|
|
if (npc.getEntity().getType().name().equals("SHULKER")) {
|
2022-02-20 14:56:51 +01:00
|
|
|
boolean wasSilent = npc.getEntity().isSilent();
|
|
|
|
npc.getEntity().setSilent(true);
|
|
|
|
NMS.setPeekShulker(npc.getEntity(), 100 - 4 * (int) Math
|
2021-01-25 14:30:48 +01:00
|
|
|
.floor(npc.getStoredLocation().distanceSquared(lookingAt.getLocation(PLAYER_LOCATION))));
|
2022-02-20 14:56:51 +01:00
|
|
|
npc.getEntity().setSilent(wasSilent);
|
2020-04-13 19:57:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void save(DataKey key) {
|
|
|
|
key.setDouble("range", range);
|
|
|
|
}
|
|
|
|
|
2021-02-04 02:51:02 +01:00
|
|
|
public void setDisableWhileNavigating(boolean set) {
|
|
|
|
disableWhileNavigating = set;
|
|
|
|
}
|
|
|
|
|
2020-04-13 19:57:20 +02:00
|
|
|
/**
|
2020-04-15 21:04:42 +02:00
|
|
|
* Enables random looking - will look at a random {@link Location} every so often if enabled.
|
2020-04-13 19:57:20 +02:00
|
|
|
*/
|
|
|
|
public void setRandomLook(boolean enableRandomLook) {
|
|
|
|
this.enableRandomLook = enableRandomLook;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the delay between random looking in ticks
|
|
|
|
*/
|
|
|
|
public void setRandomLookDelay(int delay) {
|
|
|
|
this.randomLookDelay = delay;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setRandomLookPitchRange(float min, float max) {
|
|
|
|
this.randomPitchRange = new float[] { min, max };
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setRandomLookYawRange(float min, float max) {
|
|
|
|
this.randomYawRange = new float[] { min, max };
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the maximum range in blocks to look at other Entities
|
|
|
|
*/
|
2022-04-16 16:49:20 +02:00
|
|
|
public void setRange(double d) {
|
|
|
|
this.range = d;
|
2020-04-13 19:57:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-04-15 21:04:42 +02:00
|
|
|
* Enables/disables realistic looking (using line of sight checks). More computationally expensive.
|
2020-04-13 19:57:20 +02:00
|
|
|
*/
|
|
|
|
public void setRealisticLooking(boolean realistic) {
|
|
|
|
this.realisticLooking = realistic;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean toggle() {
|
|
|
|
enabled = !enabled;
|
|
|
|
return enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return "LookClose{" + enabled + "}";
|
|
|
|
}
|
|
|
|
|
2021-01-23 03:52:53 +01:00
|
|
|
private boolean tryInvalidateTarget() {
|
|
|
|
if (lookingAt == null)
|
|
|
|
return true;
|
|
|
|
if (!isValid(lookingAt)) {
|
|
|
|
NPCLookCloseChangeTargetEvent event = new NPCLookCloseChangeTargetEvent(npc, lookingAt, null);
|
|
|
|
Bukkit.getPluginManager().callEvent(event);
|
|
|
|
if (event.getNewTarget() != null && isValid(event.getNewTarget())) {
|
|
|
|
lookingAt = event.getNewTarget();
|
|
|
|
} else {
|
|
|
|
lookingAt = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return lookingAt == null;
|
|
|
|
}
|
|
|
|
|
2020-10-07 13:16:41 +02:00
|
|
|
public boolean useRealisticLooking() {
|
|
|
|
return realisticLooking;
|
|
|
|
}
|
|
|
|
|
2022-04-19 09:20:59 +02:00
|
|
|
private static boolean isEqual(float[] array) {
|
|
|
|
return Math.abs(array[0] - array[1]) < 0.001;
|
|
|
|
}
|
|
|
|
|
2020-04-13 19:57:20 +02:00
|
|
|
private static final Location CACHE_LOCATION = new Location(null, 0, 0, 0);
|
|
|
|
private static final Location NPC_LOCATION = new Location(null, 0, 0, 0);
|
|
|
|
private static final Location PLAYER_LOCATION = new Location(null, 0, 0, 0);
|
2012-02-03 10:20:48 +01:00
|
|
|
}
|