diff --git a/PLACEHOLDERS.md b/PLACEHOLDERS.md new file mode 100644 index 0000000000..f25b2fd44d --- /dev/null +++ b/PLACEHOLDERS.md @@ -0,0 +1,299 @@ +# mcMMO Placeholders + +This document describes all placeholders available in mcMMO, including the new placeholders added to facilitate the creation of menus and more detailed ranking systems. + +## Available Skills + +All mcMMO skills can be used in the placeholders: +- `acrobatics` +- `alchemy` +- `archery` +- `axes` +- `excavation` +- `fishing` +- `herbalism` +- `mining` +- `repair` +- `salvage` +- `smelting` +- `swords` +- `taming` +- `unarmed` +- `woodcutting` +- `crossbows` +- `tridents` +- `maces` + +--- + +## Skill Placeholders (Per Skill) + +The following placeholders are available for **all skills** listed above: + +### Skill Level +**Syntax:** `%mcmmo_level_%` + +Returns the player's current level in the specified skill. + +**Examples:** +- `%mcmmo_level_mining%` - Returns Mining level (e.g., "75") +- `%mcmmo_level_swords%` - Returns Swords level +- `%mcmmo_level_woodcutting%` - Returns Woodcutting level + +### Current Skill XP +**Syntax:** `%mcmmo_xp_%` + +Returns the current amount of XP the player has in the skill. + +**Examples:** +- `%mcmmo_xp_mining%` - Returns current Mining XP (e.g., "1245") +- `%mcmmo_xp_fishing%` - Returns current Fishing XP + +### XP Needed for Next Level +**Syntax:** `%mcmmo_xp_needed_%` + +Returns the total amount of XP needed to reach the next level. + +**Examples:** +- `%mcmmo_xp_needed_mining%` - Returns XP needed (e.g., "2000") +- `%mcmmo_xp_needed_swords%` - Returns XP needed for next level + +### XP Remaining for Next Level +**Syntax:** `%mcmmo_xp_remaining_%` + +Returns how much XP is still needed to level up. + +**Examples:** +- `%mcmmo_xp_remaining_mining%` - Returns remaining XP (e.g., "755") +- `%mcmmo_xp_remaining_fishing%` - XP needed for the next level + +### Skill Rank Position +**Syntax:** `%mcmmo_rank_%` + +Returns the player's position in the leaderboard for that skill. + +**Examples:** +- `%mcmmo_rank_mining%` - Returns "12" if the player is in 12th place +- `%mcmmo_rank_swords%` - Position in Swords ranking + +### Skill XP Rate +**Syntax:** `%mcmmo_xprate_%` + +Returns the XP multiplier the player has for that skill (based on permissions). + +**Examples:** +- `%mcmmo_xprate_mining%` - Returns "2.0" if they have double XP +- `%mcmmo_xprate_fishing%` - Returns "1.5" if they have 1.5x XP + +--- + +## Power Level Placeholders (General) + +### Total Power Level +**Syntax:** `%mcmmo_power_level%` + +Returns the player's total power level (sum of all skill levels). + +**Example:** +- `%mcmmo_power_level%` - Returns "850" + +### Power Level Cap +**Syntax:** `%mcmmo_power_level_cap%` + +Returns the maximum power level cap configured on the server. + +**Example:** +- `%mcmmo_power_level_cap%` - Returns "2000" + +### Global XP Rate +**Syntax:** `%mcmmo_xprate%` + +Returns the global XP multiplier configured on the server. + +**Example:** +- `%mcmmo_xprate%` - Returns "1.5" + +### XP Event Active +**Syntax:** `%mcmmo_is_xp_event_active%` + +Returns whether an XP event is active on the server. + +**Return:** +- `true` - If there is an active XP event +- `false` - If there is no event + +--- + +## Party Placeholders + +### Is in Party +**Syntax:** `%mcmmo_in_party%` + +Checks if the player is in a party. + +**Return:** +- `true` - If in a party +- `false` - If not in a party + +### Party Name +**Syntax:** `%mcmmo_party_name%` + +Returns the player's party name (empty if not in any). + +**Example:** +- `%mcmmo_party_name%` - Returns "Adventurers" + +### Is Party Leader +**Syntax:** `%mcmmo_is_party_leader%` + +Checks if the player is the party leader. + +**Return:** +- `true` - If they are the leader +- `false` - If they are not the leader or not in a party + +### Party Leader Name +**Syntax:** `%mcmmo_party_leader%` + +Returns the name of the party leader. + +**Example:** +- `%mcmmo_party_leader%` - Returns "Steve" + +### Party Size +**Syntax:** `%mcmmo_party_size%` + +Returns the number of members in the party. + +**Example:** +- `%mcmmo_party_size%` - Returns "5" + +--- + +## Leaderboard/McTop Placeholders + +### Get Value/Level at Position X (✨ NEW) + +Returns the value (level or power level) of the player at position X in the ranking. + +**Syntax:** +- `%mcmmo_mctop_:%` - For a specific skill +- `%mcmmo_mctop_overall:%` - For overall ranking (power level) + +**Examples:** +- `%mcmmo_mctop_mining:1%` - Returns the Mining level of the 1st place player +- `%mcmmo_mctop_swords:5%` - Returns the Swords level of the 5th place player +- `%mcmmo_mctop_overall:1%` - Returns the power level of the 1st place player + +### Get Player Name at Position X (✨ NEW) + +Returns the player's name at position X in the ranking. + +**Syntax:** +- `%mcmmo_mctop_name_:%` - For a specific skill +- `%mcmmo_mctop_name_overall:%` - For overall ranking + +**Examples:** +- `%mcmmo_mctop_name_mining:1%` - Returns the name of the 1st place player in Mining +- `%mcmmo_mctop_name_swords:10%` - Returns the name of the 10th place player in Swords +- `%mcmmo_mctop_name_overall:1%` - Returns the name of the 1st place player in overall ranking + +### Get Your Overall Rank Position (✨ NEW) + +Returns the player's position in the overall ranking (power level). + +**Syntax:** +- `%mcmmo_rank_overall%` + +**Example:** +- `%mcmmo_rank_overall%` - Returns "15" if the player is in 15th place + +--- + +## Level Check Placeholder (✨ NEW) + +Checks if the player has reached the required level in a specific skill. + +**Syntax:** +- `%mcmmo_checklevel_:%` + +**Return:** +- `✔` - If the player has the required level or higher +- `✘` - If the player does NOT have the required level + +**Examples:** +- `%mcmmo_checklevel_mining:50%` - Returns ✔ if player has Mining level 50+, otherwise ✘ +- `%mcmmo_checklevel_swords:100%` - Returns ✔ if player has Swords level 100+, otherwise ✘ +- `%mcmmo_checklevel_woodcutting:25%` - Returns ✔ if player has Woodcutting level 25+, otherwise ✘ + +--- + +## Available Skills Reference + +All mcMMO skills that can be used in the placeholders above: +- `acrobatics` - Acrobatics +- `alchemy` - Alchemy +- `archery` - Archery +- `axes` - Axes +- `excavation` - Excavation +- `fishing` - Fishing +- `herbalism` - Herbalism +- `mining` - Mining +- `repair` - Repair +- `salvage` - Salvage +- `smelting` - Smelting +- `swords` - Swords +- `taming` - Taming +- `unarmed` - Unarmed +- `woodcutting` - Woodcutting +- `crossbows` - Crossbows +- `tridents` - Tridents +- `maces` - Maces + +--- + +## Menu Usage Examples + +### Top 10 Mining Menu +```yaml +display: + name: "&6Top 10 - Mining" + lore: + - "&71st - %mcmmo_mctop_name_mining:1% &f- &e%mcmmo_mctop_mining:1%" + - "&72nd - %mcmmo_mctop_name_mining:2% &f- &e%mcmmo_mctop_mining:2%" + - "&73rd - %mcmmo_mctop_name_mining:3% &f- &e%mcmmo_mctop_mining:3%" + - "&74th - %mcmmo_mctop_name_mining:4% &f- &e%mcmmo_mctop_mining:4%" + - "&75th - %mcmmo_mctop_name_mining:5% &f- &e%mcmmo_mctop_mining:5%" +``` + +### Overall Ranking Menu +```yaml +display: + name: "&6Overall Ranking - Power Level" + lore: + - "&7Your position: &e#%mcmmo_rank_overall%" + - "" + - "&6Top 3:" + - "&e1st - %mcmmo_mctop_name_overall:1% &f- &6%mcmmo_mctop_overall:1%" + - "&e2nd - %mcmmo_mctop_name_overall:2% &f- &6%mcmmo_mctop_overall:2%" + - "&e3rd - %mcmmo_mctop_name_overall:3% &f- &6%mcmmo_mctop_overall:3%" +``` + +### Requirement Check Menu +```yaml +display: + name: "&6Unlock Special Ability" + lore: + - "&7Requirements:" + - "&7Mining Level 50: %mcmmo_checklevel_mining:50%" + - "&Swords Level 75: %mcmmo_checklevel_swords:75%" + - "&7Woodcutting Level 30: %mcmmo_checklevel_woodcutting:30%" +``` + +## Technical Notes + +- Placeholders work with PlaceholderAPI +- Data is obtained directly from the mcMMO database +- PlaceholderAPI cache is used to optimize queries +- Invalid positions (less than 1 or greater than total players) return empty string +- Invalid levels or non-existent skills return ✘ for checklevel diff --git a/src/main/java/com/gmail/nossr50/placeholders/CheckLevelPlaceholder.java b/src/main/java/com/gmail/nossr50/placeholders/CheckLevelPlaceholder.java new file mode 100644 index 0000000000..b4382d31d8 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/placeholders/CheckLevelPlaceholder.java @@ -0,0 +1,48 @@ +package com.gmail.nossr50.placeholders; + +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import org.bukkit.entity.Player; + +/** + * Placeholder for checking if a player has reached a specific level in a skill + * Usage: %mcmmo_checklevel_:% + * Returns: ✔ if player has the level, ✘ if not + */ +public class CheckLevelPlaceholder implements Placeholder { + private final PapiExpansion papiExpansion; + private final PrimarySkillType skill; + + public CheckLevelPlaceholder(PapiExpansion papiExpansion, PrimarySkillType skill) { + this.papiExpansion = papiExpansion; + this.skill = skill; + // The papiExpansion parameter is intentionally "unused" in this class. + // It's essential for the class to function properly. + // The placeholder needs access to the PapiExpansion instance to query skill levels. + } + + @Override + public String process(Player player, String params) { + if (params == null || params.isEmpty()) { + return "§c✘"; + } + + int requiredLevel; + try { + requiredLevel = Integer.parseInt(params); + } catch (NumberFormatException e) { + return "§c✘"; + } + + Integer currentLevel = papiExpansion.getSkillLevel(skill, player); + if (currentLevel == null) { + return "§c✘"; + } + + return currentLevel >= requiredLevel ? "§a✔" : "§c✘"; + } + + @Override + public String getName() { + return "checklevel_" + skill.toString().toLowerCase(); + } +} diff --git a/src/main/java/com/gmail/nossr50/placeholders/McTopNamePlaceholder.java b/src/main/java/com/gmail/nossr50/placeholders/McTopNamePlaceholder.java new file mode 100644 index 0000000000..8dad0dbf7b --- /dev/null +++ b/src/main/java/com/gmail/nossr50/placeholders/McTopNamePlaceholder.java @@ -0,0 +1,56 @@ +package com.gmail.nossr50.placeholders; + +import com.gmail.nossr50.datatypes.database.PlayerStat; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * Placeholder for getting the player name at a specific position in the leaderboard + * Usage: %mcmmo_mctop_name_:% or %mcmmo_mctop_name_overall:% + */ +public class McTopNamePlaceholder implements Placeholder { + private final PapiExpansion papiExpansion; + private final PrimarySkillType skill; + + public McTopNamePlaceholder(PapiExpansion papiExpansion, PrimarySkillType skill) { + this.papiExpansion = papiExpansion; + this.skill = skill; + } + + @Override + public String process(Player player, String params) { + if (params == null || params.isEmpty()) { + return ""; + } + + int position; + try { + position = Integer.parseInt(params); + } catch (NumberFormatException e) { + return ""; + } + + if (position < 1) { + return ""; + } + + List leaderboard = papiExpansion.getLeaderboard(skill, position); + + if (leaderboard == null || leaderboard.isEmpty() || position > leaderboard.size()) { + return ""; + } + + PlayerStat stat = leaderboard.get(position - 1); + return stat.playerName(); + } + + @Override + public String getName() { + if (skill == null) { + return "mctop_name_overall"; + } + return "mctop_name_" + skill.toString().toLowerCase(); + } +} diff --git a/src/main/java/com/gmail/nossr50/placeholders/McTopPositionPlaceholder.java b/src/main/java/com/gmail/nossr50/placeholders/McTopPositionPlaceholder.java new file mode 100644 index 0000000000..6d2ddb2d4a --- /dev/null +++ b/src/main/java/com/gmail/nossr50/placeholders/McTopPositionPlaceholder.java @@ -0,0 +1,60 @@ +package com.gmail.nossr50.placeholders; + +import com.gmail.nossr50.datatypes.database.PlayerStat; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * Placeholder for getting the skill level/score at a specific position in the leaderboard + * Usage: %mcmmo_mctop_:% or %mcmmo_mctop_overall:% + */ +public class McTopPositionPlaceholder implements Placeholder { + private final PapiExpansion papiExpansion; + private final PrimarySkillType skill; + + public McTopPositionPlaceholder(PapiExpansion papiExpansion, PrimarySkillType skill) { + this.papiExpansion = papiExpansion; + this.skill = skill; + } + + @Override + public String process(Player player, String params) { + if (params == null || params.isEmpty()) { + return ""; + } + + int position; + try { + position = Integer.parseInt(params); + } catch (NumberFormatException e) { + return ""; + } + + if (position < 1) { + return ""; + } + + final int statsPerPage = 10; // Adjust if your leaderboard uses a different page size + List leaderboard = papiExpansion.getLeaderboard(skill, position); + + if (leaderboard == null || leaderboard.isEmpty()) { + return ""; + } + int pageIndex = (position - 1) % statsPerPage; + if (pageIndex >= leaderboard.size()) { + return ""; + } + PlayerStat stat = leaderboard.get(pageIndex); + return String.valueOf(stat.value()); + } + + @Override + public String getName() { + if (skill == null) { + return "mctop_overall"; + } + return "mctop_" + skill.toString().toLowerCase(); + } +} diff --git a/src/main/java/com/gmail/nossr50/placeholders/OverallRankPlaceholder.java b/src/main/java/com/gmail/nossr50/placeholders/OverallRankPlaceholder.java new file mode 100644 index 0000000000..d6b02b0b40 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/placeholders/OverallRankPlaceholder.java @@ -0,0 +1,32 @@ +package com.gmail.nossr50.placeholders; + +import com.gmail.nossr50.api.ExperienceAPI; +import org.bukkit.entity.Player; + +/** + * Placeholder for getting the overall power level rank + * Usage: %mcmmo_rank_overall% + */ +public class OverallRankPlaceholder implements Placeholder { + + public OverallRankPlaceholder(PapiExpansion papiExpansion) { + // The papiExpansion parameter is intentionally unused in this class. + // It is kept for consistency with other Placeholder implementations. + // If future functionality requires access to papiExpansion, it can be stored as a field. + } + + @Override + public String process(Player player, String params) { + try { + int rank = ExperienceAPI.getPlayerRankOverall(player.getUniqueId()); + return String.valueOf(rank); + } catch (Exception ex) { + return ""; + } + } + + @Override + public String getName() { + return "rank_overall"; + } +} diff --git a/src/main/java/com/gmail/nossr50/placeholders/PapiExpansion.java b/src/main/java/com/gmail/nossr50/placeholders/PapiExpansion.java index 06ad1b25b3..1ea45f3f40 100644 --- a/src/main/java/com/gmail/nossr50/placeholders/PapiExpansion.java +++ b/src/main/java/com/gmail/nossr50/placeholders/PapiExpansion.java @@ -1,7 +1,9 @@ package com.gmail.nossr50.placeholders; import com.gmail.nossr50.api.ExperienceAPI; +import com.gmail.nossr50.api.exceptions.InvalidSkillException; import com.gmail.nossr50.config.experience.ExperienceConfig; +import com.gmail.nossr50.datatypes.database.PlayerStat; import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; @@ -9,6 +11,7 @@ import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.text.StringUtils; +import java.util.List; import java.util.Map; import java.util.TreeMap; import me.clip.placeholderapi.PlaceholderAPIPlugin; @@ -196,6 +199,18 @@ public String isExpEventActive(Player player) { : PlaceholderAPIPlugin.booleanFalse(); } + public List getLeaderboard(PrimarySkillType skill, int position) { + try { + // Fetch only the specific entry needed + int statsPerPage = 1; + int pageNumber = position; // Assuming readLeaderboard uses pageNumber as offset for single entry + + return mcMMO.getDatabaseManager().readLeaderboard(skill, pageNumber, statsPerPage); + } catch (InvalidSkillException ex) { + return null; + } + } + public void registerPlaceholder(Placeholder placeholder) { final Placeholder registered = placeholders.get(placeholder.getName()); if (registered != null) { @@ -206,7 +221,7 @@ public void registerPlaceholder(Placeholder placeholder) { placeholders.put(placeholder.getName(), placeholder); } - protected void init() { + private void init() { for (PrimarySkillType skill : PrimarySkillType.values()) { // %mcmmo_level_% @@ -226,6 +241,15 @@ protected void init() { //%mcmmo_xprate_% registerPlaceholder(new SkillXpRatePlaceholder(this, skill)); + + //%mcmmo_mctop_:% + registerPlaceholder(new McTopPositionPlaceholder(this, skill)); + + //%mcmmo_mctop_name_:% + registerPlaceholder(new McTopNamePlaceholder(this, skill)); + + //%mcmmo_checklevel_:% + registerPlaceholder(new CheckLevelPlaceholder(this, skill)); } //%mcmmo_power_level% @@ -251,8 +275,18 @@ protected void init() { // %mcmmo_is_xp_event_active% registerPlaceholder(new XpEventActivePlaceholder(this)); + // %mcmmo_xprate% registerPlaceholder(new XpRatePlaceholder(this)); + + // %mcmmo_rank_overall% + registerPlaceholder(new OverallRankPlaceholder(this)); + + // %mcmmo_mctop_overall:% + registerPlaceholder(new McTopPositionPlaceholder(this, null)); + + // %mcmmo_mctop_name_overall:% + registerPlaceholder(new McTopNamePlaceholder(this, null)); } }