diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/ScriptEventRegistry.java b/plugin/src/main/java/com/denizenscript/denizen/events/ScriptEventRegistry.java index 3afc7ea9cb..74c7d263bc 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/ScriptEventRegistry.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/ScriptEventRegistry.java @@ -147,6 +147,7 @@ public static void registerMainEvents() { ScriptEvent.registerScriptEvent(new PlayerPicksUpScriptEvent()); ScriptEvent.registerScriptEvent(new PlayerPlacesBlockScriptEvent()); ScriptEvent.registerScriptEvent(new PlayerPlacesHangingScriptEvent()); + ScriptEvent.registerScriptEvent(new PlayerPreLoginScriptEvent()); ScriptEvent.registerScriptEvent(new PlayerPreparesAnvilCraftScriptEvent()); ScriptEvent.registerScriptEvent(new PlayerQuitsScriptEvent()); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerLoginScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerLoginScriptEvent.java index 6624fe9fb6..464ce140b5 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerLoginScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerLoginScriptEvent.java @@ -43,7 +43,8 @@ public PlayerLoginScriptEvent() { @Override public boolean couldMatch(ScriptPath path) { - return path.eventLower.startsWith("player") && (path.eventLower.contains("logs in") || path.eventLower.contains("login")); + return path.eventLower.startsWith("player login") || path.eventLower.startsWith("player first login") + || path.eventLower.startsWith("player logs in"); } @Override diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreLoginScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreLoginScriptEvent.java new file mode 100644 index 0000000000..165258534c --- /dev/null +++ b/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreLoginScriptEvent.java @@ -0,0 +1,163 @@ +package com.denizenscript.denizen.events.player; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.PlayerTag; +import com.denizenscript.denizen.utilities.DenizenAPI; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.objects.core.QueueTag; +import com.denizenscript.denizencore.scripts.ScriptEntryData; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class PlayerPreLoginScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // player prelogin + // + // @Regex ^on player prelogin$ + // + // @Triggers when a player starts to logs in to the server. + // This is during the EARLY authentication process, and should NOT be confused with <@link event player joins>. + // + // @Warning This is a very special-case handler, that delays logins until the events are handled on the main thread. + // Generally, prefer <@link event on player logs in>. + // + // @Context + // returns an ElementTag of the player's hostname. + // returns an ElementTag of the player's name. + // returns an ElementTag of the player's UUID. + // + // @Determine + // QueueTag to cause the event to wait until the queue is complete. + // "KICKED" to kick the player from the server. + // "KICKED " + ElementTag to kick the player and specify a message to show. + // + // @Player When the player has previously joined (and thus the UUID is valid). + // + // --> + + public PlayerPreLoginScriptEvent() { + instance = this; + } + + public static PlayerPreLoginScriptEvent instance; + public AsyncPlayerPreLoginEvent event; + public PlayerTag player; + public List waitForQueues = new ArrayList<>(); + + @Override + public boolean couldMatch(ScriptPath path) { + return path.eventLower.startsWith("player prelogin"); + } + + @Override + public boolean matches(ScriptPath path) { + return super.matches(path); + } + + @Override + public String getName() { + return "PlayerPreLogin"; + } + + @Override + public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { + String determination = determinationObj.toString(); + if (determinationObj instanceof ElementTag) { + if (CoreUtilities.toLowerCase(determination).startsWith("kicked")) { + String message = determination.length() > 7 ? determination.substring(7) : determination; + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, message); + return true; + } + } + if (QueueTag.matches(determination)) { + QueueTag newQueue = QueueTag.valueOf(determination); + if (newQueue != null && newQueue.getQueue() != null) { + waitForQueues.add(newQueue); + } + return true; + } + return super.applyDetermination(path, determinationObj); + } + + @Override + public ScriptEntryData getScriptEntryData() { + return new BukkitScriptEntryData(player, null); + } + + @Override + public ObjectTag getContext(String name) { + if (name.equals("hostname")) { + return new ElementTag(event.getAddress().toString()); + } + else if (name.equals("name")) { + return new ElementTag(event.getName()); + } + else if (name.equals("uuid")) { + return new ElementTag(event.getUniqueId().toString()); + } + return super.getContext(name); + } + + public boolean needsToWait() { + if (Debug.verbose) { + Debug.log("Prelogin: queues that might need waiting: " + waitForQueues.size()); + } + for (QueueTag queue : waitForQueues) { + if (!queue.getQueue().isStopped) { + if (Debug.verbose) { + Debug.log("Prelogin: need to wait for " + queue.getQueue().id); + } + return true; + } + } + if (Debug.verbose) { + Debug.log("Prelogin: no need to wait"); + } + return false; + } + + @EventHandler + public void onPlayerLogin(AsyncPlayerPreLoginEvent event) { + if (!Bukkit.isPrimaryThread()) { + PlayerPreLoginScriptEvent altEvent = (PlayerPreLoginScriptEvent) clone(); + Future future = Bukkit.getScheduler().callSyncMethod(DenizenAPI.getCurrentInstance(), () -> { + altEvent.onPlayerLogin(event); + return null; + }); + try { + future.get(30, TimeUnit.SECONDS); + while (altEvent.needsToWait()) { + Thread.sleep(50); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + return; + } + waitForQueues = new ArrayList<>(); + OfflinePlayer bukkitPlayer = Bukkit.getOfflinePlayer(event.getUniqueId()); + if (bukkitPlayer != null && bukkitPlayer.getName() != null) { + player = new PlayerTag(bukkitPlayer); + } + else { + player = null; + } + this.event = event; + fire(event); + } +} diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/WorldTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/WorldTag.java index 6b6bb43d20..f9a34e15d6 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/WorldTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/WorldTag.java @@ -12,6 +12,7 @@ import com.denizenscript.denizencore.tags.TagContext; import com.denizenscript.denizencore.tags.TagRunnable; import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.Deprecations; import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.npc.NPC; import org.bukkit.*; @@ -346,13 +347,9 @@ public static void registerTags() { return chunks; }); - // <--[tag] - // @attribute - // @returns ChunkTag - // @description - // Returns a random loaded chunk. - // --> + registerTag("random_loaded_chunk", (attribute, object) -> { + Deprecations.worldRandomLoadedChunkTag.warn(attribute.context); int random = CoreUtilities.getRandom().nextInt(object.getWorld().getLoadedChunks().length); return new ChunkTag(object.getWorld().getLoadedChunks()[random]); });