diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..68234fe5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libs/Utilities-OG"] + path = libs/Utilities-OG + url = https://github.com/true-og/Utilities-OG diff --git a/README.md b/README.md index 6912fa8d..e3d47006 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,6 @@ Full featured union system for PVP Minecraft servers! [![Issues](https://img.shields.io/github/issues/RoinujNosde/SimpleClans.svg)](https://github.com/RoinujNosde/SimpleClans/issues) [![Crowdin](https://badges.crowdin.net/simpleclans/localized.svg)](https://crowdin.com/project/simpleclans) [![Discord](https://img.shields.io/discord/719557355917934613?label=discord&logo=discord)](https://discord.gg/CkNwgdE) -[![bStats Servers](https://img.shields.io/bstats/servers/7131)](https://bstats.org/plugin/bukkit/SimpleClans/7131) -[![bStats Players](https://img.shields.io/bstats/players/7131)](https://bstats.org/plugin/bukkit/SimpleClans/7131) #### Download Link @@ -35,7 +33,7 @@ Full featured union system for PVP Minecraft servers! * [Union name on Tablist](https://simpleclans.gitbook.io/simpleclans/how-to-setup/clan-on-tablist) * Plugins & Development * [Unions-OG API Example](https://simpleclans.gitbook.io/simpleclans/other/simpleclans-api) - * [PlaceholderAPI Placeholders](https://simpleclans.gitbook.io/simpleclans/other/placeholderapi-support) + * [Placeholder and MiniPlaceholder Reference](placeholder-info.txt) * [Land Protection Plugins](https://simpleclans.gitbook.io/simpleclans/other/land-claims) #### Support & Suggestions @@ -87,7 +85,7 @@ dependencies { Feel free to learn our [Javadoc](https://ci.roinujnosde.me/job/SimpleClans/Javadoc/). Compatibility note: the published Maven coordinates, package names, and permissions still use `simpleclans`. -PlaceholderAPI supports both `simpleclans` and `simpleunions`, and union aliases such as `in_union`, `union_name`, and `topunions_#_union_name` resolve under either identifier. +PlaceholderAPI and MiniPlaceholders support both `simpleclans` and `simpleunions`; see [placeholder-info.txt](placeholder-info.txt) for the full placeholder reference. ##### Latest version: [![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo.roinujnosde.me%2Freleases%2Fnet%2Fsacredlabyrinth%2Fphaed%2Fsimpleclans%2FSimpleClans%2Fmaven-metadata.xml)](https://repo.roinujnosde.me/#/releases/net/sacredlabyrinth/phaed/simpleclans/SimpleClans) ##### API Examples diff --git a/build.gradle.kts b/build.gradle.kts index 5a36aead..a3b2f78e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -76,7 +76,7 @@ dependencies { compileOnly("com.github.MilkBowl:VaultAPI:1.7") { exclude(group = "com.googlecode.json-simple", module = "json-simple") } - implementation("org.bstats:bstats-bukkit:3.0.2") + compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG Java API (from source). compileOnly("me.clip:placeholderapi:2.11.3") compileOnly("org.jetbrains:annotations:24.0.1") implementation("io.papermc:paperlib:1.0.8-SNAPSHOT") @@ -106,7 +106,6 @@ tasks.withType().configureEach { // Ensure reproducible .ja /* ----------------------------- Shadow -------------------------------- */ tasks.shadowJar { - relocate("org.bstats", "net.sacredlabyrinth.phaed.simpleclans.metrics") relocate("io.papermc.lib", "net.sacredlabyrinth.phaed.simpleclans.paperlib") relocate("co.aikar.commands", "net.sacredlabyrinth.phaed.simpleclans.acf") relocate("co.aikar.locales", "net.sacredlabyrinth.phaed.simpleclans.locales") diff --git a/libs/Utilities-OG b/libs/Utilities-OG new file mode 160000 index 00000000..cfb722c3 --- /dev/null +++ b/libs/Utilities-OG @@ -0,0 +1 @@ +Subproject commit cfb722c3946b07952467b769a7dd313a6ca6a7e8 diff --git a/placeholder-info.txt b/placeholder-info.txt new file mode 100644 index 00000000..de7a88c2 --- /dev/null +++ b/placeholder-info.txt @@ -0,0 +1,200 @@ +Unions-OG Placeholder Reference +=============================== + +Unions-OG registers legacy PlaceholderAPI placeholders when PlaceholderAPI is installed. +It also registers MiniPlaceholders through the Utilities-OG API when Utilities-OG is installed. + +Identifiers +----------- + +All placeholders use both identifiers: + +- simpleclans +- simpleunions + +In the syntax patterns below, replace {id} with either identifier. + +Legacy PlaceholderAPI syntax: + +- Player placeholder: %{id}_{player-placeholder}% +- Union placeholder: %{id}_clan_{union-placeholder}% +- Union compatibility alias: %{id}_union_{union-placeholder}% +- Relational color placeholder: %rel_{id}_color% + +MiniPlaceholders syntax: + +- Player placeholder: <{id}_{player-placeholder}> +- Union placeholder: <{id}_clan_{union-placeholder}> +- Union compatibility alias: <{id}_union_{union-placeholder}> +- Relational color placeholder: <{id}_rel_color> + +MiniPlaceholders are audience placeholders unless marked relational. They require a player audience. +Relational MiniPlaceholders require a relational MiniPlaceholders context. + +Boolean output follows PlaceholderAPI's boolean.true and boolean.false config values when PlaceholderAPI is loaded. +Without PlaceholderAPI, boolean placeholders return yes or no. + +Player Placeholders +------------------- + +These resolve against the requested player: + +- name +- clean_name +- is_leader +- lastseendays +- total_kills +- rival_kills +- civilian_kills +- neutral_kills +- ally_kills +- is_friendlyfire_on +- deaths +- weighted_kills +- kdr +- topplayers_position +- join_date +- lastseen +- inactive_days +- in_clan +- tag +- tag_label +- is_trusted +- is_member +- clanchat_player_color +- allychat_player_color +- is_bb_enabled +- is_invite_enabled +- is_tag_enabled +- rank_displayname +- has_rank +- rank +- is_muted +- is_mutedally + +Player compatibility aliases: + +- in_union maps to in_clan +- unionchat_player_color maps to clanchat_player_color + +Examples: + +- %simpleclans_name% +- %simpleunions_in_union% +- +- + +Union Placeholders +------------------ + +These resolve against the requested player's union: + +- name +- balance +- balance_formatted +- tag +- color +- inactivedays +- friendly_fire +- allies_count +- founded +- color_tag +- size +- rivals_count +- is_verified +- is_permanent +- onlinemembers_count +- leader_size +- total_kdr +- topclans_position +- total_deaths +- average_wk +- total_kills +- total_rival +- total_neutral +- total_civilian +- total_ally +- is_anyonline +- is_unrivable +- allow_withdraw +- allow_deposit + +Union compatibility aliases: + +- Use union_ instead of clan_ for any union placeholder. +- topunions_position maps to topclans_position. + +Examples: + +- %simpleclans_clan_name% +- %simpleclans_union_name% +- %simpleunions_clan_color_tag% +- %simpleunions_union_topunions_position% +- +- +- +- + +Top Player Placeholders +----------------------- + +Legacy PlaceholderAPI syntax: + +- %{id}_topplayers_{position}_{player-placeholder}% + +MiniPlaceholders syntax: + +- <{id}_topplayers_{player-placeholder}:{position}> + +The {position} value is 1-based. The {player-placeholder} value can be any player placeholder or player compatibility alias listed above. + +Examples: + +- %simpleclans_topplayers_1_name% +- %simpleunions_topplayers_3_kdr% +- +- + +Top Union Placeholders +---------------------- + +Legacy PlaceholderAPI syntax: + +- %{id}_topclans_{position}_clan_{union-placeholder}% +- %{id}_topunions_{position}_union_{union-placeholder}% + +MiniPlaceholders syntax: + +- <{id}_topclans_clan_{union-placeholder}:{position}> +- <{id}_topunions_union_{union-placeholder}:{position}> + +The {position} value is 1-based. The {union-placeholder} value can be any union placeholder or union compatibility alias listed above. + +Examples: + +- %simpleclans_topclans_1_clan_name% +- %simpleclans_topunions_1_union_name% +- %simpleunions_topclans_5_clan_total_kdr% +- +- +- + +Relational Color Placeholder +---------------------------- + +The relational color placeholder returns a color code based on the relationship between two players: + +- same union: &a +- ally: &b +- rival: &c +- unrelated: empty string + +Legacy PlaceholderAPI syntax: + +- %rel_simpleclans_color% +- %rel_simpleunions_color% + +MiniPlaceholders syntax: + +- +- diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/SimpleClans.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/SimpleClans.java index e79c2c00..b489e018 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/SimpleClans.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/SimpleClans.java @@ -3,6 +3,7 @@ package net.sacredlabyrinth.phaed.simpleclans; import co.aikar.commands.BukkitCommandIssuer; import net.sacredlabyrinth.phaed.simpleclans.commands.SCCommandManager; import net.sacredlabyrinth.phaed.simpleclans.hooks.papi.SimpleClansExpansion; +import net.sacredlabyrinth.phaed.simpleclans.hooks.papi.SimpleClansMiniPlaceholders; import net.sacredlabyrinth.phaed.simpleclans.language.LanguageResource; import net.sacredlabyrinth.phaed.simpleclans.listeners.*; import net.sacredlabyrinth.phaed.simpleclans.loggers.BankLogger; @@ -19,9 +20,6 @@ import net.sacredlabyrinth.phaed.simpleclans.utils.ChatUtils; import net.sacredlabyrinth.phaed.simpleclans.utils.TagValidator; import net.sacredlabyrinth.phaed.simpleclans.utils.UpdateChecker; import net.sacredlabyrinth.phaed.simpleclans.uuid.UUIDMigration; -import org.bstats.bukkit.Metrics; -import org.bstats.charts.SimplePie; -import org.bstats.charts.SingleLineChart; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; @@ -140,8 +138,8 @@ public class SimpleClans extends JavaPlugin { logStatus(); startTasks(); - startMetrics(); hookIntoPAPI(); + hookIntoMiniPlaceholders(); new UpdateChecker(this).check(); } @@ -180,32 +178,14 @@ public class SimpleClans extends JavaPlugin { } - private void startMetrics() { + private void hookIntoMiniPlaceholders() { - Metrics metrics = new Metrics(this, 7131); - SettingsManager sm = getSettingsManager(); - ClanManager cm = getClanManager(); - String on = "enabled"; - String off = "disabled"; - metrics.addCustomChart(new SingleLineChart("clans", () -> cm.getClans().size())); - metrics.addCustomChart(new SingleLineChart("clan_players", () -> cm.getAllClanPlayers().size())); - metrics.addCustomChart(new SimplePie("language", () -> sm.getLanguage().toString())); - metrics.addCustomChart(new SimplePie("machine_language", () -> Locale.getDefault().toString())); - metrics.addCustomChart(new SimplePie("language_chooser", () -> sm.is(LANGUAGE_SELECTOR) ? on : off)); - metrics.addCustomChart(new SimplePie("database", () -> sm.is(MYSQL_ENABLE) ? "MySQL" : "SQLite")); - metrics.addCustomChart( - new SimplePie("save_periodically", () -> sm.is(PERFORMANCE_SAVE_PERIODICALLY) ? on : off)); - metrics.addCustomChart(new SimplePie("save_interval", () -> sm.getString(PERFORMANCE_SAVE_INTERVAL))); - metrics.addCustomChart(new SimplePie("upkeep", () -> sm.is(ECONOMY_UPKEEP_ENABLED) ? on : off)); - metrics.addCustomChart(new SimplePie("member_fee", () -> sm.is(ECONOMY_MEMBER_FEE_ENABLED) ? on : off)); - metrics.addCustomChart(new SimplePie("rejoin_cooldown", () -> sm.is(ENABLE_REJOIN_COOLDOWN) ? on : off)); - metrics.addCustomChart(new SimplePie("clan_verification", () -> sm.is(REQUIRE_VERIFICATION) ? on : off)); - metrics.addCustomChart(new SimplePie("money_per_kill", () -> sm.is(ECONOMY_MONEY_PER_KILL) ? on : off)); - metrics.addCustomChart(new SimplePie("threads", () -> sm.is(PERFORMANCE_USE_THREADS) ? on : off)); - metrics.addCustomChart(new SimplePie("bungeecord", () -> sm.is(PERFORMANCE_USE_BUNGEECORD) ? on : off)); - metrics.addCustomChart(new SimplePie("discord_chat", () -> sm.is(DISCORDCHAT_ENABLE) ? on : off)); - metrics.addCustomChart( - new SimplePie("default_rank", () -> sm.getString(CLAN_DEFAULT_RANK).isEmpty() ? off : on)); + if (getPluginManager().getPlugin("Utilities-OG") != null) { + + getLogger().info("Utilities-OG found. Registering MiniPlaceholders..."); + new SimpleClansMiniPlaceholders(this).register(); + + } } diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/PlaceholderBooleanFormatter.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/PlaceholderBooleanFormatter.java new file mode 100644 index 00000000..8f0d0e1d --- /dev/null +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/PlaceholderBooleanFormatter.java @@ -0,0 +1,41 @@ +package net.sacredlabyrinth.phaed.simpleclans.hooks.papi; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +public final class PlaceholderBooleanFormatter { + + private static final String PLACEHOLDER_API = "PlaceholderAPI"; + + private PlaceholderBooleanFormatter() { + + } + + public static @NotNull String trueValue() { + + return getConfiguredValue("boolean.true", "yes"); + + } + + public static @NotNull String falseValue() { + + return getConfiguredValue("boolean.false", "no"); + + } + + private static @NotNull String getConfiguredValue(@NotNull String path, @NotNull String fallback) { + + Plugin placeholderApi = Bukkit.getPluginManager().getPlugin(PLACEHOLDER_API); + if (placeholderApi instanceof JavaPlugin) { + + return ((JavaPlugin) placeholderApi).getConfig().getString(path, fallback); + + } + + return fallback; + + } + +} diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansExpansion.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansExpansion.java index 5a0a7f55..646634ba 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansExpansion.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansExpansion.java @@ -3,35 +3,21 @@ package net.sacredlabyrinth.phaed.simpleclans.hooks.papi; import me.clip.placeholderapi.expansion.Configurable; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.Relational; -import net.sacredlabyrinth.phaed.simpleclans.Clan; -import net.sacredlabyrinth.phaed.simpleclans.ClanPlayer; -import net.sacredlabyrinth.phaed.simpleclans.Helper; import net.sacredlabyrinth.phaed.simpleclans.SimpleClans; -import net.sacredlabyrinth.phaed.simpleclans.managers.ClanManager; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.*; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class SimpleClansExpansion extends PlaceholderExpansion implements Relational, Configurable { - private static final Pattern TOP_CLANS_PATTERN = Pattern.compile("(?^topclans_(?\\d+)_)clan_"); - private static final Pattern TOP_PLAYERS_PATTERN = Pattern.compile("(?^topplayers_(?\\d+)_)"); - private static final String CLAN_COLOR_TAG_PLACEHOLDER = "clan_color_tag"; - private static final String NO_CLAN_COLOR_TAG = "&8None"; - private static final Map RESOLVERS = new HashMap<>(); - private List placeholders; private final SimpleClans plugin; - private final ClanManager clanManager; private final String identifier; + private final SimpleClansPlaceholderEngine placeholders; public SimpleClansExpansion(SimpleClans plugin) { @@ -43,8 +29,7 @@ public class SimpleClansExpansion extends PlaceholderExpansion implements Relati this.plugin = plugin; this.identifier = identifier; - clanManager = plugin.getClanManager(); - registerResolvers(); + placeholders = new SimpleClansPlaceholderEngine(plugin); } @@ -86,18 +71,7 @@ public class SimpleClansExpansion extends PlaceholderExpansion implements Relati @Override public @NotNull List getPlaceholders() { - if (placeholders == null) { - - LinkedHashSet allPlaceholders = new LinkedHashSet<>(); - addPlaceholders(identifier + "_", ClanPlayer.class, allPlaceholders); - addAliasedPlaceholders(identifier + "_", ClanPlayer.class, allPlaceholders); - addPlaceholders(identifier + "_clan_", Clan.class, allPlaceholders); - addAliasedPlaceholders(identifier + "_union_", Clan.class, allPlaceholders); - this.placeholders = new ArrayList<>(allPlaceholders); - - } - - return placeholders; + return placeholders.getLegacyPlaceholders(identifier); } @@ -125,289 +99,15 @@ public class SimpleClansExpansion extends PlaceholderExpansion implements Relati @Nullable public String onPlaceholderRequest(Player player1, Player player2, String params) { - if (player1 == null || player2 == null) { - - return null; - - } - - if (params.equalsIgnoreCase("color")) { - - ClanPlayer cp1 = clanManager.getClanPlayer(player1); - if (cp1 == null) { - - return ""; - - } - - // noinspection ConstantConditions -- getClanPlayer != null == getClan() != null - if (cp1.getClan().isMember(player2)) { - - return getString("color.same_clan", null); - - } - - if (cp1.isRival(player2)) { - - return getString("color.rival", null); - - } - - if (cp1.isAlly(player2)) { - - return getString("color.ally", null); - - } - - return ""; - - } - - return null; + return placeholders.resolveRelational(player1, player2, params, getString("color.same_clan", null), + getString("color.rival", null), getString("color.ally", null)); } @Override public String onRequest(@Nullable OfflinePlayer player, @NotNull String params) { - params = normalizePlaceholder(params); - ClanPlayer cp = null; - if (player != null) { - - cp = clanManager.getAnyClanPlayer(player.getUniqueId()); - - } - - if (cp == null) { - - return getNoClanValue(params); - - } - - Clan clan = cp.getClan(); - if (clan == null && CLAN_COLOR_TAG_PLACEHOLDER.equals(params)) { - - return NO_CLAN_COLOR_TAG; - - } - - Matcher matcher = TOP_CLANS_PATTERN.matcher(params); - if (matcher.find()) { - - int position = Integer.parseInt(matcher.group("position")); - clan = getFromPosition(clanManager.getClans(), position, clanManager::sortClansByKDR); - params = params.replace(matcher.group("strip"), ""); - - } - - matcher = TOP_PLAYERS_PATTERN.matcher(params); - if (matcher.find()) { - - int position = Integer.parseInt(matcher.group("position")); - cp = getFromPosition(clanManager.getAllClanPlayers(), position, clanManager::sortClanPlayersByKDR); - params = params.replace(matcher.group("strip"), ""); - - } - - return getValue(player, cp, clan, params); - - } - - @NotNull - private String getNoClanValue(@NotNull String placeholder) { - - return CLAN_COLOR_TAG_PLACEHOLDER.equals(placeholder) ? NO_CLAN_COLOR_TAG : ""; - - } - - @Nullable - private T getFromPosition(List list, int position, Consumer> sort) { - - if (isPositionValid(list, position)) { - - sort.accept(list); - return list.get(position - 1); - - } - - return null; - - } - - private boolean isPositionValid(@NotNull Collection collection, int position) { - - return position >= 1 && position <= collection.size(); - - } - - @NotNull - private String getValue(@Nullable OfflinePlayer player, @Nullable ClanPlayer cp, @Nullable Clan clan, - @NotNull String placeholder) - { - - if (placeholder.startsWith("clan_")) { - - placeholder = placeholder.replace("clan_", ""); - return getValue(player, clan, placeholder); - - } - - return getValue(player, cp, placeholder); - - } - - @NotNull - private String getValue(@Nullable OfflinePlayer player, @Nullable Object object, @NotNull String placeholder) { - - if (object != null) { - - for (Method declaredMethod : object.getClass().getDeclaredMethods()) { - - Placeholder[] annotations = declaredMethod.getAnnotationsByType(Placeholder.class); - for (Placeholder p : annotations) { - - if (p.value().equals(placeholder)) { - - return resolve(player, object, declaredMethod, p.resolver(), placeholder, p.config()); - - } - - } - - } - - plugin.getLogger().warning(String.format("Placeholder %s not found", placeholder)); - - } - - return ""; - - } - - private String resolve(@Nullable OfflinePlayer player, @NotNull Object object, @NotNull Method method, - @NotNull String resolverId, @NotNull String placeholder, @NotNull String config) - { - - PlaceholderResolver resolver = RESOLVERS.get(resolverId); - if (resolver != null) { - - return resolver.resolve(player, object, method, placeholder, getConfigMap(config)); - - } - - plugin.getLogger().warning(String.format("Resolver %s for %s not found", resolverId, placeholder)); - return ""; - - } - - @NotNull - private Map getConfigMap(@NotNull String config) { - - HashMap map = new HashMap<>(); - String[] elements = config.split(","); - for (String element : elements) { - - String[] keyAndValue = element.split(":"); - map.put(keyAndValue[0], keyAndValue.length > 1 ? keyAndValue[1] : null); - - } - - return map; - - } - - private void registerResolvers() { - - Set> resolvers = Helper - .getSubTypesOf("net.sacredlabyrinth.phaed.simpleclans.hooks.papi.resolvers", PlaceholderResolver.class); - plugin.getLogger().info(String.format("Registering %d placeholder resolvers...", resolvers.size())); - for (Class r : resolvers) { - - try { - - PlaceholderResolver resolver = r.getConstructor(SimpleClans.class).newInstance(plugin); - RESOLVERS.put(resolver.getId(), resolver); - - } catch (InstantiationException | IllegalAccessException | InvocationTargetException - | NoSuchMethodException e) - { - - plugin.getLogger().log(Level.SEVERE, "Error registering placeholder resolver", e); - - } - - } - - } - - private void addPlaceholders(String prefix, Class clazz, Collection placeholders) { - - for (Method method : clazz.getDeclaredMethods()) { - - Placeholder[] annotations = method.getAnnotationsByType(Placeholder.class); - for (Placeholder annotation : annotations) { - - placeholders.add("%" + prefix + annotation.value() + "%"); - - // Commented because the list would be very long - // placeholders.add("%simpleclans_topplayers__" + annotation.value() + - // "%"); - } - - } - - } - - private void addAliasedPlaceholders(String prefix, Class clazz, Collection placeholders) { - - for (Method method : clazz.getDeclaredMethods()) { - - Placeholder[] annotations = method.getAnnotationsByType(Placeholder.class); - for (Placeholder annotation : annotations) { - - String alias = toUnionAlias(annotation.value()); - if (!alias.equals(annotation.value())) { - - placeholders.add("%" + prefix + alias + "%"); - - } - - } - - } - - } - - @NotNull - private String normalizePlaceholder(@NotNull String placeholder) { - - String normalized = placeholder; - if (normalized.startsWith("topunions_")) { - - normalized = "topclans_" + normalized.substring("topunions_".length()); - - } - - if (normalized.startsWith("union_")) { - - normalized = "clan_" + normalized.substring("union_".length()); - - } - - normalized = normalized.replace("_union_", "_clan_"); - normalized = normalized.replace("in_union", "in_clan"); - normalized = normalized.replace("unionchat_player_color", "clanchat_player_color"); - normalized = normalized.replace("topunions_position", "topclans_position"); - - return normalized; - - } - - @NotNull - private String toUnionAlias(@NotNull String placeholder) { - - return placeholder.replace("topclans", "topunions").replace("in_clan", "in_union") - .replace("clanchat_player_color", "unionchat_player_color"); + return placeholders.resolve(player, params); } diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansMiniPlaceholders.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansMiniPlaceholders.java new file mode 100644 index 00000000..658ba1a3 --- /dev/null +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansMiniPlaceholders.java @@ -0,0 +1,155 @@ +package net.sacredlabyrinth.phaed.simpleclans.hooks.papi; + +import net.sacredlabyrinth.phaed.simpleclans.SimpleClans; +import net.trueog.utilitiesog.UtilitiesOG; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +public class SimpleClansMiniPlaceholders { + + private static final List IDENTIFIERS = Arrays.asList("simpleclans", "simpleunions"); + private static final String SAME_CLAN_COLOR = "&a"; + private static final String RIVAL_COLOR = "&c"; + private static final String ALLY_COLOR = "&b"; + + private final SimpleClansPlaceholderEngine placeholders; + + public SimpleClansMiniPlaceholders(@NotNull SimpleClans plugin) { + + placeholders = new SimpleClansPlaceholderEngine(plugin); + + } + + public void register() { + + for (String identifier : IDENTIFIERS) { + + registerRelationalColor(identifier); + registerPlayerPlaceholders(identifier); + registerClanPlaceholders(identifier); + registerTopPlayerPlaceholders(identifier); + registerTopClanPlaceholders(identifier); + + } + + } + + private void registerRelationalColor(@NotNull String identifier) { + + UtilitiesOG.registerRelationalPlaceholder(identifier + "_color", (player, target) -> nullToEmpty( + placeholders.resolveRelational(player, target, "color", SAME_CLAN_COLOR, RIVAL_COLOR, ALLY_COLOR))); + + } + + private void registerPlayerPlaceholders(@NotNull String identifier) { + + for (String placeholder : placeholders.getPlayerPlaceholdersAndAliases()) { + + registerAudiencePlaceholder(identifier + "_" + placeholder, placeholder); + + } + + } + + private void registerClanPlaceholders(@NotNull String identifier) { + + for (String placeholder : placeholders.getClanPlaceholders()) { + + registerAudiencePlaceholder(identifier + "_clan_" + placeholder, "clan_" + placeholder); + + } + + for (String placeholder : placeholders.getUnionPlaceholders()) { + + registerAudiencePlaceholder(identifier + "_union_" + placeholder, "union_" + placeholder); + + } + + } + + private void registerTopPlayerPlaceholders(@NotNull String identifier) { + + for (String placeholder : placeholders.getPlayerPlaceholdersAndAliases()) { + + registerPositionedAudiencePlaceholder(identifier + "_topplayers_" + placeholder, + (player, position) -> placeholders.resolve(player, "topplayers_" + position + "_" + placeholder)); + + } + + } + + private void registerTopClanPlaceholders(@NotNull String identifier) { + + for (String placeholder : placeholders.getClanPlaceholders()) { + + registerPositionedAudiencePlaceholder(identifier + "_topclans_clan_" + placeholder, (player, + position) -> placeholders.resolve(player, "topclans_" + position + "_clan_" + placeholder)); + + } + + for (String placeholder : placeholders.getUnionPlaceholders()) { + + registerPositionedAudiencePlaceholder(identifier + "_topunions_union_" + placeholder, (player, + position) -> placeholders.resolve(player, "topunions_" + position + "_union_" + placeholder)); + + } + + } + + private void registerAudiencePlaceholder(@NotNull String name, @NotNull String params) { + + UtilitiesOG.registerAudiencePlaceholder(name, (Player player) -> placeholders.resolve(player, params)); + + } + + private void registerPositionedAudiencePlaceholder(@NotNull String name, + @NotNull PositionedPlaceholder placeholder) + { + + UtilitiesOG.registerAudiencePlaceholder(name, (player, args) -> { + + if (args.isEmpty() || !isPositiveInteger(args.get(0))) { + + return ""; + + } + + return placeholder.resolve(player, args.get(0)); + + }); + + } + + private boolean isPositiveInteger(@NotNull String value) { + + try { + + return Integer.parseInt(value) > 0; + + } catch (NumberFormatException e) { + + return false; + + } + + } + + @NotNull + private String nullToEmpty(String value) { + + return value != null ? value : ""; + + } + + @FunctionalInterface + private interface PositionedPlaceholder { + + @NotNull + String resolve(@NotNull Player player, @NotNull String position); + + } + +} diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansPlaceholderEngine.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansPlaceholderEngine.java new file mode 100644 index 00000000..aec61410 --- /dev/null +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/SimpleClansPlaceholderEngine.java @@ -0,0 +1,409 @@ +package net.sacredlabyrinth.phaed.simpleclans.hooks.papi; + +import net.sacredlabyrinth.phaed.simpleclans.Clan; +import net.sacredlabyrinth.phaed.simpleclans.ClanPlayer; +import net.sacredlabyrinth.phaed.simpleclans.Helper; +import net.sacredlabyrinth.phaed.simpleclans.SimpleClans; +import net.sacredlabyrinth.phaed.simpleclans.managers.ClanManager; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SimpleClansPlaceholderEngine { + + private static final Pattern TOP_CLANS_PATTERN = Pattern.compile("(?^topclans_(?\\d+)_)clan_"); + private static final Pattern TOP_PLAYERS_PATTERN = Pattern.compile("(?^topplayers_(?\\d+)_)"); + private static final String CLAN_COLOR_TAG_PLACEHOLDER = "clan_color_tag"; + private static final String NO_CLAN_COLOR_TAG = "&8None"; + + private final SimpleClans plugin; + private final ClanManager clanManager; + private final Map resolvers = new HashMap<>(); + + public SimpleClansPlaceholderEngine(@NotNull SimpleClans plugin) { + + this.plugin = plugin; + clanManager = plugin.getClanManager(); + registerResolvers(); + + } + + @NotNull + public List getLegacyPlaceholders(@NotNull String identifier) { + + LinkedHashSet allPlaceholders = new LinkedHashSet<>(); + addLegacyPlaceholders(identifier + "_", getPlayerPlaceholders(), allPlaceholders); + addLegacyPlaceholders(identifier + "_", getPlayerAliases(), allPlaceholders); + addLegacyPlaceholders(identifier + "_clan_", getClanPlaceholders(), allPlaceholders); + addLegacyPlaceholders(identifier + "_union_", getUnionPlaceholders(), allPlaceholders); + return new ArrayList<>(allPlaceholders); + + } + + @NotNull + public List getPlayerPlaceholders() { + + return getPlaceholderNames(ClanPlayer.class); + + } + + @NotNull + public List getPlayerAliases() { + + return getAliases(ClanPlayer.class); + + } + + @NotNull + public List getPlayerPlaceholdersAndAliases() { + + return combine(getPlayerPlaceholders(), getPlayerAliases()); + + } + + @NotNull + public List getClanPlaceholders() { + + return getPlaceholderNames(Clan.class); + + } + + @NotNull + public List getUnionPlaceholders() { + + return combine(getClanPlaceholders(), getAliases(Clan.class)); + + } + + @Nullable + public String resolveRelational(@Nullable Player player1, @Nullable Player player2, @NotNull String params, + @NotNull String sameClanColor, @NotNull String rivalColor, @NotNull String allyColor) + { + + if (player1 == null || player2 == null) { + + return null; + + } + + if (params.equalsIgnoreCase("color")) { + + ClanPlayer cp1 = clanManager.getClanPlayer(player1); + if (cp1 == null) { + + return ""; + + } + + // noinspection ConstantConditions -- getClanPlayer != null == getClan() != null + if (cp1.getClan().isMember(player2)) { + + return sameClanColor; + + } + + if (cp1.isRival(player2)) { + + return rivalColor; + + } + + if (cp1.isAlly(player2)) { + + return allyColor; + + } + + return ""; + + } + + return null; + + } + + @NotNull + public String resolve(@Nullable OfflinePlayer player, @NotNull String params) { + + params = normalizePlaceholder(params); + + ClanPlayer cp = null; + if (player != null) { + + cp = clanManager.getAnyClanPlayer(player.getUniqueId()); + + } + + Clan clan = cp != null ? cp.getClan() : null; + + Matcher matcher = TOP_CLANS_PATTERN.matcher(params); + if (matcher.find()) { + + int position = Integer.parseInt(matcher.group("position")); + clan = getFromPosition(clanManager.getClans(), position, clanManager::sortClansByKDR); + params = params.replace(matcher.group("strip"), ""); + return getValue(player, null, clan, params); + + } + + matcher = TOP_PLAYERS_PATTERN.matcher(params); + if (matcher.find()) { + + int position = Integer.parseInt(matcher.group("position")); + cp = getFromPosition(clanManager.getAllClanPlayers(), position, clanManager::sortClanPlayersByKDR); + clan = cp != null ? cp.getClan() : null; + params = params.replace(matcher.group("strip"), ""); + return getValue(player, cp, clan, params); + + } + + if (cp == null) { + + return getNoClanValue(params); + + } + + if (clan == null && CLAN_COLOR_TAG_PLACEHOLDER.equals(params)) { + + return NO_CLAN_COLOR_TAG; + + } + + return getValue(player, cp, clan, params); + + } + + @NotNull + private String getNoClanValue(@NotNull String placeholder) { + + return CLAN_COLOR_TAG_PLACEHOLDER.equals(placeholder) ? NO_CLAN_COLOR_TAG : ""; + + } + + @Nullable + private T getFromPosition(List list, int position, Consumer> sort) { + + if (isPositionValid(list, position)) { + + sort.accept(list); + return list.get(position - 1); + + } + + return null; + + } + + private boolean isPositionValid(@NotNull Collection collection, int position) { + + return position >= 1 && position <= collection.size(); + + } + + @NotNull + private String getValue(@Nullable OfflinePlayer player, @Nullable ClanPlayer cp, @Nullable Clan clan, + @NotNull String placeholder) + { + + if (placeholder.startsWith("clan_")) { + + return getValue(player, clan, placeholder.substring("clan_".length())); + + } + + return getValue(player, cp, placeholder); + + } + + @NotNull + private String getValue(@Nullable OfflinePlayer player, @Nullable Object object, @NotNull String placeholder) { + + if (object != null) { + + for (Method declaredMethod : object.getClass().getDeclaredMethods()) { + + Placeholder[] annotations = declaredMethod.getAnnotationsByType(Placeholder.class); + for (Placeholder p : annotations) { + + if (p.value().equals(placeholder)) { + + return resolve(player, object, declaredMethod, p.resolver(), placeholder, p.config()); + + } + + } + + } + + plugin.getLogger().warning(String.format("Placeholder %s not found", placeholder)); + + } + + return ""; + + } + + private String resolve(@Nullable OfflinePlayer player, @NotNull Object object, @NotNull Method method, + @NotNull String resolverId, @NotNull String placeholder, @NotNull String config) + { + + PlaceholderResolver resolver = resolvers.get(resolverId); + if (resolver != null) { + + return resolver.resolve(player, object, method, placeholder, getConfigMap(config)); + + } + + plugin.getLogger().warning(String.format("Resolver %s for %s not found", resolverId, placeholder)); + return ""; + + } + + @NotNull + private Map getConfigMap(@NotNull String config) { + + if (config.isEmpty()) { + + return Collections.emptyMap(); + + } + + HashMap map = new HashMap<>(); + String[] elements = config.split(","); + for (String element : elements) { + + String[] keyAndValue = element.split(":"); + map.put(keyAndValue[0], keyAndValue.length > 1 ? keyAndValue[1] : null); + + } + + return map; + + } + + private void registerResolvers() { + + Set> placeholderResolvers = Helper + .getSubTypesOf("net.sacredlabyrinth.phaed.simpleclans.hooks.papi.resolvers", PlaceholderResolver.class); + plugin.getLogger().info(String.format("Registering %d placeholder resolvers...", placeholderResolvers.size())); + for (Class resolverClass : placeholderResolvers) { + + try { + + PlaceholderResolver resolver = resolverClass.getConstructor(SimpleClans.class).newInstance(plugin); + resolvers.put(resolver.getId(), resolver); + + } catch (InstantiationException | IllegalAccessException | InvocationTargetException + | NoSuchMethodException e) + { + + plugin.getLogger().log(Level.SEVERE, "Error registering placeholder resolver", e); + + } + + } + + } + + private void addLegacyPlaceholders(String prefix, Collection placeholderNames, + Collection placeholders) + { + + for (String placeholderName : placeholderNames) { + + placeholders.add("%" + prefix + placeholderName + "%"); + + } + + } + + @NotNull + private List getPlaceholderNames(Class clazz) { + + LinkedHashSet placeholders = new LinkedHashSet<>(); + for (Method method : clazz.getDeclaredMethods()) { + + Placeholder[] annotations = method.getAnnotationsByType(Placeholder.class); + for (Placeholder annotation : annotations) { + + placeholders.add(annotation.value()); + + } + + } + + return new ArrayList<>(placeholders); + + } + + @NotNull + private List getAliases(Class clazz) { + + LinkedHashSet aliases = new LinkedHashSet<>(); + for (String placeholder : getPlaceholderNames(clazz)) { + + String alias = toUnionAlias(placeholder); + if (!alias.equals(placeholder)) { + + aliases.add(alias); + + } + + } + + return new ArrayList<>(aliases); + + } + + @NotNull + private List combine(Collection first, Collection second) { + + LinkedHashSet combined = new LinkedHashSet<>(first); + combined.addAll(second); + return new ArrayList<>(combined); + + } + + @NotNull + private String normalizePlaceholder(@NotNull String placeholder) { + + String normalized = placeholder; + if (normalized.startsWith("topunions_")) { + + normalized = "topclans_" + normalized.substring("topunions_".length()); + + } + + if (normalized.startsWith("union_")) { + + normalized = "clan_" + normalized.substring("union_".length()); + + } + + normalized = normalized.replace("_union_", "_clan_"); + normalized = normalized.replace("in_union", "in_clan"); + normalized = normalized.replace("unionchat_player_color", "clanchat_player_color"); + normalized = normalized.replace("topunions_position", "topclans_position"); + + return normalized; + + } + + @NotNull + private String toUnionAlias(@NotNull String placeholder) { + + return placeholder.replace("topclans", "topunions").replace("in_clan", "in_union") + .replace("clanchat_player_color", "unionchat_player_color"); + + } + +} diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/MemberStatusResolver.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/MemberStatusResolver.java index 28b620f0..8a2caf1c 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/MemberStatusResolver.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/MemberStatusResolver.java @@ -2,6 +2,7 @@ package net.sacredlabyrinth.phaed.simpleclans.hooks.papi.resolvers; import net.sacredlabyrinth.phaed.simpleclans.ClanPlayer; import net.sacredlabyrinth.phaed.simpleclans.SimpleClans; +import net.sacredlabyrinth.phaed.simpleclans.hooks.papi.PlaceholderBooleanFormatter; import net.sacredlabyrinth.phaed.simpleclans.hooks.papi.PlaceholderResolver; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; @@ -10,9 +11,6 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Method; import java.util.Map; -import static me.clip.placeholderapi.PlaceholderAPIPlugin.booleanFalse; -import static me.clip.placeholderapi.PlaceholderAPIPlugin.booleanTrue; - @SuppressWarnings("unused") public class MemberStatusResolver extends PlaceholderResolver { @@ -52,7 +50,7 @@ public class MemberStatusResolver extends PlaceholderResolver { } - return result ? booleanTrue() : booleanFalse(); + return result ? PlaceholderBooleanFormatter.trueValue() : PlaceholderBooleanFormatter.falseValue(); } diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/MethodReturnResolver.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/MethodReturnResolver.java index 1ce7aea0..4c7cabee 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/MethodReturnResolver.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/MethodReturnResolver.java @@ -1,7 +1,7 @@ package net.sacredlabyrinth.phaed.simpleclans.hooks.papi.resolvers; -import me.clip.placeholderapi.PlaceholderAPIPlugin; import net.sacredlabyrinth.phaed.simpleclans.SimpleClans; +import net.sacredlabyrinth.phaed.simpleclans.hooks.papi.PlaceholderBooleanFormatter; import net.sacredlabyrinth.phaed.simpleclans.hooks.papi.PlaceholderResolver; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; @@ -40,7 +40,8 @@ public class MethodReturnResolver extends PlaceholderResolver { if (result instanceof Boolean) { - return ((Boolean) result) ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse(); + return ((Boolean) result) ? PlaceholderBooleanFormatter.trueValue() + : PlaceholderBooleanFormatter.falseValue(); } diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/NotNullReturnResolver.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/NotNullReturnResolver.java index 6bf2df11..e28d5162 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/NotNullReturnResolver.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/hooks/papi/resolvers/NotNullReturnResolver.java @@ -1,6 +1,7 @@ package net.sacredlabyrinth.phaed.simpleclans.hooks.papi.resolvers; import net.sacredlabyrinth.phaed.simpleclans.SimpleClans; +import net.sacredlabyrinth.phaed.simpleclans.hooks.papi.PlaceholderBooleanFormatter; import net.sacredlabyrinth.phaed.simpleclans.hooks.papi.PlaceholderResolver; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; @@ -9,9 +10,6 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Method; import java.util.Map; -import static me.clip.placeholderapi.PlaceholderAPIPlugin.booleanFalse; -import static me.clip.placeholderapi.PlaceholderAPIPlugin.booleanTrue; - @SuppressWarnings("unused") public class NotNullReturnResolver extends PlaceholderResolver { @@ -33,7 +31,8 @@ public class NotNullReturnResolver extends PlaceholderResolver { @NotNull String placeholder, @NotNull Map config) { - return invoke(object, method, placeholder) != null ? booleanTrue() : booleanFalse(); + return invoke(object, method, placeholder) != null ? PlaceholderBooleanFormatter.trueValue() + : PlaceholderBooleanFormatter.falseValue(); } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 40075377..ea2a4c17 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,10 +1,12 @@ name: Unions-OG main: net.sacredlabyrinth.phaed.simpleclans.SimpleClans version: '${version}' -softdepend: - - Vault - - PlaceholderAPI - - LuckPerms +softdepend: + - Vault + - PlaceholderAPI + - Utilities-OG + - MiniPlaceholders + - LuckPerms - WorldGuard - DiscordSRV loadbefore: