Citizens2/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java

630 lines
24 KiB
Java
Raw Normal View History

package net.citizensnpcs.npc;
import java.util.ArrayList;
import java.util.Collection;
2023-02-21 16:06:11 +01:00
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
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;
2020-03-27 18:14:11 +01:00
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.scheduler.BukkitRunnable;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.SetMultimap;
import net.citizensnpcs.NPCNeedsRespawnEvent;
import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.ai.Navigator;
import net.citizensnpcs.api.astar.pathfinder.MinecraftBlockExaminer;
import net.citizensnpcs.api.astar.pathfinder.SwimmingExaminer;
import net.citizensnpcs.api.event.DespawnReason;
import net.citizensnpcs.api.event.NPCDespawnEvent;
import net.citizensnpcs.api.event.NPCSpawnEvent;
import net.citizensnpcs.api.event.NPCTeleportEvent;
2018-08-08 10:08:38 +02:00
import net.citizensnpcs.api.event.SpawnReason;
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;
import net.citizensnpcs.trait.Gravity;
import net.citizensnpcs.trait.HologramTrait;
2023-01-03 13:55:27 +01:00
import net.citizensnpcs.trait.PacketNPC;
import net.citizensnpcs.trait.ScoreboardTrait;
import net.citizensnpcs.trait.SitTrait;
2021-09-20 14:59:54 +02:00
import net.citizensnpcs.trait.SneakTrait;
import net.citizensnpcs.util.ChunkCoord;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.NMS;
2022-02-14 17:57:22 +01:00
import net.citizensnpcs.util.PlayerAnimation;
import net.citizensnpcs.util.PlayerUpdateTask;
import net.citizensnpcs.util.Util;
public class CitizensNPC extends AbstractNPC {
private ChunkCoord cachedCoord;
private EntityController entityController;
private final CitizensNavigator navigator = new CitizensNavigator(this);
private int updateCounter = 0;
2023-01-03 13:55:27 +01:00
public CitizensNPC(UUID uuid, int id, String name, EntityController controller, NPCRegistry registry) {
super(uuid, id, name, registry);
2023-01-03 13:55:27 +01:00
setEntityController(controller);
}
@Override
public boolean despawn(DespawnReason reason) {
if (getEntity() == null && reason != DespawnReason.DEATH) {
Messaging.debug("Tried to despawn", this, "while already despawned, DespawnReason." + reason);
if (reason == DespawnReason.RELOAD) {
unloadEvents();
}
return true;
}
NPCDespawnEvent event = new NPCDespawnEvent(this, reason);
if (reason == DespawnReason.CHUNK_UNLOAD) {
event.setCancelled(data().get(NPC.Metadata.KEEP_CHUNK_LOADED, Setting.KEEP_CHUNKS_LOADED.asBoolean()));
}
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled() && reason != DespawnReason.DEATH) {
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);
return false;
}
2020-09-14 11:57:58 +02:00
boolean keepSelected = getOrAddTrait(Spawned.class).shouldSpawn();
if (!keepSelected) {
data().remove("selectors");
}
if (getEntity() instanceof Player) {
PlayerUpdateTask.deregisterPlayer(getEntity());
}
navigator.onDespawn();
if (reason == DespawnReason.RELOAD) {
unloadEvents();
}
for (Trait trait : new ArrayList<Trait>(traits.values())) {
trait.onDespawn();
}
Messaging.debug("Despawned", this, "DespawnReason." + reason);
if (getEntity() instanceof SkinnableEntity) {
((SkinnableEntity) getEntity()).getSkinTracker().onRemoveNPC();
}
if (reason == DespawnReason.DEATH) {
2023-01-03 13:55:27 +01:00
entityController.die();
} else {
entityController.remove();
}
return true;
}
@Override
public void destroy() {
super.destroy();
resetCachedCoord();
}
@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();
}
2023-01-03 13:55:27 +01:00
public EntityController getEntityController() {
return entityController;
}
@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();
}
@Override
public boolean isFlyable() {
updateFlyableState();
return super.isFlyable();
}
@Override
public boolean isSpawned() {
2023-01-03 13:55:27 +01:00
return getEntity() != null && (hasTrait(PacketNPC.class) || NMS.isValid(getEntity()));
}
@Override
public boolean isUpdating(NPCUpdate update) {
return update == NPCUpdate.PACKET
2023-03-12 19:41:43 +01:00
? updateCounter > data().get(NPC.Metadata.PACKET_UPDATE_DELAY, Setting.PACKET_UPDATE_DELAY.asTicks())
: false;
}
@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 {
Messaging.debug("Tried to spawn", this, "on load but world was null");
2020-04-30 17:11:45 +02:00
}
}
navigator.load(root.getRelative("navigator"));
}
2020-07-18 13:08:10 +02:00
@Override
public boolean requiresNameHologram() {
return super.requiresNameHologram() || (getEntityType() != EntityType.ARMOR_STAND
&& !getEntityType().name().equals("TEXT_DISPLAY") && Setting.ALWAYS_USE_NAME_HOLOGRAM.asBoolean());
2020-07-18 13:08:10 +02:00
}
private void resetCachedCoord() {
if (cachedCoord == null)
return;
2023-02-21 16:06:11 +01:00
Set<NPC> npcs = CHUNK_LOADERS.get(cachedCoord);
npcs.remove(this);
if (npcs.size() == 0) {
cachedCoord.setForceLoaded(false);
}
cachedCoord = null;
}
@Override
public void save(DataKey root) {
super.save(root);
if (!data().get(NPC.Metadata.SHOULD_SAVE, true))
return;
navigator.save(root.getRelative("navigator"));
}
@Override
public void scheduleUpdate(NPCUpdate update) {
if (update == NPCUpdate.PACKET) {
updateCounter = data().get(NPC.Metadata.PACKET_UPDATE_DELAY, Setting.PACKET_UPDATE_DELAY.asTicks()) + 1;
}
}
@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);
2023-01-03 13:55:27 +01:00
boolean wasSpawned = entityController == null ? false : isSpawned();
Location prev = null;
if (wasSpawned) {
2020-03-27 18:14:11 +01:00
prev = getEntity().getLocation(CACHE_LOCATION);
despawn(DespawnReason.PENDING_RESPAWN);
}
PacketNPC packet = getTraitNullable(PacketNPC.class);
if (packet != null) {
newController = packet.wrap(newController);
}
entityController = newController;
if (wasSpawned) {
2018-08-08 10:08:38 +02:00
spawn(prev, SpawnReason.RESPAWN);
}
}
@Override
public void setFlyable(boolean flyable) {
super.setFlyable(flyable);
updateFlyableState();
}
2022-11-06 16:19:08 +01:00
@Override
public void setMoveDestination(Location destination) {
if (!isSpawned())
return;
if (destination == null) {
NMS.cancelMoveDestination(getEntity());
} else {
NMS.setDestination(getEntity(), destination.getX(), destination.getY(), destination.getZ(), 1);
}
}
@Override
public void setName(String name) {
super.setName(name);
if (requiresNameHologram() && !hasTrait(HologramTrait.class)) {
addTrait(HologramTrait.class);
}
}
@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) {
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) {
Messaging.debug("Tried to spawn", this, "while already spawned. SpawnReason." + reason);
return false;
}
2018-07-24 13:52:35 +02:00
if (at.getWorld() == null) {
Messaging.debug("Tried to spawn", this, "but the world was null. SpawnReason." + reason);
2018-07-24 13:52:35 +02:00
return false;
}
at = at.clone();
if (reason == SpawnReason.CHUNK_LOAD || reason == SpawnReason.COMMAND) {
at.getChunk().load();
}
2020-09-14 11:57:58 +02:00
getOrAddTrait(CurrentLocation.class).setLocation(at);
2023-01-03 13:55:27 +01:00
entityController.create(at.clone(), this);
2023-02-21 16:06:11 +01:00
getEntity().setMetadata("NPC", new FixedMetadataValue(CitizensAPI.getPlugin(), true));
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();
}
}
2023-01-03 13:55:27 +01:00
boolean loaded = Messaging.isDebugging() ? false : Util.isLoaded(at);
boolean couldSpawn = entityController.spawn(at);
if (!couldSpawn) {
2020-06-28 11:36:24 +02:00
if (Messaging.isDebugging()) {
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
entityController.remove();
Bukkit.getPluginManager().callEvent(new NPCNeedsRespawnEvent(this, at));
return false;
}
// 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();
}
NMS.setLocationDirectly(getEntity(), at);
NMS.setHeadYaw(getEntity(), at.getYaw());
2020-04-30 13:11:56 +02:00
NMS.setBodyYaw(getEntity(), at.getYaw());
final Location to = at;
Consumer<Runnable> postSpawn = new Consumer<Runnable>() {
private int timer;
@Override
public void accept(Runnable cancel) {
if (getEntity() == null || (!hasTrait(PacketNPC.class) && !getEntity().isValid())) {
if (timer++ > Setting.ENTITY_SPAWN_WAIT_DURATION.asTicks()) {
Messaging.debug("Couldn't spawn ", CitizensNPC.this, "waited", timer,
"ticks but entity not added to world");
entityController.remove();
cancel.run();
Bukkit.getPluginManager().callEvent(new NPCNeedsRespawnEvent(CitizensNPC.this, to));
}
2022-06-11 06:41:24 +02:00
return;
}
// Set the spawned state
getOrAddTrait(CurrentLocation.class).setLocation(to);
getOrAddTrait(Spawned.class).setSpawned(true);
NPCSpawnEvent spawnEvent = new NPCSpawnEvent(CitizensNPC.this, to, reason);
Bukkit.getPluginManager().callEvent(spawnEvent);
if (spawnEvent.isCancelled()) {
Messaging.debug("Couldn't spawn", CitizensNPC.this, "SpawnReason." + reason,
"due to event cancellation.");
2022-06-11 06:41:24 +02:00
entityController.remove();
cancel.run();
return;
}
navigator.onSpawn();
for (Trait trait : Iterables.toArray(traits.values(), Trait.class)) {
try {
trait.onSpawn();
} catch (Throwable ex) {
Messaging.severeTr(Messages.TRAIT_ONSPAWN_FAILED, trait.getName(), getId());
ex.printStackTrace();
}
}
EntityType type = getEntity().getType();
NMS.replaceTracker(getEntity());
if (type.isAlive()) {
LivingEntity entity = (LivingEntity) getEntity();
entity.setRemoveWhenFarAway(false);
2020-07-09 17:37:45 +02:00
if (NMS.getStepHeight(entity) < 1) {
NMS.setStepHeight(entity, 1);
}
if (type == EntityType.PLAYER) {
PlayerUpdateTask.registerPlayer(getEntity());
2023-01-16 16:54:21 +01:00
} else if (data().has(NPC.Metadata.AGGRESSIVE)) {
NMS.setAggressive(entity, data().<Boolean> get(NPC.Metadata.AGGRESSIVE));
}
if (SUPPORT_NODAMAGE_TICKS && (Setting.DEFAULT_SPAWN_NODAMAGE_DURATION.asTicks() != 20
2022-07-22 15:15:24 +02:00
|| data().has(NPC.Metadata.SPAWN_NODAMAGE_TICKS))) {
try {
entity.setNoDamageTicks(data().get(NPC.Metadata.SPAWN_NODAMAGE_TICKS,
Setting.DEFAULT_SPAWN_NODAMAGE_DURATION.asTicks()));
} catch (NoSuchMethodError err) {
SUPPORT_NODAMAGE_TICKS = false;
}
}
}
if (requiresNameHologram() && !hasTrait(HologramTrait.class)) {
addTrait(HologramTrait.class);
}
updateFlyableState();
updateCustomNameVisibility();
2022-11-29 23:13:55 +01:00
updateScoreboard();
2022-04-03 05:48:42 +02:00
Messaging.debug("Spawned", CitizensNPC.this, "SpawnReason." + reason);
cancel.run();
}
};
if (getEntity() != null && getEntity().isValid()) {
postSpawn.accept(() -> {
});
} else {
new BukkitRunnable() {
@Override
public void run() {
postSpawn.accept(() -> cancel());
}
}.runTaskTimer(CitizensAPI.getPlugin(), 0, 1);
}
return true;
}
2020-03-27 18:14:11 +01:00
@Override
public void teleport(Location location, TeleportCause reason) {
2020-07-15 17:50:48 +02:00
if (!isSpawned())
return;
if (hasTrait(SitTrait.class)) {
getOrAddTrait(SitTrait.class).setSitting(location);
}
2020-07-10 18:44:40 +02:00
Location npcLoc = getEntity().getLocation(CACHE_LOCATION);
if (isSpawned() && npcLoc.getWorld() == location.getWorld()) {
if (npcLoc.distance(location) < 1) {
NMS.setHeadYaw(getEntity(), location.getYaw());
}
if (getEntity().getType() == EntityType.PLAYER && !getEntity().isInsideVehicle()
&& NMS.getPassengers(getEntity()).size() == 0) {
NPCTeleportEvent event = new NPCTeleportEvent(this, location);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled())
return;
NMS.setLocationDirectly(getEntity(), location);
return;
}
2020-05-12 16:33:54 +02:00
}
super.teleport(location, reason);
2020-03-27 18:14:11 +01:00
}
@Override
public String toString() {
2022-03-15 03:11:36 +01:00
EntityType mobType = hasTrait(MobType.class) ? getTraitNullable(MobType.class).getType() : null;
2023-02-05 08:25:29 +01:00
return getId() + "{" + getFullName() + ", " + mobType + "}";
}
@Override
public void update() {
try {
super.update();
2019-05-07 14:02:14 +02:00
if (!isSpawned()) {
resetCachedCoord();
return;
2019-05-07 14:02:14 +02:00
}
2022-12-18 17:36:53 +01:00
if (data().has(NPC.Metadata.ACTIVATION_RANGE)) {
int range = data().get(NPC.Metadata.ACTIVATION_RANGE);
if (range == -1 || CitizensAPI.getLocationLookup().getNearbyPlayers(getStoredLocation(), range)
.iterator().hasNext()) {
NMS.activate(getEntity());
}
}
boolean shouldSwim = data().get(NPC.Metadata.SWIMMING, SwimmingExaminer.isWaterMob(getEntity()))
&& MinecraftBlockExaminer.isLiquid(getEntity().getLocation().getBlock().getType());
if (navigator.isNavigating()) {
if (shouldSwim) {
2023-02-15 00:19:20 +01:00
getEntity().setVelocity(getEntity().getVelocity().multiply(
data().get(NPC.Metadata.WATER_SPEED_MODIFIER, Setting.NPC_WATER_SPEED_MODIFIER.asFloat())));
Location currentDest = navigator.getPathStrategy().getCurrentDestination();
if (currentDest == null || currentDest.getY() > getStoredLocation().getY()) {
2022-12-18 17:36:53 +01:00
NMS.trySwim(getEntity());
}
}
} else if (shouldSwim) {
2022-07-22 15:15:24 +02:00
Gravity trait = getTraitNullable(Gravity.class);
if (trait == null || trait.hasGravity()) {
NMS.trySwim(getEntity());
}
}
2022-12-18 17:36:53 +01:00
2023-02-06 16:28:00 +01:00
if (SUPPORT_GLOWING && data().has(NPC.Metadata.GLOWING)) {
try {
2022-01-01 18:22:35 +01:00
getEntity().setGlowing(data().get(NPC.Metadata.GLOWING, false));
} catch (NoSuchMethodError e) {
SUPPORT_GLOWING = false;
}
2018-04-07 10:02:35 +02:00
}
2019-04-26 12:20:34 +02:00
2023-02-06 16:28:00 +01:00
if (SUPPORT_SILENT && data().has(NPC.Metadata.SILENT)) {
try {
getEntity().setSilent(Boolean.parseBoolean(data().get(NPC.Metadata.SILENT).toString()));
} catch (NoSuchMethodError e) {
SUPPORT_SILENT = false;
}
}
boolean isLiving = getEntity() instanceof LivingEntity;
if (isUpdating(NPCUpdate.PACKET)) {
if (data().get(NPC.Metadata.KEEP_CHUNK_LOADED, 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;
}
}
if (isLiving) {
2022-11-29 23:13:55 +01:00
updateScoreboard();
}
updateCounter = 0;
}
updateCustomNameVisibility();
2020-03-01 08:29:47 +01:00
if (isLiving) {
2022-01-01 18:22:35 +01:00
NMS.setKnockbackResistance((LivingEntity) getEntity(), isProtected() ? 1D : 0D);
2022-07-21 17:13:51 +02:00
if (SUPPORT_PICKUP_ITEMS) {
try {
((LivingEntity) getEntity()).setCanPickupItems(data().get(NPC.Metadata.PICKUP_ITEMS, false));
} catch (Throwable t) {
SUPPORT_PICKUP_ITEMS = false;
}
}
if (getEntity() instanceof Player) {
updateUsingItemState((Player) getEntity());
if (data().has(NPC.Metadata.SNEAKING) && !hasTrait(SneakTrait.class)) {
addTrait(SneakTrait.class);
}
}
2022-02-14 17:57:22 +01:00
}
2023-02-08 14:06:10 +01:00
navigator.run();
updateCounter++;
} catch (Exception ex) {
Throwable error = Throwables.getRootCause(ex);
Messaging.logTr(Messages.EXCEPTION_UPDATING_NPC, getId(), error.getMessage());
error.printStackTrace();
}
}
2022-11-29 23:13:55 +01:00
@Override
public void updateCustomName() {
2022-12-24 06:36:24 +01:00
if (coloredNameComponentCache != null) {
NMS.setCustomName(getEntity(), coloredNameComponentCache, coloredNameStringCache);
2022-11-29 23:13:55 +01:00
} else {
super.updateCustomName();
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";
}
if (nameplateVisible.equals("true") || nameplateVisible.equals("hover")) {
2022-11-29 23:13:55 +01:00
updateCustomName();
}
2021-04-05 10:38:10 +02:00
getEntity().setCustomNameVisible(Boolean.parseBoolean(nameplateVisible));
}
private void updateFlyableState() {
2020-09-14 11:57:58 +02:00
EntityType type = isSpawned() ? getEntity().getType() : getOrAddTrait(MobType.class).getType();
if (type == null)
return;
if (!Util.isAlwaysFlyable(type))
return;
if (!data().has(NPC.Metadata.FLYABLE)) {
data().setPersistent(NPC.Metadata.FLYABLE, true);
}
if (!hasTrait(Gravity.class)) {
2020-09-14 11:57:58 +02:00
getOrAddTrait(Gravity.class).setEnabled(true);
}
}
2022-11-29 23:13:55 +01:00
private void updateScoreboard() {
if (data().has(NPC.Metadata.SCOREBOARD_FAKE_TEAM_NAME)) {
getOrAddTrait(ScoreboardTrait.class).update();
}
}
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);
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);
private static final SetMultimap<ChunkCoord, NPC> CHUNK_LOADERS = HashMultimap.create();
private static boolean SUPPORT_GLOWING = true;
private static boolean SUPPORT_NODAMAGE_TICKS = true;
private static boolean SUPPORT_PICKUP_ITEMS = true;
2019-04-26 12:20:34 +02:00
private static boolean SUPPORT_SILENT = true;
private static boolean SUPPORT_USE_ITEM = true;
}