2017-12-30 08:36:36 +01:00
|
|
|
package net.citizensnpcs.npc;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.UUID;
|
|
|
|
|
|
|
|
import org.bukkit.Bukkit;
|
|
|
|
import org.bukkit.Location;
|
|
|
|
import org.bukkit.block.Block;
|
|
|
|
import org.bukkit.entity.Entity;
|
|
|
|
import org.bukkit.entity.EntityType;
|
|
|
|
import org.bukkit.entity.LivingEntity;
|
|
|
|
import org.bukkit.entity.Player;
|
2018-08-08 10:08:38 +02:00
|
|
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
2020-03-27 18:14:11 +01:00
|
|
|
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
|
2017-12-30 08:36:36 +01:00
|
|
|
import org.bukkit.metadata.FixedMetadataValue;
|
2022-03-16 10:13:05 +01:00
|
|
|
import org.bukkit.scheduler.BukkitRunnable;
|
2017-12-30 08:36:36 +01:00
|
|
|
import org.bukkit.scoreboard.Team;
|
|
|
|
|
|
|
|
import com.google.common.base.Preconditions;
|
|
|
|
import com.google.common.base.Throwables;
|
2019-05-29 10:45:18 +02:00
|
|
|
import com.google.common.collect.HashMultimap;
|
|
|
|
import com.google.common.collect.SetMultimap;
|
2017-12-30 08:36:36 +01:00
|
|
|
|
|
|
|
import net.citizensnpcs.NPCNeedsRespawnEvent;
|
|
|
|
import net.citizensnpcs.Settings.Setting;
|
|
|
|
import net.citizensnpcs.api.CitizensAPI;
|
|
|
|
import net.citizensnpcs.api.ai.Navigator;
|
2022-02-19 09:36:31 +01:00
|
|
|
import net.citizensnpcs.api.astar.pathfinder.SwimmingExaminer;
|
2017-12-30 08:36:36 +01:00
|
|
|
import net.citizensnpcs.api.event.DespawnReason;
|
|
|
|
import net.citizensnpcs.api.event.NPCDespawnEvent;
|
|
|
|
import net.citizensnpcs.api.event.NPCSpawnEvent;
|
2018-08-08 10:08:38 +02:00
|
|
|
import net.citizensnpcs.api.event.SpawnReason;
|
2017-12-30 08:36:36 +01:00
|
|
|
import net.citizensnpcs.api.npc.AbstractNPC;
|
|
|
|
import net.citizensnpcs.api.npc.BlockBreaker;
|
|
|
|
import net.citizensnpcs.api.npc.BlockBreaker.BlockBreakerConfiguration;
|
|
|
|
import net.citizensnpcs.api.npc.NPC;
|
|
|
|
import net.citizensnpcs.api.npc.NPCRegistry;
|
|
|
|
import net.citizensnpcs.api.trait.Trait;
|
|
|
|
import net.citizensnpcs.api.trait.trait.MobType;
|
|
|
|
import net.citizensnpcs.api.trait.trait.Spawned;
|
|
|
|
import net.citizensnpcs.api.util.DataKey;
|
|
|
|
import net.citizensnpcs.api.util.Messaging;
|
|
|
|
import net.citizensnpcs.npc.ai.CitizensNavigator;
|
|
|
|
import net.citizensnpcs.npc.skin.SkinnableEntity;
|
|
|
|
import net.citizensnpcs.trait.CurrentLocation;
|
2020-03-02 07:51:54 +01:00
|
|
|
import net.citizensnpcs.trait.Gravity;
|
2020-07-06 10:37:34 +02:00
|
|
|
import net.citizensnpcs.trait.HologramTrait;
|
2019-07-12 09:39:38 +02:00
|
|
|
import net.citizensnpcs.trait.ScoreboardTrait;
|
2021-09-20 14:59:54 +02:00
|
|
|
import net.citizensnpcs.trait.SneakTrait;
|
2019-05-29 10:45:18 +02:00
|
|
|
import net.citizensnpcs.util.ChunkCoord;
|
2017-12-30 08:36:36 +01:00
|
|
|
import net.citizensnpcs.util.Messages;
|
|
|
|
import net.citizensnpcs.util.NMS;
|
2022-02-14 17:57:22 +01:00
|
|
|
import net.citizensnpcs.util.PlayerAnimation;
|
2020-04-29 14:56:39 +02:00
|
|
|
import net.citizensnpcs.util.PlayerUpdateTask;
|
2017-12-30 08:36:36 +01:00
|
|
|
import net.citizensnpcs.util.Util;
|
|
|
|
|
|
|
|
public class CitizensNPC extends AbstractNPC {
|
2019-05-29 10:45:18 +02:00
|
|
|
private ChunkCoord cachedCoord;
|
2017-12-30 08:36:36 +01:00
|
|
|
private EntityController entityController;
|
|
|
|
private final CitizensNavigator navigator = new CitizensNavigator(this);
|
|
|
|
private int updateCounter = 0;
|
|
|
|
|
|
|
|
public CitizensNPC(UUID uuid, int id, String name, EntityController entityController, NPCRegistry registry) {
|
|
|
|
super(uuid, id, name, registry);
|
|
|
|
Preconditions.checkNotNull(entityController);
|
|
|
|
this.entityController = entityController;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean despawn(DespawnReason reason) {
|
|
|
|
if (!isSpawned() && reason != DespawnReason.DEATH) {
|
2022-03-16 10:13:05 +01:00
|
|
|
Messaging.debug("Tried to despawn", this, "while already despawned, DespawnReason." + reason);
|
2017-12-30 08:36:36 +01:00
|
|
|
if (reason == DespawnReason.RELOAD) {
|
|
|
|
unloadEvents();
|
|
|
|
}
|
2020-07-08 13:26:00 +02:00
|
|
|
return true;
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
NPCDespawnEvent event = new NPCDespawnEvent(this, reason);
|
|
|
|
if (reason == DespawnReason.CHUNK_UNLOAD) {
|
2021-05-06 19:46:59 +02:00
|
|
|
event.setCancelled(data().get(NPC.KEEP_CHUNK_LOADED_METADATA, Setting.KEEP_CHUNKS_LOADED.asBoolean()));
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
Bukkit.getPluginManager().callEvent(event);
|
2018-02-13 10:18:15 +01:00
|
|
|
if (event.isCancelled() && reason != DespawnReason.DEATH) {
|
2022-03-16 10:13:05 +01:00
|
|
|
Messaging.debug("Couldn't despawn", this, "due to despawn event cancellation. Will load chunk.",
|
2020-10-12 15:58:26 +02:00
|
|
|
getEntity().isValid(), ", DespawnReason." + reason);
|
2017-12-30 08:36:36 +01:00
|
|
|
return false;
|
|
|
|
}
|
2020-09-14 11:57:58 +02:00
|
|
|
boolean keepSelected = getOrAddTrait(Spawned.class).shouldSpawn();
|
2017-12-30 08:36:36 +01:00
|
|
|
if (!keepSelected) {
|
|
|
|
data().remove("selectors");
|
|
|
|
}
|
2020-04-29 14:56:39 +02:00
|
|
|
if (getEntity() instanceof Player) {
|
|
|
|
PlayerUpdateTask.deregisterPlayer(getEntity());
|
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
navigator.onDespawn();
|
|
|
|
if (reason == DespawnReason.RELOAD) {
|
|
|
|
unloadEvents();
|
|
|
|
}
|
|
|
|
for (Trait trait : new ArrayList<Trait>(traits.values())) {
|
|
|
|
trait.onDespawn();
|
|
|
|
}
|
2022-03-16 10:13:05 +01:00
|
|
|
Messaging.debug("Despawned", this, "DespawnReason." + reason);
|
2018-02-13 10:18:15 +01:00
|
|
|
if (reason == DespawnReason.DEATH) {
|
|
|
|
entityController.setEntity(null);
|
|
|
|
} else {
|
|
|
|
entityController.remove();
|
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-05-29 10:45:18 +02:00
|
|
|
@Override
|
|
|
|
public void destroy() {
|
|
|
|
super.destroy();
|
|
|
|
resetCachedCoord();
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
@Override
|
|
|
|
public void faceLocation(Location location) {
|
|
|
|
if (!isSpawned())
|
|
|
|
return;
|
|
|
|
Util.faceLocation(getEntity(), location);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public BlockBreaker getBlockBreaker(Block targetBlock, BlockBreakerConfiguration config) {
|
|
|
|
return NMS.getBlockBreaker(getEntity(), targetBlock, config);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Entity getEntity() {
|
|
|
|
return entityController == null ? null : entityController.getBukkitEntity();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Navigator getNavigator() {
|
|
|
|
return navigator;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Location getStoredLocation() {
|
2020-09-14 11:57:58 +02:00
|
|
|
return isSpawned() ? getEntity().getLocation() : getOrAddTrait(CurrentLocation.class).getLocation();
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isFlyable() {
|
|
|
|
updateFlyableState();
|
|
|
|
return super.isFlyable();
|
|
|
|
}
|
|
|
|
|
2019-10-03 07:14:20 +02:00
|
|
|
@Override
|
|
|
|
public boolean isSpawned() {
|
|
|
|
return getEntity() != null && NMS.isValid(getEntity());
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
@Override
|
|
|
|
public void load(final DataKey root) {
|
|
|
|
super.load(root);
|
|
|
|
// Spawn the NPC
|
2020-09-14 11:57:58 +02:00
|
|
|
CurrentLocation spawnLocation = getOrAddTrait(CurrentLocation.class);
|
|
|
|
if (getOrAddTrait(Spawned.class).shouldSpawn() && spawnLocation.getLocation() != null) {
|
2020-04-30 17:11:45 +02:00
|
|
|
if (spawnLocation.getLocation() != null) {
|
|
|
|
spawn(spawnLocation.getLocation(), SpawnReason.RESPAWN);
|
|
|
|
} else {
|
2022-03-16 10:13:05 +01:00
|
|
|
Messaging.debug("Tried to spawn", this, "on load but world was null");
|
2020-04-30 17:11:45 +02:00
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
navigator.load(root.getRelative("navigator"));
|
|
|
|
}
|
|
|
|
|
2020-07-18 13:08:10 +02:00
|
|
|
@Override
|
|
|
|
public boolean requiresNameHologram() {
|
2020-07-21 19:30:07 +02:00
|
|
|
return super.requiresNameHologram()
|
|
|
|
|| (getEntityType() != EntityType.ARMOR_STAND && Setting.ALWAYS_USE_NAME_HOLOGRAM.asBoolean());
|
2020-07-18 13:08:10 +02:00
|
|
|
}
|
|
|
|
|
2019-05-29 10:45:18 +02:00
|
|
|
private void resetCachedCoord() {
|
2020-05-01 11:49:03 +02:00
|
|
|
if (cachedCoord == null)
|
|
|
|
return;
|
|
|
|
CHUNK_LOADERS.remove(NPC_METADATA_MARKER, CHUNK_LOADERS);
|
|
|
|
CHUNK_LOADERS.remove(cachedCoord, this);
|
|
|
|
if (CHUNK_LOADERS.get(cachedCoord).size() == 0) {
|
|
|
|
cachedCoord.setForceLoaded(false);
|
2019-05-29 10:45:18 +02:00
|
|
|
}
|
2020-05-01 11:49:03 +02:00
|
|
|
cachedCoord = null;
|
2019-05-29 10:45:18 +02:00
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
@Override
|
|
|
|
public void save(DataKey root) {
|
|
|
|
super.save(root);
|
|
|
|
if (!data().get(NPC.SHOULD_SAVE_METADATA, true))
|
|
|
|
return;
|
|
|
|
navigator.save(root.getRelative("navigator"));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setBukkitEntityType(EntityType type) {
|
|
|
|
EntityController controller = EntityControllers.createForType(type);
|
|
|
|
if (controller == null)
|
|
|
|
throw new IllegalArgumentException("Unsupported entity type " + type);
|
|
|
|
setEntityController(controller);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setEntityController(EntityController newController) {
|
|
|
|
Preconditions.checkNotNull(newController);
|
|
|
|
boolean wasSpawned = isSpawned();
|
|
|
|
Location prev = null;
|
|
|
|
if (wasSpawned) {
|
2020-03-27 18:14:11 +01:00
|
|
|
prev = getEntity().getLocation(CACHE_LOCATION);
|
2017-12-30 08:36:36 +01:00
|
|
|
despawn(DespawnReason.PENDING_RESPAWN);
|
|
|
|
}
|
|
|
|
entityController = newController;
|
|
|
|
if (wasSpawned) {
|
2018-08-08 10:08:38 +02:00
|
|
|
spawn(prev, SpawnReason.RESPAWN);
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setFlyable(boolean flyable) {
|
|
|
|
super.setFlyable(flyable);
|
|
|
|
updateFlyableState();
|
|
|
|
}
|
|
|
|
|
2020-12-27 13:54:21 +01:00
|
|
|
@Override
|
|
|
|
public void setName(String name) {
|
|
|
|
super.setName(name);
|
|
|
|
|
|
|
|
if (requiresNameHologram() && !hasTrait(HologramTrait.class)) {
|
|
|
|
addTrait(HologramTrait.class);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
@Override
|
|
|
|
public boolean spawn(Location at) {
|
2018-08-08 10:08:38 +02:00
|
|
|
return spawn(at, SpawnReason.PLUGIN);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean spawn(Location at, SpawnReason reason) {
|
2017-12-30 08:36:36 +01:00
|
|
|
Preconditions.checkNotNull(at, "location cannot be null");
|
2018-08-08 10:08:38 +02:00
|
|
|
Preconditions.checkNotNull(reason, "reason cannot be null");
|
2021-09-26 19:36:23 +02:00
|
|
|
if (getEntity() != null) {
|
2022-03-16 10:13:05 +01:00
|
|
|
Messaging.debug("Tried to spawn", this, "while already spawned. SpawnReason." + reason);
|
2017-12-30 08:36:36 +01:00
|
|
|
return false;
|
|
|
|
}
|
2018-07-24 13:52:35 +02:00
|
|
|
if (at.getWorld() == null) {
|
2022-03-16 10:13:05 +01:00
|
|
|
Messaging.debug("Tried to spawn", this, "but the world was null. SpawnReason." + reason);
|
2018-07-24 13:52:35 +02:00
|
|
|
return false;
|
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
at = at.clone();
|
2019-05-29 10:45:18 +02:00
|
|
|
|
2020-03-27 18:20:20 +01:00
|
|
|
if (reason == SpawnReason.CHUNK_LOAD || reason == SpawnReason.COMMAND) {
|
2019-06-12 15:06:06 +02:00
|
|
|
at.getChunk().load();
|
|
|
|
}
|
|
|
|
|
2020-09-14 11:57:58 +02:00
|
|
|
getOrAddTrait(CurrentLocation.class).setLocation(at);
|
2020-11-17 03:15:52 +01:00
|
|
|
entityController.spawn(at.clone(), this);
|
2020-11-27 16:31:21 +01:00
|
|
|
getEntity().setMetadata(NPC_METADATA_MARKER, new FixedMetadataValue(CitizensAPI.getPlugin(), true));
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2021-09-23 08:12:44 +02:00
|
|
|
Collection<Trait> onPreSpawn = traits.values();
|
|
|
|
for (Trait trait : onPreSpawn.toArray(new Trait[onPreSpawn.size()])) {
|
|
|
|
try {
|
|
|
|
trait.onPreSpawn();
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Messaging.severeTr(Messages.TRAIT_ONSPAWN_FAILED, trait.getName(), getId());
|
|
|
|
ex.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-28 11:36:24 +02:00
|
|
|
boolean loaded = Util.isLoaded(at);
|
|
|
|
boolean couldSpawn = !loaded ? false : NMS.addEntityToWorld(getEntity(), CreatureSpawnEvent.SpawnReason.CUSTOM);
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2020-11-17 03:15:52 +01:00
|
|
|
if (!couldSpawn) {
|
2020-06-28 11:36:24 +02:00
|
|
|
if (Messaging.isDebugging()) {
|
2022-03-16 10:13:05 +01:00
|
|
|
Messaging.debug("Retrying spawn of", this, "later, SpawnReason." + reason + ". Was loaded", loaded,
|
|
|
|
"is loaded", Util.isLoaded(at));
|
2020-06-28 11:36:24 +02:00
|
|
|
}
|
|
|
|
// we need to wait before trying to spawn
|
2017-12-30 08:36:36 +01:00
|
|
|
entityController.remove();
|
|
|
|
Bukkit.getPluginManager().callEvent(new NPCNeedsRespawnEvent(this, at));
|
|
|
|
return false;
|
|
|
|
}
|
2020-11-17 03:15:52 +01:00
|
|
|
// send skin packets, if applicable, before other NMS packets are sent
|
|
|
|
SkinnableEntity skinnable = getEntity() instanceof SkinnableEntity ? ((SkinnableEntity) getEntity()) : null;
|
|
|
|
if (skinnable != null) {
|
|
|
|
skinnable.getSkinTracker().onSpawnNPC();
|
|
|
|
}
|
2022-03-16 10:13:05 +01:00
|
|
|
|
2018-11-30 15:34:47 +01:00
|
|
|
getEntity().teleport(at);
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
NMS.setHeadYaw(getEntity(), at.getYaw());
|
2020-04-30 13:11:56 +02:00
|
|
|
NMS.setBodyYaw(getEntity(), at.getYaw());
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2022-03-16 10:13:05 +01:00
|
|
|
final Location to = at;
|
|
|
|
BukkitRunnable postSpawn = new BukkitRunnable() {
|
|
|
|
private int timer;
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2022-03-16 10:13:05 +01:00
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
if (timer++ > 10) {
|
|
|
|
cancel();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (getEntity() == null || !getEntity().isValid())
|
|
|
|
return;
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2022-03-16 10:13:05 +01:00
|
|
|
// Set the spawned state
|
|
|
|
getOrAddTrait(CurrentLocation.class).setLocation(to);
|
|
|
|
getOrAddTrait(Spawned.class).setSpawned(true);
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2022-03-16 10:13:05 +01:00
|
|
|
NPCSpawnEvent spawnEvent = new NPCSpawnEvent(CitizensNPC.this, to, reason);
|
|
|
|
Bukkit.getPluginManager().callEvent(spawnEvent);
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2022-03-16 10:13:05 +01:00
|
|
|
if (spawnEvent.isCancelled()) {
|
|
|
|
entityController.remove();
|
|
|
|
Messaging.debug("Couldn't spawn", this, "SpawnReason." + reason, "due to event cancellation.");
|
|
|
|
cancel();
|
|
|
|
return;
|
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2022-03-16 10:13:05 +01:00
|
|
|
navigator.onSpawn();
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2022-03-16 10:13:05 +01:00
|
|
|
Collection<Trait> onSpawn = traits.values();
|
|
|
|
for (Trait trait : onSpawn.toArray(new Trait[onSpawn.size()])) {
|
|
|
|
try {
|
|
|
|
trait.onSpawn();
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Messaging.severeTr(Messages.TRAIT_ONSPAWN_FAILED, trait.getName(), getId());
|
|
|
|
ex.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2022-03-16 10:13:05 +01:00
|
|
|
if (getEntity() instanceof LivingEntity) {
|
|
|
|
LivingEntity entity = (LivingEntity) getEntity();
|
|
|
|
entity.setRemoveWhenFarAway(false);
|
2020-07-09 17:37:45 +02:00
|
|
|
|
2022-03-16 10:13:05 +01:00
|
|
|
if (NMS.getStepHeight(entity) < 1) {
|
|
|
|
NMS.setStepHeight(entity, 1);
|
|
|
|
}
|
|
|
|
if (getEntity() instanceof Player) {
|
|
|
|
NMS.replaceTrackerEntry((Player) getEntity());
|
|
|
|
PlayerUpdateTask.registerPlayer(getEntity());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (requiresNameHologram() && !hasTrait(HologramTrait.class)) {
|
|
|
|
addTrait(HologramTrait.class);
|
|
|
|
}
|
|
|
|
|
|
|
|
updateFlyableState();
|
|
|
|
updateCustomNameVisibility();
|
|
|
|
updateCustomName();
|
|
|
|
|
|
|
|
Messaging.debug("Spawned", this, "SpawnReason." + reason);
|
|
|
|
cancel();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if (isSpawned()) {
|
|
|
|
postSpawn.runTask(CitizensAPI.getPlugin());
|
|
|
|
} else {
|
|
|
|
postSpawn.runTaskTimer(CitizensAPI.getPlugin(), 0, 1);
|
|
|
|
}
|
2020-03-02 07:51:54 +01:00
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-03-27 18:14:11 +01:00
|
|
|
@Override
|
|
|
|
public void teleport(Location location, TeleportCause reason) {
|
|
|
|
super.teleport(location, reason);
|
2020-07-15 17:50:48 +02:00
|
|
|
if (!isSpawned())
|
|
|
|
return;
|
2020-07-10 18:44:40 +02:00
|
|
|
Location npcLoc = getEntity().getLocation(CACHE_LOCATION);
|
|
|
|
if (isSpawned() && npcLoc.getWorld() == location.getWorld() && npcLoc.distanceSquared(location) < 1) {
|
2020-05-12 16:33:54 +02:00
|
|
|
NMS.setHeadYaw(getEntity(), location.getYaw());
|
|
|
|
}
|
2020-03-27 18:14:11 +01:00
|
|
|
}
|
|
|
|
|
2022-03-13 21:25:42 +01:00
|
|
|
@Override
|
|
|
|
public String toString() {
|
2022-03-15 03:11:36 +01:00
|
|
|
EntityType mobType = hasTrait(MobType.class) ? getTraitNullable(MobType.class).getType() : null;
|
|
|
|
return getId() + "{" + getName() + ", " + mobType + "}";
|
2022-03-13 21:25:42 +01:00
|
|
|
}
|
2022-03-16 10:13:05 +01:00
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
@Override
|
|
|
|
public void update() {
|
|
|
|
try {
|
|
|
|
super.update();
|
2019-05-07 14:02:14 +02:00
|
|
|
if (!isSpawned()) {
|
2019-05-29 10:45:18 +02:00
|
|
|
resetCachedCoord();
|
2017-12-30 08:36:36 +01:00
|
|
|
return;
|
2019-05-07 14:02:14 +02:00
|
|
|
}
|
2022-02-19 09:36:31 +01:00
|
|
|
if (navigator.isNavigating()) {
|
2022-02-19 12:00:09 +01:00
|
|
|
if (data().get(NPC.Metadata.SWIMMING, true)) {
|
2022-02-19 09:36:31 +01:00
|
|
|
Location currentDest = navigator.getPathStrategy().getCurrentDestination();
|
|
|
|
if (currentDest == null || currentDest.getY() > getStoredLocation().getY()) {
|
2022-02-19 11:26:37 +01:00
|
|
|
NMS.trySwim(getEntity(), SwimmingExaminer.isWaterMob(getEntity()) ? 0.02F : 0.04F);
|
2022-02-19 09:36:31 +01:00
|
|
|
}
|
|
|
|
}
|
2022-02-19 12:00:09 +01:00
|
|
|
} else if (data().<Boolean> get(NPC.Metadata.SWIMMING, !SwimmingExaminer.isWaterMob(getEntity()))) {
|
2017-12-30 08:36:36 +01:00
|
|
|
NMS.trySwim(getEntity());
|
|
|
|
}
|
|
|
|
navigator.run();
|
2019-03-07 17:12:29 +01:00
|
|
|
if (SUPPORT_GLOWING) {
|
|
|
|
try {
|
2022-01-01 18:22:35 +01:00
|
|
|
getEntity().setGlowing(data().get(NPC.Metadata.GLOWING, false));
|
2019-03-07 17:12:29 +01:00
|
|
|
} catch (NoSuchMethodError e) {
|
|
|
|
SUPPORT_GLOWING = false;
|
|
|
|
}
|
2018-04-07 10:02:35 +02:00
|
|
|
}
|
2019-04-26 12:20:34 +02:00
|
|
|
|
2020-05-01 11:49:03 +02:00
|
|
|
boolean isLiving = getEntity() instanceof LivingEntity;
|
2022-02-14 17:57:22 +01:00
|
|
|
int packetUpdateDelay = data().get(NPC.Metadata.PACKET_UPDATE_DELAY, Setting.PACKET_UPDATE_DELAY.asInt());
|
|
|
|
if (updateCounter++ > packetUpdateDelay) {
|
2019-05-29 10:45:18 +02:00
|
|
|
if (Setting.KEEP_CHUNKS_LOADED.asBoolean()) {
|
|
|
|
ChunkCoord currentCoord = new ChunkCoord(getStoredLocation());
|
|
|
|
if (!currentCoord.equals(cachedCoord)) {
|
|
|
|
resetCachedCoord();
|
|
|
|
currentCoord.setForceLoaded(true);
|
|
|
|
CHUNK_LOADERS.put(currentCoord, this);
|
|
|
|
cachedCoord = currentCoord;
|
|
|
|
}
|
|
|
|
}
|
2020-05-01 11:49:03 +02:00
|
|
|
if (isLiving) {
|
|
|
|
updateCustomName();
|
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
updateCounter = 0;
|
|
|
|
}
|
|
|
|
|
2021-02-22 12:27:06 +01:00
|
|
|
updateCustomNameVisibility();
|
2020-03-01 08:29:47 +01:00
|
|
|
|
2020-07-06 10:37:34 +02:00
|
|
|
if (isLiving) {
|
2022-01-01 18:22:35 +01:00
|
|
|
NMS.setKnockbackResistance((LivingEntity) getEntity(), isProtected() ? 1D : 0D);
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
2022-02-16 10:36:03 +01:00
|
|
|
|
2022-02-14 17:57:22 +01:00
|
|
|
if (isLiving && getEntity() instanceof Player) {
|
|
|
|
updateUsingItemState((Player) getEntity());
|
2022-03-04 13:23:59 +01:00
|
|
|
if (data().has(NPC.Metadata.SNEAKING) && !hasTrait(SneakTrait.class)) {
|
|
|
|
addTrait(SneakTrait.class);
|
|
|
|
}
|
2022-02-14 17:57:22 +01:00
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
|
2019-03-07 17:12:29 +01:00
|
|
|
if (SUPPORT_SILENT && data().has(NPC.SILENT_METADATA)) {
|
2018-04-07 10:02:35 +02:00
|
|
|
try {
|
2022-01-01 18:22:35 +01:00
|
|
|
getEntity().setSilent(Boolean.parseBoolean(data().get(NPC.Metadata.SILENT).toString()));
|
2018-04-07 10:02:35 +02:00
|
|
|
} catch (NoSuchMethodError e) {
|
2019-03-07 17:12:29 +01:00
|
|
|
SUPPORT_SILENT = false;
|
2018-04-07 10:02:35 +02:00
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
} catch (Exception ex) {
|
|
|
|
Throwable error = Throwables.getRootCause(ex);
|
|
|
|
Messaging.logTr(Messages.EXCEPTION_UPDATING_NPC, getId(), error.getMessage());
|
|
|
|
error.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-26 12:20:34 +02:00
|
|
|
private void updateCustomName() {
|
|
|
|
boolean nameVisibility = false;
|
|
|
|
if (!getEntity().isCustomNameVisible()
|
2022-02-19 18:05:38 +01:00
|
|
|
&& !data().<Object> get(NPC.Metadata.NAMEPLATE_VISIBLE, true).toString().equals("hover")) {
|
2020-07-10 06:19:29 +02:00
|
|
|
} else if (!requiresNameHologram()) {
|
2019-04-26 12:20:34 +02:00
|
|
|
nameVisibility = true;
|
|
|
|
getEntity().setCustomName(getFullName());
|
|
|
|
}
|
|
|
|
|
2022-01-01 18:22:35 +01:00
|
|
|
String teamName = data().get(NPC.Metadata.SCOREBOARD_FAKE_TEAM_NAME, "");
|
2019-04-26 12:20:34 +02:00
|
|
|
Team team = null;
|
2022-02-14 18:35:07 +01:00
|
|
|
if (teamName.length() == 0 || (team = Util.getDummyScoreboard().getTeam(teamName)) == null)
|
2019-04-26 12:20:34 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (!Setting.USE_SCOREBOARD_TEAMS.asBoolean()) {
|
|
|
|
team.unregister();
|
|
|
|
data().remove(NPC.SCOREBOARD_FAKE_TEAM_NAME_METADATA);
|
|
|
|
return;
|
|
|
|
}
|
2020-09-14 11:57:58 +02:00
|
|
|
getOrAddTrait(ScoreboardTrait.class).apply(team, nameVisibility);
|
2019-04-26 12:20:34 +02:00
|
|
|
}
|
|
|
|
|
2021-04-05 10:38:10 +02:00
|
|
|
private void updateCustomNameVisibility() {
|
2022-01-01 18:22:35 +01:00
|
|
|
String nameplateVisible = data().<Object> get(NPC.Metadata.NAMEPLATE_VISIBLE, true).toString();
|
2021-04-05 10:38:10 +02:00
|
|
|
if (requiresNameHologram()) {
|
|
|
|
nameplateVisible = "false";
|
|
|
|
}
|
|
|
|
getEntity().setCustomNameVisible(Boolean.parseBoolean(nameplateVisible));
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
private void updateFlyableState() {
|
2020-09-14 11:57:58 +02:00
|
|
|
EntityType type = isSpawned() ? getEntity().getType() : getOrAddTrait(MobType.class).getType();
|
2017-12-30 08:36:36 +01:00
|
|
|
if (type == null)
|
|
|
|
return;
|
2020-03-21 08:19:46 +01:00
|
|
|
if (!Util.isAlwaysFlyable(type))
|
|
|
|
return;
|
|
|
|
if (!data().has(NPC.FLYABLE_METADATA)) {
|
|
|
|
data().setPersistent(NPC.FLYABLE_METADATA, true);
|
|
|
|
}
|
|
|
|
if (!hasTrait(Gravity.class)) {
|
2020-09-14 11:57:58 +02:00
|
|
|
getOrAddTrait(Gravity.class).setEnabled(true);
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-14 17:57:22 +01:00
|
|
|
private void updateUsingItemState(Player player) {
|
|
|
|
boolean useItem = data().get(NPC.Metadata.USING_HELD_ITEM, false),
|
|
|
|
offhand = data().get(NPC.Metadata.USING_OFFHAND_ITEM, false);
|
2022-02-19 18:05:38 +01:00
|
|
|
if (!SUPPORT_USE_ITEM)
|
|
|
|
return;
|
|
|
|
try {
|
|
|
|
if (useItem) {
|
|
|
|
NMS.playAnimation(PlayerAnimation.STOP_USE_ITEM, player, 64);
|
|
|
|
NMS.playAnimation(PlayerAnimation.START_USE_MAINHAND_ITEM, player, 64);
|
|
|
|
} else if (offhand) {
|
|
|
|
NMS.playAnimation(PlayerAnimation.STOP_USE_ITEM, player, 64);
|
|
|
|
NMS.playAnimation(PlayerAnimation.START_USE_OFFHAND_ITEM, player, 64);
|
|
|
|
}
|
|
|
|
} catch (UnsupportedOperationException ex) {
|
|
|
|
SUPPORT_USE_ITEM = false;
|
2022-02-14 17:57:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-27 18:51:53 +01:00
|
|
|
private static final Location CACHE_LOCATION = new Location(null, 0, 0, 0);
|
2019-05-29 10:45:18 +02:00
|
|
|
private static final SetMultimap<ChunkCoord, NPC> CHUNK_LOADERS = HashMultimap.create();
|
2017-12-30 08:36:36 +01:00
|
|
|
private static final String NPC_METADATA_MARKER = "NPC";
|
2019-03-07 17:12:29 +01:00
|
|
|
private static boolean SUPPORT_GLOWING = true;
|
2019-04-26 12:20:34 +02:00
|
|
|
private static boolean SUPPORT_SILENT = true;
|
2022-02-19 18:05:38 +01:00
|
|
|
private static boolean SUPPORT_USE_ITEM = true;
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|