add popup notifications, add client version detection

This commit is contained in:
jascotty2 2019-08-28 12:11:10 -05:00
parent ff3443043b
commit efd48ede96
6 changed files with 404 additions and 7 deletions

View File

@ -6,6 +6,7 @@ import com.songoda.core.core.PluginInfoModule;
import com.songoda.core.core.SongodaCoreCommand;
import com.songoda.core.core.SongodaCoreDiagCommand;
import com.songoda.core.commands.CommandManager;
import com.songoda.core.compatibility.ClientVersion;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.gui.GuiManager;
import java.io.IOException;
@ -25,7 +26,9 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import org.json.simple.JSONObject;
@ -44,7 +47,12 @@ public class SongodaCore {
private JavaPlugin piggybackedPlugin;
private final CommandManager commandManager;
private final EventListener loginListener = new EventListener();
private final HashMap<UUID, Long> lastCheck = new HashMap();
private final ShadedEventListener shadingListener = new ShadedEventListener();
public static boolean hasShading() {
// sneaky hack to check the package name since maven tries to re-shade all references to the package string
return !SongodaCore.class.getPackage().getName().equals(new String(new char[]{'c','o','m','.','s','o','n','g','o','d','a','.','c','o','r','e'}));
}
public static void registerPlugin(JavaPlugin plugin, int pluginID, LegacyMaterials icon) {
registerPlugin(plugin, pluginID, icon == null ? "STONE" : icon.name());
@ -58,9 +66,12 @@ public class SongodaCore {
try {
// use the active service
clazz.getMethod("registerPlugin", JavaPlugin.class, int.class, String.class).invoke(null, plugin, pluginID, icon);
if(hasShading()) {
Bukkit.getPluginManager().registerEvents(new ShadedEventListener(), plugin);
}
return;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) {
}
}
}
@ -77,6 +88,7 @@ public class SongodaCore {
commandManager.registerCommandDynamically(new SongodaCoreCommand(this))
.addSubCommand(new SongodaCoreDiagCommand(this));
Bukkit.getPluginManager().registerEvents(loginListener, javaPlugin);
Bukkit.getPluginManager().registerEvents(shadingListener, javaPlugin);
// we aggressevely want to own this command
Bukkit.getScheduler().runTaskLaterAsynchronously(javaPlugin, ()->{CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);}, 20 * 60 * 1);
Bukkit.getScheduler().runTaskLaterAsynchronously(javaPlugin, ()->{CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);}, 20 * 60 * 2);
@ -153,11 +165,52 @@ public class SongodaCore {
return INSTANCE;
}
private class EventListener implements Listener {
private static class ShadedEventListener implements Listener {
boolean via = false;
boolean proto = false;
ShadedEventListener() {
if ((via = Bukkit.getPluginManager().isPluginEnabled("ViaVersion"))) {
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginVia(p));
} else if ((proto = Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport"))) {
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginProtocol(p));
}
}
@EventHandler
void onLogin(PlayerLoginEvent event) {
if (via) {
ClientVersion.onLoginVia(event.getPlayer());
} else if (proto) {
ClientVersion.onLoginProtocol(event.getPlayer());
}
}
@EventHandler
void onLogout(PlayerQuitEvent event) {
if (via) {
ClientVersion.onLogout(event.getPlayer());
}
}
@EventHandler
void onEnable(PluginEnableEvent event) {
// technically shouldn't have online players here, but idk
if (!via && (via = event.getPlugin().getName().equals("ViaVersion"))) {
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginVia(p));
} else if (!proto && (proto = event.getPlugin().getName().equals("ProtocolSupport"))) {
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginProtocol(p));
}
}
}
private class EventListener implements Listener {
final HashMap<UUID, Long> lastCheck = new HashMap();
@EventHandler
void onLogin(PlayerLoginEvent event) {
// don't spam players with update checks
final Player player = event.getPlayer();
// don't spam players with update checks
long now = System.currentTimeMillis();
Long last = lastCheck.get(player.getUniqueId());
if(last != null && now - 10000 < last) return;
@ -176,18 +229,19 @@ public class SongodaCore {
void onDisable(PluginDisableEvent event) {
// don't track disabled plugins
PluginInfo pi = registeredPlugins.stream().filter(p -> event.getPlugin() == p.getJavaPlugin()).findFirst().orElse(null);
if(pi != null) {
if (pi != null) {
registeredPlugins.remove(pi);
}
if(event.getPlugin() == piggybackedPlugin) {
if (event.getPlugin() == piggybackedPlugin) {
// uh-oh! Abandon ship!!
Bukkit.getServicesManager().unregisterAll(piggybackedPlugin);
// can we move somewhere else?
if((pi = registeredPlugins.stream().findFirst().orElse(null)) != null) {
if ((pi = registeredPlugins.stream().findFirst().orElse(null)) != null) {
// move ourselves to this plugin
piggybackedPlugin = pi.getJavaPlugin();
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, piggybackedPlugin, ServicePriority.Normal);
Bukkit.getPluginManager().registerEvents(loginListener, piggybackedPlugin);
Bukkit.getPluginManager().registerEvents(shadingListener, piggybackedPlugin);
CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);
}
}

View File

@ -0,0 +1,103 @@
package com.songoda.core.compatibility;
import com.songoda.core.SongodaCore;
import java.util.HashMap;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* Handy reference for checking a connected client's Minecraft version<br>
* NOTE: this is automatically initialized through SongodaCore
*/
public class ClientVersion {
static HashMap<UUID, ServerVersion> players = new HashMap();
/**
* Check to see what client version this player is connected to the server
* with. Note that if a player is connecting with a newer client than the server,
* this value will simply be the server version.
*
* @param player Player to check
* @return ServerVersion that matches this player's Minecraft version
*/
public static ServerVersion getClientVersion(Player player) {
if (player == null || !players.containsKey(player.getUniqueId())) {
return ServerVersion.getServerVersion();
}
return players.get(player.getUniqueId());
}
/**
* Do Not Use: This is handled by SongodaCore.
*/
@Deprecated
public static void onLoginProtocol(Player p) {
Bukkit.getScheduler().runTaskLater(SongodaCore.getHijackedPlugin(), ()-> {
if(p.isOnline()) {
final int version = protocolsupport.api.ProtocolSupportAPI.getProtocolVersion(p).getId();
System.out.println("ProtoLogin" + version);
players.put(p.getUniqueId(), protocolToVersion(version));
}
}, 20);
}
/**
* Do Not Use: This is handled by SongodaCore.
*/
@Deprecated
public static void onLoginVia(Player p) {
Bukkit.getScheduler().runTaskLater(SongodaCore.getHijackedPlugin(), ()-> {
if(p.isOnline()) {
final int version = us.myles.ViaVersion.api.Via.getAPI().getPlayerVersion(p.getUniqueId());
System.out.println("ViaLogin" + version);
players.put(p.getUniqueId(), protocolToVersion(version));
}
}, 20);
}
private static ServerVersion protocolToVersion(int version) {
// https://github.com/ViaVersion/ViaVersion/blob/master/common/src/main/java/us/myles/ViaVersion/api/protocol/ProtocolVersion.java
// https://github.com/ProtocolSupport/ProtocolSupport/blob/master/src/protocolsupport/api/ProtocolVersion.java
switch (version) {
case 4:
case 5:
return ServerVersion.V1_7;
case 47:
return ServerVersion.V1_8;
case 107:
case 108:
case 109:
case 110:
return ServerVersion.V1_9;
case 210:
return ServerVersion.V1_10;
case 315:
case 316:
return ServerVersion.V1_11;
case 335:
case 338:
case 340:
return ServerVersion.V1_12;
case 393:
case 401:
case 404:
return ServerVersion.V1_13;
case 477:
case 485:
case 490:
case 498:
return ServerVersion.V1_14;
}
return version > 498 ? ServerVersion.getServerVersion() : ServerVersion.UNKNOWN;
}
/**
* Do Not Use: This is handled by SongodaCore.
*/
@Deprecated
public static void onLogout(Player p) {
players.remove(p.getUniqueId());
}
}

View File

@ -1133,6 +1133,15 @@ public enum LegacyMaterials {
return data != null ? data : -1;
}
/**
* Check if current material requires a data value.
*
* @return true if server is legacy and this item requires data to be defined.
*/
public boolean usesData() {
return data != null;
}
/**
* Lookup a Legacy Material by its modern id name. <br />
* This also can grab materials by their legacy, but only if there is no

View File

@ -0,0 +1,14 @@
package com.songoda.core.gui;
/**
* Background images available for use in toast messages
*/
public enum BackgroundType {
ADVENTURE, END, HUSBANDRY, NETHER, STONE;
final String key;
private BackgroundType() {
this.key = "minecraft:textures/gui/advancements/backgrounds/" + name().toLowerCase() + ".png";
}
}

View File

@ -1,6 +1,9 @@
package com.songoda.core.gui;
import com.songoda.core.compatibility.ClientVersion;
import com.songoda.core.compatibility.CompatibleSounds;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.compatibility.ServerVersion;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -73,6 +76,30 @@ public class GuiManager {
openInventories.put(player, inv);
}
public void showPopup(Player player, String message) {
showPopup(player, message, LegacyMaterials.NETHER_STAR, BackgroundType.ADVENTURE);
}
public void showPopup(Player player, String message, LegacyMaterials icon) {
showPopup(player, message, icon, BackgroundType.ADVENTURE);
}
public void showPopup(Player player, String message, LegacyMaterials icon, BackgroundType background) {
if (ClientVersion.getClientVersion(player).isServerVersionAtLeast(ServerVersion.V1_12)) {
PopupMessage popup = new PopupMessage(plugin, icon, message, background);
popup.add();
popup.grant(player);
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
popup.revoke(player);
popup.remove();
}, 70);
} else if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) {
player.sendTitle("", message, 10, 70, 10);
} else {
player.sendTitle("", message);
}
}
/**
* Close all active GUIs
*/

View File

@ -0,0 +1,190 @@
package com.songoda.core.gui;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.compatibility.ServerVersion;
import java.util.HashSet;
import java.util.UUID;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.advancement.Advancement;
import org.bukkit.advancement.AdvancementProgress;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Instance of a popup message that can be sent to a player <br>
* Popup toast messages only work on Minecraft 1.12+ <br>
* Calling this class on anything below 1.12 will cause ClassLoader Exceptions!
*/
class PopupMessage {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static final HashSet<UUID> registeredMessages = new HashSet();
final UUID id = UUID.randomUUID();
private final NamespacedKey key;
private final TextComponent title;
LegacyMaterials icon;
int iconAmount = 1; // experimental, untested
TriggerType trigger = TriggerType.IMPOSSIBLE;
FrameType frame = FrameType.GOAL;
BackgroundType background = BackgroundType.ADVENTURE;
PopupMessage(Plugin source, LegacyMaterials icon, String title) {
this.key = new NamespacedKey(source, "popup/" + id);
this.title = new TextComponent(title.length() < 74 ? title : (title.substring(0, 72) + "..."));
this.icon = icon;
}
PopupMessage(Plugin source, LegacyMaterials icon, String title, BackgroundType background) {
this.key = new NamespacedKey(source, "popup/" + id);
this.title = new TextComponent(title.length() < 74 ? title : (title.substring(0, 72) + "..."));
this.icon = icon;
this.background = background;
}
private String getJSON() {
JsonObject json = new JsonObject();
JsonObject advDisplay = new JsonObject();
if (this.icon != null) {
JsonObject displayIcon = new JsonObject();
displayIcon.addProperty("item", "minecraft:" + this.icon.getMaterial().name().toLowerCase());
if (this.icon.usesData()) {
displayIcon.addProperty("data", this.icon.getData());
}
if (this.iconAmount > 1) {
displayIcon.addProperty("amount", this.iconAmount); // not entirely sure if this works
}
advDisplay.add("icon", displayIcon);
}
advDisplay.add("title", gson.fromJson(ComponentSerializer.toString(this.title), JsonElement.class));
advDisplay.addProperty("background", background.key);
advDisplay.addProperty("description", "");
advDisplay.addProperty("frame", this.frame.id);
advDisplay.addProperty("announce_to_chat", false);
advDisplay.addProperty("show_toast", true);
advDisplay.addProperty("hidden", true);
json.add("display", advDisplay);
JsonObject advCriteria = new JsonObject();
json.add("criteria", advCriteria);
JsonObject advTrigger = new JsonObject();
advTrigger.addProperty("trigger", this.trigger.getKey());
/*if() {
JsonObject advConditions = new JsonObject();
// can add items to this list with [item,amount,data]
advTrigger.add("conditions", advConditions);
}*/
advCriteria.add("mentioned", advTrigger);
return gson.toJson(json);
}
protected void grant(final Player pl) {
final Advancement adv = getAdvancement();
final AdvancementProgress progress = pl.getAdvancementProgress(adv);
if (!progress.isDone())
progress.getRemainingCriteria().forEach((crit) -> progress.awardCriteria(crit));
}
protected void revoke(final Player pl) {
final Advancement adv = getAdvancement();
final AdvancementProgress prog = pl.getAdvancementProgress(adv);
if (prog.isDone())
prog.getAwardedCriteria().forEach((crit) -> prog.revokeCriteria(crit));
}
protected void add() {
if (!registeredMessages.contains(id)) {
registeredMessages.add(id);
try {
Bukkit.getUnsafe().loadAdvancement(key, getJSON());
} catch (IllegalArgumentException e) {
Bukkit.getLogger().warning("Failed to create popup advancement!");
}
}
}
protected void remove() {
if (registeredMessages.contains(id)) {
registeredMessages.remove(id);
Bukkit.getUnsafe().removeAdvancement(key);
}
}
public Advancement getAdvancement() {
return Bukkit.getAdvancement(key);
}
public static enum FrameType {
TASK,
CHALLENGE,
GOAL;
final String id;
private FrameType() {
id = name().toLowerCase();
}
}
public static enum TriggerType {
ARBITRARY_PLAYER_TICK(ServerVersion.V1_13, "TICK"),
BRED_ANIMALS,
BREWED_POTION,
CHANGED_DIMENSION,
CONSTRUCT_BEACON,
CONSUME_ITEM,
CURED_ZOMBIE_VILLAGER,
EFFECTS_CHANGED,
ENCHANTED_ITEM,
ENTER_BLOCK,
ENTITY_HURT_PLAYER,
ENTITY_KILLED_PLAYER,
IMPOSSIBLE,
INVENTORY_CHANGED,
ITEM_DURABILITY_CHANGED,
LEVITATION,
LOCATION,
NETHER_TRAVEL,
PLACED_BLOCK,
PLAYER_HURT_ENTITY,
PLAYER_KILL_ENTITY,
RECIPE_UNLOCKED,
SLEPT_IN_BED,
SUMMONED_ENTITY,
TAME_ANIMAL,
TICK,
USED_ENDER_EYE,
USED_TOTEM,
VILLAGER_TRADE;
final ServerVersion minVersion;
final String compatible;
final String key;
private TriggerType() {
this.minVersion = ServerVersion.UNKNOWN;
this.compatible = "";
this.key = "minecraft:" + name().toLowerCase();
}
private TriggerType(ServerVersion minVersion, String compatible) {
this.minVersion = minVersion;
this.compatible = compatible;
this.key = "minecraft:" + (ServerVersion.isServerVersionAtLeast(minVersion) ? name() : compatible).toLowerCase();
}
public String getKey() {
return key;
}
}
}