Skip to content

Commit

Permalink
Fixes #786, make future disguises async safe
Browse files Browse the repository at this point in the history
  • Loading branch information
libraryaddict committed Dec 1, 2024
1 parent 675bba5 commit 733ed63
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -268,7 +269,7 @@ private static class UsersData {
* a max of a second.
*/
@Getter
private static final HashMap<Integer, HashSet<TargetedDisguise>> futureDisguises = new HashMap<>();
private static final Map<Integer, HashSet<TargetedDisguise>> futureDisguises = new ConcurrentHashMap<>();
private static final HashSet<UUID> savedDisguiseList = new HashSet<>();
private static final HashSet<String> cachedNames = new HashSet<>();
private static final HashMap<String, String> sanitySkinCacheMap = new LinkedHashMap<>();
Expand Down Expand Up @@ -1187,11 +1188,17 @@ public static void addDisguise(Integer entityId, TargetedDisguise disguise) {
}

public static void onFutureDisguise(Entity entity) {
if (!getFutureDisguises().containsKey(entity.getEntityId())) {
checkFutureDisguises(entity, false);
}

public static void checkFutureDisguises(Entity entity, boolean failedToSpawn) {
HashSet<TargetedDisguise> disguises = getFutureDisguises().remove(entity.getEntityId());

if (disguises == null || failedToSpawn) {
return;
}

for (TargetedDisguise disguise : getFutureDisguises().remove(entity.getEntityId())) {
for (TargetedDisguise disguise : disguises) {
disguise.setEntity(entity);
disguise.startDisguise();
}
Expand Down Expand Up @@ -3687,14 +3694,50 @@ public static Disguise getDisguise(Player observer, int entityId) {
}

if (getFutureDisguises().containsKey(entityId)) {
for (Entity e : observer.getWorld().getEntities()) {
if (e.getEntityId() != entityId) {
continue;
}
boolean foundEntity = false;
Set<TargetedDisguise> disguises = getDisguises().get(entityId);

onFutureDisguise(e);
if (disguises != null && !disguises.isEmpty()) {
Disguise disguise = disguises.iterator().next();

if (disguise.getEntity() != null) {
foundEntity = true;

onFutureDisguise(disguise.getEntity());
}
}

if (!foundEntity) {
if (Bukkit.isPrimaryThread()) {
findEntities(observer, entityId);
} else {
int id = entityId;
CountDownLatch latch = new CountDownLatch(1);

new BukkitRunnable() {
@Override
public void run() {
try {
findEntities(observer, id);
} finally {
latch.countDown();
}
}
}.runTask(LibsDisguises.getInstance());

try {
boolean mainThreadSuccess = latch.await(5, TimeUnit.SECONDS);

if (!mainThreadSuccess) {
LibsDisguises.getInstance().getLogger().info(String.format(
"Packet processing was paused on '%s' for a \"future\" disguise, but took too long waiting for the main " +
"thread. The disguise may not have " + "been applied properly.", observer.getName()));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

TargetedDisguise[] disguises = getDisguises(entityId);
Expand All @@ -3718,16 +3761,50 @@ public static Disguise getDisguise(Player observer, int entityId) {
return null;
}

public static Entity getEntity(World world, int entityId) {
for (Entity e : world.getEntities()) {
private static void findEntities(Player observer, int entityId) {
for (Entity e : observer.getWorld().getEntities()) {
if (e.getEntityId() != entityId) {
continue;
}

return e;
onFutureDisguise(e);
return;
}

return null;
// Don't say anything if there's no disguises, this will be our secret
if (!getFutureDisguises().containsKey(entityId)) {
return;
}

// Warn the console, so people know why we might be lagging
LibsDisguises.getInstance().getLogger().info(String.format(
"Failed to find the entity for %s in %s's world. Yet player is still receiving packets about said entity. Now scanning all " +
"worlds.", entityId, observer.getName()));

for (World world : Bukkit.getWorlds()) {
if (world == observer.getWorld()) {
continue;
}

for (Entity e : observer.getWorld().getEntities()) {
if (e.getEntityId() != entityId) {
continue;
}

onFutureDisguise(e);
return;
}
}

// Warn in console that there's disguises being done weirdly
// It's likely that entities are being added without firing EntitySpawnEvent yet a future disguise is being requested on said
// entity, then said entity is being removed or hidden from entity list.
// Of course, it could be that a future disguise was added, then packets about said entity started being sent before
// said entity was even spawned.
LibsDisguises.getInstance().getLogger().warning(String.format(
"Failed to find the entity for %s in any world, removing disguises for that entity ID. If this happens often, then a plugin " +
"is likely sending packets about an entity that is handled weirdly.", entityId));
getFutureDisguises().remove(entityId);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
Expand Down Expand Up @@ -388,6 +389,11 @@ public void onWorldLoad(WorldLoadEvent event) {
}
}

@EventHandler(priority = EventPriority.MONITOR)
public void onSpawn(EntitySpawnEvent event) {
DisguiseUtilities.checkFutureDisguises(event.getEntity(), event.isCancelled());
}

@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player p = event.getPlayer();
Expand Down

0 comments on commit 733ed63

Please sign in to comment.