Compare commits
88
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
860d86e424 | ||
|
|
1a87bcd008
|
||
|
|
ac16d028eb
|
||
|
|
184618a0ea
|
||
|
|
9e00df0d5c
|
||
|
|
ca04362ea1
|
||
|
|
26d5fa9598
|
||
|
|
18f055d93c | ||
|
|
070f63f202 | ||
|
|
9e7ff6d170 | ||
|
|
f5aa21d785 | ||
|
|
cbb31352cb | ||
|
|
36fad60bb9 | ||
|
|
625bcf50f8 | ||
|
|
031bc8347d | ||
|
|
ead1832422 | ||
|
|
f233cc3d01 | ||
|
|
a0c6e351db | ||
|
|
71739a7e47 | ||
|
|
c98d605f44 | ||
|
|
7f2048f407 | ||
|
|
1c96e9ed01 | ||
|
|
e248f5cfa0 | ||
|
|
d36ff4723c | ||
|
|
d98aee4d64 | ||
|
|
67d9de29fb | ||
|
|
e687076830 | ||
|
|
38493f29a7 | ||
|
|
fcd0154503 | ||
|
|
1ca9aaed36 | ||
|
|
2008150b11 | ||
|
|
0c31f3a99d | ||
|
|
4f096ca141 | ||
|
|
c0c1b0e849 | ||
|
|
cae1830b4d | ||
|
|
d699c3db87 | ||
|
|
d7d0002f87 | ||
|
|
3c75c7e93f | ||
|
|
d07f0b9e0c | ||
|
|
e917f9bbd5 | ||
|
|
405204148e | ||
|
|
27bdae86bf | ||
|
|
c6fc703255 | ||
|
|
b880fa09fe | ||
|
|
488653651e | ||
|
|
82b7e43af2 | ||
|
|
f6b1b382d1 | ||
|
|
04ff28acab | ||
|
|
d197ad1047 | ||
|
|
b157619568 | ||
|
|
aebcad3f03 | ||
|
|
d6a08aaa90 | ||
|
|
311c4d5ae6 | ||
|
|
22d55cd3ec | ||
|
|
1f185b7c2b | ||
|
|
62fe86f28b | ||
|
|
c6a88e9bc8 | ||
|
|
5d85028e81 | ||
|
|
f40afbd4a4 | ||
|
|
5ae4b03d30 | ||
|
|
543912b8a1 | ||
|
|
b1ca462de9 | ||
|
|
1a47411f3b | ||
|
|
7e193edd13 | ||
|
|
c214807fdd | ||
|
|
c3e63a7475 | ||
|
|
bac11ea028 | ||
|
|
c37165e2c5 | ||
|
|
ddc4f21868 | ||
|
|
7070e2cf38 | ||
|
|
0560fdfd5d | ||
|
|
5726b86bd5 | ||
|
|
aca097a8b1 | ||
|
|
193c614cc6 | ||
|
|
7dd394e5be | ||
|
|
5525065d62 | ||
|
|
86b595a196 | ||
|
|
8030220ffb | ||
|
|
2a7645effc | ||
|
|
ff7e8156ff | ||
|
|
37e88c913c | ||
|
|
babfe91ef1 | ||
|
|
2d10ab8eb6 | ||
|
|
efa90b8101 | ||
|
|
eb41781b3c | ||
|
|
7cfda08d39 | ||
|
|
a84b72071a | ||
|
|
7cb97e7c1d |
No files matched your search
@@ -0,0 +1 @@
|
||||
.gitea/CODEOWNERS linguist-detectable=false
|
||||
@@ -0,0 +1,3 @@
|
||||
# Pull requests request review from the eligible staff team(s).
|
||||
# One approval from either an Admin or Developer satisfies branch protection.
|
||||
* @trueog/Admins @trueog/Developers
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
[submodule "libs/DiamondBank-OG"]
|
||||
path = libs/DiamondBank-OG
|
||||
url = https://github.com/true-og/DiamondBank-OG
|
||||
url = https://git.trueog.net/trueog/DiamondBank-OG
|
||||
[submodule "libs/Utilities-OG"]
|
||||
path = libs/Utilities-OG
|
||||
url = https://github.com/true-og/Utilities-OG
|
||||
url = https://git.trueog.net/trueog/Utilities-OG
|
||||
[submodule "libs/GxUI-OG"]
|
||||
path = libs/GxUI-OG
|
||||
url = https://github.com/true-og/GxUI-OG/
|
||||
url = https://git.trueog.net/trueog/GxUI-OG
|
||||
@@ -0,0 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## 1.2
|
||||
|
||||
- Unified bounty head formatting across item names, armor stand labels, and action bar messaging into a single canonical line showing victim, slayer, and diamond reward.
|
||||
- Removed redundant bounty head lore now that the item display name carries full claim information.
|
||||
- `/bounty check` now resolves offline players who still have active bounties, and tab completion suggests their names.
|
||||
- Bounty check output now formats the amount through the DiamondBank shards-to-diamonds formatter for consistency with head labels and announcements.
|
||||
- Bounty leaderboard GUI now reads the beheading chance from the `bounty-head-drop-chance` config value instead of hardcoding 50%.
|
||||
- Added the missing `playerbountiesog.command.admin.bounty.get` permission entry and Italian translation listing; removed dead `death.announce-claimed` lang key.
|
||||
- Replaced raw stack traces in the bounty data manager with logger output and pruned dead plugin-discovery code from plugin startup.
|
||||
|
||||
## 1.1
|
||||
|
||||
- Added bounty amounts to bounty head item titles so the selected hotbar item text shows the reward without using the action bar.
|
||||
- Updated placed bounty head hover labels to render on separate lines for clearer in-world head information.
|
||||
- Added right-click action bar messaging for bounty heads that shows who claimed the bounty for both hotbar items and placed heads.
|
||||
- Stored claimant metadata on bounty heads so claim information survives item drops and place/break cycles.
|
||||
@@ -1,19 +1,23 @@
|
||||
# PlayerBounties-OG
|
||||
|
||||
## Overview
|
||||
PlayerBounties-OG is a hard fork of [PlayerBountiesPlus](https://github.com/TechnicallyCoded/PlayerBountiesPlus) that lets players set and claim bounties while blocking teammates from claiming rewards. Hooks into [DiamondBank-OG](https://github.com/true-og/DiamondBank-OG) for economy handling and supports [SimpleClans](https://github.com/true-og/SimpleClans). Built for Purpur 1.19.4.
|
||||
PlayerBounties-OG is a hard fork of [PlayerBountiesPlus](https://github.com/TechnicallyCoded/PlayerBountiesPlus) that lets players set and claim bounties while blocking teammates from claiming rewards. Hooks into [DiamondBank-OG](https://git.trueog.net/trueog/DiamondBank-OG) for economy handling and supports [SimpleClans](https://git.trueog.net/trueog/Unions-OG) and API-compatible forks such as Unions-OG. Built for Purpur 1.19.4.
|
||||
|
||||
## Features
|
||||
- Clan-aware bounty system that prevents teammates from claiming each other's bounties.
|
||||
- Integrates with [DiamondBank-OG](https://github.com/true-og/DiamondBank-OG) and supports [SimpleClans](https://github.com/true-og/SimpleClans) for team detection.
|
||||
- Inventory GUI for viewing bounties.
|
||||
- Customizable odds for receiving player head from bounty victim.
|
||||
- Localized messages in multiple languages.
|
||||
- Uses [LuckPerms](https://github.com/true-og/LuckPerms) for player name formatting.
|
||||
- Integrates [CoreProtect-OG](https://git.trueog.net/trueog/CoreProtect-OG) for player head rollback injection.
|
||||
- Integrates [DiamondBank-OG](https://git.trueog.net/trueog/DiamondBank-OG) for currency.
|
||||
- Integrates [LuckPerms](https://git.trueog.net/trueog/LuckPerms) for player name and rank formatting.
|
||||
- Integrates [SimpleClans](https://git.trueog.net/trueog/Unions-OG) and compatible forks such as Unions-OG for team detection.
|
||||
|
||||
## Commands
|
||||
- `/bounty set <player> <amount>` – set a bounty on a player
|
||||
- `/bounty add <player> <amount>` – add diamonds to an existing bounty
|
||||
- `/bounty top` – list the top bounties
|
||||
- `/bounty check <player>` – check a player's bounty
|
||||
- `/bounty check <player>` – check a player's bounty (online or offline)
|
||||
- `/pbp reload` – reload configuration and messages
|
||||
- `/pbp version` – display plugin version
|
||||
- `/pbp bounty set <player> <amount>` – admin set a bounty
|
||||
@@ -25,22 +29,22 @@ PlayerBounties-OG is a hard fork of [PlayerBountiesPlus](https://github.com/Tech
|
||||
## Permissions
|
||||
| Permission | Default |
|
||||
| --- | --- |
|
||||
| `playerbountiesplus.command.bounty` | all |
|
||||
| `playerbountiesplus.command.bounty.set` | all |
|
||||
| `playerbountiesplus.event.claim` | all |
|
||||
| `playerbountiesplus.command.admin` | op |
|
||||
| `playerbountiesplus.command.admin.version` | op |
|
||||
| `playerbountiesplus.command.admin.reload` | op |
|
||||
| `playerbountiesplus.command.admin.bounty.set` | op |
|
||||
| `playerbountiesplus.command.admin.bounty.add` | op |
|
||||
| `playerbountiesplus.command.admin.bounty.remove` | op |
|
||||
| `playerbountiesplus.command.admin.bounty.delete` | op |
|
||||
| `playerbountiesplus.command.admin.bounty.get` | op |
|
||||
| `playerbountiesog.command.bounty` | all |
|
||||
| `playerbountiesog.command.bounty.set` | all |
|
||||
| `playerbountiesog.command.bounty.add` | all |
|
||||
| `playerbountiesog.event.claim` | all |
|
||||
| `playerbountiesog.command.admin` | op |
|
||||
| `playerbountiesog.command.admin.version` | op |
|
||||
| `playerbountiesog.command.admin.reload` | op |
|
||||
| `playerbountiesog.command.admin.bounty.set` | op |
|
||||
| `playerbountiesog.command.admin.bounty.add` | op |
|
||||
| `playerbountiesog.command.admin.bounty.remove` | op |
|
||||
| `playerbountiesog.command.admin.bounty.delete` | op |
|
||||
| `playerbountiesog.command.admin.bounty.get` | op |
|
||||
|
||||
## Developer Notes
|
||||
- Commands reside in `com.tcoded.playerbountiesplus.command` with subpackages for bounty and admin actions.
|
||||
- Event listeners like `DeathListener` handle bounty claim logic.
|
||||
- Hooks under `com.tcoded.playerbountiesplus.hook` integrate with DiamondBank-OG and SimpleClans.
|
||||
- Hooks under `com.tcoded.playerbountiesplus.hook` integrate with DiamondBank-OG and supported clan/team plugins such as SimpleClans and Unions-OG.
|
||||
- Inventory GUIs in `com.tcoded.playerbountiesplus.gui` present bounty data.
|
||||
- `BountyDataManager` manages persistence of bounties.
|
||||
|
||||
+3
-3
@@ -1,5 +1,3 @@
|
||||
/* This is free and unencumbered software released into the public domain */
|
||||
|
||||
/* ------------------------------ Plugins ------------------------------ */
|
||||
plugins {
|
||||
id("java") // Import Java plugin.
|
||||
@@ -29,7 +27,7 @@ kotlin { jvmToolchain(17) }
|
||||
/* ----------------------------- Metadata ------------------------------ */
|
||||
group = "com.tcoded" // Declare bundle identifier.
|
||||
|
||||
version = "1.0" // Declare plugin version (will be in .jar).
|
||||
version = "1.1" // Declare plugin version (will be in .jar).
|
||||
|
||||
val apiVersion = "1.19" // Declare minecraft server target version.
|
||||
|
||||
@@ -66,6 +64,7 @@ repositories {
|
||||
maven { url = uri("https://repo.glaremasters.me/repository/towny/") }
|
||||
maven { url = uri("https://repo.extendedclip.com/releases/") }
|
||||
maven { url = uri("https://repo.tcoded.com/public/") }
|
||||
maven { url = uri("https://maven.playpro.com") }
|
||||
}
|
||||
|
||||
/* ---------------------- Java project deps ---------------------------- */
|
||||
@@ -73,6 +72,7 @@ dependencies {
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged.
|
||||
compileOnly("net.sacredlabyrinth.phaed.simpleclans:SimpleClans:2.15.2")
|
||||
compileOnly("net.luckperms:api:5.5") // Import LuckPerms API.
|
||||
compileOnly("net.coreprotect:coreprotect:23.1")
|
||||
compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG Java API (from source).
|
||||
compileOnlyApi(project(":libs:GxUI-OG")) // Import TrueOG Network GxUI-OG Java API (from source).
|
||||
compileOnlyApi(project(":libs:DiamondBank-OG")) {
|
||||
|
||||
+1
-1
Submodule libs/DiamondBank-OG updated: ec4a71782b...39d5a2b936.
+1
-1
Submodule libs/GxUI-OG updated: ebd8df041e...287d0eb3ee.
+1
-1
Submodule libs/Utilities-OG updated: 3705eafbc3...8b5c86cc9c.
@@ -1,25 +1,28 @@
|
||||
package com.tcoded.playerbountiesplus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
import org.bukkit.plugin.ServicesManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.tcoded.playerbountiesplus.command.BountyCommand;
|
||||
import com.tcoded.playerbountiesplus.command.PlayerBountiesPlusAdminCmd;
|
||||
import com.tcoded.playerbountiesplus.hook.currency.DiamondBankOGHook;
|
||||
import com.tcoded.playerbountiesplus.hook.logging.CoreProtectHook;
|
||||
import com.tcoded.playerbountiesplus.hook.currency.EconomyHook;
|
||||
import com.tcoded.playerbountiesplus.hook.team.TeamHook;
|
||||
import com.tcoded.playerbountiesplus.listener.BountyHeadLabelListener;
|
||||
import com.tcoded.playerbountiesplus.listener.BountyHeadListener;
|
||||
import com.tcoded.playerbountiesplus.listener.BountyIncreasePromptListener;
|
||||
import com.tcoded.playerbountiesplus.listener.DeathListener;
|
||||
import com.tcoded.playerbountiesplus.listener.GuiProtectionListener;
|
||||
import com.tcoded.playerbountiesplus.manager.BountyDataManager;
|
||||
import com.tcoded.playerbountiesplus.util.BountyHeadFormatter;
|
||||
import com.tcoded.playerbountiesplus.util.BountyHeadMetadata;
|
||||
import com.tcoded.playerbountiesplus.util.LangUtil;
|
||||
|
||||
import net.luckperms.api.LuckPerms;
|
||||
@@ -45,6 +48,8 @@ public final class PlayerBountiesOG extends JavaPlugin {
|
||||
|
||||
// Utilities.
|
||||
private LangUtil langUtil;
|
||||
private BountyHeadFormatter bountyHeadFormatter;
|
||||
private BountyHeadMetadata bountyHeadMetadata;
|
||||
|
||||
// Managers.
|
||||
private BountyDataManager bountyDataManager;
|
||||
@@ -59,6 +64,12 @@ public final class PlayerBountiesOG extends JavaPlugin {
|
||||
// LuckPerms API hook.
|
||||
private LuckPerms luckPerms;
|
||||
|
||||
// CoreProtect API hook.
|
||||
private CoreProtectHook coreProtectHook;
|
||||
|
||||
private BountyIncreasePromptListener bountyIncreasePromptListener;
|
||||
private BountyHeadLabelListener bountyHeadLabelListener;
|
||||
|
||||
public PlayerBountiesOG() {
|
||||
|
||||
instance = this;
|
||||
@@ -86,6 +97,14 @@ public final class PlayerBountiesOG extends JavaPlugin {
|
||||
|
||||
}
|
||||
|
||||
this.coreProtectHook = new CoreProtectHook(this);
|
||||
if (!this.coreProtectHook.isEnabled()) {
|
||||
|
||||
getLogger().warning(
|
||||
"CoreProtect API v10+ not found - custom bounty head metadata may not rollback correctly.");
|
||||
|
||||
}
|
||||
|
||||
// Initialize the DiamondBank-OG API.
|
||||
final RegisteredServiceProvider<DiamondBankAPIJava> provider = getServer().getServicesManager()
|
||||
.getRegistration(DiamondBankAPIJava.class);
|
||||
@@ -104,6 +123,8 @@ public final class PlayerBountiesOG extends JavaPlugin {
|
||||
|
||||
// Utils.
|
||||
this.reloadLang();
|
||||
this.bountyHeadFormatter = new BountyHeadFormatter(this.diamondBankAPI, this.luckPerms);
|
||||
this.bountyHeadMetadata = new BountyHeadMetadata(this);
|
||||
|
||||
// Managers.
|
||||
this.bountyDataManager = new BountyDataManager(this);
|
||||
@@ -141,18 +162,14 @@ public final class PlayerBountiesOG extends JavaPlugin {
|
||||
}
|
||||
|
||||
// Listeners.
|
||||
this.getServer().getPluginManager().registerEvents(new DeathListener(this, this.diamondBankAPI, this.luckPerms),
|
||||
this);
|
||||
this.getServer().getPluginManager().registerEvents(new DeathListener(this), this);
|
||||
this.bountyIncreasePromptListener = new BountyIncreasePromptListener(this);
|
||||
|
||||
final List<Plugin> plugins = ImmutableList.copyOf(this.getServer().getPluginManager().getPlugins());
|
||||
findPluginWithQuery(plugins, "team");
|
||||
findPluginWithQuery(plugins, "teams");
|
||||
findPluginWithQuery(plugins, "clan");
|
||||
findPluginWithQuery(plugins, "clans");
|
||||
findPluginWithQuery(plugins, "party", "voteparty");
|
||||
findPluginWithQuery(plugins, "parties");
|
||||
findPluginWithQuery(plugins, "guild");
|
||||
findPluginWithQuery(plugins, "guilds");
|
||||
this.getServer().getPluginManager().registerEvents(new BountyHeadListener(this), this);
|
||||
this.bountyHeadLabelListener = new BountyHeadLabelListener(this);
|
||||
this.getServer().getPluginManager().registerEvents(this.bountyHeadLabelListener, this);
|
||||
this.getServer().getPluginManager().registerEvents(this.bountyIncreasePromptListener, this);
|
||||
this.getServer().getPluginManager().registerEvents(new GuiProtectionListener(this), this);
|
||||
|
||||
}
|
||||
|
||||
@@ -165,6 +182,12 @@ public final class PlayerBountiesOG extends JavaPlugin {
|
||||
@Override
|
||||
public void onDisable() {
|
||||
|
||||
if (this.bountyHeadLabelListener != null) {
|
||||
|
||||
this.bountyHeadLabelListener.shutdown();
|
||||
|
||||
}
|
||||
|
||||
HandlerList.unregisterAll(this);
|
||||
|
||||
UtilitiesOG.logToConsole(getPrefix(), "PlayerBounties-OG has been shut down successfully!");
|
||||
@@ -201,45 +224,33 @@ public final class PlayerBountiesOG extends JavaPlugin {
|
||||
|
||||
}
|
||||
|
||||
// Utilities.
|
||||
private static void findPluginWithQuery(List<Plugin> plugins, String pluginNameQuery, String... excludeStrings) {
|
||||
public LuckPerms getLuckPerms() {
|
||||
|
||||
final String firstPluginFound = plugins.stream().filter(p -> {
|
||||
return this.luckPerms;
|
||||
|
||||
final String lowerName = StringUtils.lowerCase(p.getName());
|
||||
// If the plugin name doesn't contain the query, skip.
|
||||
if (!StringUtils.contains(lowerName, pluginNameQuery)) {
|
||||
}
|
||||
|
||||
return false;
|
||||
public CoreProtectHook getCoreProtectHook() {
|
||||
|
||||
}
|
||||
return this.coreProtectHook;
|
||||
|
||||
// If the plugin name contains any of the exclude strings, skip.
|
||||
for (String excludeString : excludeStrings) {
|
||||
}
|
||||
|
||||
if (StringUtils.contains(lowerName, excludeString)) {
|
||||
public BountyHeadFormatter getBountyHeadFormatter() {
|
||||
|
||||
return false;
|
||||
return this.bountyHeadFormatter;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public BountyHeadMetadata getBountyHeadMetadata() {
|
||||
|
||||
return true;
|
||||
return this.bountyHeadMetadata;
|
||||
|
||||
}).findFirst().map(p -> {
|
||||
}
|
||||
|
||||
final List<String> authors = p.getPluginMeta().getAuthors();
|
||||
public BountyIncreasePromptListener getBountyIncreasePromptListener() {
|
||||
|
||||
return p.getName() + " (" + (authors.isEmpty() ? "N/A" : authors.get(0)) + ")";
|
||||
|
||||
}).orElse(null);
|
||||
|
||||
if (firstPluginFound == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
return this.bountyIncreasePromptListener;
|
||||
|
||||
}
|
||||
|
||||
@@ -289,4 +300,4 @@ public final class PlayerBountiesOG extends JavaPlugin {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -15,6 +16,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
import com.tcoded.playerbountiesplus.command.bounty.BountyCheckCmd;
|
||||
import com.tcoded.playerbountiesplus.command.bounty.BountyAddCmd;
|
||||
import com.tcoded.playerbountiesplus.command.bounty.BountySetCmd;
|
||||
import com.tcoded.playerbountiesplus.command.bounty.BountyTopCmd;
|
||||
|
||||
@@ -36,27 +38,30 @@ public class BountyCommand implements CommandExecutor, TabCompleter {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command cmd, String cmdName, String[] args) {
|
||||
|
||||
final Player player;
|
||||
if (!(sender instanceof Player)) {
|
||||
|
||||
UtilitiesOG.logToConsole(PlayerBountiesOG.getPrefix(), "&cERROR: Only players can use this command.");
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
player = (Player) sender;
|
||||
|
||||
}
|
||||
|
||||
if (args.length < 1) {
|
||||
|
||||
UtilitiesOG.trueogMessage(player, plugin.getLang().getColored("command.bounty.no-action"));
|
||||
if (StringUtils.equalsIgnoreCase(cmdName, "bounties")) {
|
||||
|
||||
return BountyTopCmd.handleCmd(plugin, diamondBankAPI, sender, cmd, cmdName, args);
|
||||
|
||||
}
|
||||
|
||||
final String noAction = plugin.getLang().getColored("command.bounty.no-action");
|
||||
UtilitiesOG.trueogMessage((Player) sender, noAction);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final String action = args[0].toLowerCase();
|
||||
final String action = StringUtils.lowerCase(args[0]);
|
||||
switch (action) {
|
||||
|
||||
case "set" -> {
|
||||
@@ -65,6 +70,12 @@ public class BountyCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
}
|
||||
|
||||
case "add" -> {
|
||||
|
||||
return BountyAddCmd.handleCmd(plugin, sender, cmd, cmdName, args);
|
||||
|
||||
}
|
||||
|
||||
case "top" -> {
|
||||
|
||||
return BountyTopCmd.handleCmd(plugin, diamondBankAPI, sender, cmd, cmdName, args);
|
||||
@@ -76,7 +87,6 @@ public class BountyCommand implements CommandExecutor, TabCompleter {
|
||||
return BountyCheckCmd.handleCmd(plugin, sender, cmd, cmdName, args);
|
||||
|
||||
}
|
||||
|
||||
default -> {
|
||||
|
||||
// If "/bounty" is run without a valid subcommand, treat it as "/bounty top".
|
||||
@@ -96,19 +106,26 @@ public class BountyCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
if (args.length == 1) {
|
||||
|
||||
return Lists.newArrayList("set", "top", "check").stream()
|
||||
.filter(action -> action.startsWith(args[0].toLowerCase())).collect(Collectors.toList());
|
||||
return Lists.newArrayList("set", "add", "top", "check").stream()
|
||||
.filter(action -> StringUtils.startsWith(action, StringUtils.lowerCase(args[0])))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
} else if (args.length > 1) {
|
||||
|
||||
final String subCommand = args[0].toLowerCase();
|
||||
final String subCommand = StringUtils.lowerCase(args[0]);
|
||||
return switch (subCommand) {
|
||||
|
||||
case "set" -> BountySetCmd.onTabComplete(sender, args).stream()
|
||||
.filter(suggestion -> suggestion.toLowerCase().startsWith(args[1].toLowerCase()))
|
||||
case "set" -> BountySetCmd
|
||||
.onTabComplete(sender, args).stream().filter(suggestion -> StringUtils
|
||||
.startsWith(StringUtils.lowerCase(suggestion), StringUtils.lowerCase(args[1])))
|
||||
.collect(Collectors.toList());
|
||||
case "check" -> BountyCheckCmd.onTabComplete(sender, args).stream()
|
||||
.filter(suggestion -> suggestion.toLowerCase().startsWith(args[1].toLowerCase()))
|
||||
case "add" -> BountyAddCmd
|
||||
.onTabComplete(sender, args).stream().filter(suggestion -> StringUtils
|
||||
.startsWith(StringUtils.lowerCase(suggestion), StringUtils.lowerCase(args[1])))
|
||||
.collect(Collectors.toList());
|
||||
case "check" -> BountyCheckCmd
|
||||
.onTabComplete(sender, args).stream().filter(suggestion -> StringUtils
|
||||
.startsWith(StringUtils.lowerCase(suggestion), StringUtils.lowerCase(args[1])))
|
||||
.collect(Collectors.toList());
|
||||
case "top" -> Collections.emptyList();
|
||||
default -> Collections.emptyList();
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -45,7 +46,7 @@ public class PlayerBountiesPlusAdminCmd implements CommandExecutor, TabCompleter
|
||||
|
||||
}
|
||||
|
||||
final String arg0Lower = args[0].toLowerCase();
|
||||
final String arg0Lower = StringUtils.lowerCase(args[0]);
|
||||
return switch (arg0Lower) {
|
||||
|
||||
case "reload" -> PlayerBountiesPlusReloadCmd.handleCmd(plugin, sender, command, label, args);
|
||||
@@ -96,12 +97,13 @@ public class PlayerBountiesPlusAdminCmd implements CommandExecutor, TabCompleter
|
||||
|
||||
if (args.length == 1) {
|
||||
|
||||
return completions.stream().filter(s -> s.toLowerCase().startsWith(args[0].toLowerCase()))
|
||||
return completions.stream()
|
||||
.filter(s -> StringUtils.startsWith(StringUtils.lowerCase(s), StringUtils.lowerCase(args[0])))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
} else if (args.length > 1) {
|
||||
|
||||
final String subCommand = args[0].toLowerCase();
|
||||
final String subCommand = StringUtils.lowerCase(args[0]);
|
||||
return switch (subCommand) {
|
||||
|
||||
case "bounty" -> AdminBountyCmd.onTabComplete(sender, args);
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class PlayerBountiesPlusReloadCmd implements TabCompleter {
|
||||
|
||||
private static final String RELOAD_PERMISSION = "playerbountiesplus.command.playerbountiesplus.reload";
|
||||
private static final String RELOAD_PERMISSION = "playerbountiesog.command.admin.reload";
|
||||
|
||||
public static boolean handleCmd(PlayerBountiesOG plugin, CommandSender sender, Command cmd, String cmdName,
|
||||
String[] args)
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class PlayerBountiesPlusVersionCmd {
|
||||
|
||||
private static final String VERSION_PERMISSION = "playerbountiesplus.command.playerbountiesplus.version";
|
||||
private static final String VERSION_PERMISSION = "playerbountiesog.command.admin.version";
|
||||
|
||||
public static boolean handleCmd(PlayerBountiesOG plugin, CommandSender sender, Command cmd, String cmdName,
|
||||
String[] args)
|
||||
|
||||
+15
-3
@@ -19,7 +19,7 @@ import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class AdminBountyAddCmd {
|
||||
|
||||
private static final String PERMISSION = "playerbountiesplus.command.admin.bounty.add";
|
||||
private static final String PERMISSION = "playerbountiesog.command.admin.bounty.add";
|
||||
|
||||
public static boolean handleCmd(PlayerBountiesOG plugin, CommandSender sender, Command cmd, String label,
|
||||
String[] args)
|
||||
@@ -64,10 +64,10 @@ public class AdminBountyAddCmd {
|
||||
}
|
||||
|
||||
final String playerName = args[2];
|
||||
final int amount;
|
||||
final double amount;
|
||||
try {
|
||||
|
||||
amount = Integer.parseInt(args[3]);
|
||||
amount = parseAmount(args[3]);
|
||||
|
||||
} catch (NumberFormatException numberFormatException) {
|
||||
|
||||
@@ -131,6 +131,18 @@ public class AdminBountyAddCmd {
|
||||
|
||||
}
|
||||
|
||||
private static double parseAmount(String amountInput) {
|
||||
|
||||
if (!amountInput.matches("^\\d+(?:\\.\\d)?$")) {
|
||||
|
||||
throw new NumberFormatException("Invalid amount format");
|
||||
|
||||
}
|
||||
|
||||
return Double.parseDouble(amountInput);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||
@NotNull String label, @NotNull String[] args)
|
||||
|
||||
+6
-4
@@ -5,6 +5,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -36,7 +37,7 @@ public class AdminBountyCmd {
|
||||
|
||||
}
|
||||
|
||||
final String action = args[1].toLowerCase();
|
||||
final String action = StringUtils.lowerCase(args[1]);
|
||||
return switch (action) {
|
||||
|
||||
case "set" -> AdminBountySetCmd.handleCmd(plugin, sender, cmd, label, args);
|
||||
@@ -75,7 +76,7 @@ public class AdminBountyCmd {
|
||||
|
||||
} else if (args.length > 2) {
|
||||
|
||||
final String sub = args[1].toLowerCase();
|
||||
final String sub = StringUtils.lowerCase(args[1]);
|
||||
switch (sub) {
|
||||
|
||||
case "add" -> suggestions = AdminBountyAddCmd.onTabComplete(sender, null, null, args);
|
||||
@@ -88,8 +89,9 @@ public class AdminBountyCmd {
|
||||
|
||||
}
|
||||
|
||||
final String input = args[args.length - 1].toLowerCase();
|
||||
return suggestions.stream().filter(opt -> opt.toLowerCase().startsWith(input)).collect(Collectors.toList());
|
||||
final String input = StringUtils.lowerCase(args[args.length - 1]);
|
||||
return suggestions.stream().filter(opt -> StringUtils.startsWith(StringUtils.lowerCase(opt), input))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class AdminBountyDeleteCmd {
|
||||
|
||||
private static final String PERMISSION = "playerbountiesplus.command.admin.bounty.delete";
|
||||
private static final String PERMISSION = "playerbountiesog.command.admin.bounty.delete";
|
||||
|
||||
public static boolean handleCmd(PlayerBountiesOG plugin, CommandSender sender, Command cmd, String label,
|
||||
String[] args)
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class AdminBountyGetCmd {
|
||||
|
||||
private static final String PERMISSION = "playerbountiesplus.command.admin.bounty.get";
|
||||
private static final String PERMISSION = "playerbountiesog.command.admin.bounty.get";
|
||||
|
||||
public static boolean handleCmd(PlayerBountiesOG plugin, CommandSender sender, Command cmd, String label,
|
||||
String[] args)
|
||||
|
||||
+15
-3
@@ -19,7 +19,7 @@ import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class AdminBountyRemoveCmd {
|
||||
|
||||
private static final String PERMISSION = "playerbountiesplus.command.admin.bounty.remove";
|
||||
private static final String PERMISSION = "playerbountiesog.command.admin.bounty.remove";
|
||||
|
||||
public static boolean handleCmd(PlayerBountiesOG plugin, CommandSender sender, Command cmd, String label,
|
||||
String[] args)
|
||||
@@ -63,10 +63,10 @@ public class AdminBountyRemoveCmd {
|
||||
}
|
||||
|
||||
final String playerName = args[2];
|
||||
final int amount;
|
||||
final double amount;
|
||||
try {
|
||||
|
||||
amount = Integer.parseInt(args[3]);
|
||||
amount = parseAmount(args[3]);
|
||||
|
||||
} catch (NumberFormatException numberFormatException) {
|
||||
|
||||
@@ -139,6 +139,18 @@ public class AdminBountyRemoveCmd {
|
||||
|
||||
}
|
||||
|
||||
private static double parseAmount(String amountInput) {
|
||||
|
||||
if (!amountInput.matches("^\\d+(?:\\.\\d)?$")) {
|
||||
|
||||
throw new NumberFormatException("Invalid amount format");
|
||||
|
||||
}
|
||||
|
||||
return Double.parseDouble(amountInput);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||
@NotNull String label, @NotNull String[] args)
|
||||
|
||||
+15
-3
@@ -19,7 +19,7 @@ import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class AdminBountySetCmd {
|
||||
|
||||
private static final String PERMISSION = "playerbountiesplus.command.admin.bounty.set";
|
||||
private static final String PERMISSION = "playerbountiesog.command.admin.bounty.set";
|
||||
|
||||
public static boolean handleCmd(PlayerBountiesOG plugin, CommandSender sender, Command cmd, String label,
|
||||
String[] args)
|
||||
@@ -62,10 +62,10 @@ public class AdminBountySetCmd {
|
||||
}
|
||||
|
||||
final String playerName = args[2];
|
||||
final int amount;
|
||||
final double amount;
|
||||
try {
|
||||
|
||||
amount = Integer.parseInt(args[3]);
|
||||
amount = parseAmount(args[3]);
|
||||
|
||||
} catch (NumberFormatException numberFormatException) {
|
||||
|
||||
@@ -125,6 +125,18 @@ public class AdminBountySetCmd {
|
||||
|
||||
}
|
||||
|
||||
private static double parseAmount(String amountInput) {
|
||||
|
||||
if (!amountInput.matches("^\\d+(?:\\.\\d)?$")) {
|
||||
|
||||
throw new NumberFormatException("Invalid amount format");
|
||||
|
||||
}
|
||||
|
||||
return Double.parseDouble(amountInput);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||
@NotNull String label, @NotNull String[] args)
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
package com.tcoded.playerbountiesplus.command.bounty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
import com.tcoded.playerbountiesplus.manager.BountyDataManager;
|
||||
import com.tcoded.playerbountiesplus.util.BountyWorldUtil;
|
||||
|
||||
import net.luckperms.api.LuckPerms;
|
||||
import net.luckperms.api.cacheddata.CachedMetaData;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import net.trueog.diamondbankog.DiamondBankException.EconomyDisabledException;
|
||||
import net.trueog.diamondbankog.DiamondBankException.InsufficientFundsException;
|
||||
import net.trueog.diamondbankog.DiamondBankException.InvalidPlayerException;
|
||||
import net.trueog.diamondbankog.DiamondBankException.PlayerNotOnlineException;
|
||||
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class BountyAddCmd {
|
||||
|
||||
public static boolean handleCmd(PlayerBountiesOG plugin, CommandSender sender, Command cmd, String cmdName,
|
||||
String[] args)
|
||||
{
|
||||
|
||||
if (!(sender instanceof Player player)) {
|
||||
|
||||
UtilitiesOG.logToConsole(PlayerBountiesOG.getPrefix(), "&cERROR: Only players can use this command.");
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if (!sender.hasPermission("playerbountiesog.command.bounty.add")) {
|
||||
|
||||
final String noPerm = plugin.getLang().getColored("command.no-permission");
|
||||
final String noPermDetailed = plugin.getLang().getColored("command.no-permission-detailed")
|
||||
.replace("{no-permission-msg}", noPerm)
|
||||
.replace("{permission}", "playerbountiesog.command.bounty.add");
|
||||
UtilitiesOG.trueogMessage(player, noPermDetailed);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if (!BountyWorldUtil.isBountyWorldAllowed(plugin, player)) {
|
||||
|
||||
UtilitiesOG.trueogMessage(player, plugin.getLang().getColored("command.bounty.world-not-whitelisted")
|
||||
.replace("{worlds}", BountyWorldUtil.formatBountyWorldWhitelist(plugin)));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if (args.length < 3) {
|
||||
|
||||
UtilitiesOG.trueogMessage(player, plugin.getLang().getColored("command.bounty.add.missing-args"));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final String targetNameArg = args[1];
|
||||
final double amount;
|
||||
try {
|
||||
|
||||
amount = parseAmount(args[2]);
|
||||
|
||||
} catch (NumberFormatException numberFormatException) {
|
||||
|
||||
UtilitiesOG.trueogMessage(player, plugin.getLang().getColored("command.bounty.add.amount-nan"));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final OfflinePlayer target = Bukkit.getOfflinePlayer(targetNameArg);
|
||||
if (target.getUniqueId() != null) {
|
||||
|
||||
return addBounty(plugin, player, target.getUniqueId(), target.getName(), amount, true);
|
||||
|
||||
}
|
||||
|
||||
UtilitiesOG.trueogMessage(player, plugin.getLang().getColored("command.bounty.add.player-not-found"));
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public static boolean addBounty(PlayerBountiesOG plugin, Player setter, UUID targetUuid, String targetName,
|
||||
double parsedAmount, boolean saveData)
|
||||
{
|
||||
|
||||
if (!BountyWorldUtil.isBountyWorldAllowed(plugin, setter)) {
|
||||
|
||||
UtilitiesOG.trueogMessage(setter, plugin.getLang().getColored("command.bounty.world-not-whitelisted")
|
||||
.replace("{worlds}", BountyWorldUtil.formatBountyWorldWhitelist(plugin)));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final String safeTargetName = StringUtils.defaultIfBlank(targetName,
|
||||
Bukkit.getOfflinePlayer(targetUuid).getName());
|
||||
if (safeTargetName == null || safeTargetName.isBlank()) {
|
||||
|
||||
UtilitiesOG.trueogMessage(setter, plugin.getLang().getColored("command.bounty.add.player-not-found"));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final DiamondBankAPIJava diamondBankAPI = resolveDiamondBankApi(plugin);
|
||||
|
||||
final double minimum = plugin.getConfig().getDouble("bounty-minimum", 1.0);
|
||||
if (parsedAmount < minimum) {
|
||||
|
||||
UtilitiesOG.trueogMessage(setter, plugin.getLang().getColored("command.bounty.set.under-minimum")
|
||||
.replace("{minimum}", String.valueOf(minimum)));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final double maximum = plugin.getConfig().getDouble("bounty-maximum", 1000000.0);
|
||||
if (parsedAmount > maximum) {
|
||||
|
||||
UtilitiesOG.trueogMessage(setter, plugin.getLang().getColored("command.bounty.set.over-maximum")
|
||||
.replace("{maximum}", String.valueOf(maximum)));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final long addedShards = diamondBankAPI.diamondsToShards((float) parsedAmount);
|
||||
if (addedShards <= 0L) {
|
||||
|
||||
UtilitiesOG.trueogMessage(setter, plugin.getLang().getColored("command.bounty.set.internal-invalid-value"));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if (!chargeSetter(plugin, setter, diamondBankAPI, safeTargetName, addedShards)) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final BountyDataManager bountyDataManager = plugin.getBountyDataManager();
|
||||
final long existingShards = diamondBankAPI.diamondsToShards((float) bountyDataManager.getBounty(targetUuid));
|
||||
final long totalShards = existingShards + addedShards;
|
||||
final double totalBounty = Double.parseDouble(diamondBankAPI.shardsToDiamonds(totalShards));
|
||||
|
||||
bountyDataManager.setBounty(targetUuid, totalBounty);
|
||||
|
||||
final LuckPerms luckPerms = resolveLuckPerms(plugin);
|
||||
final String setterDisplay = formatLuckPermsDisplay(luckPerms, setter);
|
||||
final String targetDisplay = resolveLuckPermsDisplayName(luckPerms, targetUuid, safeTargetName);
|
||||
final String addedDisplay = diamondBankAPI.shardsToDiamonds(addedShards);
|
||||
final String totalDisplay = diamondBankAPI.shardsToDiamonds(totalShards);
|
||||
|
||||
UtilitiesOG.trueogMessage(setter, plugin.getLang().getColored("command.bounty.add.success")
|
||||
.replace("{amount}", addedDisplay).replace("{target}", targetDisplay).replace("{total}", totalDisplay));
|
||||
|
||||
if (plugin.getConfig().getBoolean("bounty-placed-announce", true)) {
|
||||
|
||||
final String announce = plugin.getLang().getColored("command.bounty.add.announce")
|
||||
.replace("{amount}", addedDisplay).replace("{target}", targetDisplay)
|
||||
.replace("{total}", totalDisplay).replace("{player}", setterDisplay);
|
||||
|
||||
Bukkit.getOnlinePlayers().forEach(
|
||||
player -> UtilitiesOG.trueogMessage(player, PlayerBountiesOG.getPrefix() + "&a" + announce));
|
||||
|
||||
}
|
||||
|
||||
if (saveData) {
|
||||
|
||||
bountyDataManager.saveBountiesAsync();
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private static boolean chargeSetter(PlayerBountiesOG plugin, Player setter, DiamondBankAPIJava diamondBankAPI,
|
||||
String targetName, long addedShards)
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
diamondBankAPI.consumeFromPlayer(setter.getUniqueId(), addedShards,
|
||||
"Player " + setter.getName() + " contributed " + diamondBankAPI.shardsToDiamonds(addedShards)
|
||||
+ " Diamonds toward a bounty on " + targetName + ".",
|
||||
"Plugin: PlayerBounties-OG");
|
||||
|
||||
return true;
|
||||
|
||||
} catch (EconomyDisabledException economyDisabledException) {
|
||||
|
||||
PlayerBountiesOG.disableSelf(
|
||||
"[PlayerBounties-OG] DiamondBank-OG economy disabled: " + economyDisabledException.getMessage());
|
||||
return false;
|
||||
|
||||
} catch (InvalidPlayerException invalidPlayerException) {
|
||||
|
||||
plugin.getLogger().warning("Invalid player for consumeFromPlayer: " + setter.getName());
|
||||
UtilitiesOG.trueogMessage(setter,
|
||||
"&cERROR: your player account could not be found by DiamondBank-OG. Contact an administrator!");
|
||||
return false;
|
||||
|
||||
} catch (InsufficientFundsException insufficientFundsException) {
|
||||
|
||||
UtilitiesOG.trueogMessage(setter, plugin.getLang().getColored("command.bounty.set.not-enough-money"));
|
||||
return false;
|
||||
|
||||
}
|
||||
// TODO: Remove PlayerNotOnlineException catch block after next DiamondBank-OG
|
||||
// update.
|
||||
catch (PlayerNotOnlineException temporaryPlayerNotOnlineError) {
|
||||
|
||||
plugin.getLogger().warning("Player " + setter.getName()
|
||||
+ " not online when attempting to consumeFromPlayer. This error is expected to be removed in the next DiamondBank-OG update.");
|
||||
temporaryPlayerNotOnlineError.printStackTrace();
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static double parseAmount(String amountInput) {
|
||||
|
||||
if (!amountInput.matches("^\\d+(?:\\.\\d)?$")) {
|
||||
|
||||
throw new NumberFormatException("Invalid amount format");
|
||||
|
||||
}
|
||||
|
||||
return Double.parseDouble(amountInput);
|
||||
|
||||
}
|
||||
|
||||
private static DiamondBankAPIJava resolveDiamondBankApi(PlayerBountiesOG plugin) {
|
||||
|
||||
final RegisteredServiceProvider<DiamondBankAPIJava> provider = plugin.getServer().getServicesManager()
|
||||
.getRegistration(DiamondBankAPIJava.class);
|
||||
|
||||
if (provider == null) {
|
||||
|
||||
throw new IllegalStateException("DiamondBank-OG API is null");
|
||||
|
||||
}
|
||||
|
||||
return provider.getProvider();
|
||||
|
||||
}
|
||||
|
||||
private static LuckPerms resolveLuckPerms(PlayerBountiesOG plugin) {
|
||||
|
||||
final RegisteredServiceProvider<LuckPerms> provider = plugin.getServer().getServicesManager()
|
||||
.getRegistration(LuckPerms.class);
|
||||
|
||||
return provider == null ? null : provider.getProvider();
|
||||
|
||||
}
|
||||
|
||||
private static String resolveLuckPermsDisplayName(LuckPerms luckPerms, UUID playerId, String playerName) {
|
||||
|
||||
final String safeName = StringUtils.defaultIfBlank(playerName, "Unknown Player");
|
||||
if (luckPerms == null) {
|
||||
|
||||
return "&f" + safeName;
|
||||
|
||||
}
|
||||
|
||||
final User user = luckPerms.getUserManager().getUser(playerId);
|
||||
if (user == null) {
|
||||
|
||||
return "&f" + safeName;
|
||||
|
||||
}
|
||||
|
||||
final CachedMetaData meta = user.getCachedData().getMetaData();
|
||||
final String prefix = StringUtils.trim(StringUtils.defaultString(meta.getPrefix()).replace('§', '&'));
|
||||
final String suffix = StringUtils.trim(StringUtils.defaultString(meta.getSuffix()).replace('§', '&'));
|
||||
final boolean metaContainsName = containsVisibleName(prefix, safeName) || containsVisibleName(suffix, safeName);
|
||||
|
||||
final StringBuilder out = new StringBuilder();
|
||||
if (!prefix.isBlank()) {
|
||||
|
||||
out.append(prefix).append(' ');
|
||||
|
||||
}
|
||||
|
||||
if (!metaContainsName) {
|
||||
|
||||
out.append(safeName);
|
||||
|
||||
}
|
||||
|
||||
if (!suffix.isBlank()) {
|
||||
|
||||
out.append(' ').append(suffix);
|
||||
|
||||
}
|
||||
|
||||
return sanitizeDisplay(out.toString(), safeName);
|
||||
|
||||
}
|
||||
|
||||
private static String formatLuckPermsDisplay(LuckPerms luckPerms, Player player) {
|
||||
|
||||
return resolveLuckPermsDisplayName(luckPerms, player.getUniqueId(), player.getName());
|
||||
|
||||
}
|
||||
|
||||
private static String sanitizeDisplay(String formatted, String fallbackName) {
|
||||
|
||||
final String cleaned = StringUtils.trimToEmpty(formatted).replaceAll("(?i)(?:\\s*(?:<reset>|[&§]r))+$", "")
|
||||
.replaceAll("\\s*>$", "");
|
||||
|
||||
return cleaned.isBlank() ? fallbackName : cleaned;
|
||||
|
||||
}
|
||||
|
||||
private static boolean containsVisibleName(String text, String playerName) {
|
||||
|
||||
if (text == null || playerName == null || playerName.isBlank()) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
final String normalizedText = StringUtils.lowerCase(UtilitiesOG.stripFormatting(text), Locale.ROOT);
|
||||
final String normalizedName = StringUtils.lowerCase(playerName, Locale.ROOT);
|
||||
|
||||
return StringUtils.contains(normalizedText, normalizedName);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<String> onTabComplete(@NotNull CommandSender sender, @NotNull String[] args) {
|
||||
|
||||
if (args.length == 2) {
|
||||
|
||||
return sender.getServer().getOnlinePlayers().stream().map(Player::getName)
|
||||
.filter(name -> StringUtils.startsWith(StringUtils.lowerCase(name), StringUtils.lowerCase(args[1])))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
} else if (args.length == 3) {
|
||||
|
||||
return Collections.singletonList("<amount>");
|
||||
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,11 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -42,36 +46,50 @@ public class BountyCheckCmd {
|
||||
|
||||
final String playerNameArg = args[1];
|
||||
|
||||
final Player target = plugin.getServer().getPlayerExact(playerNameArg);
|
||||
final UUID targetUuid;
|
||||
final String targetName;
|
||||
|
||||
if (target == null) {
|
||||
final Player onlineTarget = plugin.getServer().getPlayerExact(playerNameArg);
|
||||
if (onlineTarget != null) {
|
||||
|
||||
final String playerNotFoundMessage = plugin.getLang().getColored("command.bounty.check.player-not-found");
|
||||
if (!(sender instanceof Player)) {
|
||||
targetUuid = onlineTarget.getUniqueId();
|
||||
targetName = onlineTarget.getName();
|
||||
|
||||
UtilitiesOG.logToConsole(PlayerBountiesOG.getPrefix(), playerNotFoundMessage);
|
||||
} else {
|
||||
|
||||
} else {
|
||||
final OfflinePlayer offlineTarget = resolveOfflineTarget(playerNameArg);
|
||||
if (offlineTarget == null) {
|
||||
|
||||
UtilitiesOG.trueogMessage((Player) sender, playerNotFoundMessage);
|
||||
final String playerNotFoundMessage = plugin.getLang()
|
||||
.getColored("command.bounty.check.player-not-found");
|
||||
if (!(sender instanceof Player)) {
|
||||
|
||||
UtilitiesOG.logToConsole(PlayerBountiesOG.getPrefix(), playerNotFoundMessage);
|
||||
|
||||
} else {
|
||||
|
||||
UtilitiesOG.trueogMessage((Player) sender, playerNotFoundMessage);
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
targetUuid = offlineTarget.getUniqueId();
|
||||
targetName = StringUtils.defaultIfBlank(offlineTarget.getName(), playerNameArg);
|
||||
|
||||
}
|
||||
|
||||
final UUID playerUUID = target.getUniqueId();
|
||||
|
||||
final BountyDataManager bountyDataManager = plugin.getBountyDataManager();
|
||||
final boolean hasBounty = bountyDataManager.hasBounty(playerUUID);
|
||||
final boolean hasBounty = bountyDataManager.hasBounty(targetUuid);
|
||||
|
||||
if (hasBounty) {
|
||||
|
||||
// Confirmation.
|
||||
final double bounty = bountyDataManager.getBounty(playerUUID);
|
||||
final double bounty = bountyDataManager.getBounty(targetUuid);
|
||||
final String formattedBounty = plugin.getBountyHeadFormatter().formatDiamonds(bounty) + " Diamonds";
|
||||
final String bountyFoundMessage = plugin.getLang().getColored("command.bounty.check.bounty-found")
|
||||
.replace("{target}", target.getName()).replace("{bounty}", Double.toString(bounty));
|
||||
.replace("{target}", targetName).replace("{bounty}", formattedBounty);
|
||||
if (!(sender instanceof Player)) {
|
||||
|
||||
UtilitiesOG.logToConsole(PlayerBountiesOG.getPrefix(), bountyFoundMessage);
|
||||
@@ -85,7 +103,7 @@ public class BountyCheckCmd {
|
||||
} else {
|
||||
|
||||
final String noBountyFoundMessage = plugin.getLang().getColored("command.bounty.check.no-bounty")
|
||||
.replace("{target}", target.getName());
|
||||
.replace("{target}", targetName);
|
||||
if (!(sender instanceof Player)) {
|
||||
|
||||
UtilitiesOG.logToConsole(PlayerBountiesOG.getPrefix(), noBountyFoundMessage);
|
||||
@@ -102,18 +120,43 @@ public class BountyCheckCmd {
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<String> onTabComplete(@NotNull CommandSender sender, @NotNull String[] args) {
|
||||
private static OfflinePlayer resolveOfflineTarget(String name) {
|
||||
|
||||
if (args.length == 2) {
|
||||
for (OfflinePlayer candidate : Bukkit.getOfflinePlayers()) {
|
||||
|
||||
// Suggest online player names for the username.
|
||||
return sender.getServer().getOnlinePlayers().stream().map(Player::getName)
|
||||
.filter(name -> name.toLowerCase().startsWith(args[1].toLowerCase())).collect(Collectors.toList());
|
||||
if (StringUtils.equalsIgnoreCase(candidate.getName(), name)) {
|
||||
|
||||
return candidate;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
return Bukkit.getOfflinePlayerIfCached(name);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<String> onTabComplete(@NotNull CommandSender sender, @NotNull String[] args) {
|
||||
|
||||
if (args.length != 2) {
|
||||
|
||||
return Collections.emptyList();
|
||||
|
||||
}
|
||||
|
||||
final String prefix = StringUtils.lowerCase(args[1]);
|
||||
final PlayerBountiesOG plugin = PlayerBountiesOG.getInstance();
|
||||
final BountyDataManager bountyDataManager = plugin == null ? null : plugin.getBountyDataManager();
|
||||
|
||||
final Stream<String> onlineNames = sender.getServer().getOnlinePlayers().stream().map(Player::getName);
|
||||
final Stream<String> offlineBountyNames = bountyDataManager == null ? Stream.empty()
|
||||
: bountyDataManager.getBounties().keySet().stream().map(Bukkit::getOfflinePlayer)
|
||||
.map(OfflinePlayer::getName).filter(name -> name != null && !name.isBlank());
|
||||
|
||||
return Stream.concat(onlineNames, offlineBountyNames).distinct()
|
||||
.filter(name -> StringUtils.startsWith(StringUtils.lowerCase(name), prefix))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,24 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
import com.tcoded.playerbountiesplus.event.BountySetEvent;
|
||||
import com.tcoded.playerbountiesplus.manager.BountyDataManager;
|
||||
import com.tcoded.playerbountiesplus.util.BountyWorldUtil;
|
||||
|
||||
import net.luckperms.api.LuckPerms;
|
||||
import net.luckperms.api.cacheddata.CachedMetaData;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class BountySetCmd {
|
||||
@@ -41,12 +48,12 @@ public class BountySetCmd {
|
||||
|
||||
}
|
||||
|
||||
if (sender instanceof Player && !sender.hasPermission("playerbountiesplus.command.bounty.set")) {
|
||||
if (sender instanceof Player && !sender.hasPermission("playerbountiesog.command.bounty.set")) {
|
||||
|
||||
final String noPerm = plugin.getLang().getColored("command.no-permission");
|
||||
final String noPermDetailed = plugin.getLang().getColored("command.no-permission-detailed")
|
||||
.replace("{no-permission-msg}", noPerm)
|
||||
.replace("{permission}", "playerbountiesplus.command.bounty.set");
|
||||
.replace("{permission}", "playerbountiesog.command.bounty.set");
|
||||
if (!(sender instanceof Player)) {
|
||||
|
||||
UtilitiesOG.logToConsole(PlayerBountiesOG.getPrefix(), noPermDetailed);
|
||||
@@ -61,11 +68,19 @@ public class BountySetCmd {
|
||||
|
||||
}
|
||||
|
||||
if (sender instanceof Player player && !BountyWorldUtil.isBountyWorldAllowed(plugin, player)) {
|
||||
|
||||
UtilitiesOG.trueogMessage(player, plugin.getLang().getColored("command.bounty.world-not-whitelisted")
|
||||
.replace("{worlds}", BountyWorldUtil.formatBountyWorldWhitelist(plugin)));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final String playerNameArg = args[1];
|
||||
final double parsedAmount;
|
||||
try {
|
||||
|
||||
parsedAmount = Integer.parseInt(args[2]);
|
||||
parsedAmount = parseAmount(args[2]);
|
||||
|
||||
} catch (NumberFormatException numberFormatException) {
|
||||
|
||||
@@ -215,47 +230,41 @@ public class BountySetCmd {
|
||||
|
||||
// Calculate total bounty including previous bounties.
|
||||
final BountyDataManager bountyDataManager = plugin.getBountyDataManager();
|
||||
final double bountyAlreadyPresent = bountyDataManager.getBounty(playerUUID);
|
||||
final double chargeAndBountyAmount = (int) amount;
|
||||
final double totalBounty = chargeAndBountyAmount + bountyAlreadyPresent;
|
||||
final DiamondBankAPIJava diamondBankAPI = resolveDiamondBankApi(plugin);
|
||||
final long newBountyShards = diamondBankAPI.diamondsToShards(amount);
|
||||
final long existingBountyShards = diamondBankAPI
|
||||
.diamondsToShards((float) bountyDataManager.getBounty(playerUUID));
|
||||
final long totalBountyShards = newBountyShards + existingBountyShards;
|
||||
final double totalBounty = Double.parseDouble(diamondBankAPI.shardsToDiamonds(totalBountyShards));
|
||||
bountyDataManager.setBounty(playerUUID, totalBounty);
|
||||
|
||||
// Confirmation.
|
||||
final String bountySetSuccessMessage = plugin.getLang().getColored("command.bounty.set.success")
|
||||
.replace("{bounty}", String.valueOf(chargeAndBountyAmount))
|
||||
.replace("{total}", String.valueOf(totalBounty)).replace("{target}", target.getName())
|
||||
.replace("{player}", sender.getName());
|
||||
if (!(sender instanceof Player)) {
|
||||
|
||||
UtilitiesOG.logToConsole(PlayerBountiesOG.getPrefix(), bountySetSuccessMessage);
|
||||
|
||||
} else {
|
||||
|
||||
UtilitiesOG.trueogMessage((Player) sender, bountySetSuccessMessage);
|
||||
|
||||
}
|
||||
|
||||
// Announcement.
|
||||
final String extra;
|
||||
if (bountyAlreadyPresent == 0) {
|
||||
if (existingBountyShards == 0L) {
|
||||
|
||||
extra = "";
|
||||
|
||||
} else {
|
||||
|
||||
extra = plugin.getLang().getColored("command.bounty.set.announce-extra").replace("{total}",
|
||||
String.valueOf(totalBounty));
|
||||
diamondBankAPI.shardsToDiamonds(totalBountyShards));
|
||||
|
||||
}
|
||||
|
||||
if (plugin.getConfig().getBoolean("bounty-placed-announce", true)) {
|
||||
|
||||
final LuckPerms luckPerms = resolveLuckPerms(plugin);
|
||||
final String setterDisplay = sender instanceof Player player ? formatLuckPermsDisplay(luckPerms, player)
|
||||
: sender.getName();
|
||||
final String targetDisplay = formatLuckPermsDisplay(luckPerms, target);
|
||||
final String bountyDisplay = diamondBankAPI.shardsToDiamonds(newBountyShards);
|
||||
|
||||
Bukkit.getOnlinePlayers()
|
||||
.forEach((Player player) -> UtilitiesOG.trueogMessage(player,
|
||||
plugin.getLang().getColored("command.bounty.set.announce")
|
||||
.replace("{bounty}", String.valueOf(chargeAndBountyAmount))
|
||||
.replace("{target}", target.getName()).replace("{player}", sender.getName())
|
||||
.replace("{extra}", extra)));
|
||||
PlayerBountiesOG.getPrefix() + "&a"
|
||||
+ plugin.getLang().getColored("command.bounty.set.announce")
|
||||
.replace("{bounty}", bountyDisplay).replace("{target}", targetDisplay)
|
||||
.replace("{player}", setterDisplay).replace("{extra}", extra)));
|
||||
|
||||
}
|
||||
|
||||
@@ -265,6 +274,142 @@ public class BountySetCmd {
|
||||
|
||||
}
|
||||
|
||||
private static double parseAmount(String amountInput) {
|
||||
|
||||
if (!amountInput.matches("^\\d+(?:\\.\\d)?$")) {
|
||||
|
||||
throw new NumberFormatException("Invalid amount format");
|
||||
|
||||
}
|
||||
|
||||
return Double.parseDouble(amountInput);
|
||||
|
||||
}
|
||||
|
||||
private static DiamondBankAPIJava resolveDiamondBankApi(PlayerBountiesOG plugin) {
|
||||
|
||||
final RegisteredServiceProvider<DiamondBankAPIJava> provider = plugin.getServer().getServicesManager()
|
||||
.getRegistration(DiamondBankAPIJava.class);
|
||||
|
||||
if (provider == null) {
|
||||
|
||||
throw new IllegalStateException("DiamondBank-OG API is null");
|
||||
|
||||
}
|
||||
|
||||
return provider.getProvider();
|
||||
|
||||
}
|
||||
|
||||
private static LuckPerms resolveLuckPerms(PlayerBountiesOG plugin) {
|
||||
|
||||
final RegisteredServiceProvider<LuckPerms> provider = plugin.getServer().getServicesManager()
|
||||
.getRegistration(LuckPerms.class);
|
||||
|
||||
return provider == null ? null : provider.getProvider();
|
||||
|
||||
}
|
||||
|
||||
private static String formatLuckPermsDisplay(LuckPerms luckPerms, Player player) {
|
||||
|
||||
final String prefix = stripLeadingReset(stripTrailingReset(getLuckPermsPrefixLegacy(luckPerms, player)));
|
||||
final String leadingColorCodes = extractLeadingColorCodes(prefix);
|
||||
|
||||
if (prefix.isBlank()) {
|
||||
|
||||
return player.getName().replaceAll("\\s*>$", "");
|
||||
|
||||
}
|
||||
|
||||
if (leadingColorCodes.isBlank()) {
|
||||
|
||||
return (prefix + " " + player.getName()).replaceAll("\\s*>$", "");
|
||||
|
||||
}
|
||||
|
||||
return (prefix + " " + leadingColorCodes + player.getName()).replaceAll("\\s*>$", "");
|
||||
|
||||
}
|
||||
|
||||
private static String getLuckPermsPrefixLegacy(LuckPerms luckPerms, Player player) {
|
||||
|
||||
if (luckPerms == null) {
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
final User user = luckPerms.getUserManager().getUser(player.getUniqueId());
|
||||
if (user == null) {
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
final CachedMetaData meta = user.getCachedData().getMetaData();
|
||||
final String prefix = meta.getPrefix();
|
||||
|
||||
if (prefix == null || prefix.isBlank()) {
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
return StringUtils.trim(prefix).replace('§', '&');
|
||||
|
||||
}
|
||||
|
||||
private static String stripTrailingReset(String input) {
|
||||
|
||||
if (input == null || input.isBlank()) {
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
return StringUtils.trim(input.replaceAll("(?i)(?:\\s*(?:<reset>|[&§]r))+$", ""));
|
||||
|
||||
}
|
||||
|
||||
private static String stripLeadingReset(String input) {
|
||||
|
||||
if (input == null || input.isBlank()) {
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
return StringUtils.trim(input.replaceFirst("(?i)^(?:\\s*(?:<reset>|[&§]r))+", ""));
|
||||
|
||||
}
|
||||
|
||||
private static String extractLeadingColorCodes(String input) {
|
||||
|
||||
if (input == null || input.length() < 2) {
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
final StringBuilder out = new StringBuilder();
|
||||
for (int i = 0; i < input.length() - 1; i++) {
|
||||
|
||||
final char current = input.charAt(i);
|
||||
if (current != '&' && current != '§') {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
out.append(current).append(input.charAt(i + 1));
|
||||
i++;
|
||||
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<String> onTabComplete(@NotNull CommandSender sender, @NotNull String[] args) {
|
||||
|
||||
@@ -272,7 +417,8 @@ public class BountySetCmd {
|
||||
|
||||
// Suggest online player names for the username.
|
||||
return sender.getServer().getOnlinePlayers().stream().map(Player::getName)
|
||||
.filter(name -> name.toLowerCase().startsWith(args[1].toLowerCase())).collect(Collectors.toList());
|
||||
.filter(name -> StringUtils.startsWith(StringUtils.lowerCase(name), StringUtils.lowerCase(args[1])))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
} else if (args.length == 3) {
|
||||
|
||||
@@ -285,4 +431,4 @@ public class BountySetCmd {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -13,6 +14,7 @@ import org.bukkit.entity.Player;
|
||||
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
import com.tcoded.playerbountiesplus.gui.MainBountyGui;
|
||||
import com.tcoded.playerbountiesplus.util.BountyHeadFormatter;
|
||||
|
||||
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
@@ -25,7 +27,8 @@ public class BountyTopCmd {
|
||||
|
||||
if (sender instanceof Player player) {
|
||||
|
||||
new MainBountyGui(plugin, player, () -> buildGuiEntries(plugin), diamondBankAPI).open();
|
||||
final List<MainBountyGui.BountyGuiEntry> entries = buildGuiEntries(plugin);
|
||||
new MainBountyGui(plugin, player, () -> entries, diamondBankAPI).open();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -39,6 +42,8 @@ public class BountyTopCmd {
|
||||
|
||||
private static List<MainBountyGui.BountyGuiEntry> buildGuiEntries(PlayerBountiesOG plugin) {
|
||||
|
||||
final BountyHeadFormatter formatter = plugin.getBountyHeadFormatter();
|
||||
|
||||
final Set<Map.Entry<UUID, Double>> bountiesSet = plugin.getBountyDataManager().getBounties().entrySet();
|
||||
final List<Map.Entry<UUID, Double>> bounties = new ArrayList<>(bountiesSet);
|
||||
|
||||
@@ -62,7 +67,9 @@ public class BountyTopCmd {
|
||||
|
||||
}
|
||||
|
||||
entries.add(new MainBountyGui.BountyGuiEntry(entry.getKey(), targetName, entry.getValue()));
|
||||
final String displayName = formatter.resolvePlayerDisplay(entry.getKey(), targetName);
|
||||
|
||||
entries.add(new MainBountyGui.BountyGuiEntry(entry.getKey(), targetName, displayName, entry.getValue()));
|
||||
|
||||
}
|
||||
|
||||
@@ -74,6 +81,8 @@ public class BountyTopCmd {
|
||||
CommandSender sender)
|
||||
{
|
||||
|
||||
final BountyHeadFormatter formatter = plugin.getBountyHeadFormatter();
|
||||
|
||||
final Set<Map.Entry<UUID, Double>> bountiesSet = plugin.getBountyDataManager().getBounties().entrySet();
|
||||
final List<Map.Entry<UUID, Double>> bounties = new ArrayList<>(bountiesSet);
|
||||
|
||||
@@ -102,9 +111,12 @@ public class BountyTopCmd {
|
||||
.shardsToDiamonds(diamondBankAPI.diamondsToShards(entry.getValue()));
|
||||
|
||||
strb.append("&7 - ");
|
||||
strb.append(targetName);
|
||||
strb.append(": ");
|
||||
final String displayName = formatter.resolvePlayerDisplay(entry.getKey(), targetName);
|
||||
|
||||
strb.append(displayName);
|
||||
strb.append(": &b");
|
||||
strb.append(formattedBounty);
|
||||
strb.append(" &bDiamonds");
|
||||
strb.append('\n');
|
||||
|
||||
}
|
||||
@@ -116,7 +128,7 @@ public class BountyTopCmd {
|
||||
}
|
||||
|
||||
String message = strb.toString();
|
||||
message = message.substring(0, message.length() - 1);
|
||||
message = StringUtils.substring(message, 0, message.length() - 1);
|
||||
|
||||
if (!(sender instanceof Player)) {
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -17,6 +17,8 @@ import net.trueog.diamondbankog.api.DiamondBankAPIJava;
|
||||
import net.trueog.gxui.GUIBase;
|
||||
import net.trueog.gxui.GUIButton;
|
||||
import net.trueog.gxui.GUIItem;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public class MainBountyGui extends GUIBase {
|
||||
|
||||
@@ -28,8 +30,6 @@ public class MainBountyGui extends GUIBase {
|
||||
private static final int NEXT_PAGE_SLOT = 51;
|
||||
private static final int CLOSE_SLOT = 53;
|
||||
|
||||
private static final Pattern MINECRAFT_USERNAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]{1,16}$");
|
||||
|
||||
private final PlayerBountiesOG plugin;
|
||||
private final Player viewer;
|
||||
private final Supplier<List<BountyGuiEntry>> bountySupplier;
|
||||
@@ -76,9 +76,10 @@ public class MainBountyGui extends GUIBase {
|
||||
if (sortedBounties.isEmpty()) {
|
||||
|
||||
addItem(22,
|
||||
createStaticItem(Material.WRITABLE_BOOK, "&e&lNo Active Bounties",
|
||||
List.of("&7Nobody currently has a bounty.", "&7Place one with your bounty command to get",
|
||||
"&7this leaderboard moving again.")));
|
||||
createStaticItem(Material.WRITABLE_BOOK, "&6&lNo Active Bounties",
|
||||
List.of(UtilitiesOG.trueogColorize("&7Nobody currently has a bounty."),
|
||||
UtilitiesOG.trueogColorize("&7Place one with your bounty command to get"),
|
||||
UtilitiesOG.trueogColorize("&7this leaderboard populated again."))));
|
||||
return;
|
||||
|
||||
}
|
||||
@@ -105,7 +106,7 @@ public class MainBountyGui extends GUIBase {
|
||||
if (currentPage > 0) {
|
||||
|
||||
addItem(PREVIOUS_PAGE_SLOT, createButtonItem(Material.ARROW, "&a&lPrevious Page",
|
||||
List.of("&7Go to page &f" + currentPage), new GUIButton()
|
||||
List.of(UtilitiesOG.trueogColorize("&7Go to page &f" + currentPage)), new GUIButton()
|
||||
{
|
||||
|
||||
@Override
|
||||
@@ -140,20 +141,18 @@ public class MainBountyGui extends GUIBase {
|
||||
|
||||
} else {
|
||||
|
||||
addItem(PREVIOUS_PAGE_SLOT,
|
||||
createStaticItem(Material.GRAY_DYE, "&7Previous Page", List.of("&8Already on the first page.")));
|
||||
addItem(PREVIOUS_PAGE_SLOT, createStaticItem(Material.GRAY_DYE, "&7Previous Page",
|
||||
List.of(UtilitiesOG.trueogColorize("&8Already on the first page."))));
|
||||
|
||||
}
|
||||
|
||||
addItem(PAGE_INFO_SLOT,
|
||||
createStaticItem(Material.BOOK, "&e&lPage " + (currentPage + 1) + "/" + (maxPage + 1),
|
||||
List.of("&7Active bounties: &f" + totalBounties, "&7Shown per page: &f" + PYRAMID_SLOTS.size(),
|
||||
"&7Sorted from highest to lowest.")));
|
||||
addItem(PAGE_INFO_SLOT, createStaticItem(Material.BOOK, "&3&lPage " + (currentPage + 1) + "/" + (maxPage + 1),
|
||||
List.of(UtilitiesOG.trueogColorize("&6Active bounties: &c" + totalBounties))));
|
||||
|
||||
if (currentPage < maxPage) {
|
||||
|
||||
addItem(NEXT_PAGE_SLOT, createButtonItem(Material.ARROW, "&a&lNext Page",
|
||||
List.of("&7Go to page &f" + (currentPage + 2)), new GUIButton()
|
||||
List.of(UtilitiesOG.trueogColorize("&7Go to page &f" + (currentPage + 2))), new GUIButton()
|
||||
{
|
||||
|
||||
@Override
|
||||
@@ -188,13 +187,13 @@ public class MainBountyGui extends GUIBase {
|
||||
|
||||
} else {
|
||||
|
||||
addItem(NEXT_PAGE_SLOT,
|
||||
createStaticItem(Material.GRAY_DYE, "&7Next Page", List.of("&8Already on the last page.")));
|
||||
addItem(NEXT_PAGE_SLOT, createStaticItem(Material.GRAY_DYE, "&7Next Page",
|
||||
List.of(UtilitiesOG.trueogColorize("&8Already on the last page."))));
|
||||
|
||||
}
|
||||
|
||||
addItem(CLOSE_SLOT,
|
||||
createButtonItem(Material.BARRIER, "&c&lClose", List.of("&7Close the bounty menu."), new GUIButton()
|
||||
addItem(CLOSE_SLOT, createButtonItem(Material.BARRIER, "&c&lClose",
|
||||
List.of(UtilitiesOG.trueogColorize("&7Close the bounty menu.")), new GUIButton()
|
||||
{
|
||||
|
||||
@Override
|
||||
@@ -247,42 +246,110 @@ public class MainBountyGui extends GUIBase {
|
||||
|
||||
private GUIItem createBountyItem(BountyGuiEntry entry, int rank) {
|
||||
|
||||
final String targetName = safeName(entry.targetName());
|
||||
final ArrayList<String> lore = new ArrayList<>();
|
||||
final String displayName = safeDisplayName(entry.displayName());
|
||||
final ArrayList<TextComponent> lore = new ArrayList<>();
|
||||
|
||||
lore.add("&7Rank: &f#" + rank);
|
||||
lore.add("&7Target: &f" + targetName);
|
||||
lore.add("&7Bounty: &6" + formatBounty(entry.bountyDiamonds()));
|
||||
lore.add("");
|
||||
lore.add("&8Highest bounty sits at the top.");
|
||||
lore.add(UtilitiesOG.trueogColorize("&eBounty: &b" + formatBounty(entry.bountyDiamonds())));
|
||||
lore.add(UtilitiesOG.trueogColorize("&7Click to increase this player's bounty."));
|
||||
lore.add(UtilitiesOG.trueogColorize("&8You will be prompted in chat for the amount."));
|
||||
lore.add(UtilitiesOG.trueogColorize(""));
|
||||
final double dropChance = Math.max(0D,
|
||||
Math.min(100D, plugin.getConfig().getDouble("bounty-head-drop-chance", 50D)));
|
||||
lore.add(UtilitiesOG.trueogColorize("&6Claiming the bounty will give you a " + formatDropChance(dropChance)
|
||||
+ "% chance of beheading your victim!"));
|
||||
|
||||
final GUIItem item;
|
||||
if (isUsableHeadName(targetName)) {
|
||||
if (entry.targetName() != null && !entry.targetName().isBlank()) {
|
||||
|
||||
item = new GUIItem(Material.PLAYER_HEAD, 1, "&6&l#" + rank + " &f" + targetName, targetName);
|
||||
item = new GUIItem(Material.PLAYER_HEAD, 1, " &f#" + rank + " " + displayName, entry.targetName());
|
||||
|
||||
} else {
|
||||
|
||||
item = new GUIItem(Material.PAPER, 1, "&6&l#" + rank + " &f" + targetName);
|
||||
item = new GUIItem(Material.PAPER, 1, "&6&l#" + rank + " " + displayName);
|
||||
|
||||
}
|
||||
|
||||
item.lore(lore);
|
||||
item.setButton(new GUIButton() {
|
||||
|
||||
@Override
|
||||
public boolean leftClick() {
|
||||
|
||||
return promptIncrease(entry);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean leftClickShift() {
|
||||
|
||||
return promptIncrease(entry);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rightClick() {
|
||||
|
||||
return promptIncrease(entry);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rightClickShift() {
|
||||
|
||||
return promptIncrease(entry);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
item.setPlayErrorSound(false);
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
private boolean promptIncrease(BountyGuiEntry entry) {
|
||||
|
||||
if (entry.targetUuid() == null) {
|
||||
|
||||
UtilitiesOG.trueogMessage(viewer, plugin.getLang().getColored("command.bounty.add.player-not-found"));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final String targetName = StringUtils.defaultIfBlank(entry.targetName(), "Unknown Player");
|
||||
plugin.getBountyIncreasePromptListener().prompt(viewer.getUniqueId(), entry.targetUuid(), targetName);
|
||||
|
||||
final String promptMessage = plugin.getLang().getColored("command.bounty.add.prompt").replace("{target}",
|
||||
safeDisplayName(entry.displayName()));
|
||||
UtilitiesOG.trueogMessage(viewer, promptMessage);
|
||||
|
||||
viewer.closeInventory();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private String formatBounty(double bountyDiamonds) {
|
||||
|
||||
final long shards = diamondBankAPI.diamondsToShards(bountyDiamonds);
|
||||
|
||||
return diamondBankAPI.shardsToDiamonds(shards);
|
||||
return diamondBankAPI.shardsToDiamonds(shards) + " Diamonds";
|
||||
|
||||
}
|
||||
|
||||
private GUIItem createStaticItem(Material material, String displayName, List<String> lore) {
|
||||
private String formatDropChance(double chance) {
|
||||
|
||||
if (chance == Math.floor(chance)) {
|
||||
|
||||
return Long.toString((long) chance);
|
||||
|
||||
}
|
||||
|
||||
return StringUtils.stripEnd(StringUtils.stripEnd(String.format("%.2f", chance), "0"), ".");
|
||||
|
||||
}
|
||||
|
||||
private GUIItem createStaticItem(Material material, String displayName, List<TextComponent> lore) {
|
||||
|
||||
final GUIItem item = new GUIItem(material, 1, displayName);
|
||||
item.lore(new ArrayList<>(lore));
|
||||
@@ -292,7 +359,9 @@ public class MainBountyGui extends GUIBase {
|
||||
|
||||
}
|
||||
|
||||
private GUIItem createButtonItem(Material material, String displayName, List<String> lore, GUIButton button) {
|
||||
private GUIItem createButtonItem(Material material, String displayName, List<TextComponent> lore,
|
||||
GUIButton button)
|
||||
{
|
||||
|
||||
final GUIItem item = new GUIItem(material, 1, displayName);
|
||||
item.lore(new ArrayList<>(lore));
|
||||
@@ -335,24 +404,23 @@ public class MainBountyGui extends GUIBase {
|
||||
|
||||
private String safeName(String name) {
|
||||
|
||||
if (name == null || name.isBlank()) {
|
||||
return name == null || name.isBlank() ? "Unknown Player" : name;
|
||||
|
||||
return "Unknown Player";
|
||||
}
|
||||
|
||||
private String safeDisplayName(String displayName) {
|
||||
|
||||
if (displayName == null || displayName.isBlank()) {
|
||||
|
||||
return "&fUnknown Player";
|
||||
|
||||
}
|
||||
|
||||
return name;
|
||||
return displayName;
|
||||
|
||||
}
|
||||
|
||||
private boolean isUsableHeadName(String name) {
|
||||
|
||||
return name != null && !name.isBlank() && !"Unknown Player".equals(name)
|
||||
&& MINECRAFT_USERNAME_PATTERN.matcher(name).matches();
|
||||
|
||||
}
|
||||
|
||||
public static record BountyGuiEntry(UUID targetUuid, String targetName, double bountyDiamonds) {
|
||||
public static record BountyGuiEntry(UUID targetUuid, String targetName, String displayName, double bountyDiamonds) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.tcoded.playerbountiesplus.hook.logging;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import net.coreprotect.CoreProtect;
|
||||
import net.coreprotect.CoreProtectAPI;
|
||||
|
||||
public class CoreProtectHook {
|
||||
|
||||
private static final int MINIMUM_API_VERSION = 10;
|
||||
private final CoreProtectAPI coreProtectAPI;
|
||||
|
||||
public CoreProtectHook(Plugin plugin) {
|
||||
|
||||
this.coreProtectAPI = findCoreProtect(plugin);
|
||||
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
|
||||
return this.coreProtectAPI != null;
|
||||
|
||||
}
|
||||
|
||||
public boolean logPlacement(String user, BlockState blockState) {
|
||||
|
||||
if (this.coreProtectAPI == null || user == null || blockState == null) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return this.coreProtectAPI.logPlacement(user, blockState.getLocation(), blockState.getType(),
|
||||
blockState.getBlockData());
|
||||
|
||||
}
|
||||
|
||||
public boolean logRemoval(String user, BlockState blockState) {
|
||||
|
||||
if (this.coreProtectAPI == null || user == null || blockState == null) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return this.coreProtectAPI.logRemoval(user, blockState.getLocation(), blockState.getType(),
|
||||
blockState.getBlockData());
|
||||
|
||||
}
|
||||
|
||||
public boolean logContainerTransaction(String user, Location location) {
|
||||
|
||||
if (this.coreProtectAPI == null || user == null || location == null) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return this.coreProtectAPI.logContainerTransaction(user, location);
|
||||
|
||||
}
|
||||
|
||||
public boolean logInteraction(String user, Location location) {
|
||||
|
||||
if (this.coreProtectAPI == null || user == null || location == null) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return this.coreProtectAPI.logInteraction(user, location);
|
||||
|
||||
}
|
||||
|
||||
public boolean hasPlaced(String user, Block block, int time, int offset) {
|
||||
|
||||
if (this.coreProtectAPI == null || user == null || block == null) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return this.coreProtectAPI.hasPlaced(user, block, time, offset);
|
||||
|
||||
}
|
||||
|
||||
private static CoreProtectAPI findCoreProtect(Plugin plugin) {
|
||||
|
||||
if (plugin == null) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
final Plugin coreProtectPlugin = plugin.getServer().getPluginManager().getPlugin("CoreProtect-OG");
|
||||
if (!(coreProtectPlugin instanceof CoreProtect coreProtect)) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
final CoreProtectAPI api = coreProtect.getAPI();
|
||||
if (api == null || !api.isEnabled() || api.APIVersion() < MINIMUM_API_VERSION) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return api;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,13 +30,8 @@ public abstract class AbstractTeamHook implements TeamHook {
|
||||
public String getAuthor() {
|
||||
|
||||
final List<String> authors = this.teamPlugin.getPluginMeta().getAuthors();
|
||||
if (authors.isEmpty()) {
|
||||
|
||||
return "N/A";
|
||||
|
||||
}
|
||||
|
||||
return authors.get(0);
|
||||
return authors.isEmpty() ? "N/A" : authors.get(0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -35,13 +35,7 @@ public class SimpleClansHook extends AbstractTeamHook {
|
||||
|
||||
}
|
||||
|
||||
if (clan2 == null) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return clan1 == clan2;
|
||||
return clan2 == null ? false : clan1 == clan2;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ public interface TeamHook {
|
||||
final PluginManager pluginManager = plugin.getServer().getPluginManager();
|
||||
|
||||
// RoinujNosde's SimpleClans: TrueOG Network branch.
|
||||
// https://github.com/true-og/SimpleClans
|
||||
// https://git.trueog.net/trueog/Unions-OG
|
||||
// License: GPLv3
|
||||
// (https://raw.githubusercontent.com/true-og/SimpleClans/refs/heads/master/LICENSE)
|
||||
// (https://git.trueog.net/trueog/Unions-OG/raw/branch/master/LICENSE)
|
||||
final Plugin simpleClansPlugin = pluginManager.getPlugin("SimpleClans");
|
||||
if (simpleClansPlugin != null && simpleClansPlugin.isEnabled()) {
|
||||
|
||||
@@ -23,6 +23,15 @@ public interface TeamHook {
|
||||
|
||||
}
|
||||
|
||||
// Unions-OG is a soft fork of SimpleClans that preserves the same API
|
||||
// and main class but exposes a different plugin name to Bukkit.
|
||||
final Plugin unionsPlugin = pluginManager.getPlugin("Unions-OG");
|
||||
if (unionsPlugin != null && unionsPlugin.isEnabled()) {
|
||||
|
||||
return new SimpleClansHook(plugin, unionsPlugin);
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -35,4 +44,4 @@ public interface TeamHook {
|
||||
|
||||
boolean isFriendly(Player player1, Player player2);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package com.tcoded.playerbountiesplus.listener;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.FluidCollisionMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Skull;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
import com.tcoded.playerbountiesplus.util.BountyHeadData;
|
||||
|
||||
public final class BountyHeadLabelListener implements Listener {
|
||||
|
||||
private static final long UPDATE_PERIOD_TICKS = 2L;
|
||||
|
||||
// Two stacked armor stands per head: top = name + slayer, bottom = amount.
|
||||
// Spacing tuned for small marker stands so the hologram stays compact.
|
||||
private static final double LABEL_BOTTOM_Y_OFFSET = 0.85D;
|
||||
private static final double LABEL_TOP_Y_OFFSET = 1.10D;
|
||||
|
||||
private final PlayerBountiesOG plugin;
|
||||
private final Map<HeadLabelKey, LabelPair> activeLabels = new HashMap<>();
|
||||
private final BukkitTask tickTask;
|
||||
|
||||
public BountyHeadLabelListener(PlayerBountiesOG plugin) {
|
||||
|
||||
this.plugin = plugin;
|
||||
this.tickTask = plugin.getServer().getScheduler().runTaskTimer(plugin, this::tick, UPDATE_PERIOD_TICKS,
|
||||
UPDATE_PERIOD_TICKS);
|
||||
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
||||
if (tickTask != null) {
|
||||
|
||||
tickTask.cancel();
|
||||
|
||||
}
|
||||
|
||||
clearLabels();
|
||||
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
|
||||
if (!isHoverModeEnabled()) {
|
||||
|
||||
clearLabels();
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final Set<HeadLabelKey> visibleTargets = new HashSet<>();
|
||||
for (Player player : plugin.getServer().getOnlinePlayers()) {
|
||||
|
||||
final HeadTarget target = findTargetedHead(player);
|
||||
if (target == null) {
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
visibleTargets.add(target.key());
|
||||
upsertLabel(target);
|
||||
|
||||
}
|
||||
|
||||
final Set<HeadLabelKey> staleLabels = new HashSet<>(activeLabels.keySet());
|
||||
staleLabels.removeAll(visibleTargets);
|
||||
staleLabels.forEach(this::removeLabel);
|
||||
|
||||
}
|
||||
|
||||
private HeadTarget findTargetedHead(Player player) {
|
||||
|
||||
final double maxRange = Math.max(1D, plugin.getConfig().getDouble("bounty-head-label-range", 8D));
|
||||
final RayTraceResult rayTrace = player.getWorld().rayTraceBlocks(player.getEyeLocation(),
|
||||
player.getEyeLocation().getDirection(), maxRange, FluidCollisionMode.NEVER, true);
|
||||
if (rayTrace == null) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
final Block hitBlock = rayTrace.getHitBlock();
|
||||
if (hitBlock == null || !isHeadMaterial(hitBlock.getType())) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
final BlockState state = hitBlock.getState();
|
||||
if (!(state instanceof Skull skull)) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
final BountyHeadData headData = plugin.getBountyHeadMetadata().read(skull.getPersistentDataContainer());
|
||||
if (headData == null) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return new HeadTarget(HeadLabelKey.fromBlock(hitBlock), hitBlock.getLocation(), headData);
|
||||
|
||||
}
|
||||
|
||||
private void upsertLabel(HeadTarget target) {
|
||||
|
||||
final LabelPair existing = activeLabels.get(target.key());
|
||||
if (existing != null && existing.top().isValid() && existing.bottom().isValid()) {
|
||||
|
||||
existing.top().customName(plugin.getBountyHeadFormatter().buildCanonicalLineTop(target.data()));
|
||||
existing.bottom().customName(plugin.getBountyHeadFormatter().buildCanonicalLineBottom(target.data()));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (existing != null) {
|
||||
|
||||
removeLabel(target.key());
|
||||
|
||||
}
|
||||
|
||||
final World world = target.location().getWorld();
|
||||
final Location bottomLocation = target.location().clone().add(0.5D, LABEL_BOTTOM_Y_OFFSET, 0.5D);
|
||||
final Location topLocation = target.location().clone().add(0.5D, LABEL_TOP_Y_OFFSET, 0.5D);
|
||||
|
||||
final ArmorStand top = world.spawn(topLocation, ArmorStand.class, stand -> {
|
||||
|
||||
configureLabelStand(stand);
|
||||
stand.customName(plugin.getBountyHeadFormatter().buildCanonicalLineTop(target.data()));
|
||||
|
||||
});
|
||||
|
||||
final ArmorStand bottom = world.spawn(bottomLocation, ArmorStand.class, stand -> {
|
||||
|
||||
configureLabelStand(stand);
|
||||
stand.customName(plugin.getBountyHeadFormatter().buildCanonicalLineBottom(target.data()));
|
||||
|
||||
});
|
||||
|
||||
activeLabels.put(target.key(), new LabelPair(top, bottom));
|
||||
|
||||
}
|
||||
|
||||
private static void configureLabelStand(ArmorStand stand) {
|
||||
|
||||
stand.setInvisible(true);
|
||||
stand.setGravity(false);
|
||||
stand.setMarker(true);
|
||||
stand.setSmall(true);
|
||||
stand.setInvulnerable(true);
|
||||
stand.setSilent(true);
|
||||
stand.setPersistent(false);
|
||||
stand.setBasePlate(false);
|
||||
stand.setArms(false);
|
||||
stand.setCustomNameVisible(true);
|
||||
|
||||
}
|
||||
|
||||
private void removeLabel(HeadLabelKey key) {
|
||||
|
||||
final LabelPair pair = activeLabels.remove(key);
|
||||
if (pair == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (pair.top() != null && pair.top().isValid()) {
|
||||
|
||||
pair.top().remove();
|
||||
|
||||
}
|
||||
|
||||
if (pair.bottom() != null && pair.bottom().isValid()) {
|
||||
|
||||
pair.bottom().remove();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void clearLabels() {
|
||||
|
||||
final Set<HeadLabelKey> keys = new HashSet<>(activeLabels.keySet());
|
||||
keys.forEach(this::removeLabel);
|
||||
|
||||
}
|
||||
|
||||
private boolean isHoverModeEnabled() {
|
||||
|
||||
return StringUtils.equalsIgnoreCase(plugin.getConfig().getString("bounty-head-label-mode", "hover"), "hover");
|
||||
|
||||
}
|
||||
|
||||
private static boolean isHeadMaterial(Material material) {
|
||||
|
||||
return material == Material.PLAYER_HEAD || material == Material.PLAYER_WALL_HEAD;
|
||||
|
||||
}
|
||||
|
||||
private record HeadTarget(HeadLabelKey key, Location location, BountyHeadData data) {
|
||||
}
|
||||
|
||||
private record LabelPair(ArmorStand top, ArmorStand bottom) {
|
||||
}
|
||||
|
||||
private record HeadLabelKey(UUID worldId, int x, int y, int z) {
|
||||
|
||||
private static HeadLabelKey fromBlock(Block block) {
|
||||
|
||||
final World world = block.getWorld();
|
||||
|
||||
return new HeadLabelKey(world.getUID(), block.getX(), block.getY(), block.getZ());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package com.tcoded.playerbountiesplus.listener;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Skull;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
import com.tcoded.playerbountiesplus.hook.logging.CoreProtectHook;
|
||||
import com.tcoded.playerbountiesplus.util.BountyHeadData;
|
||||
|
||||
public class BountyHeadListener implements Listener {
|
||||
|
||||
private final PlayerBountiesOG plugin;
|
||||
private final CoreProtectHook coreProtectHook;
|
||||
|
||||
public BountyHeadListener(PlayerBountiesOG plugin) {
|
||||
|
||||
this.plugin = plugin;
|
||||
this.coreProtectHook = plugin.getCoreProtectHook();
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlace(BlockPlaceEvent event) {
|
||||
|
||||
if (!isHeadMaterial(event.getBlockPlaced().getType())) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final ItemMeta itemMeta = event.getItemInHand().getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final BountyHeadData headData = plugin.getBountyHeadMetadata().read(itemMeta.getPersistentDataContainer());
|
||||
if (headData == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final BlockState state = event.getBlockPlaced().getState();
|
||||
if (!(state instanceof Skull skull)) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
plugin.getBountyHeadMetadata().write(skull.getPersistentDataContainer(), headData);
|
||||
skull.update(true, false);
|
||||
|
||||
if (this.coreProtectHook != null) {
|
||||
|
||||
this.coreProtectHook.logPlacement(event.getPlayer().getName(), skull);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onBreak(BlockBreakEvent event) {
|
||||
|
||||
if (!isHeadMaterial(event.getBlock().getType())) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final BlockState state = event.getBlock().getState();
|
||||
final BlockState snapshot = state;
|
||||
if (!(state instanceof Skull skull)) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final BountyHeadData headData = plugin.getBountyHeadMetadata().read(skull.getPersistentDataContainer());
|
||||
if (headData == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final ItemStack drop = new ItemStack(Material.PLAYER_HEAD, 1);
|
||||
final SkullMeta meta = (SkullMeta) drop.getItemMeta();
|
||||
|
||||
plugin.getBountyHeadFormatter().applyHeadMeta(meta, headData, skull.getPlayerProfile(),
|
||||
plugin.getBountyHeadMetadata());
|
||||
drop.setItemMeta(meta);
|
||||
|
||||
if (this.coreProtectHook != null) {
|
||||
|
||||
this.coreProtectHook.logRemoval(event.getPlayer().getName(), snapshot);
|
||||
|
||||
}
|
||||
|
||||
event.setDropItems(false);
|
||||
event.getBlock().setType(Material.AIR, false);
|
||||
event.getBlock().getWorld().dropItemNaturally(event.getBlock().getLocation(), drop);
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onRightClick(PlayerInteractEvent event) {
|
||||
|
||||
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getClickedBlock() != null
|
||||
&& isHeadMaterial(event.getClickedBlock().getType()))
|
||||
{
|
||||
|
||||
final BlockState state = event.getClickedBlock().getState();
|
||||
if (state instanceof Skull skull) {
|
||||
|
||||
sendClaimantActionbar(event.getPlayer(), skull.getPersistentDataContainer());
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final ItemStack item = event.getItem();
|
||||
if (item != null && isHeadMaterial(item.getType())) {
|
||||
|
||||
sendClaimantActionbar(event.getPlayer(), item.getItemMeta());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendClaimantActionbar(Player player, ItemMeta itemMeta) {
|
||||
|
||||
if (itemMeta == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
sendClaimantActionbar(player, itemMeta.getPersistentDataContainer());
|
||||
|
||||
}
|
||||
|
||||
private void sendClaimantActionbar(Player player, PersistentDataContainer dataContainer) {
|
||||
|
||||
final BountyHeadData headData = plugin.getBountyHeadMetadata().read(dataContainer);
|
||||
if (headData == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final net.kyori.adventure.text.Component line = plugin.getBountyHeadFormatter().buildCanonicalLine(headData);
|
||||
|
||||
// Send action bar immediately
|
||||
player.sendActionBar(line);
|
||||
|
||||
// Also schedule a second send to effectively double the visible duration.
|
||||
// 40 ticks ~= 2 seconds; sending again after that will keep the action bar
|
||||
// visible
|
||||
// for roughly twice the default single-send duration.
|
||||
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
|
||||
|
||||
if (player.isOnline()) {
|
||||
|
||||
player.sendActionBar(line);
|
||||
|
||||
}
|
||||
|
||||
}, 40L);
|
||||
|
||||
}
|
||||
|
||||
private static boolean isHeadMaterial(Material material) {
|
||||
|
||||
return material == Material.PLAYER_HEAD || material == Material.PLAYER_WALL_HEAD;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.tcoded.playerbountiesplus.listener;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
import com.tcoded.playerbountiesplus.command.bounty.BountyAddCmd;
|
||||
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class BountyIncreasePromptListener implements Listener {
|
||||
|
||||
private final PlayerBountiesOG plugin;
|
||||
private final Map<UUID, PendingIncreasePrompt> pendingPrompts = new ConcurrentHashMap<>();
|
||||
|
||||
public BountyIncreasePromptListener(PlayerBountiesOG plugin) {
|
||||
|
||||
this.plugin = plugin;
|
||||
|
||||
}
|
||||
|
||||
public void prompt(UUID setterUuid, UUID targetUuid, String targetName) {
|
||||
|
||||
pendingPrompts.put(setterUuid, new PendingIncreasePrompt(targetUuid, targetName));
|
||||
|
||||
}
|
||||
|
||||
public boolean hasPrompt(UUID setterUuid) {
|
||||
|
||||
return pendingPrompts.containsKey(setterUuid);
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onChat(AsyncChatEvent event) {
|
||||
|
||||
final PendingIncreasePrompt prompt = pendingPrompts.get(event.getPlayer().getUniqueId());
|
||||
if (prompt == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
final String input = StringUtils.trimToEmpty(event.message().examinableName());
|
||||
if (StringUtils.equalsIgnoreCase(input, "cancel")) {
|
||||
|
||||
pendingPrompts.remove(event.getPlayer().getUniqueId());
|
||||
|
||||
UtilitiesOG.trueogMessage(event.getPlayer(), plugin.getLang().getColored("command.bounty.add.cancelled"));
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final double amount;
|
||||
try {
|
||||
|
||||
if (!input.matches("^\\d+(?:\\.\\d)?$")) {
|
||||
|
||||
throw new NumberFormatException("Invalid amount format");
|
||||
|
||||
}
|
||||
|
||||
amount = Double.parseDouble(input);
|
||||
|
||||
} catch (NumberFormatException numberFormatException) {
|
||||
|
||||
UtilitiesOG.trueogMessage(event.getPlayer(), plugin.getLang().getColored("command.bounty.add.amount-nan"));
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
pendingPrompts.remove(event.getPlayer().getUniqueId());
|
||||
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> BountyAddCmd.addBounty(plugin, event.getPlayer(),
|
||||
prompt.targetUuid(), prompt.targetName(), amount, true));
|
||||
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent playerQuitEvent) {
|
||||
|
||||
pendingPrompts.remove(playerQuitEvent.getPlayer().getUniqueId());
|
||||
|
||||
}
|
||||
|
||||
private static record PendingIncreasePrompt(UUID targetUuid, String targetName) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +1,49 @@
|
||||
package com.tcoded.playerbountiesplus.listener;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
|
||||
import com.destroystokyo.paper.profile.PlayerProfile;
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
import com.tcoded.playerbountiesplus.event.BountyClaimEvent;
|
||||
import com.tcoded.playerbountiesplus.hook.team.TeamHook;
|
||||
import com.tcoded.playerbountiesplus.manager.BountyDataManager;
|
||||
|
||||
import com.tcoded.playerbountiesplus.util.BountyHeadData;
|
||||
import com.tcoded.playerbountiesplus.util.BountyWorldUtil;
|
||||
import net.luckperms.api.LuckPerms;
|
||||
import net.luckperms.api.cacheddata.CachedMetaData;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class DeathListener implements Listener {
|
||||
|
||||
public static final String BOUNTY_HEAD_NAME_KEY = "bounty_head_target";
|
||||
public static final String BOUNTY_HEAD_UUID_KEY = "bounty_head_target_uuid";
|
||||
public static final String BOUNTY_HEAD_AMOUNT_KEY = "bounty_head_diamonds";
|
||||
public static final String BOUNTY_HEAD_CLAIMANT_KEY = "bounty_head_claimant";
|
||||
public static final String BOUNTY_HEAD_CLAIMANT_UUID_KEY = "bounty_head_claimant_uuid";
|
||||
|
||||
private final PlayerBountiesOG plugin;
|
||||
private final DiamondBankAPIJava diamondBankAPI;
|
||||
private final LuckPerms luckPerms;
|
||||
|
||||
public DeathListener(PlayerBountiesOG plugin, DiamondBankAPIJava diamondBankAPI, LuckPerms luckPerms) {
|
||||
public DeathListener(PlayerBountiesOG plugin) {
|
||||
|
||||
this.plugin = plugin;
|
||||
this.diamondBankAPI = diamondBankAPI;
|
||||
this.luckPerms = luckPerms;
|
||||
this.luckPerms = plugin.getLuckPerms();
|
||||
|
||||
}
|
||||
|
||||
@@ -47,7 +59,7 @@ public class DeathListener implements Listener {
|
||||
|
||||
}
|
||||
|
||||
if (!killer.hasPermission("playerbountiesplus.event.claim")) {
|
||||
if (!killer.hasPermission("playerbountiesog.event.claim")) {
|
||||
|
||||
UtilitiesOG.trueogMessage(killer, plugin.getLang().getColored("death.no-permission"));
|
||||
return;
|
||||
@@ -63,6 +75,14 @@ public class DeathListener implements Listener {
|
||||
|
||||
}
|
||||
|
||||
if (!BountyWorldUtil.isBountyWorldAllowed(plugin, victim.getWorld())) {
|
||||
|
||||
UtilitiesOG.trueogMessage(killer, plugin.getLang().getColored("death.world-not-whitelisted")
|
||||
.replace("{worlds}", BountyWorldUtil.formatBountyWorldWhitelist(plugin)));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final TeamHook teamHook = this.plugin.getTeamHook();
|
||||
if (teamHook != null && teamHook.isFriendly(killer, victim)) {
|
||||
|
||||
@@ -113,36 +133,52 @@ public class DeathListener implements Listener {
|
||||
|
||||
}
|
||||
|
||||
// Suppress the normal Minecraft death message because this death is being
|
||||
// handled as a bounty claim.
|
||||
event.deathMessage(null);
|
||||
|
||||
final String killerDisplay = formatLuckPermsDisplay(killer);
|
||||
final String victimDisplay = formatLuckPermsDisplay(victim);
|
||||
|
||||
final boolean beheaded = maybeDropPlayerHead(event, killer, victim, bounty);
|
||||
|
||||
if (plugin.getConfig().getBoolean("bounty-claimed-announce", true)) {
|
||||
|
||||
final String killerDisplay = formatLuckPermsDisplay(killer);
|
||||
final String victimDisplay = formatLuckPermsDisplay(victim);
|
||||
final String bountyDisplay = formatDiamonds(bounty);
|
||||
final String awardedDisplay = formatDiamonds(awardedAmount);
|
||||
|
||||
final String message;
|
||||
if (bountyClaimable && awardedAmount > 0D) {
|
||||
|
||||
if (Double.compare(awardedAmount, bounty) == 0) {
|
||||
if (beheaded) {
|
||||
|
||||
message = killerDisplay + " &akilled " + victimDisplay + " &aand earned the &b" + awardedDisplay
|
||||
+ " &abounty!";
|
||||
|
||||
} else {
|
||||
|
||||
message = killerDisplay + " &akilled " + victimDisplay + " &aand claimed the &b" + bountyDisplay
|
||||
+ " &abounty for &b" + awardedDisplay + "&a!";
|
||||
|
||||
}
|
||||
message = killerDisplay + "&r &abeheaded " + victimDisplay + "&r &afor &b" + bountyDisplay
|
||||
+ " &bDiamonds&a!";
|
||||
|
||||
} else {
|
||||
|
||||
message = killerDisplay + " &akilled " + victimDisplay + " &aand claimed the &b" + bountyDisplay
|
||||
+ " &abounty!";
|
||||
final String awardedDisplay = formatDiamonds(awardedAmount);
|
||||
if (bountyClaimable && awardedAmount > 0D) {
|
||||
|
||||
if (Double.compare(awardedAmount, bounty) == 0) {
|
||||
|
||||
message = killerDisplay + "&r &akilled " + victimDisplay + "&r &aand earned &b" + awardedDisplay
|
||||
+ " &bDiamonds &afor the bounty!";
|
||||
|
||||
} else {
|
||||
|
||||
message = killerDisplay + "&r &akilled " + victimDisplay + "&r &aand claimed &b" + bountyDisplay
|
||||
+ " &bDiamonds &afor &b" + awardedDisplay + " &bDiamonds&a!";
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
message = killerDisplay + "&r &akilled " + victimDisplay + "&r &aand claimed &b" + bountyDisplay
|
||||
+ " &bDiamonds &abounty!";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Bukkit.getOnlinePlayers().forEach(player -> UtilitiesOG.trueogMessage(player, message));
|
||||
announceBountyClaim(message, killer, victim);
|
||||
|
||||
}
|
||||
|
||||
@@ -151,38 +187,136 @@ public class DeathListener implements Listener {
|
||||
|
||||
}
|
||||
|
||||
private String formatDiamonds(double diamonds) {
|
||||
private void announceBountyClaim(String message, Player killer, Player victim) {
|
||||
|
||||
Bukkit.getOnlinePlayers().stream().filter(this::isClaimAnnouncementRecipient).forEach(player -> {
|
||||
|
||||
UtilitiesOG.trueogMessage(player, message);
|
||||
player.playSound(player.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0F, 1.0F);
|
||||
|
||||
final Particle fireworkParticle = resolveFireworkParticle();
|
||||
player.spawnParticle(fireworkParticle, killer.getLocation().add(0D, 1D, 0D), 40, 0.6D, 0.8D, 0.6D, 0.01D);
|
||||
player.spawnParticle(fireworkParticle, victim.getLocation().add(0D, 1D, 0D), 40, 0.6D, 0.8D, 0.6D, 0.01D);
|
||||
player.spawnParticle(Particle.TOTEM, killer.getLocation().add(0D, 1D, 0D), 20, 0.5D, 0.7D, 0.5D, 0.01D);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private boolean isClaimAnnouncementRecipient(Player player) {
|
||||
|
||||
final List<String> worldWhitelist = plugin.getConfig().getStringList("bounty-claimed-announce-world-whitelist");
|
||||
final boolean hasConfiguredWorld = worldWhitelist.stream().anyMatch(worldName -> !worldName.isBlank());
|
||||
|
||||
return !hasConfiguredWorld || worldWhitelist.stream().anyMatch(
|
||||
worldName -> StringUtils.equalsIgnoreCase(StringUtils.trim(worldName), player.getWorld().getName()));
|
||||
|
||||
}
|
||||
|
||||
private Particle resolveFireworkParticle() {
|
||||
|
||||
try {
|
||||
|
||||
return diamondBankAPI.shardsToDiamonds(diamondBankAPI.diamondsToShards(diamonds));
|
||||
return Particle.valueOf("FIREWORK");
|
||||
|
||||
} catch (RuntimeException runtimeException) {
|
||||
} catch (IllegalArgumentException exception) {
|
||||
|
||||
return String.valueOf(diamonds);
|
||||
return Particle.FIREWORKS_SPARK;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String formatDiamonds(double diamonds) {
|
||||
|
||||
return plugin.getBountyHeadFormatter().formatDiamonds(diamonds);
|
||||
|
||||
}
|
||||
|
||||
private String formatLuckPermsDisplay(Player player) {
|
||||
|
||||
final String playerName = player.getName();
|
||||
final String prefix = stripLeadingReset(stripTrailingReset(getLuckPermsPrefixLegacy(player)));
|
||||
final String suffix = stripLeadingReset(stripTrailingReset(getLuckPermsSuffixLegacy(player)));
|
||||
final String leadingColorCodes = extractLeadingColorCodes(prefix);
|
||||
|
||||
if (prefix.isBlank()) {
|
||||
final StringBuilder out = new StringBuilder();
|
||||
if (!prefix.isBlank()) {
|
||||
|
||||
return player.getName();
|
||||
out.append(prefix).append(" ");
|
||||
|
||||
}
|
||||
|
||||
if (leadingColorCodes.isBlank()) {
|
||||
|
||||
return prefix + " " + player.getName();
|
||||
out.append(playerName);
|
||||
|
||||
} else {
|
||||
|
||||
out.append(leadingColorCodes).append(playerName);
|
||||
|
||||
}
|
||||
|
||||
return prefix + " " + leadingColorCodes + player.getName();
|
||||
if (!suffix.isBlank()) {
|
||||
|
||||
out.append(" ").append(suffix);
|
||||
|
||||
}
|
||||
|
||||
return StringUtils.trim(out.toString()).replaceAll("\\s*>$", "");
|
||||
|
||||
}
|
||||
|
||||
private boolean maybeDropPlayerHead(PlayerDeathEvent event, Player killer, Player victim, double bounty) {
|
||||
|
||||
final double dropChance = Math.max(0D,
|
||||
Math.min(100D, plugin.getConfig().getDouble("bounty-head-drop-chance", 50D)));
|
||||
if (ThreadLocalRandom.current().nextDouble(100D) >= dropChance) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
final ItemStack head = new ItemStack(Material.PLAYER_HEAD, 1);
|
||||
final SkullMeta headMeta = (SkullMeta) head.getItemMeta();
|
||||
final BountyHeadData headData = new BountyHeadData(victim.getUniqueId(), victim.getName(), bounty,
|
||||
killer.getUniqueId(), killer.getName());
|
||||
|
||||
final PlayerProfile profile = Bukkit.createProfile(victim.getUniqueId());
|
||||
plugin.getBountyHeadFormatter().applyHeadMeta(headMeta, headData, profile, plugin.getBountyHeadMetadata());
|
||||
head.setItemMeta(headMeta);
|
||||
|
||||
event.getDrops().add(head);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private String getLuckPermsSuffixLegacy(Player player) {
|
||||
|
||||
if (luckPerms == null) {
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
final User user = luckPerms.getUserManager().getUser(player.getUniqueId());
|
||||
if (user == null) {
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
final CachedMetaData meta = user.getCachedData().getMetaData();
|
||||
final String suffix = meta.getSuffix();
|
||||
|
||||
if (suffix == null || suffix.isBlank()) {
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
return StringUtils.trim(suffix).replace('§', '&');
|
||||
|
||||
}
|
||||
|
||||
@@ -262,8 +396,8 @@ public class DeathListener implements Listener {
|
||||
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
return StringUtils.trim(out.toString()).replaceAll("\\s*>$", "");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.tcoded.playerbountiesplus.listener;
|
||||
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class GuiProtectionListener implements Listener {
|
||||
|
||||
private final PlayerBountiesOG plugin;
|
||||
|
||||
public GuiProtectionListener(PlayerBountiesOG plugin) {
|
||||
|
||||
this.plugin = plugin;
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
|
||||
if (!isMainBountyGui(event.getView())) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (event.getRawSlot() < event.getView().getTopInventory().getSize()) {
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onInventoryDrag(InventoryDragEvent event) {
|
||||
|
||||
if (!isMainBountyGui(event.getView())) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final int topInventorySize = event.getView().getTopInventory().getSize();
|
||||
for (int rawSlot : event.getRawSlots()) {
|
||||
|
||||
if (rawSlot < topInventorySize) {
|
||||
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean isMainBountyGui(InventoryView view) {
|
||||
|
||||
final HumanEntity player = view.getPlayer();
|
||||
if (player == null) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return UtilitiesOG.stripFormatting(view.title().examinableName())
|
||||
.equals(UtilitiesOG.stripFormatting(plugin.getLang().getColored("gui.main.title")));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
@@ -95,7 +96,8 @@ public class BountyDataManager {
|
||||
|
||||
} catch (IOException ioException) {
|
||||
|
||||
ioException.printStackTrace();
|
||||
this.plugin.getLogger().log(Level.SEVERE,
|
||||
"Failed to save bounties file: " + this.bountiesFile.getPath(), ioException);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.tcoded.playerbountiesplus.util;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record BountyHeadData(UUID targetUuid, String targetName, double bountyAmount, UUID claimantUuid,
|
||||
String claimantName)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package com.tcoded.playerbountiesplus.util;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
|
||||
import com.destroystokyo.paper.profile.PlayerProfile;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.luckperms.api.LuckPerms;
|
||||
import net.luckperms.api.cacheddata.CachedMetaData;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import net.luckperms.api.model.user.UserManager;
|
||||
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public final class BountyHeadFormatter {
|
||||
|
||||
// LuckPerms data for offline players is not in memory, so we load it async
|
||||
// and cache the resolved display string. Entries expire so rank changes get
|
||||
// picked up without requiring a server restart.
|
||||
private static final long DISPLAY_CACHE_TTL_MILLIS = 5L * 60L * 1000L;
|
||||
|
||||
private final DiamondBankAPIJava diamondBankAPI;
|
||||
private final LuckPerms luckPerms;
|
||||
private final Map<UUID, CachedDisplay> offlineDisplayCache = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, Boolean> offlineLoadsInFlight = new ConcurrentHashMap<>();
|
||||
|
||||
public BountyHeadFormatter(DiamondBankAPIJava diamondBankAPI, LuckPerms luckPerms) {
|
||||
|
||||
this.diamondBankAPI = diamondBankAPI;
|
||||
this.luckPerms = luckPerms;
|
||||
|
||||
}
|
||||
|
||||
public void applyHeadMeta(SkullMeta meta, BountyHeadData data, PlayerProfile profile, BountyHeadMetadata metadata) {
|
||||
|
||||
if (profile != null) {
|
||||
|
||||
meta.setPlayerProfile(profile);
|
||||
|
||||
}
|
||||
|
||||
meta.displayName(buildCanonicalLine(data));
|
||||
metadata.write(meta.getPersistentDataContainer(), data);
|
||||
|
||||
}
|
||||
|
||||
public TextComponent buildCanonicalLine(BountyHeadData data) {
|
||||
|
||||
return UtilitiesOG.trueogColorize(resolvePlayerDisplay(data.targetUuid(), data.targetName()) + " &cslain by "
|
||||
+ resolveClaimantDisplay(data) + " &cfor a bounty of &b" + formatDiamonds(data.bountyAmount())
|
||||
+ " &bDiamonds");
|
||||
|
||||
}
|
||||
|
||||
public TextComponent buildCanonicalLineTop(BountyHeadData data) {
|
||||
|
||||
return UtilitiesOG.trueogColorize(resolvePlayerDisplay(data.targetUuid(), data.targetName()) + " &cslain by "
|
||||
+ resolveClaimantDisplay(data));
|
||||
|
||||
}
|
||||
|
||||
public TextComponent buildCanonicalLineBottom(BountyHeadData data) {
|
||||
|
||||
return UtilitiesOG.trueogColorize("&cfor a bounty of &b" + formatDiamonds(data.bountyAmount()) + " &bDiamonds");
|
||||
|
||||
}
|
||||
|
||||
public String resolveClaimantDisplay(BountyHeadData data) {
|
||||
|
||||
if (data == null) {
|
||||
|
||||
return "&fUnknown";
|
||||
|
||||
}
|
||||
|
||||
return resolvePlayerDisplay(data.claimantUuid(), data.claimantName());
|
||||
|
||||
}
|
||||
|
||||
public String resolvePlayerDisplay(UUID targetUuid, String fallbackName) {
|
||||
|
||||
final String playerName = StringUtils.defaultIfBlank(fallbackName, "Unknown Player");
|
||||
if (targetUuid == null || luckPerms == null) {
|
||||
|
||||
return "&f" + playerName;
|
||||
|
||||
}
|
||||
|
||||
final User loadedUser = luckPerms.getUserManager().getUser(targetUuid);
|
||||
if (loadedUser != null) {
|
||||
|
||||
return formatFromUser(loadedUser, playerName);
|
||||
|
||||
}
|
||||
|
||||
final CachedDisplay cached = offlineDisplayCache.get(targetUuid);
|
||||
if (cached != null && !cached.isExpired()) {
|
||||
|
||||
return cached.display();
|
||||
|
||||
}
|
||||
|
||||
scheduleOfflineUserLoad(targetUuid, playerName);
|
||||
|
||||
return cached != null ? cached.display() : "&f" + playerName;
|
||||
|
||||
}
|
||||
|
||||
private void scheduleOfflineUserLoad(UUID targetUuid, String playerName) {
|
||||
|
||||
if (luckPerms == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (offlineLoadsInFlight.putIfAbsent(targetUuid, Boolean.TRUE) != null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final UserManager userManager = luckPerms.getUserManager();
|
||||
userManager.loadUser(targetUuid).whenComplete((user, throwable) -> {
|
||||
|
||||
try {
|
||||
|
||||
if (user == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final String display = formatFromUser(user, playerName);
|
||||
offlineDisplayCache.put(targetUuid,
|
||||
new CachedDisplay(display, System.currentTimeMillis() + DISPLAY_CACHE_TTL_MILLIS));
|
||||
|
||||
// LuckPerms keeps loaded users resident until explicitly released.
|
||||
// Release only when the UUID is not currently online so we do not
|
||||
// evict live data another system may depend on.
|
||||
if (Bukkit.getPlayer(targetUuid) == null) {
|
||||
|
||||
userManager.cleanupUser(user);
|
||||
|
||||
}
|
||||
|
||||
} finally {
|
||||
|
||||
offlineLoadsInFlight.remove(targetUuid);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private String formatFromUser(User user, String playerName) {
|
||||
|
||||
final CachedMetaData meta = user.getCachedData().getMetaData();
|
||||
final String prefix = StringUtils.trim(StringUtils.defaultString(meta.getPrefix()).replace('§', '&'));
|
||||
final String suffix = StringUtils.trim(StringUtils.defaultString(meta.getSuffix()).replace('§', '&'));
|
||||
final boolean metaContainsName = containsVisibleName(prefix, playerName)
|
||||
|| containsVisibleName(suffix, playerName);
|
||||
|
||||
final StringBuilder out = new StringBuilder();
|
||||
if (!prefix.isBlank()) {
|
||||
|
||||
out.append(prefix).append(' ');
|
||||
|
||||
}
|
||||
|
||||
if (!metaContainsName) {
|
||||
|
||||
out.append(playerName);
|
||||
|
||||
}
|
||||
|
||||
if (!suffix.isBlank()) {
|
||||
|
||||
out.append(' ').append(suffix);
|
||||
|
||||
}
|
||||
|
||||
return sanitizeDisplay(out.toString(), playerName);
|
||||
|
||||
}
|
||||
|
||||
public String formatDiamonds(double diamonds) {
|
||||
|
||||
if (diamondBankAPI == null) {
|
||||
|
||||
return String.valueOf(diamonds);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
return diamondBankAPI.shardsToDiamonds(diamondBankAPI.diamondsToShards(diamonds));
|
||||
|
||||
} catch (RuntimeException runtimeException) {
|
||||
|
||||
return String.valueOf(diamonds);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String sanitizeDisplay(String formatted, String fallbackName) {
|
||||
|
||||
final String cleaned = StringUtils.trimToEmpty(formatted).replaceAll("(?i)(?:\\s*(?:<reset>|[&§]r))+$", "")
|
||||
.replaceAll("\\s*>$", "");
|
||||
|
||||
return cleaned.isBlank() ? fallbackName : cleaned;
|
||||
|
||||
}
|
||||
|
||||
private static boolean containsVisibleName(String text, String playerName) {
|
||||
|
||||
if (text == null || playerName == null || playerName.isBlank()) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
final String normalizedText = StringUtils.lowerCase(UtilitiesOG.stripFormatting(text), Locale.ROOT);
|
||||
final String normalizedName = StringUtils.lowerCase(playerName, Locale.ROOT);
|
||||
|
||||
return StringUtils.contains(normalizedText, normalizedName);
|
||||
|
||||
}
|
||||
|
||||
private record CachedDisplay(String display, long expiresAtMillis) {
|
||||
|
||||
boolean isExpired() {
|
||||
|
||||
return System.currentTimeMillis() >= expiresAtMillis;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.tcoded.playerbountiesplus.util;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
import com.tcoded.playerbountiesplus.listener.DeathListener;
|
||||
|
||||
public final class BountyHeadMetadata {
|
||||
|
||||
private final NamespacedKey targetKey;
|
||||
private final NamespacedKey targetUuidKey;
|
||||
private final NamespacedKey amountKey;
|
||||
private final NamespacedKey claimantKey;
|
||||
private final NamespacedKey claimantUuidKey;
|
||||
|
||||
public BountyHeadMetadata(PlayerBountiesOG plugin) {
|
||||
|
||||
this.targetKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_NAME_KEY);
|
||||
this.targetUuidKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_UUID_KEY);
|
||||
this.amountKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_AMOUNT_KEY);
|
||||
this.claimantKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_CLAIMANT_KEY);
|
||||
this.claimantUuidKey = new NamespacedKey(plugin, DeathListener.BOUNTY_HEAD_CLAIMANT_UUID_KEY);
|
||||
|
||||
}
|
||||
|
||||
public BountyHeadData read(PersistentDataContainer dataContainer) {
|
||||
|
||||
final String targetName = dataContainer.get(targetKey, PersistentDataType.STRING);
|
||||
final String targetUuid = dataContainer.get(targetUuidKey, PersistentDataType.STRING);
|
||||
final Double bountyAmount = dataContainer.get(amountKey, PersistentDataType.DOUBLE);
|
||||
final String claimantName = dataContainer.get(claimantKey, PersistentDataType.STRING);
|
||||
final String claimantUuid = dataContainer.get(claimantUuidKey, PersistentDataType.STRING);
|
||||
|
||||
if (StringUtils.isBlank(targetName) || StringUtils.isBlank(targetUuid) || bountyAmount == null) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
return new BountyHeadData(UUID.fromString(targetUuid), targetName, bountyAmount,
|
||||
parseOptionalUuid(claimantUuid), claimantName);
|
||||
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void write(PersistentDataContainer dataContainer, BountyHeadData data) {
|
||||
|
||||
dataContainer.set(targetKey, PersistentDataType.STRING, data.targetName());
|
||||
dataContainer.set(targetUuidKey, PersistentDataType.STRING, data.targetUuid().toString());
|
||||
dataContainer.set(amountKey, PersistentDataType.DOUBLE, data.bountyAmount());
|
||||
if (StringUtils.isBlank(data.claimantName()) || data.claimantUuid() == null) {
|
||||
|
||||
dataContainer.remove(claimantKey);
|
||||
dataContainer.remove(claimantUuidKey);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
dataContainer.set(claimantKey, PersistentDataType.STRING, data.claimantName());
|
||||
dataContainer.set(claimantUuidKey, PersistentDataType.STRING, data.claimantUuid().toString());
|
||||
|
||||
}
|
||||
|
||||
private static UUID parseOptionalUuid(String value) {
|
||||
|
||||
if (StringUtils.isBlank(value)) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
return UUID.fromString(value);
|
||||
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.tcoded.playerbountiesplus.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.tcoded.playerbountiesplus.PlayerBountiesOG;
|
||||
|
||||
public final class BountyWorldUtil {
|
||||
|
||||
private static final String BOUNTY_WORLD_WHITELIST_KEY = "bounty-world-whitelist";
|
||||
private static final List<String> DEFAULT_BOUNTY_WORLD_WHITELIST = List.of("world", "world_nether",
|
||||
"world_the_end");
|
||||
|
||||
private BountyWorldUtil() {
|
||||
|
||||
}
|
||||
|
||||
public static boolean isBountyWorldAllowed(PlayerBountiesOG plugin, Player player) {
|
||||
|
||||
return player != null && isBountyWorldAllowed(plugin, player.getWorld());
|
||||
|
||||
}
|
||||
|
||||
public static boolean isBountyWorldAllowed(PlayerBountiesOG plugin, World world) {
|
||||
|
||||
return world != null && isBountyWorldAllowed(plugin, world.getName());
|
||||
|
||||
}
|
||||
|
||||
public static boolean isBountyWorldAllowed(PlayerBountiesOG plugin, String worldName) {
|
||||
|
||||
final List<String> worldWhitelist = getBountyWorldWhitelist(plugin);
|
||||
final boolean hasConfiguredWorld = worldWhitelist.stream().anyMatch(world -> !StringUtils.isBlank(world));
|
||||
|
||||
return !hasConfiguredWorld || worldWhitelist.stream()
|
||||
.anyMatch(world -> StringUtils.equalsIgnoreCase(StringUtils.trim(world), worldName));
|
||||
|
||||
}
|
||||
|
||||
public static String formatBountyWorldWhitelist(PlayerBountiesOG plugin) {
|
||||
|
||||
final List<String> worldWhitelist = getBountyWorldWhitelist(plugin).stream().map(StringUtils::trimToEmpty)
|
||||
.filter(StringUtils::isNotBlank).collect(Collectors.toList());
|
||||
|
||||
return worldWhitelist.isEmpty() ? "every world" : StringUtils.join(worldWhitelist, ", ");
|
||||
|
||||
}
|
||||
|
||||
private static List<String> getBountyWorldWhitelist(PlayerBountiesOG plugin) {
|
||||
|
||||
if (plugin.getConfig().contains(BOUNTY_WORLD_WHITELIST_KEY)) {
|
||||
|
||||
return plugin.getConfig().getStringList(BOUNTY_WORLD_WHITELIST_KEY);
|
||||
|
||||
}
|
||||
|
||||
return DEFAULT_BOUNTY_WORLD_WHITELIST;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
@@ -40,7 +41,7 @@ public class LangUtil {
|
||||
|
||||
for (SupportedLang lang : SupportedLang.values()) {
|
||||
|
||||
if (lang.name().equalsIgnoreCase(name)) {
|
||||
if (StringUtils.equalsIgnoreCase(lang.name(), name)) {
|
||||
|
||||
return lang;
|
||||
|
||||
@@ -54,7 +55,7 @@ public class LangUtil {
|
||||
|
||||
public String fileName() {
|
||||
|
||||
return this.name().toLowerCase(Locale.ROOT) + ".yml";
|
||||
return StringUtils.lowerCase(this.name(), Locale.ROOT) + ".yml";
|
||||
|
||||
}
|
||||
|
||||
@@ -66,7 +67,7 @@ public class LangUtil {
|
||||
this.logger = plugin.getLogger();
|
||||
|
||||
final SupportedLang resolvedLang = resolveSupportedLang(configuredLang);
|
||||
this.lang = resolvedLang.name().toLowerCase(Locale.ROOT);
|
||||
this.lang = StringUtils.lowerCase(resolvedLang.name(), Locale.ROOT);
|
||||
|
||||
final String englishFilePath = "lang/" + SupportedLang.EN_US.fileName();
|
||||
final String langFilePath = "lang/" + resolvedLang.fileName();
|
||||
@@ -102,7 +103,7 @@ public class LangUtil {
|
||||
private SupportedLang resolveSupportedLang(String configuredLang) {
|
||||
|
||||
final String normalized = configuredLang == null ? DEFAULT_LANG_CODE
|
||||
: configuredLang.trim().toUpperCase(Locale.ROOT);
|
||||
: StringUtils.upperCase(StringUtils.trim(configuredLang), Locale.ROOT);
|
||||
|
||||
final SupportedLang supportedLang = SupportedLang.find(normalized);
|
||||
|
||||
@@ -217,4 +218,4 @@ public class LangUtil {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
# We currently only support a few languages, but please contribute your own translations!
|
||||
# To contribute new languages, please join our Discord server or open a pull request on GitHub!
|
||||
# You can find both of these links on the plugin download page: https://www.spigotmc.org/resources/player-bounties-plus.108637/
|
||||
#
|
||||
# PlayerBounties-OG Config File.
|
||||
# Manually verified translations:
|
||||
# de_de - German (Germany)
|
||||
# en_us - English (US)
|
||||
@@ -13,32 +10,52 @@
|
||||
# es_es - Spanish (Spain)
|
||||
# hi_in - Hindi (India)
|
||||
# hu_hu - Hungarian (Hungary)
|
||||
# it_it - Italian (Italy)
|
||||
# ru_ru - Russian (Russia)
|
||||
# se_se - Swedish (Sweden)
|
||||
# zh_cn - Chinese (China)
|
||||
# en_uwu - English (Don't ask)
|
||||
lang: "en_us"
|
||||
|
||||
# Add more money on the victim player than the command sender spends
|
||||
# For example: /bounty set TechnicallyCoded 100 -> turns into a bounty of 200 while only taking 100 from the sender
|
||||
# Add more money on the victim player than the command sender spends.
|
||||
# For example: /bounty set TechnicallyCoded 100 -> turns into a bounty of 200 while only taking 100 from the sender.
|
||||
bounty-multiplier: 1.0
|
||||
|
||||
# Minimum and maximum bounty amount - These values are checked BEFORE the multiplier above is applied.
|
||||
bounty-minimum: 1.0
|
||||
bounty-maximum: 1000000.0
|
||||
|
||||
# If we should give the bounty money to the killer
|
||||
# If we should give the bounty money to the killer.
|
||||
bounty-claimable: true
|
||||
|
||||
# Worlds where players may set, add to, and claim bounties.
|
||||
# Leave empty to allow bounty placement and claims in every world. World names are case-insensitive.
|
||||
bounty-world-whitelist: ["world", "world_nether", "world_the_end"]
|
||||
|
||||
# Give more or less money to the killer. This value can be negative if you want to.
|
||||
bounty-claim-multiplier: 1.0
|
||||
|
||||
# If we should announce that the bounty was claimed
|
||||
# If we should announce that the bounty was claimed.
|
||||
bounty-claimed-announce: true
|
||||
|
||||
# If we should announce that the bounty was placed
|
||||
# Worlds whose players should receive bounty claim and beheading announcements.
|
||||
# Leave empty to announce to players in every world. World names are case-insensitive.
|
||||
bounty-claimed-announce-world-whitelist: ["world", "world_nether", "world_the_end"]
|
||||
|
||||
# Chance (0-100) that claiming a bounty drops the victim's player head.
|
||||
bounty-head-drop-chance: 50.0
|
||||
|
||||
# How placed bounty heads should display extra labels.
|
||||
# none = disabled
|
||||
# hover = show a temporary floating label while a player aims at the head
|
||||
bounty-head-label-mode: hover
|
||||
|
||||
# Maximum range in blocks for detecting a looked-at bounty head.
|
||||
bounty-head-label-range: 8.0
|
||||
|
||||
# If we should announce that the bounty was placed.
|
||||
bounty-placed-announce: true
|
||||
|
||||
# Should the bounty money be taken from the player who dies?
|
||||
# This can reduce same-team killing (or the use of alts) to claim the bounty from themselves
|
||||
bounty-take-from-victim: false
|
||||
# This can reduce same-team killing (or the use of alts) to claim the bounty from themselves.
|
||||
bounty-take-from-victim: false
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aDer Spieler, den du getötet hast, hat eine aktive Belohnung, aber du bist im selben Team wie er!"
|
||||
announce-claimed: "&4&l{killer} hat die Belohnung von {victim} im Wert von ${bounty} beansprucht"
|
||||
no-permission: "&cSie dürfen keine Kopfgelder beanspruchen!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aThe player you killed has an active bounty but you are in the same team as they are!"
|
||||
announce-claimed: "&4&l{killer} claimed {victim}'s bounty worth ${bounty}"
|
||||
no-permission: "&cYou are not allowed to claim bounties!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
death:
|
||||
same-team: "&aThe player you killed has an active bounty but you are in the same team as they are!"
|
||||
announce-claimed: "&4&l{killer} claimed {victim}'s bounty worth ${bounty}"
|
||||
no-permission: "&cYou are not allowed to claim bounties!"
|
||||
world-not-whitelisted: "&cYou can only claim bounties in these worlds: {worlds}."
|
||||
|
||||
command:
|
||||
no-permission: "&cYou don't have the permission to use this command!"
|
||||
@@ -36,8 +36,9 @@ command:
|
||||
player-not-found: "&cThat player is not online or doesn't exist!"
|
||||
success: "&a{target} currently has a bounty of {bounty}."
|
||||
bounty:
|
||||
no-action: "&cYou need to specify an action! /bounty <set/top/check>"
|
||||
no-action: "&cYou need to specify an action! /bounty <set/add/top/check>"
|
||||
invalid-action: "&cThat's not a valid action!"
|
||||
world-not-whitelisted: "&cYou can only set or increase bounties in these worlds: {worlds}."
|
||||
set:
|
||||
missing-args: "&cYou need to specify a player and an amount! /bounty set <player> <amount>"
|
||||
amount-nan: "&cThat's not a valid number!"
|
||||
@@ -47,8 +48,17 @@ command:
|
||||
not-enough-money: "&cYou don't have enough money for that!"
|
||||
internal-invalid-value: "&cAn invalid value was reached during internal processing. Please report this to an administrator!"
|
||||
success: "&cYou placed a bounty of {bounty} on {target}'s head!"
|
||||
announce: "\nA bounty of {bounty} was placed on {target}'s head by {player}!{extra}\n"
|
||||
announce: "\nA bounty of {bounty} was placed on {target}'s &ahead by&r {player}!{extra}\n"
|
||||
announce-extra: " (Total: {total})"
|
||||
cancelled: "&cBounty placement was cancelled."
|
||||
add:
|
||||
missing-args: "&cYou need to specify a player and an amount! /bounty add <player> <amount>"
|
||||
amount-nan: "&cThat's not a valid number!"
|
||||
player-not-found: "&cThat player could not be found!"
|
||||
success: "&aYou increased {target}'s bounty by {amount}. Total: {total}."
|
||||
announce: "\n{player} increased {target}'s bounty by {amount}&a! &7(Total: &b{total}&7)\n"
|
||||
prompt: "&eType how many diamonds to add to {target}'s bounty in chat. Type &ccancel &eto abort."
|
||||
cancelled: "&cBounty increase cancelled."
|
||||
check:
|
||||
no-player-specified: "&cYou need to specify a player! /bounty check <player>"
|
||||
player-not-found: "&cThat player is not online or doesn't exist!"
|
||||
@@ -61,4 +71,3 @@ command:
|
||||
gui:
|
||||
main:
|
||||
title: "&eBounties"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aDa pwayer you kiwwed has an active bounty but you awe in da same team as they awe!"
|
||||
announce-claimed: "&4&w{killer} cwaimed {victim}'s bounty wowth ${bounty}"
|
||||
no-permission: "&cYou awe nyot awwowed to cwaim bounties!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aEl jugador que mataste tiene una recompensa activa pero estás en el mismo equipo que él."
|
||||
announce-claimed: "&4&l{killer} reclamó la recompensa de {victim} valorada en ${bounty}"
|
||||
no-permission: "&c¡No se le permite reclamar recompensas!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aLe joueur que vous avez tué a une prime sur sa tête mais vous êtes dans la même équipe!"
|
||||
announce-claimed: "&4&l{killer} a gagné la prime de {victim} au prix de ${bounty}"
|
||||
no-permission: "&cVous n'êtes pas autorisé à réclamer des primes!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aवह खिलाड़ी जिसे आपने मार डाला है, उसके पास एक सक्रिय इनाम है, लेकिन आप उनकी टीम में हैं!"
|
||||
announce-claimed: "&4&l{killer} ने {victim} के इनाम {bounty} को मांग लिया"
|
||||
no-permission: "&cआपको इनामों का दावा करने की अनुमति नहीं है!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
death:
|
||||
same-team: "&aA játékos, akit megöltél, aktív fejpénzt volt a fején, de ugyanabba a csapatba tartozol, mint ő!"
|
||||
announce-claimed: "&4&l{killer} behajtotta {victim} fejpénzét és megkapta jutalmát aminek értéke: ${bounty}"
|
||||
no-permission: "&cNem igényelheted a fejpénzeket!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aIl giocatore che hai ucciso ha una taglia attiva, ma sei nella stessa squadra!"
|
||||
announce-claimed: "&4&l{killer} ha reclamato la taglia di {victim} del valore di ${bounty}"
|
||||
no-permission: "&cNon ti è permesso riscuotere le taglie!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aDe speler die je hebt gedood heeft een actieve beloning, maar je zit in hetzelfde team als zij!"
|
||||
announce-claimed: "&4&l{killer} heeft de beloning van {victim} ter waarde van ${bounty} geclaimd"
|
||||
no-permission: "&cU mag geen premies claimen!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aИгрок, которого вы убили, имеет активную награду, но вы находитесь в одной команде с ними!"
|
||||
announce-claimed: "&4&l{killer} получил награду {victim} на сумму ${bounty}"
|
||||
no-permission: "&cВам не разрешено требовать наград!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&aDen spelare du dödade har en aktiv prissumma, men du tillhör samma lag som de är!"
|
||||
announce-claimed: "&4&l{killer} har tagit {victim}s prissumma värd {bounty} dollar"
|
||||
no-permission: "&cDu får inte hämta in prissummor!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
death:
|
||||
same-team: "&a你击杀的玩家有悬赏,但你与他们在同一队伍中!"
|
||||
announce-claimed: "&4&l{killer} 已领取 {victim} 的悬赏,价值 ${bounty}"
|
||||
no-permission: "&c你没有权限领取悬赏!"
|
||||
|
||||
command:
|
||||
|
||||
@@ -4,36 +4,41 @@ main: com.tcoded.playerbountiesplus.PlayerBountiesOG
|
||||
api-version: '${apiVersion}'
|
||||
authors: [ TechnicallyCoded, NotAlexNoyle ]
|
||||
description: A player bounty plugin for TrueOG Network using our custom Diamond Economy.
|
||||
website: https://github.com/true-og/PlayerBounties-OG/
|
||||
website: https://git.trueog.net/trueog/PlayerBounties-OG/
|
||||
folia-supported: false
|
||||
|
||||
depend:
|
||||
- DiamondBank-OG
|
||||
- CoreProtect-OG
|
||||
softdepend:
|
||||
- SimpleClans
|
||||
- Unions-OG
|
||||
- LuckPerms
|
||||
|
||||
commands:
|
||||
bounty:
|
||||
permission: playerbountiesplus.command.bounty
|
||||
permission: playerbountiesog.command.bounty
|
||||
aliases: [bounties, prime, primes]
|
||||
playerbountiesplusadmin:
|
||||
permission: playerbountiesplus.command.admin
|
||||
permission: playerbountiesog.command.admin
|
||||
aliases: [playerbountiesplus, pbpa, pbp]
|
||||
|
||||
permissions:
|
||||
playerbountiesplus.command.bounty:
|
||||
playerbountiesog.command.bounty:
|
||||
default: true
|
||||
playerbountiesplus.command.admin:
|
||||
playerbountiesog.command.admin:
|
||||
default: op
|
||||
children:
|
||||
playerbountiesplus.command.admin.version: {}
|
||||
playerbountiesplus.command.admin.reload: {}
|
||||
playerbountiesplus.command.admin.bounty.set: {}
|
||||
playerbountiesplus.command.admin.bounty.add: {}
|
||||
playerbountiesplus.command.admin.bounty.remove: {}
|
||||
playerbountiesplus.command.admin.bounty.delete: {}
|
||||
playerbountiesplus.command.bounty.set:
|
||||
playerbountiesog.command.admin.version: {}
|
||||
playerbountiesog.command.admin.reload: {}
|
||||
playerbountiesog.command.admin.bounty.set: {}
|
||||
playerbountiesog.command.admin.bounty.add: {}
|
||||
playerbountiesog.command.admin.bounty.remove: {}
|
||||
playerbountiesog.command.admin.bounty.delete: {}
|
||||
playerbountiesog.command.admin.bounty.get: {}
|
||||
playerbountiesog.command.bounty.set:
|
||||
default: true
|
||||
playerbountiesplus.event.claim:
|
||||
playerbountiesog.command.bounty.add:
|
||||
default: true
|
||||
playerbountiesog.event.claim:
|
||||
default: true
|
||||
Reference in new issue
Block a user