Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
11ee901
Ajout Folia Supported dans plugin.yml
Euphillya Oct 28, 2025
33aa586
Folia ne permet pas de supprimer les NPcs quand il s'arrête
Euphillya Oct 28, 2025
69127b3
Replace scheduler dans Citizens.java
Euphillya Oct 28, 2025
67ee2c4
Replace scheduler dans EventListen.java
Euphillya Oct 28, 2025
ecb0be3
Replace scheduler in Metrics.java
Euphillya Oct 28, 2025
26a5979
Replace scheduler dans NPCCommands.java
Euphillya Oct 28, 2025
9873001
Replace scheduler dans CitizensNPC.java
Euphillya Oct 28, 2025
d49c245
Replace scheduler dans Skin.java
Euphillya Oct 28, 2025
303106c
Replace scheduler dans SkinPacketTracker.java
Euphillya Oct 28, 2025
e6be69c
Replace scheduler dans SkinUpdateTracker.java
Euphillya Oct 28, 2025
ce49c2c
Replace scheduler dans ProfileFetchThread.java
Euphillya Oct 28, 2025
20f9d91
Replace scheduler dans ProfileFetcher.java
Euphillya Oct 28, 2025
9802e10
Replace scheduler dans ProfileRequest.java
Euphillya Oct 28, 2025
4626065
Replace scheduler dans CommandTrait.java
Euphillya Oct 28, 2025
c0f6622
Replace scheduler dans PacketNPC.java
Euphillya Oct 28, 2025
b418e49
Scordboard not supported on folia
Euphillya Oct 28, 2025
a8dec93
Replace scheduler dans ShopTrait.java
Euphillya Oct 28, 2025
6ef64b9
Replace scheduler dans OpenShopAction.java
Euphillya Oct 28, 2025
1d77e3e
Replace scheduler dans GuidedWaypointProvider.java
Euphillya Oct 28, 2025
29659f4
Replace scheduler dans LinearWaypointProvider.java
Euphillya Oct 28, 2025
0a19dac
Replace scheduler dans WanderWaypointProvider.java
Euphillya Oct 28, 2025
170a618
Replace scheduler dans Waypoint.java
Euphillya Oct 28, 2025
740fa96
Replace scheduler dans DelayTrigger.java
Euphillya Oct 28, 2025
77ebfdf
Replace scheduler dans PlayerAnimation.java
Euphillya Oct 28, 2025
800fbdc
Replace scheduler dans PlayerUpdateTask.java
Euphillya Oct 28, 2025
ded99e6
callPossiblySync peut pas être appeler dans Folia
Euphillya Oct 28, 2025
f0aba2f
Ajout d'une vérification sur certaines fonctions pour savoir si nous …
Euphillya Oct 28, 2025
1a5992e
Replace callPossiblySync dans le NMS
Euphillya Oct 28, 2025
71c926b
Support Tracker entity folia
Euphillya Oct 28, 2025
6ba15fc
Replace scheduler dans NMS EntityHumanNPC
Euphillya Oct 28, 2025
f886b57
Merge branch 'CitizensDev:master' into support-folia
Euphillya Oct 28, 2025
12f2fda
NPCs can spawn
Euphillya Oct 28, 2025
daf51ca
Fix teleport not async on folia
Euphillya Oct 28, 2025
b3618fa
Merge branch 'CitizensDev:master' into support-folia
Euphillya Oct 29, 2025
87759cb
Executer une commande sur le bon thread
Euphillya Oct 29, 2025
af87bbf
Merge branch 'support-folia' of https://github.com/Euphillya/Citizens…
Euphillya Oct 29, 2025
b571f12
Fix double appel super.updatePlayer
Euphillya Oct 29, 2025
da25c58
Fix forceload
Euphillya Oct 29, 2025
ae453cb
Fix spawn mauvais thread pour les autres entités qui ne sont pas des …
Euphillya Oct 29, 2025
e1a3372
Merge branch 'CitizensDev:master' into support-folia
Euphillya Nov 1, 2025
3e3b046
On ne peux pas Entity#snapTo sur Folia, on fait une teleportation
Euphillya Nov 1, 2025
3178e34
Resupport spigot /npc create
Euphillya Nov 1, 2025
415d866
Fix update ticket
Euphillya Nov 3, 2025
a6249a7
COW ne permet pas de remove (UnsupportedOperationException)
Euphillya Nov 4, 2025
1792599
Merge branch 'CitizensDev:master' into support-folia
Euphillya Nov 8, 2025
3a9004a
Merge branch 'CitizensDev:master' into support-folia
Euphillya Nov 14, 2025
46ebee2
Merge branch 'master' into support-folia
Euphillya Nov 19, 2025
85902de
Merge branch 'CitizensDev:master' into support-folia
Euphillya Nov 21, 2025
dbfc5a1
Enlever la lambda syntax
Euphillya Nov 24, 2025
64ad032
Fix upstream
Euphillya Nov 24, 2025
5e5954b
Mettre en import SchedulerRunnable
Euphillya Nov 24, 2025
df155cd
Changer cancelledUpdatePlayer en cancellableUpdatePlayer
Euphillya Nov 24, 2025
1604d7b
Cache method handled
Euphillya Nov 24, 2025
234e80f
Merge branch 'master' into support-folia
Euphillya Nov 24, 2025
60bbf5f
Fix update upstream
Euphillya Nov 25, 2025
469dcb9
Utiliser snapTo seulement sur la même region
Euphillya Nov 25, 2025
cff0682
enlever le commentaire todo
Euphillya Nov 25, 2025
caab819
Merge branch 'CitizensDev:master' into support-folia
Euphillya Nov 25, 2025
d084a13
Merge branch 'CitizensDev:master' into support-folia
Euphillya Nov 28, 2025
4f48cc5
Remove Util#callPossiblySync
Euphillya Nov 28, 2025
34eae05
Etre sur que NpcRegistry et TraitFactory n'ai pas de problème de conc…
Euphillya Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions main/src/main/java/net/citizensnpcs/Citizens.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Map;
import java.util.UUID;

