mirror of
https://github.com/rockyhawk64/CommandPanels.git
synced 2025-11-18 07:14:17 +01:00
Compare commits
3 Commits
f5993c525a
...
0c107e9c00
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c107e9c00 | ||
|
|
9d3c400acb | ||
|
|
4408bacc7e |
@ -1,5 +1,6 @@
|
|||||||
package me.rockyhawk.commandpanels;
|
package me.rockyhawk.commandpanels;
|
||||||
|
|
||||||
|
import me.rockyhawk.commandpanels.builder.inventory.items.utils.CustomHeads;
|
||||||
import me.rockyhawk.commandpanels.commands.MainCommand;
|
import me.rockyhawk.commandpanels.commands.MainCommand;
|
||||||
import me.rockyhawk.commandpanels.formatter.Placeholders;
|
import me.rockyhawk.commandpanels.formatter.Placeholders;
|
||||||
import me.rockyhawk.commandpanels.formatter.language.TextFormatter;
|
import me.rockyhawk.commandpanels.formatter.language.TextFormatter;
|
||||||
@ -18,6 +19,7 @@ public class Context {
|
|||||||
public PanelOpenCommand panelCommand;
|
public PanelOpenCommand panelCommand;
|
||||||
public DataLoader dataLoader;
|
public DataLoader dataLoader;
|
||||||
public GenerateManager generator;
|
public GenerateManager generator;
|
||||||
|
public CustomHeads customHeads;
|
||||||
|
|
||||||
public Context(CommandPanels pl) {
|
public Context(CommandPanels pl) {
|
||||||
plugin = pl;
|
plugin = pl;
|
||||||
@ -30,6 +32,7 @@ public class Context {
|
|||||||
panelCommand = new PanelOpenCommand(this);
|
panelCommand = new PanelOpenCommand(this);
|
||||||
dataLoader = new DataLoader(this);
|
dataLoader = new DataLoader(this);
|
||||||
generator = new GenerateManager(this);
|
generator = new GenerateManager(this);
|
||||||
|
customHeads = new CustomHeads(this);
|
||||||
|
|
||||||
// Register plugin command
|
// Register plugin command
|
||||||
plugin.registerCommand("panels", new MainCommand(this));
|
plugin.registerCommand("panels", new MainCommand(this));
|
||||||
|
|||||||
@ -18,13 +18,13 @@ public class HeadComponent implements MaterialComponent {
|
|||||||
public ItemStack createItem(Context ctx, String head, Player player, PanelItem item) {
|
public ItemStack createItem(Context ctx, String head, Player player, PanelItem item) {
|
||||||
try {
|
try {
|
||||||
ItemStack s;
|
ItemStack s;
|
||||||
CustomHeads customHeads = new CustomHeads();
|
String headName = ctx.text.parseTextToString(player, head);
|
||||||
if (ctx.text.parseTextToString(player,head).length() <= 16) {
|
if (ctx.text.parseTextToString(player,head).length() <= 16) {
|
||||||
//if [head] username
|
//if [head] username
|
||||||
s = customHeads.getPlayerHead(ctx.text.parseTextToString(player, head));
|
s = ctx.customHeads.getPlayerHeadSync(headName);
|
||||||
} else {
|
} else {
|
||||||
//custom data [head] base64
|
//custom data [head] base64
|
||||||
s = customHeads.getCustomHead(ctx.text.parseTextToString(player, head));
|
s = ctx.customHeads.getCustomHead(headName);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
} catch (Exception var32) {
|
} catch (Exception var32) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package me.rockyhawk.commandpanels.builder.inventory.items.utils;
|
|||||||
import com.destroystokyo.paper.profile.PlayerProfile;
|
import com.destroystokyo.paper.profile.PlayerProfile;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
|
import me.rockyhawk.commandpanels.Context;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
@ -14,10 +15,34 @@ import java.net.MalformedURLException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
public class CustomHeads {
|
public class CustomHeads {
|
||||||
|
|
||||||
private static final Map<String, PlayerProfile> profileCache = new HashMap<>();
|
private final Context ctx;
|
||||||
|
private static final int MAX_CACHE_SIZE = 5000;
|
||||||
|
private static final Map<String, PlayerProfile> profileCache =
|
||||||
|
// Used to define a set limit for how long profileCache can get
|
||||||
|
Collections.synchronizedMap(new LinkedHashMap<>(16, 0.75f, true) {
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<String, PlayerProfile> eldest) {
|
||||||
|
return size() > MAX_CACHE_SIZE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
private static final Queue<String> lookupQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
private static boolean queueTaskRunning = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class must have a single instance across the plugin
|
||||||
|
* so that heads are cached and the queue is utilised properly.s
|
||||||
|
*/
|
||||||
|
public CustomHeads(Context ctx) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// BASE64 (SYNC ONLY)
|
||||||
|
// ===========================
|
||||||
|
|
||||||
public ItemStack getCustomHead(String base64Texture) {
|
public ItemStack getCustomHead(String base64Texture) {
|
||||||
PlayerProfile profile = getOrCreateProfile(base64Texture);
|
PlayerProfile profile = getOrCreateProfile(base64Texture);
|
||||||
@ -32,22 +57,6 @@ public class CustomHeads {
|
|||||||
return skull; // New item each time, only shares profile (skin)
|
return skull; // New item each time, only shares profile (skin)
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemStack getPlayerHead(String playerName) {
|
|
||||||
PlayerProfile profile = profileCache.computeIfAbsent(playerName, key -> {
|
|
||||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName);
|
|
||||||
|
|
||||||
return offlinePlayer.getPlayerProfile();
|
|
||||||
});
|
|
||||||
|
|
||||||
ItemStack skull = new ItemStack(Material.PLAYER_HEAD, 1);
|
|
||||||
if (!(skull.getItemMeta() instanceof SkullMeta skullMeta)) return skull;
|
|
||||||
|
|
||||||
skullMeta.setPlayerProfile(profile);
|
|
||||||
skull.setItemMeta(skullMeta);
|
|
||||||
|
|
||||||
return skull;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PlayerProfile getOrCreateProfile(String base64Texture) {
|
private PlayerProfile getOrCreateProfile(String base64Texture) {
|
||||||
return profileCache.computeIfAbsent(base64Texture, key -> {
|
return profileCache.computeIfAbsent(base64Texture, key -> {
|
||||||
String skinUrl = extractSkinUrlFromBase64(base64Texture);
|
String skinUrl = extractSkinUrlFromBase64(base64Texture);
|
||||||
@ -77,4 +86,88 @@ public class CustomHeads {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// ===========================
|
||||||
|
// PLAYER HEADS (SYNC + ASYNC)
|
||||||
|
// ===========================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If head is not cached run the async warmer to cache the head async.
|
||||||
|
*/
|
||||||
|
public ItemStack getPlayerHeadSync(String playerName) {
|
||||||
|
String key = playerName.toLowerCase();
|
||||||
|
|
||||||
|
// Start with a PLAYER_HEAD item and a skull meta
|
||||||
|
ItemStack skull = new ItemStack(Material.PLAYER_HEAD, 1);
|
||||||
|
SkullMeta skullMeta = (SkullMeta) skull.getItemMeta();
|
||||||
|
|
||||||
|
// Try to get a profile from cache first
|
||||||
|
PlayerProfile profile = profileCache.get(key);
|
||||||
|
|
||||||
|
if (profile == null) {
|
||||||
|
// Fallback to offline player profile to show textures when players are already online
|
||||||
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
profile = offlinePlayer.getPlayerProfile();
|
||||||
|
|
||||||
|
// Enqueue async task to fill cache for next time
|
||||||
|
enqueuePlayerHead(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put profile on the head
|
||||||
|
skullMeta.setPlayerProfile(profile);
|
||||||
|
skull.setItemMeta(skullMeta);
|
||||||
|
|
||||||
|
return skull;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous cache warmer.
|
||||||
|
* Will resolve and store the PlayerProfile in the cache in the background.
|
||||||
|
* Does not return an ItemStack.
|
||||||
|
*/
|
||||||
|
private void cachePlayerHeadAsync(String playerName) {
|
||||||
|
String key = playerName.toLowerCase();
|
||||||
|
if (profileCache.containsKey(key)) return; // already cached
|
||||||
|
|
||||||
|
Bukkit.getAsyncScheduler().runNow(ctx.plugin, (t) -> {
|
||||||
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
PlayerProfile p = offlinePlayer.getPlayerProfile();
|
||||||
|
if (p == null) {
|
||||||
|
UUID offlineUuid = UUID.nameUUIDFromBytes(playerName.getBytes(StandardCharsets.UTF_8));
|
||||||
|
p = Bukkit.createProfile(offlineUuid, playerName);
|
||||||
|
}
|
||||||
|
p.complete(true); // network call
|
||||||
|
profileCache.put(key, p);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Player head async queue code below
|
||||||
|
* queue is used to help avoid timeouts from Mojang
|
||||||
|
*/
|
||||||
|
private void enqueuePlayerHead(String key) {
|
||||||
|
if (profileCache.containsKey(key) || lookupQueue.contains(key)) return;
|
||||||
|
lookupQueue.add(key);
|
||||||
|
startQueueProcessor();
|
||||||
|
}
|
||||||
|
private void startQueueProcessor() {
|
||||||
|
if (queueTaskRunning) return;
|
||||||
|
queueTaskRunning = true;
|
||||||
|
|
||||||
|
Bukkit.getGlobalRegionScheduler().runAtFixedRate(ctx.plugin, task -> {
|
||||||
|
int maxPerTick = 3;
|
||||||
|
for (int i = 0; i < maxPerTick; i++) {
|
||||||
|
String next = lookupQueue.poll();
|
||||||
|
if (next == null) {
|
||||||
|
// no more tasks, stop processor
|
||||||
|
task.cancel();
|
||||||
|
queueTaskRunning = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Run the async fetch for this key
|
||||||
|
Bukkit.getAsyncScheduler().runNow(ctx.plugin, t -> cachePlayerHeadAsync(next));
|
||||||
|
}
|
||||||
|
}, 1, 15); // tick interval per head api lookup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -47,7 +47,6 @@ public class CommandRunner {
|
|||||||
|
|
||||||
String command = commands.get(index).trim();
|
String command = commands.get(index).trim();
|
||||||
|
|
||||||
// Handle the delay tag at flow level
|
|
||||||
if (command.startsWith("[delay]")) {
|
if (command.startsWith("[delay]")) {
|
||||||
String delayStr = ctx.text.applyPlaceholders(
|
String delayStr = ctx.text.applyPlaceholders(
|
||||||
player,
|
player,
|
||||||
@ -70,10 +69,7 @@ public class CommandRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run the command
|
// Run the command
|
||||||
Bukkit.getGlobalRegionScheduler().run(
|
runCommand(panel, player, command);
|
||||||
ctx.plugin,
|
|
||||||
task -> runCommand(panel, player, command)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Move to the next command
|
// Move to the next command
|
||||||
runCommands(panel, player, commands, index + 1);
|
runCommands(panel, player, commands, index + 1);
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package me.rockyhawk.commandpanels.interaction.commands.tags;
|
|||||||
import me.rockyhawk.commandpanels.Context;
|
import me.rockyhawk.commandpanels.Context;
|
||||||
import me.rockyhawk.commandpanels.interaction.commands.CommandTagResolver;
|
import me.rockyhawk.commandpanels.interaction.commands.CommandTagResolver;
|
||||||
import me.rockyhawk.commandpanels.session.Panel;
|
import me.rockyhawk.commandpanels.session.Panel;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
public class ChatTag implements CommandTagResolver {
|
public class ChatTag implements CommandTagResolver {
|
||||||
|
|||||||
@ -15,6 +15,8 @@ public class ConsoleCmdTag implements CommandTagResolver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(Context ctx, Panel panel, Player player, String raw, String command) {
|
public void handle(Context ctx, Panel panel, Player player, String raw, String command) {
|
||||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
Bukkit.getGlobalRegionScheduler().run(ctx.plugin,
|
||||||
|
task -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,8 +19,8 @@ public class RefreshPanelTag implements CommandTagResolver {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void handle(Context ctx, Panel panel, Player player, String raw, String command) {
|
public void handle(Context ctx, Panel panel, Player player, String raw, String command) {
|
||||||
Bukkit.getGlobalRegionScheduler().run(ctx.plugin, task -> {
|
Bukkit.getGlobalRegionScheduler().run(ctx.plugin, task ->
|
||||||
panel.open(ctx, player, false);
|
panel.open(ctx, player, false)
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,7 +6,6 @@ import me.rockyhawk.commandpanels.interaction.commands.RequirementRunner;
|
|||||||
import me.rockyhawk.commandpanels.session.ClickActions;
|
import me.rockyhawk.commandpanels.session.ClickActions;
|
||||||
import me.rockyhawk.commandpanels.session.inventory.InventoryPanel;
|
import me.rockyhawk.commandpanels.session.inventory.InventoryPanel;
|
||||||
import me.rockyhawk.commandpanels.session.inventory.PanelItem;
|
import me.rockyhawk.commandpanels.session.inventory.PanelItem;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user