Add a proper 1.14 solution for KEEP_CHUNKS_LOADED

This commit is contained in:
fullwall 2019-05-29 16:45:18 +08:00
parent 489438b2f1
commit e7cba79e1a
3 changed files with 114 additions and 64 deletions

View File

@ -7,7 +7,6 @@ import java.util.Map.Entry;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
@ -93,6 +92,7 @@ import net.citizensnpcs.editor.Editor;
import net.citizensnpcs.npc.skin.SkinUpdateTracker;
import net.citizensnpcs.trait.Controllable;
import net.citizensnpcs.trait.CurrentLocation;
import net.citizensnpcs.util.ChunkCoord;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util;
@ -142,7 +142,7 @@ public class EventListen implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onChunkLoad(ChunkLoadEvent event) {
respawnAllFromCoord(toCoord(event.getChunk()));
respawnAllFromCoord(new ChunkCoord(event.getChunk()));
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@ -150,7 +150,7 @@ public class EventListen implements Listener {
Runnable runnable = new Runnable() {
@Override
public void run() {
ChunkCoord coord = toCoord(event.getChunk());
ChunkCoord coord = new ChunkCoord(event.getChunk());
Location loc = new Location(null, 0, 0, 0);
boolean loadChunk = false;
for (NPC npc : getAllNPCs()) {
@ -161,14 +161,12 @@ public class EventListen implements Listener {
if (!sameChunkCoordinates || !event.getWorld().equals(loc.getWorld()))
continue;
if (!npc.despawn(DespawnReason.CHUNK_UNLOAD)) {
try {
((Cancellable) event).setCancelled(true);
} catch (Throwable e) {
// TODO: event.getChunk().setForceLoaded(true); ?
if (!(event instanceof Cancellable)) {
loadChunk = true;
toRespawn.put(coord, npc);
continue;
}
((Cancellable) event).setCancelled(true);
if (Messaging.isDebugging()) {
Messaging.debug("Cancelled chunk unload at [" + coord.x + "," + coord.z + "]");
}
@ -193,10 +191,10 @@ public class EventListen implements Listener {
}
}
};
if (Util.getMinecraftRevision().contains("1_14")) {
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), runnable);
} else {
if (event instanceof Cancellable) {
runnable.run();
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), runnable);
}
}
@ -395,7 +393,7 @@ public class EventListen implements Listener {
@EventHandler
public void onNeedsRespawn(NPCNeedsRespawnEvent event) {
ChunkCoord coord = toCoord(event.getSpawnLocation());
ChunkCoord coord = new ChunkCoord(event.getSpawnLocation());
if (toRespawn.containsEntry(coord, event.getNPC()))
return;
Messaging.debug("Stored", event.getNPC().getId(), "for respawn from NPCNeedsRespawnEvent");
@ -409,7 +407,7 @@ public class EventListen implements Listener {
Messaging.debug("Preventing further respawns of " + event.getNPC().getId() + " due to DespawnReason."
+ event.getReason().name());
if (event.getNPC().getStoredLocation() != null) {
toRespawn.remove(toCoord(event.getNPC().getStoredLocation()), event.getNPC());
toRespawn.remove(new ChunkCoord(event.getNPC().getStoredLocation()), event.getNPC());
}
} else {
Messaging.debug("Removing " + event.getNPC().getId() + " from skin tracker due to DespawnReason."
@ -640,55 +638,6 @@ public class EventListen implements Listener {
}
private void storeForRespawn(NPC npc) {
toRespawn.put(toCoord(npc.getEntity().getLocation()), npc);
}
private ChunkCoord toCoord(Chunk chunk) {
return new ChunkCoord(chunk);
}
private ChunkCoord toCoord(Location loc) {
return new ChunkCoord(loc.getWorld().getUID(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4);
}
private static class ChunkCoord {
private final UUID worldUUID;
private final int x;
private final int z;
private ChunkCoord(Chunk chunk) {
this(chunk.getWorld().getUID(), chunk.getX(), chunk.getZ());
}
private ChunkCoord(UUID worldUUID, int x, int z) {
this.x = x;
this.z = z;
this.worldUUID = worldUUID;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ChunkCoord other = (ChunkCoord) obj;
if (worldUUID == null) {
if (other.worldUUID != null) {
return false;
}
} else if (!worldUUID.equals(other.worldUUID)) {
return false;
}
return x == other.x && z == other.z;
}
@Override
public int hashCode() {
final int prime = 31;
return prime * (prime * (prime + ((worldUUID == null) ? 0 : worldUUID.hashCode())) + x) + z;
}
toRespawn.put(new ChunkCoord(npc.getEntity().getLocation()), npc);
}
}

View File

@ -20,6 +20,8 @@ import org.bukkit.scoreboard.Team.OptionStatus;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import net.citizensnpcs.NPCNeedsRespawnEvent;
import net.citizensnpcs.Settings.Setting;
@ -42,11 +44,13 @@ 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.util.ChunkCoord;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.NMS;
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;
@ -75,8 +79,7 @@ public class CitizensNPC extends AbstractNPC {
}
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled() && reason != DespawnReason.DEATH) {
getEntity().getLocation().getChunk();
Messaging.debug("Couldn't despawn", getId(), "due to despawn event cancellation. Force loaded chunk.",
Messaging.debug("Couldn't despawn", getId(), "due to despawn event cancellation. Will load chunk.",
getEntity().isValid());
return false;
}
@ -100,6 +103,12 @@ public class CitizensNPC extends AbstractNPC {
return true;
}
@Override
public void destroy() {
super.destroy();
resetCachedCoord();
}
@Override
public void faceLocation(Location location) {
if (!isSpawned())
@ -149,6 +158,17 @@ public class CitizensNPC extends AbstractNPC {
navigator.load(root.getRelative("navigator"));
}
private void resetCachedCoord() {
if (cachedCoord != null) {
CHUNK_LOADERS.remove(NPC_METADATA_MARKER, CHUNK_LOADERS);
CHUNK_LOADERS.remove(cachedCoord, this);
if (CHUNK_LOADERS.get(cachedCoord).size() == 0) {
cachedCoord.setForceLoaded(false);
}
cachedCoord = null;
}
}
@Override
public void save(DataKey root) {
super.save(root);
@ -205,6 +225,7 @@ public class CitizensNPC extends AbstractNPC {
data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
at = at.clone();
getTrait(CurrentLocation.class).setLocation(at);
entityController.spawn(at, this);
@ -282,6 +303,7 @@ public class CitizensNPC extends AbstractNPC {
try {
super.update();
if (!isSpawned()) {
resetCachedCoord();
return;
}
if (data().get(NPC.SWIMMING_METADATA, true)) {
@ -297,6 +319,15 @@ public class CitizensNPC extends AbstractNPC {
}
if (!getNavigator().isNavigating() && updateCounter++ > Setting.PACKET_UPDATE_DELAY.asInt()) {
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;
}
}
updateCounter = 0;
Player player = getEntity().getType() == EntityType.PLAYER ? (Player) getEntity() : null;
@ -385,6 +416,7 @@ public class CitizensNPC extends AbstractNPC {
}
}
private static final SetMultimap<ChunkCoord, NPC> CHUNK_LOADERS = HashMultimap.create();
private static final String NPC_METADATA_MARKER = "NPC";
private static boolean SUPPORT_GLOWING = true;
private static boolean SUPPORT_SILENT = true;

View File

@ -0,0 +1,69 @@
package net.citizensnpcs.util;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
public class ChunkCoord {
public final UUID worldUUID;
public final int x;
public final int z;
public ChunkCoord(Chunk chunk) {
this(chunk.getWorld().getUID(), chunk.getX(), chunk.getZ());
}
public ChunkCoord(Location loc) {
this(loc.getWorld().getUID(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4);
}
public ChunkCoord(UUID worldUUID, int x, int z) {
this.x = x;
this.z = z;
this.worldUUID = worldUUID;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ChunkCoord other = (ChunkCoord) obj;
if (worldUUID == null) {
if (other.worldUUID != null) {
return false;
}
} else if (!worldUUID.equals(other.worldUUID)) {
return false;
}
return x == other.x && z == other.z;
}
public Chunk getChunk() {
return Bukkit.getWorld(worldUUID).getChunkAt(x, z);
}
@Override
public int hashCode() {
final int prime = 31;
return prime * (prime * (prime + ((worldUUID == null) ? 0 : worldUUID.hashCode())) + x) + z;
}
public void setForceLoaded(boolean b) {
Chunk chunk = getChunk();
if (chunk != null && SUPPORTS_FORCE_LOADED) {
try {
chunk.setForceLoaded(b);
} catch (NoSuchMethodError e) {
SUPPORTS_FORCE_LOADED = false;
}
}
}
private static boolean SUPPORTS_FORCE_LOADED = true;
}