import net.citizensnpcs.api.util.schedulers.SchedulerTask;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.BlockCommandSender;
Expand Down Expand Up @@ -147,7 +148,7 @@ public void updateInventoryTitle(Player player, InventoryViewAPI view, String ne
private CitizensNPCRegistry npcRegistry;
private boolean packetEventsEnabled = true;
private PacketEventsListener packetEventsListener;
private BukkitTask playerUpdateTask;
private SchedulerTask playerUpdateTask;
private boolean saveOnDisable = true;
private NPCDataStore saves;
private NPCSelector selector;
Expand Down Expand Up @@ -192,6 +193,9 @@ private void despawnNPCs(boolean save) {
registry.saveToStore();
}
}
if (net.citizensnpcs.api.util.SpigotUtil.isFoliaServer()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because you can't "despawn" the NPCs while the server is down. But fortunately, they despawn during the shutdown.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because you can't "despawn" the NPCs while the server is down. But fortunately, they despawn during the shutdown.

More specifically, Folia's regional threads are all shutting down during the server shutdown, so it is impossible to interact with them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is also called by reload (I know...) so maybe needs some change there

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I return it if the plugin is disabled, which is false while the server is running, and true when the server stops.

if (!this.isEnabled()) return;
}
registry.despawnNPCs(DespawnReason.RELOAD);
}
}
Expand Down Expand Up @@ -463,7 +467,7 @@ public void onEnable() {

// Setup NPCs after all plugins have been enabled (allows for multiworld
// support and for NPCs to properly register external settings)
if (getServer().getScheduler().scheduleSyncDelayedTask(this, new CitizensLoadTask(), 1) == -1) {
if (CitizensAPI.getScheduler().runTaskLater(new CitizensLoadTask(), 1) == null) {
Messaging.severeTr(Messages.LOAD_TASK_NOT_SCHEDULED);
Bukkit.getPluginManager().disablePlugin(this);
}
Expand Down Expand Up @@ -534,7 +538,7 @@ public void removeNamedNPCRegistry(String name) {
}

private void scheduleSaveTask(int delay) {
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new CitizensSaveTask(), delay, delay);
CitizensAPI.getScheduler().runTaskTimer(new CitizensSaveTask(), delay, delay);
}

@Override
Expand Down
30 changes: 16 additions & 14 deletions main/src/main/java/net/citizensnpcs/EventListen.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.List;
import java.util.Objects;

import net.citizensnpcs.api.util.schedulers.SchedulerRunnable;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
Expand Down Expand Up @@ -63,7 +64,6 @@
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.RegisteredListener;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;

import com.google.common.base.Joiner;
Expand Down Expand Up @@ -264,7 +264,8 @@ void loadNPCs(ChunkEvent event) {
if (event instanceof Cancellable) {
runnable.run();
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, runnable);
final org.bukkit.Chunk chunk = event.getChunk();
CitizensAPI.getScheduler().runRegionTask(chunk.getWorld(), chunk.getX(), chunk.getZ(), runnable);
}
}

Expand Down Expand Up @@ -419,7 +420,7 @@ public void onEntityDeath(EntityDeathEvent event) {
return;

int deathAnimationTicks = event.getEntity() instanceof LivingEntity ? 20 : 2;
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
CitizensAPI.getScheduler().runRegionTaskLater(location, () -> {
if (!npc.isSpawned() && npc.getOwningRegistry().getByUniqueId(npc.getUniqueId()) == npc) {
npc.spawn(location, SpawnReason.TIMED_RESPAWN);
}
Expand Down Expand Up @@ -535,7 +536,7 @@ public void onNPCLinkToPlayer(NPCLinkToPlayerEvent event) {
}
if (npc.data().has(NPC.Metadata.HOLOGRAM_RENDERER)) {
HologramRenderer hr = npc.data().get(NPC.Metadata.HOLOGRAM_RENDERER);
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> hr.onSeenByPlayer(npc, event.getPlayer()), 2);
CitizensAPI.getScheduler().runEntityTaskLater(event.getPlayer(), () -> hr.onSeenByPlayer(npc, event.getPlayer()), 2);
}
}

Expand All @@ -557,12 +558,12 @@ private void onNPCPlayerLinkToPlayer(NPCLinkToPlayerEvent event) {
if (!sendTabRemove || !event.getNPC().shouldRemoveFromTabList()) {
NMS.sendRotationPacket(tracker, ImmutableList.of(event.getPlayer()), null, null, NMS.getHeadYaw(tracker));
if (resetYaw) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin,
CitizensAPI.getScheduler().runEntityTask(tracker,
() -> PlayerAnimation.ARM_SWING.play((Player) tracker, event.getPlayer()));
}
return;
}
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
CitizensAPI.getScheduler().runRegionTaskLater(tracker.getLocation(), () -> {
if (!tracker.isValid() || !event.getPlayer().isValid())
return;

Expand Down Expand Up @@ -611,7 +612,7 @@ public void onPlayerChangedWorld(PlayerChangedWorldEvent event) {
if (plugin.getNPCRegistry().getNPC(event.getPlayer()) == null)
return;

Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
CitizensAPI.getScheduler().runEntityTaskLater(event.getPlayer(), () -> {
NMS.replaceTracker(event.getPlayer());
NMS.removeFromServerPlayerList(event.getPlayer());
}, 1);
Expand Down Expand Up @@ -674,7 +675,7 @@ public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if (SUPPORT_STOP_USE_ITEM) {
try {
PlayerAnimation.STOP_USE_ITEM.play(player);
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin,
CitizensAPI.getScheduler().runEntityTask(player,
() -> PlayerAnimation.STOP_USE_ITEM.play(player));
} catch (UnsupportedOperationException e) {
SUPPORT_STOP_USE_ITEM = false;
Expand Down Expand Up @@ -778,7 +779,7 @@ public void onProjectileHit(ProjectileHitEvent event) {
if (!(event.getEntity() instanceof FishHook))
return;
NMS.removeHookIfNecessary((FishHook) event.getEntity());
new BukkitRunnable() {
new SchedulerRunnable() {
int n = 0;

@Override
Expand All @@ -789,7 +790,7 @@ public void run() {
}
NMS.removeHookIfNecessary((FishHook) event.getEntity());
}
}.runTaskTimer(plugin, 0, 1);
}.runEntityTaskTimer(plugin, event.getEntity(), null, 0, 1);
}

@EventHandler
Expand Down Expand Up @@ -899,7 +900,7 @@ private void registerKnockbackEvent(Class<?> kbc) {
}
}

private void registerMoveEvent(Class<?> clazz) {
private void registerMoveEvent(Class<?> clazz) {
try {
final HandlerList handlers = (HandlerList) clazz.getMethod("getHandlerList").invoke(null);
final Method getEntity = clazz.getMethod("getEntity");
Expand All @@ -920,12 +921,12 @@ private void registerMoveEvent(Class<?> clazz) {
Bukkit.getPluginManager().callEvent(npcMoveEvent);
if (npcMoveEvent.isCancelled()) {
final Location eventFrom = npcMoveEvent.getFrom();
Bukkit.getScheduler().runTaskLater(plugin, () -> entity.teleport(eventFrom), 1L);
CitizensAPI.getScheduler().runEntityTaskLater(entity, () -> SpigotUtil.teleportAsync(entity, eventFrom), 1L);
return;
}
final Location eventTo = npcMoveEvent.getTo();
if (eventTo.getWorld() != to.getWorld() || eventTo.distance(to) > 0.001) {
Bukkit.getScheduler().runTaskLater(plugin, () -> entity.teleport(eventTo), 1L);
CitizensAPI.getScheduler().runEntityTaskLater(entity, () -> SpigotUtil.teleportAsync(entity, eventTo), 1L);
}
} catch (Throwable ex) {
ex.printStackTrace();
Expand Down Expand Up @@ -1033,7 +1034,8 @@ private void unloadNPCs(ChunkEvent event, List<NPC> toDespawn) {
}
if (loadChunk) {
Messaging.idebug(() -> Joiner.on(' ').join("Loading chunk in 10 ticks due to forced chunk load at", coord));
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
final org.bukkit.Chunk chunk = event.getChunk();
CitizensAPI.getScheduler().runRegionTaskLater(chunk.getWorld(), chunk.getX(), chunk.getZ(), () -> {
if (!event.getChunk().isLoaded()) {
event.getChunk().load();
}
Expand Down
3 changes: 2 additions & 1 deletion main/src/main/java/net/citizensnpcs/Metrics.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import javax.net.ssl.HttpsURLConnection;

import net.citizensnpcs.api.CitizensAPI;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
Expand Down Expand Up @@ -88,7 +89,7 @@ public Metrics(JavaPlugin plugin, int serviceId) {
boolean logSentData = config.getBoolean("logSentData", false);
boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false);
metricsBase = new MetricsBase("bukkit", serverUUID, serviceId, enabled, this::appendPlatformData,
this::appendServiceData, submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask),
this::appendServiceData, submitDataTask -> CitizensAPI.getScheduler().runTask(submitDataTask),
plugin::isEnabled, (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error),
message -> this.plugin.getLogger().log(Level.INFO, message), logErrors, logSentData,
logResponseStatusText);
Expand Down
14 changes: 7 additions & 7 deletions main/src/main/java/net/citizensnpcs/commands/NPCCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@ else if (!EntityControllers.controllerExistsForType(type))
}
if (temporaryDuration != null) {
NPC temp = npc;
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), () -> {
CitizensAPI.getScheduler().runEntityTaskLater(temp.getEntity(), () -> {
if (temporaryRegistry.getByUniqueId(temp.getUniqueId()) == temp) {
temp.destroy();
}
Expand Down Expand Up @@ -3186,14 +3186,14 @@ public void skin(CommandContext args, CommandSender sender, NPC npc, @Flag("url"
return;
} else if (url != null || file != null) {
Messaging.sendTr(sender, Messages.FETCHING_SKIN, url == null ? file : url);
Bukkit.getScheduler().runTaskAsynchronously(CitizensAPI.getPlugin(), () -> {
CitizensAPI.getScheduler().runTaskAsynchronously(() -> {
try {
JSONObject data = null;
if (file != null) {
File skinsFolder = new File(CitizensAPI.getDataFolder(), "skins");
File skin = new File(skinsFolder, Placeholders.replace(file, sender, npc));
if (!skin.exists() || !skin.isFile() || skin.isHidden() || !isInDirectory(skin, skinsFolder)) {
Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(),
CitizensAPI.getScheduler().runTask(
() -> Messaging.sendErrorTr(sender, Messages.INVALID_SKIN_FILE, file));
return;
}
Expand All @@ -3208,7 +3208,7 @@ public void skin(CommandContext args, CommandSender sender, NPC npc, @Flag("url"
String textureEncoded = (String) texture.get("value");
String signature = (String) texture.get("signature");

Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(), () -> {
CitizensAPI.getScheduler().runEntityTask(npc.getEntity(), () -> {
try {
trait.setSkinPersistent(uuid, signature, textureEncoded);
Messaging.sendTr(sender, Messages.SKIN_URL_SET, npc.getName(), url == null ? file : url);
Expand All @@ -3220,7 +3220,7 @@ public void skin(CommandContext args, CommandSender sender, NPC npc, @Flag("url"
if (Messaging.isDebugging()) {
t.printStackTrace();
}
Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(), () -> Messaging.sendErrorTr(sender,
CitizensAPI.getScheduler().runTask(() -> Messaging.sendErrorTr(sender,
Messages.ERROR_SETTING_SKIN_URL, url == null ? file : url));
}
});
Expand Down Expand Up @@ -3599,7 +3599,7 @@ public void tp(CommandContext args, Player player, NPC npc) {
to = to.clone().add(to.getDirection().setY(0));
to.setDirection(to.getDirection().multiply(-1)).setPitch(0);
}
player.teleport(to, TeleportCause.COMMAND);
SpigotUtil.teleportAsync(player, to, TeleportCause.COMMAND);
Messaging.sendTr(player, Messages.TELEPORTED_TO_NPC, npc.getName());
}

Expand Down Expand Up @@ -3700,7 +3700,7 @@ public void tpto(CommandContext args, CommandSender sender, NPC npc) throws Comm
throw new CommandException(Messages.FROM_ENTITY_NOT_FOUND);
if (to == null)
throw new CommandException(Messages.TPTO_ENTITY_NOT_FOUND);
from.teleport(to);
SpigotUtil.teleportAsync(from, to.getLocation());
Messaging.sendTr(sender, Messages.TPTO_SUCCESS);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,9 @@ public void remove() {
public boolean spawn(Location at) {
return !Util.isLoaded(at) ? false : NMS.addEntityToWorld(bukkitEntity, CreatureSpawnEvent.SpawnReason.CUSTOM);
}

@Override
public void spawn(Location at, java.util.function.Consumer<Boolean> isSpawned) {
NMS.addEntityToWorld(bukkitEntity, CreatureSpawnEvent.SpawnReason.CUSTOM, isSpawned);
}
}
26 changes: 22 additions & 4 deletions main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.UUID;
import java.util.function.Consumer;

import net.citizensnpcs.api.util.schedulers.SchedulerRunnable;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Registry;
Expand Down Expand Up @@ -187,7 +188,13 @@ public void load(DataKey root) {
if (getOrAddTrait(Spawned.class).shouldSpawn()) {
CurrentLocation current = getOrAddTrait(CurrentLocation.class);
if (current.getLocation() != null) {
spawn(current.getLocation(), SpawnReason.RESPAWN);
if (CitizensAPI.getScheduler().isOnOwnerThread(current.getLocation())) {
spawn(current.getLocation(), SpawnReason.RESPAWN);
} else {
CitizensAPI.getScheduler().runRegionTask(current.getLocation(), () -> {
spawn(current.getLocation(), SpawnReason.RESPAWN);
});
}
} else if (current.getChunkCoord() != null) {
Bukkit.getPluginManager().callEvent(new NPCNeedsRespawnEvent(this, current.getChunkCoord()));
}
Expand Down Expand Up @@ -333,8 +340,19 @@ public boolean spawn(Location at, SpawnReason reason, Consumer<Entity> callback)
}
data().set(NPC.Metadata.NPC_SPAWNING_IN_PROGRESS, true);
boolean wasLoaded = Messaging.isDebugging() ? Util.isLoaded(at) : false;
boolean couldSpawn = entityController.spawn(at);
final Location location = at;
if (net.citizensnpcs.api.util.SpigotUtil.isFoliaServer()) {
entityController.spawn(location, couldSpawn -> {
this.spawn(couldSpawn, reason, wasLoaded, location, callback);
});
return true;
} else {
boolean couldSpawn = entityController.spawn(location);
return spawn(couldSpawn, reason, wasLoaded, at, callback);
}
}

private boolean spawn(boolean couldSpawn, SpawnReason reason, boolean wasLoaded, Location at, Consumer<Entity> callback) {
if (!couldSpawn) {
if (Messaging.isDebugging()) {
Messaging.debug("Retrying spawn of", this, "later, SpawnReason." + reason + ". Was loaded", wasLoaded,
Expand Down Expand Up @@ -436,12 +454,12 @@ public void accept(Runnable cancel) {
postSpawn.accept(() -> {
});
} else {
new BukkitRunnable() {
new SchedulerRunnable() {
@Override
public void run() {
postSpawn.accept(this::cancel);
}
}.runTaskTimer(CitizensAPI.getPlugin(), 0, 1);
}.runEntityTaskTimer(CitizensAPI.getPlugin(), getEntity(), null, 0, 1);
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class CitizensNPCRegistry implements NPCRegistry {
private final String name;
private final Int2ObjectOpenHashMap<NPC> npcs = new Int2ObjectOpenHashMap<>();
private final NPCDataStore saves;
private final Map<UUID, NPC> uniqueNPCs = Maps.newHashMap();
private final Map<UUID, NPC> uniqueNPCs = Maps.newConcurrentMap();

public CitizensNPCRegistry(NPCDataStore store) {
this(store, "");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@
import net.citizensnpcs.util.NMS;

public class CitizensTraitFactory implements TraitFactory {
private final List<TraitInfo> defaultTraits = Lists.newArrayList();
private final Map<String, TraitInfo> registered = Maps.newHashMap();
private final List<TraitInfo> defaultTraits = Lists.newCopyOnWriteArrayList();
private final Map<String, TraitInfo> registered = Maps.newConcurrentMap();

public CitizensTraitFactory(Citizens plugin) {
registerTrait(TraitInfo.create(Age.class));
Expand Down
2 changes: 2 additions & 0 deletions main/src/main/java/net/citizensnpcs/npc/EntityController.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ public interface EntityController {
void remove();

boolean spawn(Location at);

void spawn(Location at, java.util.function.Consumer<Boolean> callback);
}
21 changes: 19 additions & 2 deletions main/src/main/java/net/citizensnpcs/npc/ai/CitizensNavigator.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
Expand Down Expand Up @@ -534,14 +535,30 @@ private void updateTicket(Location target) {

// switch ticket to the new chunk
if (activeTicket != null) {
activeTicket.getChunk().removePluginChunkTicket(CitizensAPI.getPlugin());
final ChunkCoord tempTicket = activeTicket;
final World tempWorld = tempTicket.getWorld();
if (CitizensAPI.getScheduler().isOnOwnerThread(tempWorld, tempTicket.x, tempTicket.z)) {
tempTicket.getChunk().removePluginChunkTicket(CitizensAPI.getPlugin());
} else {
CitizensAPI.getScheduler().runRegionTask(tempWorld, tempTicket.x, tempTicket.z, () -> {
tempTicket.getChunk().removePluginChunkTicket(CitizensAPI.getPlugin());
});
}
}
if (target == null) {
activeTicket = null;
return;
}
activeTicket = coord;
activeTicket.getChunk().addPluginChunkTicket(CitizensAPI.getPlugin());
final ChunkCoord tempTicket = activeTicket;
final World tempTicketWorld = tempTicket.getWorld();
if (CitizensAPI.getScheduler().isOnOwnerThread(tempTicketWorld, tempTicket.x, tempTicket.z)) {
tempTicket.getChunk().addPluginChunkTicket(CitizensAPI.getPlugin());
} else {
CitizensAPI.getScheduler().runRegionTask(tempTicketWorld, tempTicket.x, tempTicket.z, () -> {
tempTicket.getChunk().addPluginChunkTicket(CitizensAPI.getPlugin());
});
}
}

private static boolean SUPPORT_CHUNK_TICKETS = true;
Expand Down
Loading