Replaced global material with Global Rule
This commit is contained in:
38
Ideas.md
38
Ideas.md
@@ -1,36 +1,2 @@
|
||||
Infinite Item - one that always stays at max stack size.
|
||||
|
||||
|
||||
| Action (Event Handler) | Blocks Protected? | Blocks Final? | Notes / Hand Nuance |
|
||||
|--------------------------------------------------------------|---------------------------------|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **onCraftItem**<br/>(`CraftItemEvent`) | ✅ result | ✅ ingredients when modifying | Only cancels crafting **Protected** results. For **Final** ingredients it uses a heuristic (`isModifyingCraft`) to see if the recipe would alter the item. |
|
||||
| **onSmithingTableUse**<br/>(`PrepareSmithingEvent`) | ✅ result | ✅ base or addition | Smithing always “modifies” the base; both base and addition are checked for **Final**. |
|
||||
| **onEnchantItem**<br/>(`EnchantItemEvent`) | ❌ (Protected can be enchanted) | ✅ item | Only checks the single item in the enchanting slot (no off-hand concept). |
|
||||
| **onPrepareEnchant**<br/>(`PrepareItemEnchantEvent`) | ❌ | ✅ item | Prevents placing a **Final** item into the table; no hand distinction. |
|
||||
| **onAnvilUse**<br/>(`PrepareAnvilEvent`) | ✅ result | ✅ first/second if modifying | Heuristic same as crafting. Cancels creation of **Protected** results and any **Final** input being “modified.” |
|
||||
| **onBrew**<br/>(`BrewEvent`) | ✅ ingredient & results | ✅ bottles | Checks the single “ingredient” slot for **Protected**, then all bottle slots for **Final**. |
|
||||
| **onBrewingStandFuel**<br/>(`BrewingStandFuelEvent`) | ✅ fuel | ❌ | Only checks the one fuel slot. |
|
||||
| **onFurnaceBurn**<br/>(`FurnaceBurnEvent`) | ❌ | ✅ fuel | Only the one fuel slot. |
|
||||
| **onFurnaceSmelt**<br/>(`FurnaceSmeltEvent`) | ✅ result | ✅ source | Checks both source (no fuel here) for **Final** and result for **Protected**. |
|
||||
| **onSpecialCraft**<br/>(`PrepareItemCraftEvent`) | ✅ result | ✅ various inputs | Covers Loom, Cartography, Grindstone, Stonecutter. Only the specific input slots per inventory type are checked for **Final**. |
|
||||
| **onCauldron**<br/>(`CauldronLevelChangeEvent`) | ❌ | ✅ main & off | Explicitly checks both `getItemInMainHand()` **and** `getItemInOffHand()` for **Final**. |
|
||||
| **onCommand**<br/>(`PlayerCommandPreprocessEvent`) | ❌ | ✅ main & off | Checks both hands’ items against configured command-regex; if the command could modify a **Final** item in either hand, it’s cancelled. |
|
||||
| **onPickUp**<br/>(`EntityPickupItemEvent`) | ✅ non-player only | ❌ | Only blocks non-players from picking up **Protected** items. |
|
||||
| **onBlockPlace**<br/>(`BlockPlaceEvent`) | ✅ in-hand item | ❌ | Uses `event.getItemInHand()` (the hand used) to prevent placing **Protected** items. |
|
||||
| **onPlayerInteract**<br/>(`PlayerInteractEvent`) | ✅ item | ✅ only when filling bottles | Blocks any use of **Protected** items. For **Final**, only blocks filling a bottle/bucket (checks both material and water target), using `event.getItem()` (hand-sensitive). |
|
||||
| **onBucketEmpty**<br/>(`PlayerBucketEmptyEvent`) | ✅ hand item | ✅ hand item | Uses `event.getHand()` to locate the bucket (main vs. off) and blocks emptying either **Protected** or **Final** buckets. |
|
||||
| **onBucketFill**<br/>(`PlayerBucketFillEvent`) | ✅ hand item | ✅ hand item | Same as empty: checks bucket in the hand specified by `event.getHand()`. |
|
||||
| **onBucketFish**<br/>(`PlayerBucketEntityEvent`) | ✅ original bucket | ✅ original bucket | Uses `event.getOriginalBucket()`, so it covers the bucket used to capture an entity (no main/off distinction here). |
|
||||
| **onPlayerInteractEntity**<br/>(`PlayerInteractEntityEvent`) | ✅ hand item | ❌ | Uses `event.getHand()`, but only blocks **Protected** items. |
|
||||
| **onItemConsume**<br/>(`PlayerItemConsumeEvent`) | ✅ item | ❌ | Single `event.getItem()`, blocks **Protected** consumables only. |
|
||||
| **onBowShoot**<br/>(`EntityShootBowEvent`) | ✅ bow or ammo | ✅ bow | Checks both the bow (main hand item) and the ammo for **Protected**, and stops any **Final** bow use. |
|
||||
| **onProjectileLaunch**<br/>(`ProjectileLaunchEvent`) | ✅ held item (incl. projectiles) | ❌ | Looks at `ItemStack` in hand or from the projectile’s `getItem()`. |
|
||||
| **onAttack**<br/>(`EntityDamageByEntityEvent`) | ✅ main hand item | ❌ | Only checks `player.getInventory().getItemInMainHand()`. |
|
||||
| **onBreakBlock**<br/>(`BlockBreakEvent`) | ✅ main hand item | ❌ | Same: only the main hand tool is checked. |
|
||||
| **onDispense**<br/>(`BlockDispenseEvent`) | ✅ dispensed item | ❌ | Does not consider any player hand. |
|
||||
| **onItemDamage**<br/>(`PlayerItemDamageEvent`) | ❌ | ✅ item | Prevents durability loss on **Final** items (single item context). |
|
||||
| **onItemMend**<br/>(`PlayerItemMendEvent`) | ❌ | ✅ item | Prevents XP-mending of **Final** items. |
|
||||
| **onBlockDrop**<br/>(`BlockDropItemEvent`) | ✅ world drops | ❌ | Filters out **Protected** item drops from any block. |
|
||||
| **onItemSpawn**<br/>(`ItemSpawnEvent`) | ✅ world spawns | ❌ | Cancels spawning of **Protected** items from non-player sources. |
|
||||
| **onInventoryClick**<br/>(`InventoryClickEvent`) | ✅ protected in trade result | ❌ | Only blocks taking **Protected** items from a villager “RESULT” slot (no check on **Final**). |
|
||||
| **onInventoryMove**<br/>(`InventoryMoveItemEvent`) | ✅ item | ❌ | Prevents hoppers/droppers from moving **Protected** items only. |
|
||||
match based on certain features such as enchant, trim, lore string, or name.
|
||||
on death, drop any items stored in chest GUI.
|
||||
61
build.gradle
61
build.gradle
@@ -1,61 +0,0 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id("xyz.jpenilla.run-paper") version "2.3.1"
|
||||
id 'com.gradleup.shadow' version '9.0.0-beta10'
|
||||
}
|
||||
|
||||
group = 'me.trouper'
|
||||
version = '0.0.1'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "papermc-repo"
|
||||
url = "https://repo.papermc.io/repository/maven-public/"
|
||||
}
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = "https://oss.sonatype.org/content/groups/public/"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:1.21.5-R0.1-SNAPSHOT")
|
||||
implementation("me.trouper:alias:1.0-1.21.1-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks {
|
||||
runServer {
|
||||
minecraftVersion("1.21.5")
|
||||
}
|
||||
}
|
||||
|
||||
def targetJavaVersion = 21
|
||||
java {
|
||||
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
|
||||
if (JavaVersion.current() < javaVersion) {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
|
||||
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
|
||||
options.release.set(targetJavaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
def props = [version: version]
|
||||
inputs.properties props
|
||||
filteringCharset 'UTF-8'
|
||||
filesMatching('plugin.yml') {
|
||||
expand props
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
//minimize()
|
||||
}
|
||||
41
build.gradle.kts
Normal file
41
build.gradle.kts
Normal file
@@ -0,0 +1,41 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
|
||||
id("xyz.jpenilla.run-paper") version "2.3.1"
|
||||
id("com.gradleup.shadow") version "9.0.0-rc1"
|
||||
}
|
||||
|
||||
group = "me.trouper.dupealias"
|
||||
version = "0.0.1"
|
||||
description = "A powerful dupe plugin with niche features for servers looking to stand out."
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT")
|
||||
implementation("me.trouper:alias:1.0-1.21.5-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveClassifier.set("")
|
||||
}
|
||||
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.release = 21
|
||||
}
|
||||
javadoc {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.configuration-cache=true
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
10
gradlew
vendored
Executable file → Normal file
10
gradlew
vendored
Executable file → Normal file
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -112,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -203,7 +205,7 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
@@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
6
gradlew.bat
vendored
6
gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -68,11 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
rootProject.name = 'DupeAlias'
|
||||
5
settings.gradle.kts
Normal file
5
settings.gradle.kts
Normal file
@@ -0,0 +1,5 @@
|
||||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0"
|
||||
}
|
||||
|
||||
rootProject.name = "DupeAlias"
|
||||
@@ -3,9 +3,9 @@ package me.trouper.dupealias;
|
||||
import me.trouper.alias.AliasContext;
|
||||
import me.trouper.alias.AliasContextProvider;
|
||||
import me.trouper.alias.data.Common;
|
||||
import me.trouper.dupealias.data.CommonConfig;
|
||||
import me.trouper.dupealias.data.DupeConfig;
|
||||
import me.trouper.dupealias.data.PlayerData;
|
||||
import me.trouper.dupealias.data.files.CommonConfig;
|
||||
import me.trouper.dupealias.data.files.DupeConfig;
|
||||
import me.trouper.dupealias.data.files.NBTStorage;
|
||||
import me.trouper.dupealias.server.DupeManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@@ -29,7 +29,7 @@ public final class DupeAlias extends JavaPlugin {
|
||||
alias.initialize();
|
||||
alias.getDataManager().load(CommonConfig.class).save();
|
||||
alias.getDataManager().load(DupeConfig.class).save();
|
||||
alias.getDataManager().load(PlayerData.class).save();
|
||||
alias.getDataManager().load(NBTStorage.class).save();
|
||||
updateCommon();
|
||||
|
||||
dupe = new DupeManager();
|
||||
@@ -39,7 +39,7 @@ public final class DupeAlias extends JavaPlugin {
|
||||
public void onDisable() {
|
||||
alias.getDataManager().save(CommonConfig.class);
|
||||
alias.getDataManager().save(DupeConfig.class);
|
||||
alias.getDataManager().save(PlayerData.class);
|
||||
alias.getDataManager().save(NBTStorage.class);
|
||||
alias.shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ package me.trouper.dupealias;
|
||||
|
||||
import me.trouper.alias.server.ContextAware;
|
||||
import me.trouper.alias.server.events.listeners.GuiInputListener;
|
||||
import me.trouper.dupealias.data.CommonConfig;
|
||||
import me.trouper.dupealias.data.DupeConfig;
|
||||
import me.trouper.dupealias.data.files.CommonConfig;
|
||||
import me.trouper.dupealias.data.files.DupeConfig;
|
||||
import me.trouper.dupealias.data.files.NBTStorage;
|
||||
import me.trouper.dupealias.server.DupeManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@@ -24,6 +25,11 @@ public interface DupeContext extends ContextAware {
|
||||
default DupeConfig getConfig() {
|
||||
return getDataManager().get(DupeConfig.class);
|
||||
}
|
||||
|
||||
default NBTStorage getNbtStorage() {
|
||||
return getDataManager().get(NBTStorage.class);
|
||||
}
|
||||
|
||||
default GuiInputListener getGuiListener() {
|
||||
return getContext().getGuiInputListener();
|
||||
}
|
||||
|
||||
168
src/main/java/me/trouper/dupealias/data/GlobalRule.java
Normal file
168
src/main/java/me/trouper/dupealias/data/GlobalRule.java
Normal file
@@ -0,0 +1,168 @@
|
||||
package me.trouper.dupealias.data;
|
||||
|
||||
import me.trouper.alias.data.enums.*;
|
||||
import me.trouper.alias.utils.misc.MapUtils;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ArmorMeta;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.PotionMeta;
|
||||
import org.bukkit.inventory.meta.trim.ArmorTrim;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GlobalRule {
|
||||
|
||||
public enum MatchMode {
|
||||
AND, OR, NAND, XOR
|
||||
}
|
||||
|
||||
public enum MaterialMatchMode {
|
||||
WHITELIST,
|
||||
BLACKLIST,
|
||||
IGNORE
|
||||
}
|
||||
|
||||
public MatchMode matchMode = MatchMode.AND;
|
||||
public MaterialMatchMode materialMode = MaterialMatchMode.IGNORE;
|
||||
public Set<Material> effectedMaterials = EnumSet.noneOf(Material.class);
|
||||
|
||||
public String nameContainsRegex = "";
|
||||
public String loreContainsRegex = "";
|
||||
public Set<Integer> legacyModelData = new HashSet<>();
|
||||
public Set<ItemFlag> itemFlags = EnumSet.noneOf(ItemFlag.class);
|
||||
public Map<ValidEnchantment, Integer> enchantments = new HashMap<>();
|
||||
public Map<ValidPotionEffectType, Integer> potionEffects = new HashMap<>();
|
||||
public Map<ValidAttribute, Double> attributes = new HashMap<>();
|
||||
public Set<ValidTrimPattern> trimPatterns = EnumSet.noneOf(ValidTrimPattern.class);
|
||||
public Set<ValidTrimMaterial> trimMaterials = EnumSet.noneOf(ValidTrimMaterial.class);
|
||||
|
||||
public Set<ItemTag> appliedTags = EnumSet.noneOf(ItemTag.class);
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean doesMatch(ItemStack item) {
|
||||
if (item == null || item.getType() == Material.AIR) return false;
|
||||
|
||||
switch (materialMode) {
|
||||
case WHITELIST -> {
|
||||
if (!effectedMaterials.contains(item.getType())) return false;
|
||||
}
|
||||
case BLACKLIST -> {
|
||||
if (effectedMaterials.contains(item.getType())) return false;
|
||||
}
|
||||
case IGNORE -> {}
|
||||
}
|
||||
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return false;
|
||||
|
||||
List<Boolean> results = new ArrayList<>();
|
||||
|
||||
if (!nameContainsRegex.isEmpty()) {
|
||||
Component nameComponent = meta.displayName();
|
||||
Pattern namePattern = safeCompileRegex(nameContainsRegex);
|
||||
String name = nameComponent != null ? LegacyComponentSerializer.legacyAmpersand().serialize(nameComponent) : "";
|
||||
results.add(namePattern.matcher(name).find());
|
||||
}
|
||||
|
||||
if (!loreContainsRegex.isEmpty() && meta.hasLore()) {
|
||||
List<String> lore = meta.lore().stream().map(line-> LegacyComponentSerializer.legacyAmpersand().serialize(line)).toList();
|
||||
Pattern lorePattern = safeCompileRegex(loreContainsRegex);
|
||||
boolean found = !lore.isEmpty() && lore.stream().anyMatch(line -> lorePattern.matcher(line).find());
|
||||
results.add(found);
|
||||
}
|
||||
|
||||
if (!enchantments.isEmpty()) {
|
||||
Map<Enchantment, Integer> itemEnchants = item.getEnchantments();
|
||||
results.add(MapUtils.allValuesMatch(itemEnchants, enchantments.entrySet().stream().collect(Collectors.toMap(
|
||||
entry -> entry.getKey().getCanonical(),
|
||||
Map.Entry::getValue
|
||||
))));
|
||||
}
|
||||
|
||||
if (!potionEffects.isEmpty() && meta instanceof PotionMeta potionMeta) {
|
||||
Map<PotionEffectType, Integer> itemPotions = potionMeta.getAllEffects().stream()
|
||||
.collect(Collectors.toMap(
|
||||
PotionEffect::getType,
|
||||
PotionEffect::getAmplifier
|
||||
));
|
||||
results.add(MapUtils.allValuesMatch(itemPotions, potionEffects.entrySet().stream().collect(Collectors.toMap(
|
||||
entry -> entry.getKey().getCanonical(),
|
||||
Map.Entry::getValue
|
||||
))));
|
||||
}
|
||||
|
||||
if (!attributes.isEmpty() && meta.hasAttributeModifiers() && meta.getAttributeModifiers() != null) {
|
||||
Map<Attribute, Double> itemModifiers = meta.getAttributeModifiers().entries().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> e.getValue().getAmount()
|
||||
));
|
||||
results.add(MapUtils.allValuesMatch(itemModifiers, attributes.entrySet().stream().collect(Collectors.toMap(
|
||||
entry -> entry.getKey().getCanonical(),
|
||||
Map.Entry::getValue
|
||||
))));
|
||||
}
|
||||
|
||||
if (!legacyModelData.isEmpty()) {
|
||||
results.add(meta.hasCustomModelData() && legacyModelData.contains(meta.getCustomModelData()));
|
||||
}
|
||||
|
||||
if (!itemFlags.isEmpty()) {
|
||||
results.add(meta.getItemFlags().containsAll(itemFlags));
|
||||
}
|
||||
|
||||
if (!trimMaterials.isEmpty() && meta instanceof ArmorMeta armorMeta) {
|
||||
ArmorTrim actualTrim = armorMeta.hasTrim() ? armorMeta.getTrim() : null;
|
||||
results.add(actualTrim != null && trimMaterials.stream().anyMatch(material-> material.getCanonical().equals(actualTrim.getMaterial())));
|
||||
}
|
||||
|
||||
if (!trimPatterns.isEmpty() && meta instanceof ArmorMeta armorMeta) {
|
||||
ArmorTrim actualTrim = armorMeta.hasTrim() ? armorMeta.getTrim() : null;
|
||||
results.add(actualTrim != null && trimPatterns.stream().anyMatch(pattern-> pattern.getCanonical().equals(actualTrim.getPattern())));
|
||||
}
|
||||
|
||||
int trueCount = (int) results.stream().filter(Boolean::booleanValue).count();
|
||||
int total = results.size();
|
||||
|
||||
return switch (matchMode) {
|
||||
case AND -> trueCount == total;
|
||||
case OR -> trueCount > 0;
|
||||
case NAND -> trueCount != total;
|
||||
case XOR -> trueCount == 1;
|
||||
};
|
||||
}
|
||||
|
||||
private Pattern safeCompileRegex(String input) {
|
||||
try {
|
||||
return Pattern.compile(input);
|
||||
} catch (Exception e) {
|
||||
return Pattern.compile(Pattern.quote(input));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getCriteriaCount() {
|
||||
int criteriaCount = 0;
|
||||
if (!nameContainsRegex.isEmpty()) criteriaCount++;
|
||||
if (!loreContainsRegex.isEmpty()) criteriaCount++;
|
||||
if (!enchantments.isEmpty()) criteriaCount++;
|
||||
if (!potionEffects.isEmpty()) criteriaCount++;
|
||||
if (!attributes.isEmpty()) criteriaCount++;
|
||||
if (!itemFlags.isEmpty()) criteriaCount++;
|
||||
if (!legacyModelData.isEmpty()) criteriaCount++;
|
||||
if (!trimPatterns.isEmpty()) criteriaCount++;
|
||||
if (!trimMaterials.isEmpty()) criteriaCount++;
|
||||
return criteriaCount;
|
||||
}
|
||||
}
|
||||
81
src/main/java/me/trouper/dupealias/data/ItemCapture.java
Normal file
81
src/main/java/me/trouper/dupealias/data/ItemCapture.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package me.trouper.dupealias.data;
|
||||
|
||||
import me.trouper.alias.utils.ItemSimilarity;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.*;
|
||||
|
||||
public class ItemCapture {
|
||||
|
||||
private String serializedItem;
|
||||
private double similarityThreshold;
|
||||
private final ItemSimilarity.SimilarityConfiguration configuration;
|
||||
private final Map<ItemTag, Boolean> tags;
|
||||
|
||||
public ItemCapture() {
|
||||
this.similarityThreshold = 1;
|
||||
this.configuration = new ItemSimilarity.SimilarityConfiguration();
|
||||
this.tags = new HashMap<>();
|
||||
}
|
||||
|
||||
public ItemCapture(ItemStack stack) {
|
||||
this.serializedItem = serialize(stack);
|
||||
this.similarityThreshold = 1;
|
||||
this.configuration = new ItemSimilarity.SimilarityConfiguration();
|
||||
this.tags = new HashMap<>();
|
||||
}
|
||||
|
||||
private String serialize(ItemStack itemStack) {
|
||||
try {
|
||||
return Base64.getEncoder().encodeToString(itemStack.serializeAsBytes());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to serialize ItemStack", e);
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack deserialize(String serializedItemStack) {
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(serializedItemStack));
|
||||
byte[] itemBytes = inputStream.readAllBytes();
|
||||
return ItemStack.deserializeBytes(itemBytes);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to deserialize ItemStack", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<ItemTag, Boolean> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public ItemStack getStack() {
|
||||
return deserialize(serializedItem);
|
||||
}
|
||||
|
||||
public ItemMeta getMeta() {
|
||||
return getStack().getItemMeta();
|
||||
}
|
||||
|
||||
public boolean matches(ItemStack item) {
|
||||
if (similarityThreshold >= 1) return item.isSimilar(getStack());
|
||||
return similarityThreshold <= similarityTo(item);
|
||||
}
|
||||
|
||||
public double similarityTo(ItemStack item) {
|
||||
return ItemSimilarity.calculateSimilarity(item,getStack());
|
||||
}
|
||||
|
||||
public double getThreshold() {
|
||||
return similarityThreshold;
|
||||
}
|
||||
|
||||
public ItemSimilarity.SimilarityConfiguration getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void setThreshold(double similarityThreshold) {
|
||||
this.similarityThreshold = similarityThreshold;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package me.trouper.dupealias.data;
|
||||
|
||||
import me.trouper.alias.data.JsonSerializable;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class PlayerData implements JsonSerializable<PlayerData>, DupeContext {
|
||||
@Override
|
||||
public File getFile() {
|
||||
return new File(getInstance().getDataFolder(), "playerdata.json");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.trouper.dupealias.data;
|
||||
package me.trouper.dupealias.data.files;
|
||||
|
||||
import me.trouper.alias.data.Common;
|
||||
import me.trouper.alias.data.JsonSerializable;
|
||||
@@ -1,7 +1,8 @@
|
||||
package me.trouper.dupealias.data;
|
||||
package me.trouper.dupealias.data.files;
|
||||
|
||||
import me.trouper.alias.data.JsonSerializable;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import org.bukkit.Material;
|
||||
|
||||
@@ -23,13 +24,36 @@ public class DupeConfig implements JsonSerializable<DupeConfig>, DupeContext {
|
||||
"\"(?:itemlore|lore|elore|ilore|eilore|eitemlore)\"gmi"
|
||||
));
|
||||
|
||||
public Map<ItemTag,String> tagLore = new HashMap<>(Map.of(
|
||||
public Map<ItemTag,String> trueTagLore = new HashMap<>(Map.of(
|
||||
ItemTag.PROTECTED, "<dark_purple><bold>|</bold><light_purple> Protected",
|
||||
ItemTag.FINAL, "<dark_red><bold>|</bold><red> Final",
|
||||
ItemTag.UNIQUE, "<dark_blue><bold>|</bold><blue> Unique",
|
||||
ItemTag.INFINITE, "<dark_green><bold>|</bold><green> Infinite"
|
||||
));
|
||||
|
||||
public Map<Material, Set<ItemTag>> globalMaterials = new HashMap<>();
|
||||
public Map<ItemTag,String> falseTagLore = new HashMap<>(Map.of(
|
||||
ItemTag.PROTECTED, "<dark_purple><bold>|</bold><light_purple> Unprotected",
|
||||
ItemTag.FINAL, "<dark_red><bold>|</bold><red> Mutable",
|
||||
ItemTag.UNIQUE, "<dark_blue><bold>|</bold><blue> Dupeable",
|
||||
ItemTag.INFINITE, "<dark_green><bold>|</bold><green> Finite"
|
||||
));
|
||||
|
||||
public List<GlobalRule> globalRules = new ArrayList<>();
|
||||
|
||||
public Replicator replicator = new Replicator();
|
||||
public Chest chest = new Chest();
|
||||
public Inventory inventory = new Inventory();
|
||||
|
||||
public class Replicator {
|
||||
public int baseRefreshDelayTicks = 1;
|
||||
public int baseInputCooldownTicks = 20;
|
||||
}
|
||||
|
||||
public class Chest {
|
||||
public int baseRefreshDelayTicks = 1;
|
||||
}
|
||||
|
||||
public class Inventory {
|
||||
public int baseRefreshDelayTicks = 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package me.trouper.dupealias.data.files;
|
||||
|
||||
import me.trouper.alias.data.JsonSerializable;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.ItemCapture;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
public class NBTStorage implements JsonSerializable<NBTStorage>, DupeContext {
|
||||
@Override
|
||||
public File getFile() {
|
||||
return new File(getInstance().getDataFolder(), ".nbtstorage.json");
|
||||
}
|
||||
|
||||
public List<ItemCapture> captures = new ArrayList<>();
|
||||
|
||||
public ItemCapture getCapture(ItemStack input) {
|
||||
if (getNbtStorage().captures.isEmpty()) return null;
|
||||
ItemCapture match = null;
|
||||
double closest = -1;
|
||||
|
||||
for (ItemCapture capture : getNbtStorage().captures) {
|
||||
boolean isSimilar = capture.getStack().isSimilar(input);
|
||||
if (isSimilar) return capture;
|
||||
double threshold = capture.getThreshold();
|
||||
|
||||
if (threshold >= 1) continue; // Don't bother calculating similarity if the item isn't similar.
|
||||
double sim = capture.similarityTo(input);
|
||||
if (sim >= threshold && sim >= closest) {
|
||||
closest = sim;
|
||||
match = capture;
|
||||
}
|
||||
}
|
||||
|
||||
if (closest == -1) return null;
|
||||
|
||||
return match;
|
||||
}
|
||||
}
|
||||
@@ -2,21 +2,51 @@ package me.trouper.dupealias.server;
|
||||
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.data.ItemCapture;
|
||||
import me.trouper.dupealias.server.functions.UniqueCheck;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.permissions.PermissionAttachmentInfo;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
public class DupeManager implements DupeContext {
|
||||
|
||||
/**
|
||||
* @return false if the item was modified.
|
||||
*/
|
||||
public boolean verifyTag(ItemStack item) {
|
||||
if (getNbtStorage().captures.isEmpty()) return true;
|
||||
ItemCapture capture = getNbtStorage().getCapture(item);
|
||||
if (capture == null) return true;
|
||||
boolean modified = false;
|
||||
|
||||
for (Map.Entry<ItemTag, Boolean> tagEntry : capture.getTags().entrySet()) {
|
||||
ItemTag tag = tagEntry.getKey();
|
||||
boolean value = tagEntry.getValue();
|
||||
boolean set = hasIndividualTag(item,tag);
|
||||
if (set && checkIndividualTag(item,tag) == value) {
|
||||
continue;
|
||||
} else if (!set) {
|
||||
setTag(item,tagEntry.getKey(),value);
|
||||
} else {
|
||||
removeTag(item,tag);
|
||||
setTag(item,tag,value);
|
||||
}
|
||||
|
||||
modified = true;
|
||||
}
|
||||
|
||||
return !modified;
|
||||
}
|
||||
|
||||
public boolean isUnique(ItemStack item) {
|
||||
return !new UniqueCheck().passes(item);
|
||||
}
|
||||
@@ -31,101 +61,202 @@ public class DupeManager implements DupeContext {
|
||||
return Boolean.TRUE.equals(input.getPersistentDataContainer().get(tag.getKey(), PersistentDataType.BOOLEAN));
|
||||
}
|
||||
|
||||
public boolean checkGlobalTag(Material material, ItemTag tag) {
|
||||
Set<ItemTag> tags = getConfig().globalMaterials.getOrDefault(material,new HashSet<>());
|
||||
return tags.contains(tag);
|
||||
}
|
||||
|
||||
public boolean checkEffectiveTag(ItemStack input, ItemTag tag) {
|
||||
if (tag == null || input == null) return false;
|
||||
if (input.isEmpty()) return false;
|
||||
|
||||
boolean set = hasIndividualTag(input,tag);
|
||||
boolean global = getDupe().checkGlobalTag(input.getType(),tag);
|
||||
boolean individual = Boolean.TRUE.equals(input.getPersistentDataContainer().get(tag.getKey(), PersistentDataType.BOOLEAN));
|
||||
boolean individual = set && Boolean.TRUE.equals(input.getPersistentDataContainer().get(tag.getKey(), PersistentDataType.BOOLEAN));
|
||||
|
||||
// Check individual tag first
|
||||
if (set) return individual;
|
||||
return global;
|
||||
|
||||
// Check global rules
|
||||
return checkGlobalRuleTag(input, tag);
|
||||
}
|
||||
|
||||
public boolean addGlobalTag(Material material, ItemTag tag) {
|
||||
Set<ItemTag> tags = getConfig().globalMaterials.getOrDefault(material,new HashSet<>());
|
||||
boolean result = tags.add(tag);
|
||||
getConfig().globalMaterials.put(material,tags);
|
||||
getConfig().save();
|
||||
return result;
|
||||
/**
|
||||
* Gets all global rules that apply to a given material
|
||||
*/
|
||||
public List<GlobalRule> getApplicableRules(Material material) {
|
||||
return getConfig().globalRules.stream()
|
||||
.filter(rule -> {
|
||||
return switch (rule.materialMode) {
|
||||
case WHITELIST -> rule.effectedMaterials.contains(material);
|
||||
case BLACKLIST -> !rule.effectedMaterials.contains(material);
|
||||
case IGNORE -> true;
|
||||
default -> false;
|
||||
};
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
public boolean removeGlobalTag(Material material, ItemTag tag) {
|
||||
Set<ItemTag> tags = getConfig().globalMaterials.getOrDefault(material,new HashSet<>());
|
||||
boolean result = tags.remove(tag);
|
||||
getConfig().globalMaterials.put(material,tags);
|
||||
|
||||
/**
|
||||
* Gets all global rules that apply to a specific item
|
||||
*/
|
||||
public List<GlobalRule> getMatchingRules(ItemStack item) {
|
||||
return getConfig().globalRules.stream()
|
||||
.filter(rule -> rule.doesMatch(item))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any global rule applies this tag to the given item
|
||||
*/
|
||||
public boolean checkGlobalRuleTag(ItemStack input, ItemTag tag) {
|
||||
for (GlobalRule rule : getConfig().globalRules) {
|
||||
if (rule.appliedTags.contains(tag) && rule.doesMatch(input)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new global rule that applies the specified tag to items matching the criteria
|
||||
*/
|
||||
public GlobalRule createGlobalRule(ItemTag tag) {
|
||||
GlobalRule rule = new GlobalRule();
|
||||
rule.appliedTags.add(tag);
|
||||
getConfig().globalRules.add(rule);
|
||||
getConfig().save();
|
||||
return result;
|
||||
return rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all global rules that apply the specified tag to the specified material
|
||||
*/
|
||||
public boolean removeGlobalRulesForMaterial(Material material, ItemTag tag) {
|
||||
boolean removed = false;
|
||||
Iterator<GlobalRule> iterator = getConfig().globalRules.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
GlobalRule rule = iterator.next();
|
||||
if (rule.appliedTags.contains(tag) &&
|
||||
(rule.materialMode == GlobalRule.MaterialMatchMode.WHITELIST && rule.effectedMaterials.contains(material))) {
|
||||
iterator.remove();
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
getConfig().save();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a global rule for a specific material and tag
|
||||
*/
|
||||
public boolean addGlobalRuleForMaterial(Material material, ItemTag tag) {
|
||||
// Check if rule already exists
|
||||
for (GlobalRule rule : getConfig().globalRules) {
|
||||
if (rule.appliedTags.contains(tag) &&
|
||||
rule.materialMode == GlobalRule.MaterialMatchMode.WHITELIST &&
|
||||
rule.effectedMaterials.contains(material)) {
|
||||
return false; // Rule already exists
|
||||
}
|
||||
}
|
||||
|
||||
GlobalRule rule = new GlobalRule();
|
||||
rule.materialMode = GlobalRule.MaterialMatchMode.WHITELIST;
|
||||
rule.effectedMaterials.add(material);
|
||||
rule.appliedTags.add(tag);
|
||||
getConfig().globalRules.add(rule);
|
||||
getConfig().save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean addTag(ItemStack item, ItemTag tag) {
|
||||
if (hasIndividualTag(item, tag) && getDupe().checkIndividualTag(item, tag)) return false;
|
||||
|
||||
ItemBuilder builder = ItemBuilder.of(item);
|
||||
builder.loreMiniMessage(getConfig().tagLore.get(tag));
|
||||
builder.loreMiniMessage(getConfig().trueTagLore.get(tag));
|
||||
builder.modifyMeta(itemMeta -> {
|
||||
itemMeta.getPersistentDataContainer().set(tag.getKey(), PersistentDataType.BOOLEAN, true);
|
||||
return itemMeta;
|
||||
});
|
||||
|
||||
ItemStack result = builder.buildAndGet();
|
||||
|
||||
item.setItemMeta(result.getItemMeta());
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeTag(ItemStack item, ItemTag tag) {
|
||||
ItemBuilder builder = ItemBuilder.of(item);
|
||||
|
||||
if (hasIndividualTag(item, tag) && !checkIndividualTag(item, tag)) return false;
|
||||
|
||||
builder.modifyMeta(itemMeta->{
|
||||
if (itemMeta.hasLore()) {
|
||||
removeTagLore(itemMeta,tag);
|
||||
}
|
||||
|
||||
return itemMeta;
|
||||
});
|
||||
|
||||
try {
|
||||
builder.modifyMeta(itemMeta -> {
|
||||
itemMeta.getPersistentDataContainer().remove(tag.getKey());
|
||||
if (itemMeta.hasLore()) {
|
||||
List<Component> lore = item.lore();
|
||||
if (lore == null) return itemMeta;
|
||||
int lines = lore.size();
|
||||
for (int i = 0; i < lines - 1; i++) {
|
||||
for (Map.Entry<ItemTag, String> entry : getConfig().tagLore.entrySet()) {
|
||||
if (tag.equals(entry.getKey())) continue;
|
||||
String search = entry.getValue();
|
||||
String searchPlain = search.replaceAll("<[^>]+>", "");
|
||||
String componentPlain = PlainTextComponentSerializer.plainText().serialize(lore.get(i));
|
||||
if (componentPlain.equals(searchPlain)) {
|
||||
lore.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
itemMeta.lore(lore);
|
||||
}
|
||||
return itemMeta;
|
||||
});
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return false;
|
||||
}
|
||||
ItemStack result = builder.buildAndGet();
|
||||
|
||||
ItemStack result = builder.buildAndGet();
|
||||
item.setItemMeta(result.getItemMeta());
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setTag(ItemStack item, ItemTag tag, boolean value) {
|
||||
ItemBuilder builder = ItemBuilder.of(item);
|
||||
if (value) builder.loreMiniMessage(getConfig().tagLore.get(tag));
|
||||
|
||||
builder.modifyMeta(itemMeta -> {
|
||||
if (itemMeta.hasLore()) {
|
||||
removeTagLore(itemMeta,tag);
|
||||
}
|
||||
return itemMeta;
|
||||
});
|
||||
|
||||
if (value && getConfig().trueTagLore.containsKey(tag)) {
|
||||
builder.loreMiniMessage(getConfig().trueTagLore.get(tag));
|
||||
} else if (!value && getConfig().falseTagLore.containsKey(tag)) {
|
||||
builder.loreMiniMessage(getConfig().falseTagLore.get(tag));
|
||||
}
|
||||
|
||||
builder.modifyMeta(itemMeta -> {
|
||||
itemMeta.getPersistentDataContainer().set(tag.getKey(), PersistentDataType.BOOLEAN, value);
|
||||
return itemMeta;
|
||||
});
|
||||
|
||||
ItemStack result = builder.buildAndGet();
|
||||
|
||||
item.setItemMeta(result.getItemMeta());
|
||||
}
|
||||
|
||||
public void removeTagLore(ItemMeta meta, ItemTag tag) {
|
||||
List<Component> lore = meta.lore();
|
||||
if (lore != null) {
|
||||
List<String> removeLores = new ArrayList<>();
|
||||
if (getConfig().trueTagLore.containsKey(tag)) {
|
||||
removeLores.add(getConfig().trueTagLore.get(tag));
|
||||
}
|
||||
if (getConfig().falseTagLore.containsKey(tag)) {
|
||||
removeLores.add(getConfig().falseTagLore.get(tag));
|
||||
}
|
||||
|
||||
lore.removeIf(component -> {
|
||||
String componentPlain = PlainTextComponentSerializer.plainText().serialize(component);
|
||||
return removeLores.stream().anyMatch(loreStr ->
|
||||
componentPlain.equals(loreStr.replaceAll("<[^>]+>", ""))
|
||||
);
|
||||
});
|
||||
|
||||
meta.lore(lore);
|
||||
}
|
||||
}
|
||||
|
||||
public ItemTag getTag(NamespacedKey key) {
|
||||
for (ItemTag value : ItemTag.values()) {
|
||||
if (value.getKey().equals(key)) return value;
|
||||
@@ -133,4 +264,23 @@ public class DupeManager implements DupeContext {
|
||||
throw new IllegalArgumentException("Invalid NameSpacedKey '%s'".formatted(key.value()));
|
||||
}
|
||||
|
||||
public int getPermissionValue(Player player, String rootPermission, int fallback) {
|
||||
int lowestCooldown = Integer.MAX_VALUE;
|
||||
|
||||
for (PermissionAttachmentInfo permInfo : player.getEffectivePermissions()) {
|
||||
String perm = permInfo.getPermission();
|
||||
|
||||
if (perm.startsWith(rootPermission)) {
|
||||
String valueStr = perm.substring(rootPermission.length());
|
||||
try {
|
||||
int value = Integer.parseInt(valueStr);
|
||||
if (value < lowestCooldown) {
|
||||
lowestCooldown = value;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
return (lowestCooldown == Integer.MAX_VALUE) ? fallback : lowestCooldown;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,10 @@ import me.trouper.alias.server.commands.Permission;
|
||||
import me.trouper.alias.server.commands.QuickCommand;
|
||||
import me.trouper.alias.server.commands.completions.CompletionBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminGui;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import me.trouper.dupealias.server.gui.admin.MainAdminGui;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -17,7 +19,7 @@ import org.bukkit.inventory.ItemStack;
|
||||
@CommandRegistry(
|
||||
value = "dupealias",
|
||||
permission = @Permission(value = "dupealias.admin", message = "Only server administrators can use this command."),
|
||||
usage = "/da <gui|tag> [unique|final|infinite|useless] [<material>|global|remove] [remove]",
|
||||
usage = "/da <gui|tag|rule> [unique|final|infinite|useless] [<material>|global|remove] [remove]",
|
||||
blocksAllowed = false,
|
||||
printStackTrace = true
|
||||
)
|
||||
@@ -43,6 +45,10 @@ public class AdminCommand implements QuickCommand, DupeContext {
|
||||
handleTag(sender,args);
|
||||
}
|
||||
|
||||
case "rule" -> {
|
||||
handleRule(sender,args);
|
||||
}
|
||||
|
||||
default -> {
|
||||
errorAny(sender,"Invalid subcommand!");
|
||||
}
|
||||
@@ -69,6 +75,22 @@ public class AdminCommand implements QuickCommand, DupeContext {
|
||||
b.arg("remove","false")
|
||||
)
|
||||
)
|
||||
).then(
|
||||
b.arg("rule")
|
||||
.then(
|
||||
b.arg("create")
|
||||
.then(
|
||||
b.argEnum(ItemTag.class)
|
||||
)
|
||||
).then(
|
||||
b.arg("list")
|
||||
)
|
||||
.then(
|
||||
b.arg("remove")
|
||||
.then(
|
||||
b.arg("<rule_index>")
|
||||
)
|
||||
)
|
||||
).then(
|
||||
b.arg("gui")
|
||||
);
|
||||
@@ -121,9 +143,121 @@ public class AdminCommand implements QuickCommand, DupeContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRule(CommandSender sender, Args args) {
|
||||
if (args.getSize() < 2) {
|
||||
errorAny(sender, "Usage: /da rule <create|list|remove|info> ...");
|
||||
return;
|
||||
}
|
||||
|
||||
String subCommand = args.get(1).toString().toLowerCase();
|
||||
|
||||
switch (subCommand) {
|
||||
case "create" -> {
|
||||
if (args.getSize() < 3) {
|
||||
errorAny(sender, "Usage: /da rule create <tag>");
|
||||
return;
|
||||
}
|
||||
|
||||
final ItemTag tag;
|
||||
try {
|
||||
tag = args.get(2).toEnum(ItemTag.class);
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorAny(sender, "Argument '{0}' is not a valid item tag.", args.get(2).toString());
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalRule rule = getDupe().createGlobalRule(tag);
|
||||
successAny(sender, "Created new global rule (#{0}) that applies {1} tag. Use /da gui to configure matching criteria.",
|
||||
getConfig().globalRules.indexOf(rule), tag.getName());
|
||||
}
|
||||
|
||||
case "list" -> {
|
||||
if (getConfig().globalRules.isEmpty()) {
|
||||
infoAny(sender, "No global rules are currently configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
infoAny(sender, "Global Rules ({0}):", getConfig().globalRules.size());
|
||||
for (int i = 0; i < getConfig().globalRules.size(); i++) {
|
||||
GlobalRule rule = getConfig().globalRules.get(i);
|
||||
StringBuilder tagList = new StringBuilder();
|
||||
for (ItemTag tag : rule.appliedTags) {
|
||||
if (tagList.length() > 0) tagList.append(", ");
|
||||
tagList.append(tag.getName());
|
||||
}
|
||||
infoAny(sender, " #{0}: Tags: {1}, Match Mode: {2}, Material Mode: {3}",
|
||||
i, tagList.toString(), rule.matchMode, rule.materialMode);
|
||||
}
|
||||
}
|
||||
|
||||
case "remove" -> {
|
||||
if (args.getSize() < 3) {
|
||||
errorAny(sender, "Usage: /da rule remove <rule_index>");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int index = Integer.parseInt(args.get(2).toString());
|
||||
if (index < 0 || index >= getConfig().globalRules.size()) {
|
||||
errorAny(sender, "Invalid rule index. Use '/da rule list' to see available rules.");
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalRule removedRule = getConfig().globalRules.remove(index);
|
||||
getConfig().save();
|
||||
|
||||
StringBuilder tagList = new StringBuilder();
|
||||
for (ItemTag tag : removedRule.appliedTags) {
|
||||
if (tagList.length() > 0) tagList.append(", ");
|
||||
tagList.append(tag.getName());
|
||||
}
|
||||
|
||||
successAny(sender, "Removed global rule #{0} (Tags: {1}).", index, tagList.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
errorAny(sender, "'{0}' is not a valid number.", args.get(2).toString());
|
||||
}
|
||||
}
|
||||
|
||||
case "info" -> {
|
||||
if (args.getSize() < 3) {
|
||||
errorAny(sender, "Usage: /da rule info <rule_index>");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int index = Integer.parseInt(args.get(2).toString());
|
||||
if (index < 0 || index >= getConfig().globalRules.size()) {
|
||||
errorAny(sender, "Invalid rule index. Use '/da rule list' to see available rules.");
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalRule rule = getConfig().globalRules.get(index);
|
||||
infoAny(sender, "Global Rule #{0}:", index);
|
||||
infoAny(sender, " Applied Tags: {0}", rule.appliedTags.stream().map(ItemTag::getName).reduce((a,b) -> a + ", " + b).orElse("None"));
|
||||
infoAny(sender, " Match Mode: {0}", rule.matchMode);
|
||||
infoAny(sender, " Material Mode: {0}", rule.materialMode);
|
||||
infoAny(sender, " Affected Materials: {0}", rule.effectedMaterials.size());
|
||||
infoAny(sender, " Name Regex: {0}", rule.nameContainsRegex.isEmpty() ? "None" : rule.nameContainsRegex);
|
||||
infoAny(sender, " Lore Regex: {0}", rule.loreContainsRegex.isEmpty() ? "None" : rule.loreContainsRegex);
|
||||
infoAny(sender, " Enchantments: {0}", rule.enchantments.size());
|
||||
infoAny(sender, " Potion Effects: {0}", rule.potionEffects.size());
|
||||
infoAny(sender, " Attributes: {0}", rule.attributes.size());
|
||||
infoAny(sender, "Use the GUI for detailed configuration.");
|
||||
} catch (NumberFormatException e) {
|
||||
errorAny(sender, "'{0}' is not a valid number.", args.get(2).toString());
|
||||
}
|
||||
}
|
||||
|
||||
default -> {
|
||||
errorAny(sender, "Invalid subcommand '{0}'. Valid options: create, list, remove, info", subCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTag(CommandSender sender, Args args) {
|
||||
if (args.getSize() < 2) {
|
||||
errorAny(sender, "You must specify an item tag. Usage: /gui tag <tag> ...");
|
||||
errorAny(sender, "You must specify an item tag. Usage: /da tag <tag> ...");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,7 +270,7 @@ public class AdminCommand implements QuickCommand, DupeContext {
|
||||
return;
|
||||
}
|
||||
|
||||
// gui tag <tag>
|
||||
// da tag <tag>
|
||||
if (args.getSize() == 2) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
errorAny(sender, "This command can only be run by a player to tag a held item. To manage material tags, specify a material or 'global'.");
|
||||
@@ -159,11 +293,11 @@ public class AdminCommand implements QuickCommand, DupeContext {
|
||||
// Argument 2
|
||||
String subCommand = args.get(2).toString().toLowerCase();
|
||||
|
||||
// gui tag <tag> remove|false
|
||||
// da tag <tag> remove|false
|
||||
switch (subCommand) {
|
||||
case "remove" -> {
|
||||
if (args.getSize() != 3) {
|
||||
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> remove");
|
||||
errorAny(sender, "Invalid arguments. Usage: /da tag <tag> remove");
|
||||
return;
|
||||
}
|
||||
if (!(sender instanceof Player player)) {
|
||||
@@ -184,11 +318,11 @@ public class AdminCommand implements QuickCommand, DupeContext {
|
||||
}
|
||||
case "false" -> {
|
||||
if (args.getSize() != 3) {
|
||||
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> remove");
|
||||
errorAny(sender, "Invalid arguments. Usage: /da tag <tag> false");
|
||||
return;
|
||||
}
|
||||
if (!(sender instanceof Player player)) {
|
||||
errorAny(sender, "This command can only be run by a player to add a tag from a held item.");
|
||||
errorAny(sender, "This command can only be run by a player to set a tag on a held item.");
|
||||
return;
|
||||
}
|
||||
ItemStack heldItem = player.getInventory().getItemInMainHand();
|
||||
@@ -197,12 +331,12 @@ public class AdminCommand implements QuickCommand, DupeContext {
|
||||
return;
|
||||
}
|
||||
getDupe().setTag(heldItem, tag, false);
|
||||
successAny(sender, "Set tag {0} from your {1} to {2}.", tag.getName(), heldItem.getType(), "false");
|
||||
successAny(sender, "Set tag {0} on your {1} to {2}.", tag.getName(), heldItem.getType(), "false");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// gui tag <tag> global [remove]
|
||||
// da tag <tag> global [remove]
|
||||
case "global" -> {
|
||||
if (!(sender instanceof Player player)) {
|
||||
errorAny(sender, "The 'global' subcommand must be run by a player.");
|
||||
@@ -217,54 +351,53 @@ public class AdminCommand implements QuickCommand, DupeContext {
|
||||
boolean isRemove = args.getSize() > 3 && "remove".equalsIgnoreCase(args.get(3).toString());
|
||||
if (isRemove) {
|
||||
if (args.getSize() != 4) {
|
||||
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> global remove");
|
||||
errorAny(sender, "Invalid arguments. Usage: /da tag <tag> global remove");
|
||||
return;
|
||||
}
|
||||
if (getDupe().removeGlobalTag(heldMaterial, tag)) {
|
||||
successAny(sender, "Removed global tag {0} from all {1} items.", tag.getName(), heldMaterial);
|
||||
if (getDupe().removeGlobalRulesForMaterial(heldMaterial, tag)) {
|
||||
successAny(sender, "Removed global rules applying tag {0} to {1} items.", tag.getName(), heldMaterial);
|
||||
} else {
|
||||
infoAny(sender, "{0} is not globally tagged as {1}.", heldMaterial, tag.getName());
|
||||
infoAny(sender, "No global rules found applying {0} tag to {1}.", tag.getName(), heldMaterial);
|
||||
}
|
||||
} else {
|
||||
if (args.getSize() != 3) {
|
||||
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> global");
|
||||
errorAny(sender, "Invalid arguments. Usage: /da tag <tag> global");
|
||||
return;
|
||||
}
|
||||
if (getDupe().addGlobalTag(heldMaterial, tag)) {
|
||||
successAny(sender, "All {0} items are now globally tagged as {1} and {2}.", heldMaterial, tag.getName(), tag.getDesc());
|
||||
if (getDupe().addGlobalRuleForMaterial(heldMaterial, tag)) {
|
||||
successAny(sender, "Created global rule: all {0} items are now tagged as {1} and {2}.", heldMaterial, tag.getName(), tag.getDesc());
|
||||
} else {
|
||||
infoAny(sender, "All {0} items are already tagged as {1} and {2}.", heldMaterial, tag.getName(), tag.getDesc());
|
||||
infoAny(sender, "A global rule already exists that tags {0} items as {1}.", heldMaterial, tag.getName());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// gui tag <tag> <material> [remove]
|
||||
// da tag <tag> <material> [remove]
|
||||
try {
|
||||
Material material = args.get(2).toEnum(Material.class);
|
||||
boolean isRemove = args.getSize() > 3 && "remove".equalsIgnoreCase(args.get(3).toString());
|
||||
|
||||
if (isRemove) {
|
||||
if (args.getSize() != 4) {
|
||||
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> <material> remove");
|
||||
errorAny(sender, "Invalid arguments. Usage: /da tag <tag> <material> remove");
|
||||
return;
|
||||
}
|
||||
if (getDupe().removeGlobalTag(material, tag)) {
|
||||
successAny(sender, "Removed global tag {0} from {1}.", tag.getName(), material);
|
||||
if (getDupe().removeGlobalRulesForMaterial(material, tag)) {
|
||||
successAny(sender, "Removed global rules applying tag {0} to {1}.", tag.getName(), material);
|
||||
} else {
|
||||
infoAny(sender, "{0} is not tagged as {1} globally.", material, tag.getName());
|
||||
infoAny(sender, "No global rules found applying {0} tag to {1}.", tag.getName(), material);
|
||||
}
|
||||
} else {
|
||||
if (args.getSize() != 3) {
|
||||
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> <material>");
|
||||
errorAny(sender, "Invalid arguments. Usage: /da tag <tag> <material>");
|
||||
return;
|
||||
}
|
||||
getDupe().addGlobalTag(material, tag);
|
||||
if (getDupe().addGlobalTag(material, tag)) {
|
||||
successAny(sender, "All {0} items are now tagged as {1} and {2}.", material, tag.getName(), tag.getDesc());
|
||||
if (getDupe().addGlobalRuleForMaterial(material, tag)) {
|
||||
successAny(sender, "Created global rule: all {0} items are now tagged as {1} and {2}.", material, tag.getName(), tag.getDesc());
|
||||
} else {
|
||||
infoAny(sender, "All {0} items are already tagged as {1} and {2}.", material, tag.getName(), tag.getDesc());
|
||||
infoAny(sender, "A global rule already exists that tags {0} items as {1}.", material, tag.getName());
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -275,7 +408,7 @@ public class AdminCommand implements QuickCommand, DupeContext {
|
||||
|
||||
public void openBaseGui(CommandSender sender) {
|
||||
if (sender instanceof Player player) {
|
||||
new AdminGui().openMainGui(player);
|
||||
new MainAdminGui(new AdminPanelManager()).open(player);
|
||||
} else {
|
||||
errorAny(sender, "Console may not open a GUI.");
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
|
||||
}
|
||||
|
||||
if (args.isEmpty()) {
|
||||
if (dupeHeld(player,0)) {
|
||||
if (dupeHeld(player,1)) {
|
||||
dupeCooldown.setCooldown(player.getUniqueId(), getConfig().dupeCooldownMillis);
|
||||
} else {
|
||||
dupeGui.openDefaultGui(player);
|
||||
@@ -94,7 +94,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
|
||||
int baseCount = inHand.getAmount();
|
||||
int maxPerStack = inHand.getMaxStackSize();
|
||||
|
||||
for (int i = 0; i <= amount; i++) {
|
||||
for (int i = 0; i <= amount - 1; i++) {
|
||||
int remaining = baseCount * (1 << i);
|
||||
|
||||
while (remaining > 0) {
|
||||
@@ -111,7 +111,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
|
||||
}
|
||||
}
|
||||
|
||||
int totalGiven = baseCount * ((1 << (amount + 1)) - 1);
|
||||
int totalGiven = baseCount * ((1 << amount) - 1);
|
||||
successAny(player,"You have duplicated {0} items!", totalGiven);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.bukkit.persistence.PersistentDataType;
|
||||
public class UniqueCheck implements Check<ItemStack> {
|
||||
@Override
|
||||
public boolean passes(ItemStack input) {
|
||||
boolean globallyUnique = getDupe().checkGlobalTag(input.getType(),ItemTag.UNIQUE);
|
||||
boolean globallyUnique = getDupe().checkGlobalRuleTag(input,ItemTag.UNIQUE);
|
||||
boolean set = input.hasItemMeta() && input.getPersistentDataContainer().has(ItemTag.UNIQUE.getKey());
|
||||
boolean individuallyUnique = Boolean.TRUE.equals(input.getPersistentDataContainer().get(ItemTag.UNIQUE.getKey(), PersistentDataType.BOOLEAN));
|
||||
|
||||
|
||||
@@ -4,9 +4,14 @@ import me.trouper.alias.utils.FormatUtils;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeAlias;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.data.type.Light;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.BlockStateMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
public interface CommonItems extends DupeContext {
|
||||
@@ -46,7 +51,13 @@ public interface CommonItems extends DupeContext {
|
||||
&& Boolean.TRUE.equals(item.getItemMeta().getPersistentDataContainer().get(CANCEL_CLICK(), PersistentDataType.BOOLEAN)));
|
||||
}
|
||||
|
||||
default ItemStack createPopulatedItem(ItemStack item) {
|
||||
default ItemStack createPopulatedItem(ItemStack item, double progress) {
|
||||
if (progress < 1) {
|
||||
return ItemBuilder.of(EMPTY(Material.RED_STAINED_GLASS_PANE))
|
||||
.displayName("<yellow>Item Refilling...")
|
||||
.loreComponent(getTextSystem().createProgressBar(progress,(char) '|',20, TextColor.color(0x5AFF89),TextColor.color(0x6F6F6F)))
|
||||
.build();
|
||||
}
|
||||
if (item == null || item.isEmpty()) return EMPTY(Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemStack clone = item.clone();
|
||||
if (getDupe().isUnique(clone)) return ItemBuilder.of(EMPTY(Material.BARRIER))
|
||||
|
||||
@@ -1,681 +0,0 @@
|
||||
package me.trouper.dupealias.server.gui.admin;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class AdminGui implements DupeContext, CommonItems {
|
||||
|
||||
public void openMainGui(Player player) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#6b6bff:#9999ff><bold>DupeAlias Admin Panel</gradient>")
|
||||
.rows(5)
|
||||
|
||||
.item(11, ItemBuilder.create(player.getInventory().getItemInMainHand().isEmpty() ? Material.BARRIER : Material.DIAMOND_SWORD)
|
||||
.displayName("<gradient:#4ecdc4:#45b7d1><bold>Held Item Actions</bold></gradient>")
|
||||
.loreMiniMessage(
|
||||
"<gray>Manage tags for the item",
|
||||
"<gray>you're currently holding",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to open menu"
|
||||
)
|
||||
.hideAllFlags()
|
||||
.build(),
|
||||
(q, event) -> openHeldItemGui(player))
|
||||
|
||||
.item(13, ItemBuilder.create(Material.BOOKSHELF)
|
||||
.displayName("<gradient:#ff6b6b:#ffa726><bold>Global Material Tags</bold></gradient>")
|
||||
.loreMiniMessage(
|
||||
"<gray>Configure global tags that apply",
|
||||
"<gray>to all items of specific materials",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to open menu"
|
||||
)
|
||||
.build(),
|
||||
(q, event) -> openGlobalMaterialGui(player,player.getInventory().getItemInMainHand().getType()))
|
||||
|
||||
.item(15, ItemBuilder.create(Material.KNOWLEDGE_BOOK)
|
||||
.displayName("<gradient:#cb59b6:#8e44ad><bold>Information & Help</bold></gradient>")
|
||||
.loreMiniMessage(
|
||||
"<gray>Learn about item tags and",
|
||||
"<gray>how to use this system",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to view help"
|
||||
)
|
||||
.build(),
|
||||
(q, event) -> openHelpGui(player))
|
||||
|
||||
.item(29, ItemBuilder.create(Material.COMPARATOR)
|
||||
.displayName("<gradient:#ff6bff:#ffa7ff><bold>Configuration</bold></gradient>")
|
||||
.loreMiniMessage(
|
||||
"<gray>Modify plugin parameters",
|
||||
"<gray>name and colors",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to open config"
|
||||
)
|
||||
.build(), (q,event) -> new ConfigGui().open(player, q))
|
||||
|
||||
.item(31, createPreviewItem(player.getInventory().getItemInMainHand()))
|
||||
|
||||
.item(33, ItemBuilder.create(Material.DIAMOND)
|
||||
.displayName("<#AAAAFF><bold>Dupe<#00DDFF>Alias</bold> <white>Credits")
|
||||
.loreMiniMessage(
|
||||
"<dark_gray><bold>|</bold><gray> Built with Alias Development Kit",
|
||||
"<dark_gray><bold>|</bold><gray>",
|
||||
"<dark_gray><bold>|</bold> <gradient:#e38c01:#eccd00:#FFFFFF:#62afdd:#1f3857>Written by obvWolf</gradient>",
|
||||
" ",
|
||||
"<dark_gray>Copyright © 2025 DupeAlias",
|
||||
"<dark_gray>Do Not Redistribute"
|
||||
)
|
||||
.build())
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
public void openHeldItemGui(Player player) {
|
||||
ItemStack heldItem = player.getInventory().getItemInMainHand();
|
||||
|
||||
if (heldItem.getType().isAir()) {
|
||||
errorAny(player, "You must be holding an item to use this menu!");
|
||||
return;
|
||||
}
|
||||
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#4ecdc4:#45b7d1><bold>Held Item: " + heldItem.getType().name() + "</bold></gradient>")
|
||||
.rows(4)
|
||||
.fillBorder(EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE))
|
||||
|
||||
.item(0, BACK(),
|
||||
(g, e) -> openMainGui(player))
|
||||
|
||||
.item(13, ItemBuilder.create(heldItem.getType())
|
||||
.displayName("<white><bold>" + heldItem.getType().name() + "</bold>")
|
||||
.loreMiniMessage(getItemTagStatus(heldItem))
|
||||
.build())
|
||||
|
||||
.item(11, ItemBuilder.create(Material.EMERALD)
|
||||
.displayName("<green><bold>Add UNIQUE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Makes this specific item",
|
||||
"<red>unable to be duplicated",
|
||||
"",
|
||||
"<yellow>▶ <white>Left click to apply tag",
|
||||
"<yellow>▶ <white>Right click to remove tag",
|
||||
"<yellow>▶ <white>Shift click to set tag to false"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> tagHeldItem(player, ItemTag.UNIQUE, e.getClick()))
|
||||
|
||||
.item(20, ItemBuilder.create(Material.BARRIER)
|
||||
.displayName("<red><bold>Add FINAL Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Makes this specific item",
|
||||
"<red>unable to be modified",
|
||||
"",
|
||||
"<yellow>▶ <white>Left click to apply tag",
|
||||
"<yellow>▶ <white>Right click to remove tag",
|
||||
"<yellow>▶ <white>Shift click to set tag to false"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> tagHeldItem(player, ItemTag.FINAL, e.getClick()))
|
||||
|
||||
.item(15, ItemBuilder.create(Material.WATER_BUCKET)
|
||||
.displayName("<blue><bold>Add INFINITE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Makes this specific item",
|
||||
"<green>always have max stack size",
|
||||
"",
|
||||
"<yellow>▶ <white>Left click to apply tag",
|
||||
"<yellow>▶ <white>Right click to remove tag",
|
||||
"<yellow>▶ <white>Shift click to set tag to false"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> tagHeldItem(player, ItemTag.INFINITE, e.getClick()))
|
||||
|
||||
.item(24, ItemBuilder.create(Material.STRUCTURE_VOID)
|
||||
.displayName("<dark_purple><bold>Add PROTECTED Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Makes this specific item",
|
||||
"<red>not able to be manually created",
|
||||
"",
|
||||
"<yellow>▶ <white>Left click to apply tag",
|
||||
"<yellow>▶ <white>Right click to remove tag",
|
||||
"<yellow>▶ <white>Shift click to set tag to false"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> tagHeldItem(player, ItemTag.PROTECTED, e.getClick()))
|
||||
|
||||
.item(22, ItemBuilder.create(Material.TNT)
|
||||
.displayName("<dark_red><bold>Remove All Tags</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Removes all tags from",
|
||||
"<gray>this specific item",
|
||||
"",
|
||||
"<red>⚠ <white>This cannot be undone!",
|
||||
"<yellow>▶ <white>Click to remove tags"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> removeAllTagsFromHeld(player))
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
public void openGlobalMaterialGui(Player player, Material material) {
|
||||
if (material == null) {
|
||||
material = Material.AIR;
|
||||
}
|
||||
final Material mat = material;
|
||||
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#ff6b6b:#ffa726><bold>Global Material Tags</bold></gradient>")
|
||||
.rows(4)
|
||||
.fillBorder(EMPTY(Material.ORANGE_STAINED_GLASS_PANE))
|
||||
|
||||
// Back button
|
||||
.item(0, BACK(),
|
||||
(g, e) -> openMainGui(player))
|
||||
|
||||
.item(13, createMaterialTagItem(mat))
|
||||
|
||||
.item(11, ItemBuilder.create(Material.EMERALD_BLOCK)
|
||||
.displayName("<green><bold>Global UNIQUE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Apply UNIQUE tag to ALL items",
|
||||
"<gray>of the held material type",
|
||||
"",
|
||||
"<yellow>▶ <white>Left-click to add",
|
||||
"<yellow>▶ <white>Right-click to remove"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> handleGlobalTag(player, mat, ItemTag.UNIQUE, e.isLeftClick()))
|
||||
|
||||
.item(20, ItemBuilder.create(Material.REDSTONE_BLOCK)
|
||||
.displayName("<red><bold>Global FINAL Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Apply FINAL tag to ALL items",
|
||||
"<gray>of the held material type",
|
||||
"",
|
||||
"<yellow>▶ <white>Left-click to add",
|
||||
"<yellow>▶ <white>Right-click to remove"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> handleGlobalTag(player, mat, ItemTag.FINAL, e.isLeftClick()))
|
||||
|
||||
.item(15, ItemBuilder.create(Material.LAPIS_BLOCK)
|
||||
.displayName("<blue><bold>Global INFINITE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Apply INFINITE tag to ALL items",
|
||||
"<gray>of the held material type",
|
||||
"",
|
||||
"<yellow>▶ <white>Left-click to add",
|
||||
"<yellow>▶ <white>Right-click to remove"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> handleGlobalTag(player, mat, ItemTag.INFINITE, e.isLeftClick()))
|
||||
|
||||
.item(24, ItemBuilder.create(Material.STRUCTURE_BLOCK)
|
||||
.displayName("<dark_purple><bold>Global PROTECTED Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Apply PROTECTED tag to ALL items",
|
||||
"<gray>of the held material type",
|
||||
"",
|
||||
"<yellow>▶ <white>Left-click to add",
|
||||
"<yellow>▶ <white>Right-click to remove"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> handleGlobalTag(player, mat, ItemTag.PROTECTED, e.isLeftClick()))
|
||||
|
||||
.item(22, ItemBuilder.create(Material.COAL_BLOCK)
|
||||
.displayName("<dark_gray><bold>Material Browser</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Browse and manage tags",
|
||||
"<gray>for any material type",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to open browser"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> openMaterialBrowser(player))
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
public void openHelpGui(Player player) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#9b59b6:#8e44ad><bold>DupeAlias Help</bold></gradient>")
|
||||
.rows(6)
|
||||
.fillBorder(EMPTY(Material.PURPLE_STAINED_GLASS_PANE))
|
||||
|
||||
.item(0, BACK(),
|
||||
(g, e) -> openMainGui(player))
|
||||
|
||||
.item(20, ItemBuilder.create(Material.EMERALD)
|
||||
.displayName("<green><bold>UNIQUE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>What it does:",
|
||||
"<gray>• Prevents item duplication",
|
||||
"<gray>• Works globally or per item",
|
||||
"",
|
||||
"<white>Use cases:",
|
||||
"<gray>• Crate Keys",
|
||||
"<gray>• Special or rare items",
|
||||
"<gray>• Admin-only gear",
|
||||
"",
|
||||
"<red>⚠ Conflict:",
|
||||
"<gray>• Avoid combining with <red>INFINITE"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(21, ItemBuilder.create(Material.BARRIER)
|
||||
.displayName("<red><bold>FINAL Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>What it does:",
|
||||
"<gray>• Blocks all item modifications",
|
||||
"<gray>• Prevents renaming, enchanting, etc.",
|
||||
"",
|
||||
"<white>Use cases:",
|
||||
"<gray>• Name-dependent items",
|
||||
"<gray>• Rank kits or prizes",
|
||||
"<gray>• Event rewards",
|
||||
"",
|
||||
"<gray>✔ Can be combined safely with all tags"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(22, ItemBuilder.create(Material.WATER_BUCKET)
|
||||
.displayName("<blue><bold>INFINITE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>What it does:",
|
||||
"<gray>• Enforces max stack size (99)",
|
||||
"<gray>• Item instantly refills when used",
|
||||
"",
|
||||
"<white>Use cases:",
|
||||
"<gray>• Infinite building blocks",
|
||||
"<gray>• 'Infinity' for tipped arrows",
|
||||
"<gray>• Creative-like resource flow",
|
||||
"",
|
||||
"<red>⚠ Conflicts:",
|
||||
"<gray>• Avoid combining with <red>UNIQUE",
|
||||
"<gray>• Avoid combining with <red>PROTECTED"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(23, ItemBuilder.create(Material.STRUCTURE_VOID)
|
||||
.displayName("<dark_purple><bold>PROTECTED Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>What it does:",
|
||||
"<gray>• Blocks all use: crafting, consuming, enchanting",
|
||||
"<gray>• Makes item functionally inert",
|
||||
"<gray>• This does <red>NOT</red> prevent duping",
|
||||
"",
|
||||
"<white>Use cases:",
|
||||
"<gray>• Crate keys or Coupons",
|
||||
"<gray>• Decorative/admin-only items",
|
||||
"",
|
||||
"<red>⚠ Conflict:",
|
||||
"<gray>• Avoid combining with <red>INFINITE"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(24, ItemBuilder.create(Material.REDSTONE_TORCH)
|
||||
.displayName("<red><bold>Important Notes</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>Things to remember:",
|
||||
"<gray>• Individual tags <i>override</i> global tags",
|
||||
"<gray>• Global tags affect ALL of a material",
|
||||
"<gray>• PROTECTED items are <red>not</red> UNIQUE by default",
|
||||
"<gray>• UNIQUE items can <i>still</i> be duped with <u>external</u> exploits",
|
||||
"",
|
||||
"<white>Tag Combinations:",
|
||||
"<gray>✔ <green>FINAL + PROTECTED</green> = Immutable Inert item",
|
||||
"<gray>✔ <green>PROTECTED + UNIQUE</green> = Inert Dupe-Proof Item",
|
||||
"<gray>❌ <red>INFINITE + UNIQUE</red> = Paradox",
|
||||
"<gray>❌ <red>INFINITE + PROTECTED</red> = Contradiction"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(30, ItemBuilder.create(Material.WRITABLE_BOOK)
|
||||
.displayName("<yellow><bold>Individual vs Global Tags</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>Individual Tags:",
|
||||
"<gray>• Stored directly on the item",
|
||||
"<gray>• Apply only to that one instance",
|
||||
"<gray>• Use 'Held Item Actions' menu",
|
||||
"",
|
||||
"<white>Global Tags:",
|
||||
"<gray>• Apply to ALL items of a material",
|
||||
"<gray>• Managed via config",
|
||||
"<gray>• Use 'Global Material Tags' menu",
|
||||
"",
|
||||
"<red>⚠ No per-world or per-player support"
|
||||
))
|
||||
.build())
|
||||
.item(32, ItemBuilder.create(Material.COMMAND_BLOCK)
|
||||
.displayName("<gold><bold>Command Equivalents</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>This GUI replaces these commands:",
|
||||
"<gray>• <white>/da tag <tag>",
|
||||
"<gray>• <white>/da tag <tag> remove",
|
||||
"<gray>• <white>/da tag <tag> global",
|
||||
"<gray>• <white>/da tag <tag> <material>",
|
||||
"",
|
||||
"<aqua>💡 <white>GUI is easier and safer!"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(37, ItemBuilder.create(Material.NAME_TAG)
|
||||
.displayName("<aqua><bold>Tag Glossary</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white><bold>UNIQUE</bold><gray>: Prevents <i>intended</i> duplication",
|
||||
"<white><bold>FINAL</bold><gray>: Cancels editing/modification",
|
||||
"<white><bold>PROTECTED</bold><gray>: Blocks all use (crafting, consuming)",
|
||||
"<white><bold>INFINITE</bold><gray>: Always max stack size (99)",
|
||||
"",
|
||||
"<gray>Tags can be combined, but some conflict!"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(43, ItemBuilder.create(Material.TRIAL_KEY)
|
||||
.displayName("<white><bold>Permissions Guide</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>Access Permissions:",
|
||||
"<gray>• <white>dupealias.dupe<gray> - Use /dupe",
|
||||
"<gray>• <white>dupealias.gui<gray> - Use duplication GUI",
|
||||
"<gray>• Includes replicator, chest, inventory menus",
|
||||
"",
|
||||
"<white>Bypass Permissions:",
|
||||
"<gray>• <white>dupealias.unique.bypass<gray> - Dupe unique items",
|
||||
"<gray>• <white>dupealias.final.bypass<gray> - Modify final items",
|
||||
"<gray>• <white>dupealias.protected.bypass<gray> - Use protected items",
|
||||
"",
|
||||
"<white>Other:",
|
||||
"<gray>• <white>dupealias.infinite<gray> - Use infinite-tagged items",
|
||||
"",
|
||||
"<red>⚠ Misuse Warning:",
|
||||
"<gray>Giving bypass perms to players allows",
|
||||
"<gray>them to ignore tag protections entirely!"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(49, createExplainedItem(player.getInventory().getItemInMainHand()))
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
|
||||
private ItemStack createExplainedItem(ItemStack item) {
|
||||
if (item == null || item.isEmpty()) {
|
||||
return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE)
|
||||
.displayName("<gray><italic>No item held</italic>")
|
||||
.loreMiniMessage("<aqua>💡 <white>Hold an item to get information on it")
|
||||
.build();
|
||||
}
|
||||
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("<white><bold>Held Item Explanation:</bold>");
|
||||
|
||||
Set<ItemTag> activeTags = new HashSet<>();
|
||||
|
||||
for (ItemTag tag : ItemTag.values()) {
|
||||
boolean global = getDupe().checkGlobalTag(item.getType(), tag);
|
||||
boolean hasMeta = item.hasItemMeta() &&
|
||||
item.getItemMeta().getPersistentDataContainer().has(tag.getKey());
|
||||
|
||||
if (hasMeta) {
|
||||
Boolean individual = item.getItemMeta()
|
||||
.getPersistentDataContainer()
|
||||
.get(tag.getKey(), PersistentDataType.BOOLEAN);
|
||||
if (Boolean.TRUE.equals(individual)) {
|
||||
lore.add("<gray>• <green>" + tag.getName() + "</green> (Individual): " + tag.getDesc());
|
||||
activeTags.add(tag);
|
||||
} else {
|
||||
lore.add("<gray>• <red>" + tag.getName() + "</red> (Individually false)");
|
||||
if (global) {
|
||||
lore.add(" <gray>- Global is active: " + tag.getDesc());
|
||||
lore.add(" <gray>- Global is overridden by Individual tag.");
|
||||
activeTags.add(tag);
|
||||
}
|
||||
}
|
||||
} else if (global) {
|
||||
lore.add("<gray>• <yellow>" + tag.getName() + "</yellow> (Global): " + tag.getDesc());
|
||||
activeTags.add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
if (getDupe().isUnique(item)) {
|
||||
lore.add("<gray>• Detected UNIQUE by UniqueCheck");
|
||||
activeTags.add(ItemTag.UNIQUE);
|
||||
}
|
||||
|
||||
if (lore.size() == 1) {
|
||||
lore.add("<gray>• No DupeAlias tags apply to this item");
|
||||
}
|
||||
|
||||
List<String> conflicts = new ArrayList<>();
|
||||
if (activeTags.contains(ItemTag.INFINITE) && activeTags.contains(ItemTag.UNIQUE)) {
|
||||
conflicts.add("INFINITE ↔ UNIQUE");
|
||||
}
|
||||
if (activeTags.contains(ItemTag.INFINITE) && activeTags.contains(ItemTag.PROTECTED)) {
|
||||
conflicts.add("INFINITE ↔ PROTECTED");
|
||||
}
|
||||
|
||||
if (!conflicts.isEmpty()) {
|
||||
lore.add("");
|
||||
lore.add("<red>⚠ <bold>Conflicts detected:</bold>");
|
||||
for (String c : conflicts) {
|
||||
lore.add("<gray>• " + c);
|
||||
}
|
||||
lore.add("<gray>Consider removing one of the above tags.");
|
||||
}
|
||||
|
||||
return ItemBuilder.of(item)
|
||||
.displayName("<white><bold>Item Details</bold>")
|
||||
.loreMiniMessage(lore)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
public void openMaterialBrowser(Player player) {
|
||||
QuickGui gui = new MaterialBrowserGui().createGUI(player);
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
public ItemStack createMaterialTagItem(Material material) {
|
||||
if (material.isAir()) {
|
||||
return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE)
|
||||
.displayName("<gray><bold>No Material Selected</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Hold an item to see",
|
||||
"<gray>its current tag status or",
|
||||
"<gray>select one in the browser",
|
||||
"",
|
||||
"<yellow>💡 <white>Hold an item and reopen this GUI"
|
||||
))
|
||||
.build();
|
||||
}
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("<white>Material: <yellow>" + material.name());
|
||||
lore.add("");
|
||||
|
||||
boolean hasUnique = getDupe().checkGlobalTag(material, ItemTag.UNIQUE);
|
||||
boolean hasFinal = getDupe().checkGlobalTag(material, ItemTag.FINAL);
|
||||
boolean hasInfinite = getDupe().checkGlobalTag(material, ItemTag.INFINITE);
|
||||
boolean hasProtected = getDupe().checkGlobalTag(material, ItemTag.PROTECTED);
|
||||
|
||||
if (hasUnique || hasFinal || hasInfinite || hasProtected) {
|
||||
lore.add("<white>Global Tags:");
|
||||
if (hasUnique) lore.add("<green>✓ UNIQUE");
|
||||
if (hasFinal) lore.add("<red>✓ FINAL");
|
||||
if (hasInfinite) lore.add("<blue>✓ INFINITE");
|
||||
if (hasProtected) lore.add("<dark_purple>✓ PROTECTED");
|
||||
} else {
|
||||
lore.add("<gray>No global tags applied");
|
||||
}
|
||||
|
||||
lore.add("");
|
||||
lore.add("<yellow>▶ <white>Left-click to manage tags");
|
||||
|
||||
return ItemBuilder.of(material)
|
||||
.displayName("<white><bold>" + material.name() + "</bold>")
|
||||
.loreMiniMessage(lore)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void tagHeldItem(Player player, ItemTag tag, ClickType click) {
|
||||
ItemStack heldItem = player.getInventory().getItemInMainHand();
|
||||
|
||||
if (heldItem.getType().isAir()) {
|
||||
errorAny(player, "You must be holding an item to tag it!");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (click) {
|
||||
case LEFT -> {
|
||||
getDupe().addTag(heldItem, tag);
|
||||
successAny(player, "Added {0} tag to your {1}. {2}", tag.getName(), heldItem.getType(), tag.getDesc());
|
||||
}
|
||||
case RIGHT -> {
|
||||
getDupe().removeTag(heldItem, tag);
|
||||
successAny(player, "Removed {0} tag from your {1}.", tag.getName(), heldItem.getType());
|
||||
}
|
||||
case SHIFT_LEFT, SHIFT_RIGHT -> {
|
||||
getDupe().setTag(heldItem, tag, false);
|
||||
successAny(player, "Set {0} tag from your {1} to {2}.", tag.getName(), heldItem.getType(), "false");
|
||||
}
|
||||
}
|
||||
|
||||
player.closeInventory();
|
||||
openHeldItemGui(player);
|
||||
}
|
||||
|
||||
private void removeAllTagsFromHeld(Player player) {
|
||||
ItemStack heldItem = player.getInventory().getItemInMainHand();
|
||||
|
||||
if (heldItem.getType().isAir()) {
|
||||
errorAny(player, "You must be holding an item to remove tags from it!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (ItemTag tag : ItemTag.values()) {
|
||||
getDupe().removeTag(heldItem, tag);
|
||||
}
|
||||
|
||||
successAny(player, "Removed all tags from your {0}.", heldItem.getType());
|
||||
|
||||
player.closeInventory();
|
||||
openHeldItemGui(player);
|
||||
}
|
||||
|
||||
private void handleGlobalTag(Player player, Material material, ItemTag tag, boolean isAdd) {
|
||||
if (material.isAir()) {
|
||||
errorAny(player, "You must have a material selected to use global material tagging!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAdd) {
|
||||
if (getDupe().addGlobalTag(material, tag)) {
|
||||
successAny(player, "All {0} items are now globally tagged as {1}. {2}", material, tag.getName(), tag.getDesc());
|
||||
} else {
|
||||
infoAny(player, "All {0} items are already tagged as {1}.", material, tag.getName());
|
||||
}
|
||||
} else {
|
||||
if (getDupe().removeGlobalTag(material, tag)) {
|
||||
successAny(player, "Removed global {0} tag from all {1} items.", tag.getName(), material);
|
||||
} else {
|
||||
infoAny(player, "{0} is not globally tagged as {1}.", material, tag.getName());
|
||||
}
|
||||
}
|
||||
|
||||
openGlobalMaterialGui(player,material);
|
||||
}
|
||||
|
||||
private ItemStack createPreviewItem(ItemStack stack) {
|
||||
if (stack.getType().isAir()) {
|
||||
return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE)
|
||||
.displayName("<gray><bold>No Item Held</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Hold an item to see",
|
||||
"<gray>its current tag status",
|
||||
"",
|
||||
"<yellow>💡 <white>Hold an item and reopen this GUI"
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
return ItemBuilder.create(stack.getType())
|
||||
.displayName("<white><bold>Currently Held: " + stack.getType().name() + "</bold>")
|
||||
.loreMiniMessage(getItemTagStatus(stack))
|
||||
.build();
|
||||
}
|
||||
|
||||
private List<String> getItemTagStatus(ItemStack item) {
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("<white>Item: <yellow>" + item.getType().name());
|
||||
lore.add("");
|
||||
|
||||
List<String> individualTags = new ArrayList<>();
|
||||
for (ItemTag tag : ItemTag.values()) {
|
||||
if (getDupe().hasIndividualTag(item,tag)) {
|
||||
individualTags.add("<" + getTagColor(tag) + ">" + (getDupe().checkIndividualTag(item,tag) ? "✔" : "❌") + " " + tag.getName());
|
||||
}
|
||||
}
|
||||
|
||||
List<String> globalTags = new ArrayList<>();
|
||||
for (ItemTag tag : ItemTag.values()) {
|
||||
if (getDupe().checkGlobalTag(item.getType(), tag)) {
|
||||
globalTags.add("<" + getTagColor(tag) + ">🌍 " + tag.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (!individualTags.isEmpty()) {
|
||||
lore.add("<white>Individual Tags:");
|
||||
lore.addAll(individualTags);
|
||||
}
|
||||
|
||||
if (!globalTags.isEmpty()) {
|
||||
if (!individualTags.isEmpty()) lore.add("");
|
||||
lore.add("<white>Global Tags:");
|
||||
lore.addAll(globalTags);
|
||||
}
|
||||
|
||||
if (individualTags.isEmpty() && globalTags.isEmpty()) {
|
||||
lore.add("<gray>No tags applied");
|
||||
}
|
||||
|
||||
return lore;
|
||||
}
|
||||
|
||||
private String getTagColor(ItemTag tag) {
|
||||
return switch (tag) {
|
||||
case UNIQUE -> "green";
|
||||
case FINAL -> "red";
|
||||
case INFINITE -> "blue";
|
||||
case PROTECTED -> "dark_purple";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
package me.trouper.dupealias.server.gui.admin;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.config.ConfigGui;
|
||||
import me.trouper.dupealias.server.gui.admin.globalrule.*;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AdminPanelManager implements DupeContext, CommonItems {
|
||||
|
||||
public void openMainGui(Player player) {
|
||||
new MainAdminGui(this).open(player);
|
||||
}
|
||||
|
||||
public void openHeldItemGui(Player player) {
|
||||
new HeldItemGui(this).open(player);
|
||||
}
|
||||
|
||||
public void openHelpGui(Player player) {
|
||||
new HelpGui(this).open(player);
|
||||
}
|
||||
|
||||
public void openGlobalRuleList(Player player) {
|
||||
new GlobalRuleListGui(this).createGUI(player).open(player);
|
||||
}
|
||||
|
||||
public void openConfigGui(Player player) {
|
||||
new ConfigGui(this).open(player);
|
||||
}
|
||||
|
||||
public void openGlobalRuleEditor(Player player, GlobalRule rule) {
|
||||
new GlobalRuleEditorGui(this, rule).open(player);
|
||||
}
|
||||
|
||||
public void openMaterialSelector(Player player, GlobalRule rule) {
|
||||
new GlobalRuleMaterialSelector(this, rule).createGUI(player).open(player);
|
||||
}
|
||||
|
||||
public void openNameCriteriaEditor(Player player, GlobalRule rule) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#ffeb3b:#ffc107><bold>Name Contains</bold></gradient>")
|
||||
.rows(3)
|
||||
.item(13, ItemBuilder.create(Material.NAME_TAG)
|
||||
.displayName("<yellow><bold>Current Pattern")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Set a regex pattern to match",
|
||||
"<gray>against item display names",
|
||||
"",
|
||||
"<white>Current: <yellow>" + (rule.nameContainsRegex.isEmpty() ? "Not set" : rule.nameContainsRegex),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to set pattern",
|
||||
"<yellow>▶ <white>Right-click to clear"
|
||||
))
|
||||
.build(), (g, e) -> {
|
||||
if (e.isRightClick()) {
|
||||
rule.nameContainsRegex = "";
|
||||
getConfig().save();
|
||||
successAny(player, "Cleared name pattern");
|
||||
openGlobalRuleEditor(player, rule);
|
||||
} else {
|
||||
g.requestInput(player, "modelData");
|
||||
}
|
||||
})
|
||||
.item(31, BACK(), (g, e) -> openGlobalRuleEditor(player, rule))
|
||||
.fillEmpty(EMPTY())
|
||||
.callback("modelData", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
try {
|
||||
int value = Integer.parseInt(input);
|
||||
if (rule.legacyModelData.contains(value)) {
|
||||
infoAny(player, "Model data value {0} already exists", value);
|
||||
} else {
|
||||
rule.legacyModelData.add(value);
|
||||
getConfig().save();
|
||||
successAny(player, "Added model data value: {0}", value);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
errorAny(player, "Invalid number: {0}", input);
|
||||
}
|
||||
openModelDataEditor(player, rule);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
int slot = 19;
|
||||
for (Integer value : rule.legacyModelData.stream().limit(5).toList()) {
|
||||
gui.updateItem(slot++, ItemBuilder.create(Material.FILLED_MAP)
|
||||
.displayName("<aqua><bold>Value: " + value)
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Model data value",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to remove"
|
||||
))
|
||||
.build(), (g, e) -> {
|
||||
rule.legacyModelData.remove(value);
|
||||
getConfig().save();
|
||||
successAny(player, "Removed model data value: {0}", value);
|
||||
openModelDataEditor(player, rule);
|
||||
});
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
public void openPotionEffectEditor(Player player, GlobalRule rule) {
|
||||
new GlobalRulePotionEffectEditor(this, rule).createGUI(player).open(player);
|
||||
}
|
||||
|
||||
public void openArmorTrimEditor(Player player, GlobalRule rule) {
|
||||
new GlobalRuleArmorTrimEditor(this, rule).open(player);
|
||||
}
|
||||
|
||||
public void openLoreCriteriaEditor(Player player, GlobalRule rule) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#9c27b0:#7b1fa2><bold>Lore Contains</bold></gradient>")
|
||||
.rows(3)
|
||||
.item(13, ItemBuilder.create(Material.WRITABLE_BOOK)
|
||||
.displayName("<light_purple><bold>Current Pattern")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Set a regex pattern to match",
|
||||
"<gray>against item lore lines",
|
||||
"",
|
||||
"<white>Current: <light_purple>" + (rule.loreContainsRegex.isEmpty() ? "Not set" : rule.loreContainsRegex),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to set pattern",
|
||||
"<yellow>▶ <white>Right-click to clear"
|
||||
))
|
||||
.build(), (g, e) -> {
|
||||
if (e.isRightClick()) {
|
||||
rule.loreContainsRegex = "";
|
||||
getConfig().save();
|
||||
successAny(player, "Cleared lore pattern");
|
||||
openGlobalRuleEditor(player, rule);
|
||||
} else {
|
||||
g.requestInput(player, "lorePattern");
|
||||
}
|
||||
})
|
||||
.item(22, BACK(), (g, e) -> openGlobalRuleEditor(player, rule))
|
||||
.fillEmpty(EMPTY())
|
||||
.callback("lorePattern", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
rule.loreContainsRegex = input;
|
||||
getConfig().save();
|
||||
successAny(player, "Set lore pattern to: " + input);
|
||||
openGlobalRuleEditor(player, rule);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
public void openEnchantmentEditor(Player player, GlobalRule rule) {
|
||||
new GlobalRuleEnchantmentEditor(this, rule).createGUI(player).open(player);
|
||||
}
|
||||
|
||||
public void openAttributeEditor(Player player, GlobalRule rule) {
|
||||
new GlobalRuleAttributeEditor(this, rule).createGUI(player).open(player);
|
||||
}
|
||||
|
||||
public void openItemFlagEditor(Player player, GlobalRule rule) {
|
||||
new GlobalRuleItemFlagEditor(this, rule).open(player);
|
||||
}
|
||||
|
||||
public void openModelDataEditor(Player player, GlobalRule rule) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#00bcd4:#0097a7><bold>Model Data Values</bold></gradient>")
|
||||
.rows(4)
|
||||
.item(13, ItemBuilder.create(Material.COMPASS)
|
||||
.displayName("<aqua><bold>Model Data Values")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Manage custom model data values",
|
||||
"<gray>that items must have",
|
||||
"",
|
||||
"<white>Current values: <aqua>" + rule.legacyModelData.size(),
|
||||
rule.legacyModelData.isEmpty() ? "" : "<gray>" + rule.legacyModelData.stream()
|
||||
.limit(5)
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(", ")),
|
||||
rule.legacyModelData.size() > 5 ? "<gray>... and " + (rule.legacyModelData.size() - 5) + " more" : "",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to add value",
|
||||
"<yellow>▶ <white>Right-click to clear all"
|
||||
))
|
||||
.build(), (g, e) -> {
|
||||
if (e.isRightClick()) {
|
||||
rule.legacyModelData.clear();
|
||||
getConfig().save();
|
||||
successAny(player, "Cleared all model data values");
|
||||
openModelDataEditor(player, rule);
|
||||
} else {
|
||||
g.requestInput(player,"namePattern");
|
||||
}
|
||||
})
|
||||
.item(22, BACK(), (g, e) -> openGlobalRuleEditor(player, rule))
|
||||
.fillEmpty(EMPTY())
|
||||
.callback("namePattern", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
rule.nameContainsRegex = input;
|
||||
getConfig().save();
|
||||
successAny(player, "Set name pattern to: " + input);
|
||||
openGlobalRuleEditor(player, rule);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
public ItemStack createExplainedItem(ItemStack item) {
|
||||
if (item == null || item.isEmpty()) {
|
||||
return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE)
|
||||
.displayName("<gray><italic>No item held</italic>")
|
||||
.loreMiniMessage("<aqua>💡 <white>Hold an item to get information on it")
|
||||
.build();
|
||||
}
|
||||
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("<white><bold>Held Item Explanation:</bold>");
|
||||
|
||||
Set<ItemTag> activeTags = new HashSet<>();
|
||||
|
||||
for (ItemTag tag : ItemTag.values()) {
|
||||
boolean global = getDupe().checkGlobalRuleTag(item, tag);
|
||||
boolean hasMeta = item.hasItemMeta() &&
|
||||
item.getItemMeta().getPersistentDataContainer().has(tag.getKey());
|
||||
|
||||
if (hasMeta) {
|
||||
Boolean individual = item.getItemMeta()
|
||||
.getPersistentDataContainer()
|
||||
.get(tag.getKey(), PersistentDataType.BOOLEAN);
|
||||
if (Boolean.TRUE.equals(individual)) {
|
||||
lore.add("<gray>• <green>" + tag.getName() + "</green> (Individual): " + tag.getDesc());
|
||||
activeTags.add(tag);
|
||||
} else {
|
||||
lore.add("<gray>• <red>" + tag.getName() + "</red> (Individually false)");
|
||||
if (global) {
|
||||
lore.add(" <gray>- Global is active: " + tag.getDesc());
|
||||
lore.add(" <gray>- Global is overridden by Individual tag.");
|
||||
activeTags.add(tag);
|
||||
}
|
||||
}
|
||||
} else if (global) {
|
||||
lore.add("<gray>• <yellow>" + tag.getName() + "</yellow> (Global): " + tag.getDesc());
|
||||
activeTags.add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
if (getDupe().isUnique(item)) {
|
||||
lore.add("<gray>• Detected UNIQUE by UniqueCheck");
|
||||
activeTags.add(ItemTag.UNIQUE);
|
||||
}
|
||||
|
||||
if (lore.size() == 1) {
|
||||
lore.add("<gray>• No DupeAlias tags apply to this item");
|
||||
}
|
||||
|
||||
List<String> conflicts = new ArrayList<>();
|
||||
if (activeTags.contains(ItemTag.INFINITE) && activeTags.contains(ItemTag.UNIQUE)) {
|
||||
conflicts.add("INFINITE ↔ UNIQUE");
|
||||
}
|
||||
if (activeTags.contains(ItemTag.INFINITE) && activeTags.contains(ItemTag.PROTECTED)) {
|
||||
conflicts.add("INFINITE ↔ PROTECTED");
|
||||
}
|
||||
|
||||
if (!conflicts.isEmpty()) {
|
||||
lore.add("");
|
||||
lore.add("<red>⚠ <bold>Conflicts detected:</bold>");
|
||||
for (String c : conflicts) {
|
||||
lore.add("<gray>• " + c);
|
||||
}
|
||||
lore.add("<gray>Consider removing one of the above tags.");
|
||||
}
|
||||
|
||||
return ItemBuilder.of(item)
|
||||
.displayName("<white><bold>Item Details</bold>")
|
||||
.loreMiniMessage(lore)
|
||||
.build();
|
||||
}
|
||||
|
||||
public ItemStack createPreviewItem(ItemStack stack) {
|
||||
if (stack.getType().isAir()) {
|
||||
return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE)
|
||||
.displayName("<gray><bold>No Item Held</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Hold an item to see",
|
||||
"<gray>its current tag status",
|
||||
"",
|
||||
"<yellow>💡 <white>Hold an item and reopen this GUI"
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
return ItemBuilder.create(stack.getType())
|
||||
.displayName("<white><bold>Currently Held: " + stack.getType().name() + "</bold>")
|
||||
.loreMiniMessage(getItemTagStatus(stack))
|
||||
.build();
|
||||
}
|
||||
|
||||
public List<String> getItemTagStatus(ItemStack item) {
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("<white>Item: <yellow>" + item.getType().name());
|
||||
lore.add("");
|
||||
|
||||
List<String> individualTags = new ArrayList<>();
|
||||
for (ItemTag tag : ItemTag.values()) {
|
||||
if (getDupe().hasIndividualTag(item,tag)) {
|
||||
individualTags.add("<" + getTagColor(tag) + ">" + (getDupe().checkIndividualTag(item,tag) ? "✔" : "❌") + " " + tag.getName());
|
||||
}
|
||||
}
|
||||
|
||||
List<String> globalTags = new ArrayList<>();
|
||||
for (ItemTag tag : ItemTag.values()) {
|
||||
if (getDupe().checkGlobalRuleTag(item, tag)) {
|
||||
globalTags.add("<" + getTagColor(tag) + ">🌍 " + tag.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (!individualTags.isEmpty()) {
|
||||
lore.add("<white>Individual Tags:");
|
||||
lore.addAll(individualTags);
|
||||
}
|
||||
|
||||
if (!globalTags.isEmpty()) {
|
||||
if (!individualTags.isEmpty()) lore.add("");
|
||||
lore.add("<white>Global Tags:");
|
||||
lore.addAll(globalTags);
|
||||
}
|
||||
|
||||
if (individualTags.isEmpty() && globalTags.isEmpty()) {
|
||||
lore.add("<gray>No tags applied");
|
||||
}
|
||||
|
||||
return lore;
|
||||
}
|
||||
|
||||
public String getTagColor(ItemTag tag) {
|
||||
return switch (tag) {
|
||||
case UNIQUE -> "green";
|
||||
case FINAL -> "red";
|
||||
case INFINITE -> "blue";
|
||||
case PROTECTED -> "dark_purple";
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package me.trouper.dupealias.server.gui.admin;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ConfigGui implements DupeContext, CommonItems {
|
||||
public void open(Player player, QuickGui backGui) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<dark_blue><Bold>DupeAlias Config")
|
||||
.defaultTimeout(30000)
|
||||
.rows(6)
|
||||
.item(0,BACK(),(g,e) -> backGui.open(player))
|
||||
.callback("dupe_cooldown", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
try {
|
||||
long millis = Long.parseLong(input);
|
||||
infoAny(player,"You have set the dupe cooldown to {0}ms.",input);
|
||||
getDupe().getConfig().dupeCooldownMillis = millis;
|
||||
getDupe().getConfig().save();
|
||||
open(player,backGui);
|
||||
} catch (NumberFormatException ex) {
|
||||
errorAny(player,"Please input a valid long number of milliseconds.");
|
||||
requestInput(gui,player,"dupe_cooldown","Number format error, please input a value.");
|
||||
}
|
||||
}
|
||||
})
|
||||
.item(13, ItemBuilder.integerItem(Material.DIAMOND,"<aqua>Dupe Command Cooldown", List.of(
|
||||
"<gray>How long players have",
|
||||
"<gray>to wait before running",
|
||||
"<gray>the /dupe command again.",
|
||||
" ",
|
||||
"<white>Click to set value."), (int) getConfig().dupeCooldownMillis),(g, e)->{
|
||||
Player p = (Player) e.getWhoClicked();
|
||||
requestInput(g,p,"dupe_cooldown","<aqua>Insert a long value of Milliseconds.\n<gray> 1000ms = 1 Second\n\n <yellow>The current value is set to <white>" + getConfig().dupeCooldownMillis + "\n");
|
||||
})
|
||||
.fillEmpty(EMPTY())
|
||||
.build();
|
||||
|
||||
player.openInventory(gui.getInventory());
|
||||
}
|
||||
|
||||
|
||||
private void requestInput(QuickGui gui, Player player, String callbackId, String prompt) {
|
||||
getDupe().getGuiListener().registerWaitingPlayer(player, gui);
|
||||
|
||||
gui.requestInput(player, callbackId);
|
||||
|
||||
getDupe().getGuiListener().sendInputInstructions(player, prompt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package me.trouper.dupealias.server.gui.admin;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class HeldItemGui implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
|
||||
public HeldItemGui(AdminPanelManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public void open(Player player) {
|
||||
ItemStack heldItem = player.getInventory().getItemInMainHand();
|
||||
|
||||
if (heldItem.getType().isAir()) {
|
||||
errorAny(player, "You must be holding an item to use this menu!");
|
||||
return;
|
||||
}
|
||||
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#4ecdc4:#45b7d1><bold>Held Item: " + heldItem.getType().name() + "</bold></gradient>")
|
||||
.rows(4)
|
||||
.fillBorder(EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE))
|
||||
|
||||
.item(0, BACK(),
|
||||
(g, e) -> manager.openMainGui(player))
|
||||
|
||||
.item(13, ItemBuilder.create(heldItem.getType())
|
||||
.displayName("<white><bold>" + heldItem.getType().name() + "</bold>")
|
||||
.loreMiniMessage(manager.getItemTagStatus(heldItem))
|
||||
.build())
|
||||
|
||||
.item(11, ItemBuilder.create(Material.EMERALD)
|
||||
.displayName("<green><bold>Add UNIQUE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Makes this specific item",
|
||||
"<red>unable to be duplicated",
|
||||
"",
|
||||
"<yellow>▶ <white>Left click to apply tag",
|
||||
"<yellow>▶ <white>Right click to remove tag",
|
||||
"<yellow>▶ <white>Shift click to set tag to false"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> tagHeldItem(player, ItemTag.UNIQUE, e.getClick()))
|
||||
|
||||
.item(20, ItemBuilder.create(Material.BARRIER)
|
||||
.displayName("<red><bold>Add FINAL Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Makes this specific item",
|
||||
"<red>unable to be modified",
|
||||
"",
|
||||
"<yellow>▶ <white>Left click to apply tag",
|
||||
"<yellow>▶ <white>Right click to remove tag",
|
||||
"<yellow>▶ <white>Shift click to set tag to false"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> tagHeldItem(player, ItemTag.FINAL, e.getClick()))
|
||||
|
||||
.item(15, ItemBuilder.create(Material.WATER_BUCKET)
|
||||
.displayName("<blue><bold>Add INFINITE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Makes this specific item",
|
||||
"<green>always have max stack size",
|
||||
"",
|
||||
"<yellow>▶ <white>Left click to apply tag",
|
||||
"<yellow>▶ <white>Right click to remove tag",
|
||||
"<yellow>▶ <white>Shift click to set tag to false"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> tagHeldItem(player, ItemTag.INFINITE, e.getClick()))
|
||||
|
||||
.item(24, ItemBuilder.create(Material.STRUCTURE_VOID)
|
||||
.displayName("<dark_purple><bold>Add PROTECTED Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Makes this specific item",
|
||||
"<red>not able to be manually created",
|
||||
"",
|
||||
"<yellow>▶ <white>Left click to apply tag",
|
||||
"<yellow>▶ <white>Right click to remove tag",
|
||||
"<yellow>▶ <white>Shift click to set tag to false"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> tagHeldItem(player, ItemTag.PROTECTED, e.getClick()))
|
||||
|
||||
.item(22, ItemBuilder.create(Material.TNT)
|
||||
.displayName("<dark_red><bold>Remove All Tags</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Removes all tags from",
|
||||
"<gray>this specific item",
|
||||
"",
|
||||
"<red>⚠ <white>This cannot be undone!",
|
||||
"<yellow>▶ <white>Click to remove tags"
|
||||
))
|
||||
.build(),
|
||||
(g, e) -> removeAllTagsFromHeld(player))
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
private void tagHeldItem(Player player, ItemTag tag, ClickType click) {
|
||||
ItemStack heldItem = player.getInventory().getItemInMainHand();
|
||||
|
||||
if (heldItem.getType().isAir()) {
|
||||
errorAny(player, "You must be holding an item to tag it!");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (click) {
|
||||
case LEFT -> {
|
||||
getDupe().addTag(heldItem, tag);
|
||||
successAny(player, "Added {0} tag to your {1}. {2}", tag.getName(), heldItem.getType(), tag.getDesc());
|
||||
}
|
||||
case RIGHT -> {
|
||||
getDupe().removeTag(heldItem, tag);
|
||||
successAny(player, "Removed {0} tag from your {1}.", tag.getName(), heldItem.getType());
|
||||
}
|
||||
case SHIFT_LEFT, SHIFT_RIGHT -> {
|
||||
getDupe().setTag(heldItem, tag, false);
|
||||
successAny(player, "Set {0} tag from your {1} to {2}.", tag.getName(), heldItem.getType(), "false");
|
||||
}
|
||||
}
|
||||
|
||||
player.closeInventory();
|
||||
open(player); // Re-open the GUI to update
|
||||
}
|
||||
|
||||
private void removeAllTagsFromHeld(Player player) {
|
||||
ItemStack heldItem = player.getInventory().getItemInMainHand();
|
||||
|
||||
if (heldItem.getType().isAir()) {
|
||||
errorAny(player, "You must be holding an item to remove tags from it!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (ItemTag tag : ItemTag.values()) {
|
||||
getDupe().removeTag(heldItem, tag);
|
||||
}
|
||||
|
||||
successAny(player, "Removed all tags from your {0}.", heldItem.getType());
|
||||
|
||||
player.closeInventory();
|
||||
open(player); // Re-open the GUI to update
|
||||
}
|
||||
}
|
||||
224
src/main/java/me/trouper/dupealias/server/gui/admin/HelpGui.java
Normal file
224
src/main/java/me/trouper/dupealias/server/gui/admin/HelpGui.java
Normal file
@@ -0,0 +1,224 @@
|
||||
package me.trouper.dupealias.server.gui.admin;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class HelpGui implements CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
|
||||
public HelpGui(AdminPanelManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public void open(Player player) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#9b59b6:#8e44ad><bold>DupeAlias Help</bold></gradient>")
|
||||
.rows(6)
|
||||
.fillBorder(EMPTY(Material.PURPLE_STAINED_GLASS_PANE))
|
||||
|
||||
.item(0, BACK(),
|
||||
(g, e) -> manager.openMainGui(player))
|
||||
|
||||
.item(20, ItemBuilder.create(Material.EMERALD)
|
||||
.displayName("<green><bold>UNIQUE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>What it does:",
|
||||
"<gray>• Prevents item duplication",
|
||||
"<gray>• Works globally or per item",
|
||||
"",
|
||||
"<white>Use cases:",
|
||||
"<gray>• Crate Keys",
|
||||
"<gray>• Special or rare items",
|
||||
"<gray>• Admin-only gear",
|
||||
"",
|
||||
"<red>⚠ Conflict:",
|
||||
"<gray>• Avoid combining with <red>INFINITE"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(21, ItemBuilder.create(Material.BARRIER)
|
||||
.displayName("<red><bold>FINAL Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>What it does:",
|
||||
"<gray>• Blocks all item modifications",
|
||||
"<gray>• Prevents renaming, enchanting, etc.",
|
||||
"",
|
||||
"<white>Use cases:",
|
||||
"<gray>• Name-dependent items",
|
||||
"<gray>• Rank kits or prizes",
|
||||
"<gray>• Event rewards",
|
||||
"",
|
||||
"<gray>✔ Can be combined safely with all tags"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(22, ItemBuilder.create(Material.WATER_BUCKET)
|
||||
.displayName("<blue><bold>INFINITE Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>What it does:",
|
||||
"<gray>• Enforces max stack size (99)",
|
||||
"<gray>• Item instantly refills when used",
|
||||
"",
|
||||
"<white>Use cases:",
|
||||
"<gray>• Infinite building blocks",
|
||||
"<gray>• 'Infinity' for tipped arrows",
|
||||
"<gray>• Creative-like resource flow",
|
||||
"",
|
||||
"<red>⚠ Conflicts:",
|
||||
"<gray>• Avoid combining with <red>UNIQUE",
|
||||
"<gray>• Avoid combining with <red>PROTECTED"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(23, ItemBuilder.create(Material.STRUCTURE_VOID)
|
||||
.displayName("<dark_purple><bold>PROTECTED Tag</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>What it does:",
|
||||
"<gray>• Blocks all use: crafting, consuming, enchanting",
|
||||
"<gray>• Makes item functionally inert",
|
||||
"<gray>• This does <red>NOT</red> prevent duping",
|
||||
"",
|
||||
"<white>Use cases:",
|
||||
"<gray>• Crate keys or Coupons",
|
||||
"<gray>• Decorative/admin-only items",
|
||||
"",
|
||||
"<red>⚠ Conflict:",
|
||||
"<gray>• Avoid combining with <red>INFINITE"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(24, ItemBuilder.create(Material.REDSTONE_TORCH)
|
||||
.displayName("<red><bold>Important Notes</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>Things to remember:",
|
||||
"<gray>• Individual tags <i>override</i> global rules",
|
||||
"<gray>• Global rules can match complex criteria",
|
||||
"<gray>• PROTECTED items are <red>not</red> UNIQUE by default",
|
||||
"<gray>• UNIQUE items can <i>still</i> be duped with <u>external</u> exploits",
|
||||
"",
|
||||
"<white>Tag Combinations:",
|
||||
"<gray>✔ <green>FINAL + PROTECTED</green> = Immutable Inert item",
|
||||
"<gray>✔ <green>PROTECTED + UNIQUE</green> = Inert Dupe-Proof Item",
|
||||
"<gray>❌ <red>INFINITE + UNIQUE</red> = Paradox",
|
||||
"<gray>❌ <red>INFINITE + PROTECTED</red> = Contradiction"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(30, ItemBuilder.create(Material.WRITABLE_BOOK)
|
||||
.displayName("<yellow><bold>Individual vs Global Tags</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>Individual Tags:",
|
||||
"<gray>• Stored directly on the item",
|
||||
"<gray>• Apply only to that one instance",
|
||||
"<gray>• Use 'Held Item Actions' menu",
|
||||
"",
|
||||
"<white>Global Rules:",
|
||||
"<gray>• Apply tags based on item properties",
|
||||
"<gray>• Match by material, name, enchants, etc.",
|
||||
"<gray>• Use 'Global Rules' menu",
|
||||
"",
|
||||
"<red>⚠ Individual tags override global rules"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(31, ItemBuilder.create(Material.COMPARATOR)
|
||||
.displayName("<gold><bold>Global Rules System</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>Match items by:",
|
||||
"<gray>• <white>Materials<gray> (whitelist/blacklist)",
|
||||
"<gray>• <white>Name/Lore<gray> (regex patterns)",
|
||||
"<gray>• <white>Enchantments<gray> (type & level)",
|
||||
"<gray>• <white>Attributes<gray> (exact values)",
|
||||
"<gray>• <white>Model Data<gray> (custom values)",
|
||||
"<gray>• <white>Potion Effects<gray> (type & amp)",
|
||||
"<gray>• <white>Armor Trim<gray> (pattern & material)",
|
||||
"<gray>• <white>Item Flags<gray> (hide tooltips)",
|
||||
"",
|
||||
"<white>Match Modes:",
|
||||
"<gray>• <green>AND<gray>: All criteria must match",
|
||||
"<gray>• <yellow>OR<gray>: Any criteria matches",
|
||||
"<gray>• <red>NAND<gray>: Not all match",
|
||||
"<gray>• <aqua>XOR<gray>: Exactly one matches"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(32, ItemBuilder.create(Material.COMMAND_BLOCK)
|
||||
.displayName("<aqua><bold>Rule Examples</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white><bold>Example Criteria:",
|
||||
"",
|
||||
"<white>1. Prevent Duping Netherite:",
|
||||
"<gray>• Material: [NETHERITE_INGOT, ANCIENT_DEBRIS, ...]",
|
||||
"<gray>• Tag: UNIQUE",
|
||||
"",
|
||||
"<white>2. Protect Crate Keys by Name",
|
||||
"<gray>• Name Regex: 'key'",
|
||||
"<gray>• Tags: PROTECTED, UNIQUE",
|
||||
"",
|
||||
"<white>3. Lock Silence Trim Armor:",
|
||||
"<gray>• Material: Ignore",
|
||||
"<gray>• Trim: [Silence]",
|
||||
"<gray>• Match Mode: AND",
|
||||
"<gray>• Tag: FINAL"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(37, ItemBuilder.create(Material.NAME_TAG)
|
||||
.displayName("<aqua><bold>Tag Glossary</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white><bold>UNIQUE</bold><gray>: Prevents <i>intended</i> duplication",
|
||||
"<white><bold>FINAL</bold><gray>: Cancels editing/modification",
|
||||
"<white><bold>PROTECTED</bold><gray>: Blocks all use (crafting, consuming)",
|
||||
"<white><bold>INFINITE</bold><gray>: Always max stack size (99)",
|
||||
"",
|
||||
"<gray>Tags can be combined, but some conflict!"
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(43, ItemBuilder.create(Material.TRIAL_KEY)
|
||||
.displayName("<white><bold>Permissions Guide</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>Access Permissions:",
|
||||
"<green>The root permission node is <bold>dupealias",
|
||||
"<gray>• <white>.dupe<gray> - Use /dupe command",
|
||||
"<gray>• <white>.gui<gray> - Access duplication GUI",
|
||||
"",
|
||||
"<white>Dupe GUI & Sessions:",
|
||||
"<gray>• <white>.gui.<type>.refresh.<n><gray> - GUI refill time",
|
||||
"<gray>• <white>.gui.<type>.keep<gray> - Retain items in GUI session",
|
||||
"<gray>• <white>.gui.replicator<gray> - Shift-click duplicate",
|
||||
"<gray>• <white>.gui.replicator.cooldown<gray> - Item input cooldown",
|
||||
"<gray>• <white>.gui.inventory<gray> - View personal inventory",
|
||||
"<gray>• <white>.gui.chest<gray> - Dupe via container",
|
||||
"",
|
||||
"<white>Bypass Permissions:",
|
||||
"<gray>• <white>.unique.bypass<gray> - Dupe unique items",
|
||||
"<gray>• <white>.final.bypass<gray> - Modify final items",
|
||||
"<gray>• <white>.protected.bypass<gray> - Use protected items",
|
||||
"<gray>• <white>.dupe.cooldownbypass<gray> - Skip /dupe cooldown",
|
||||
"",
|
||||
"<white>Other:",
|
||||
"<gray>• <white>.infinite<gray> - Use infinite-tagged items",
|
||||
"",
|
||||
"<red>⚠ Misuse Warning:",
|
||||
"<gray>Bypass perms override protections!",
|
||||
"<gray>Use caution when assigning them."
|
||||
))
|
||||
.build())
|
||||
|
||||
.item(49, manager.createExplainedItem(player.getInventory().getItemInMainHand()))
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package me.trouper.dupealias.server.gui.admin;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.config.ConfigGui;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class MainAdminGui implements CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
|
||||
public MainAdminGui(AdminPanelManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public void open(Player player) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#6b6bff:#9999ff><bold>DupeAlias Admin Panel</gradient>")
|
||||
.rows(5)
|
||||
|
||||
.item(11, ItemBuilder.create(player.getInventory().getItemInMainHand().isEmpty() ? Material.BARRIER : Material.DIAMOND_SWORD)
|
||||
.displayName("<gradient:#4ecdc4:#45b7d1><bold>Held Item Actions</bold></gradient>")
|
||||
.loreMiniMessage(
|
||||
"<gray>Manage tags for the item",
|
||||
"<gray>you're currently holding",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to open menu"
|
||||
)
|
||||
.hideAllFlags()
|
||||
.build(),
|
||||
(q, event) -> manager.openHeldItemGui(player))
|
||||
|
||||
.item(13, ItemBuilder.create(Material.BOOKSHELF)
|
||||
.displayName("<gradient:#ff6b6b:#ffa726><bold>Global Rules</bold></gradient>")
|
||||
.loreMiniMessage(
|
||||
"<gray>Configure global rules to apply",
|
||||
"<gray>tags based on item properties",
|
||||
"",
|
||||
"<white>Rules: <yellow>" + getConfig().globalRules.size(),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to manage rules"
|
||||
)
|
||||
.build(),
|
||||
(q, event) -> manager.openGlobalRuleList(player))
|
||||
|
||||
.item(15, ItemBuilder.create(Material.KNOWLEDGE_BOOK)
|
||||
.displayName("<gradient:#cb59b6:#8e44ad><bold>Information & Help</bold></gradient>")
|
||||
.loreMiniMessage(
|
||||
"<gray>Learn about item tags and",
|
||||
"<gray>how to use this system",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to view help"
|
||||
)
|
||||
.build(),
|
||||
(q, event) -> manager.openHelpGui(player))
|
||||
|
||||
.item(29, ItemBuilder.create(Material.COMPARATOR)
|
||||
.displayName("<gradient:#ff6bff:#ffa7ff><bold>Configuration</bold></gradient>")
|
||||
.loreMiniMessage(
|
||||
"<gray>Modify plugin parameters",
|
||||
"<gray>name and colors",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to open config"
|
||||
)
|
||||
.build(), (q,event) -> manager.openConfigGui(player))
|
||||
|
||||
.item(31, manager.createPreviewItem(player.getInventory().getItemInMainHand()))
|
||||
|
||||
.item(33, ItemBuilder.create(Material.DIAMOND)
|
||||
.displayName("<#AAAAFF><bold>Dupe<#00DDFF>Alias</bold> <white>Credits")
|
||||
.loreMiniMessage(
|
||||
"<dark_gray><bold>|</bold><gray> Built with Alias Development Kit",
|
||||
"<dark_gray><bold>|</bold><gray>",
|
||||
"<dark_gray><bold>|</bold> <gradient:#e38c01:#eccd00:#FFFFFF:#62afdd:#1f3857>Written by obvWolf</gradient>",
|
||||
" ",
|
||||
"<dark_gray>Copyright © 2025 DupeAlias",
|
||||
"<dark_gray>Do Not Redistribute"
|
||||
)
|
||||
.build())
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package me.trouper.dupealias.server.gui.admin;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.server.systems.gui.QuickPaginatedGUI;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class MaterialBrowserGui extends QuickPaginatedGUI<Material> implements DupeContext {
|
||||
|
||||
private Material PICKED_MATERIAL = Material.AIR;
|
||||
|
||||
@Override
|
||||
protected String getTitle(Player player) {
|
||||
return "<yellow><bold>Material Browser";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Material> getAllItems(Player player) {
|
||||
return Arrays.stream(Material.values()).filter(material -> !material.isLegacy() && !material.isAir()).filter(Material::isItem).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack createDisplayItem(Material item) {
|
||||
return new AdminGui().createMaterialTagItem(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleItemClick(Player player, Material item, InventoryClickEvent event) {
|
||||
PICKED_MATERIAL = item;
|
||||
new AdminGui().openGlobalMaterialGui(player,PICKED_MATERIAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addFilterItems(QuickGui.GuiBuilder filterGui, Player player, Set<String> filters) {
|
||||
filterGui.item(0, createFilterToggleItem("Infinite",Material.LAPIS_BLOCK,filters.contains("I")), (gui, event) ->
|
||||
toggleFilter(player,"I"));
|
||||
filterGui.item(1, createFilterToggleItem("Unique",Material.EMERALD_BLOCK,filters.contains("U")), (gui, event) ->
|
||||
toggleFilter(player,"U"));
|
||||
filterGui.item(2, createFilterToggleItem("Final",Material.REDSTONE_BLOCK,filters.contains("F")), (gui, event) ->
|
||||
toggleFilter(player,"F"));
|
||||
filterGui.item(3, createFilterToggleItem("Protected",Material.COMMAND_BLOCK,filters.contains("P")), (gui, event) ->
|
||||
toggleFilter(player,"P"));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean testFilter(Player player, Material item, String filterKey) {
|
||||
return switch (filterKey) {
|
||||
case "I" -> getConfig().globalMaterials.getOrDefault(item,new HashSet<>()).contains(ItemTag.INFINITE);
|
||||
case "U" -> getConfig().globalMaterials.getOrDefault(item,new HashSet<>()).contains(ItemTag.UNIQUE);
|
||||
case "F" -> getConfig().globalMaterials.getOrDefault(item,new HashSet<>()).contains(ItemTag.FINAL);
|
||||
case "P" -> getConfig().globalMaterials.getOrDefault(item,new HashSet<>()).contains(ItemTag.PROTECTED);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openBackGUI(Player player) {
|
||||
new AdminGui().openGlobalMaterialGui(player,PICKED_MATERIAL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package me.trouper.dupealias.server.gui.admin.config;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.server.systems.gui.QuickPaginatedGUI;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class CommandRegexGui extends QuickPaginatedGUI<String> implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
|
||||
public CommandRegexGui(AdminPanelManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle(Player player) {
|
||||
return "<red><bold>Final Command Regex</bold>";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getAllItems(Player player) {
|
||||
return getConfig().finalCommandRegex;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack createDisplayItem(String pattern) {
|
||||
return ItemBuilder.create(Material.PAPER)
|
||||
.displayName("<yellow>Blocked Pattern")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<white>" + pattern,
|
||||
"",
|
||||
"<red>▶ <white>Right-click to remove"
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuickGui createGUI(Player player) {
|
||||
QuickGui gui = super.createGUI(player);
|
||||
gui.updateItem(51,ItemBuilder.create(Material.LIME_DYE)
|
||||
.displayName("<green><bold>+ Add Regex Pattern</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Add a new regex pattern for",
|
||||
"<gray>commands to block with FINAL items",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to add pattern"
|
||||
))
|
||||
.build(),(g,e)->{
|
||||
QuickGui inputGui = QuickGui.create()
|
||||
.titleMini("<gradient:#e91e63:#c2185b><bold>Set Regex Pattern</bold></gradient>")
|
||||
.rows(3)
|
||||
.callback("add_regex", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
getConfig().finalCommandRegex.add(input);
|
||||
getConfig().save();
|
||||
successAny(player, "Added {0} to the final command regex.");
|
||||
createGUI(player).open(player);
|
||||
}
|
||||
})
|
||||
.item(13, ItemBuilder.create(Material.EXPERIENCE_BOTTLE)
|
||||
.displayName("<light_purple><bold>Click me!")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Enter the regex of",
|
||||
"<gray>the command to block"
|
||||
))
|
||||
.build(), (q, ev) -> getDupe().getGuiListener().requestChatInput(q, player, "add_regex",
|
||||
"<red>Enter a regex pattern for commands to block:\n<gray>Example: \"(?:itemname|iname)\"gmi"))
|
||||
.item(22, BACK(), (q, ev) -> createGUI(player).open(player))
|
||||
.fillEmpty(EMPTY())
|
||||
.build();
|
||||
|
||||
inputGui.open(player);
|
||||
});
|
||||
return gui;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleItemClick(Player player, String regex, InventoryClickEvent e) {
|
||||
if (e.isRightClick()) {
|
||||
getConfig().finalCommandRegex.remove(regex);
|
||||
getConfig().save();
|
||||
successAny(player, "Removed regex pattern");
|
||||
createGUI(player).open(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addFilterItems(QuickGui.GuiBuilder guiBuilder, Player player, Set<String> set) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openBackGUI(Player player) {
|
||||
manager.openConfigGui(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package me.trouper.dupealias.server.gui.admin.config;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeAlias;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.files.CommonConfig;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A GUI for modifying the settings within the common.json file.
|
||||
* This class provides an interface for changing general plugin settings
|
||||
* such as colors, prefixes, and display names.
|
||||
*/
|
||||
public class CommonConfigGui implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
|
||||
/**
|
||||
* Constructs a new CommonConfigGui.
|
||||
*
|
||||
* @param manager The AdminPanelManager to handle navigation.
|
||||
*/
|
||||
public CommonConfigGui(AdminPanelManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the Common Config GUI for a player.
|
||||
*
|
||||
* @param player The player to open the GUI for.
|
||||
*/
|
||||
public void open(Player player) {
|
||||
// Retrieve the current common configuration instance.
|
||||
CommonConfig config = getCommonConfig();
|
||||
|
||||
// Create the GUI using the QuickGui builder.
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<dark_aqua><bold>Common Config</bold></dark_aqua>")
|
||||
.defaultTimeout(30000)
|
||||
.rows(3)
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.5f, 1.0f)
|
||||
|
||||
// Back button to return to the main admin panel.
|
||||
.item(0, BACK(), (g, e) -> manager.openMainGui(player))
|
||||
|
||||
// Item and callback for modifying the main color.
|
||||
.callback("main_color", (g, p, input, source) -> {
|
||||
try {
|
||||
// Parse the hex string into an integer color value.
|
||||
int color = Integer.parseInt(input.replace("#", ""), 16);
|
||||
config.mainColor = color;
|
||||
config.save();
|
||||
// Reload the common settings in the core to apply changes immediately.
|
||||
getInstance().getCommon().update(config.generateCommon());
|
||||
successAny(p, "Main color set to <#{0}>#{0}</#{0}>.", Integer.toHexString(color));
|
||||
open(p); // Re-open the GUI to show the updated value.
|
||||
} catch (NumberFormatException ex) {
|
||||
errorAny(p, "Invalid hex color format. Use RRGGBB (e.g., AAAAFF).");
|
||||
}
|
||||
})
|
||||
.item(10, ItemBuilder.create(Material.BLUE_WOOL)
|
||||
.displayName("<blue><bold>Main Color</bold>")
|
||||
.loreMiniMessage(List.of(
|
||||
"<gray>The color for the message border.",
|
||||
"",
|
||||
String.format("<yellow>Current: <#%s>#%s</#%s>", Integer.toHexString(config.mainColor), Integer.toHexString(config.mainColor), Integer.toHexString(config.mainColor)),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to modify"
|
||||
)).build(), (g, e) ->
|
||||
// Request chat input from the player.
|
||||
getDupe().getGuiListener().requestChatInput(g, player, "main_color",
|
||||
"<blue>Enter a hex color code for the main color.\n<gray>Example: AAAAFF"))
|
||||
|
||||
// Item and callback for modifying the secondary color.
|
||||
.callback("secondary_color", (g, p, input, source) -> {
|
||||
try {
|
||||
int color = Integer.parseInt(input.replace("#", ""), 16);
|
||||
config.secondaryColor = color;
|
||||
config.save();
|
||||
getInstance().getCommon().update(config.generateCommon());
|
||||
successAny(p, "Secondary color set to <#{0}>#{0}</#{0}>.", Integer.toHexString(color));
|
||||
open(p);
|
||||
} catch (NumberFormatException ex) {
|
||||
errorAny(p, "Invalid hex color format. Use RRGGBB (e.g., 00DDFF).");
|
||||
}
|
||||
})
|
||||
.item(11, ItemBuilder.create(Material.CYAN_WOOL)
|
||||
.displayName("<aqua><bold>Secondary Color</bold>")
|
||||
.loreMiniMessage(List.of(
|
||||
"<gray>The color used for the plugin's name.",
|
||||
"",
|
||||
String.format("<yellow>Current: <#%s>#%s</#%s>", Integer.toHexString(config.secondaryColor), Integer.toHexString(config.secondaryColor), Integer.toHexString(config.secondaryColor)),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to modify"
|
||||
)).build(), (g, e) ->
|
||||
getDupe().getGuiListener().requestChatInput(g, player, "secondary_color",
|
||||
"<aqua>Enter a hex color code for the secondary color.\n<gray>Example: 00DDFF"))
|
||||
|
||||
// Item and callback for modifying the plugin name.
|
||||
.callback("plugin_name", (g, p, input, source) -> {
|
||||
config.pluginName = input;
|
||||
config.save();
|
||||
getInstance().getCommon().update(config.generateCommon());
|
||||
successAny(p, "Plugin name set to: {0}", input);
|
||||
open(p);
|
||||
})
|
||||
.item(12, ItemBuilder.create(Material.NAME_TAG)
|
||||
.displayName("<green><bold>Plugin Name</bold>")
|
||||
.loreMiniMessage(List.of(
|
||||
"<gray>The name of the plugin displayed in messages.",
|
||||
"",
|
||||
"<yellow>Current: <white>" + config.pluginName,
|
||||
"",
|
||||
"<yellow>▶ <white>Click to modify"
|
||||
)).build(), (g, e) ->
|
||||
getDupe().getGuiListener().requestChatInput(g, player, "plugin_name",
|
||||
"<green>Enter the new plugin name."))
|
||||
|
||||
// Item and callback for modifying the flat prefix.
|
||||
.callback("flat_prefix", (g, p, input, source) -> {
|
||||
config.flatPrefix = input;
|
||||
config.save();
|
||||
getInstance().getCommon().update(config.generateCommon());
|
||||
successAny(p, "Flat prefix set to: {0}", input);
|
||||
open(p);
|
||||
})
|
||||
.item(13, ItemBuilder.create(Material.PAPER)
|
||||
.displayName("<gray><bold>Flat Prefix</bold>")
|
||||
.loreMiniMessage(List.of(
|
||||
"<gray>The prefix used when 'flat' mode is enabled.",
|
||||
"<gray>Uses legacy '&' color codes.",
|
||||
"",
|
||||
"<yellow>Current: <white>" + config.flatPrefix,
|
||||
"",
|
||||
"<yellow>▶ <white>Click to modify"
|
||||
)).build(), (g, e) ->
|
||||
getDupe().getGuiListener().requestChatInput(g, player, "flat_prefix",
|
||||
"<gray>Enter the new flat prefix."))
|
||||
|
||||
// Item and click handler for toggling flat mode.
|
||||
.item(14, ItemBuilder.create(config.flat ? Material.LIME_DYE : Material.GRAY_DYE)
|
||||
.displayName("<white><bold>Flat Mode</bold>")
|
||||
.loreMiniMessage(List.of(
|
||||
"<gray>If true, uses the simple flat message system",
|
||||
"<gray>instead of the complex line wrapping feature.",
|
||||
"",
|
||||
"<yellow>Current: <white>" + config.flat,
|
||||
"",
|
||||
"<yellow>▶ <white>Click to toggle"
|
||||
)).build(), (g, e) -> {
|
||||
config.flat = !config.flat;
|
||||
config.save();
|
||||
getInstance().getCommon().update(config.generateCommon());
|
||||
infoAny(player, "Flat mode set to: {0}", config.flat);
|
||||
open(player); // Re-open to update the item's appearance.
|
||||
})
|
||||
|
||||
// Fill the rest of the GUI with empty panes.
|
||||
.fillEmpty(EMPTY())
|
||||
.build();
|
||||
|
||||
// Display the GUI to the player.
|
||||
gui.open(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
package me.trouper.dupealias.server.gui.admin.config;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ConfigGui implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
|
||||
public ConfigGui(AdminPanelManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public void open(Player player) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<dark_blue><bold>DupeAlias Config</bold></dark_blue>")
|
||||
.defaultTimeout(30000)
|
||||
.rows(5)
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.5f, 1.0f)
|
||||
|
||||
// Back button
|
||||
.item(0, BACK(), (g, e) -> manager.openMainGui(player))
|
||||
|
||||
// Dupe Cooldown
|
||||
.callback("dupe_cooldown", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
try {
|
||||
long millis = Long.parseLong(input);
|
||||
if (millis < 0) {
|
||||
errorAny(player, "Cooldown cannot be negative!");
|
||||
return;
|
||||
}
|
||||
infoAny(player, "You have set the dupe cooldown to {0}ms.", input);
|
||||
getConfig().dupeCooldownMillis = millis;
|
||||
getConfig().save();
|
||||
open(player);
|
||||
} catch (NumberFormatException ex) {
|
||||
errorAny(player, "Please input a valid number of milliseconds.");
|
||||
getDupe().getGuiListener().requestChatInput(gui, player, "dupe_cooldown", "Number format error, please input a valid value.");
|
||||
}
|
||||
}
|
||||
})
|
||||
.item(11, ItemBuilder.integerItem(Material.DIAMOND, "<aqua><bold>Dupe Command Cooldown</bold>", List.of(
|
||||
"<gray>How long players have to wait",
|
||||
"<gray>before running the /dupe command again.",
|
||||
"",
|
||||
"<yellow>Current: <white>" + getConfig().dupeCooldownMillis + "ms",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to modify"
|
||||
), (int) getConfig().dupeCooldownMillis), (g, e) ->
|
||||
getDupe().getGuiListener().requestChatInput(g, player, "dupe_cooldown",
|
||||
"<aqua>Insert a long value of Milliseconds.\n<gray> 1000ms = 1 Second\n\n<yellow>Current value: <white>" + getConfig().dupeCooldownMillis + "ms"))
|
||||
|
||||
// Default Dupe GUI
|
||||
.item(12, ItemBuilder.create(Material.CHEST)
|
||||
.displayName("<green><bold>Default Dupe GUI</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>The GUI type that opens when",
|
||||
"<gray>players use the /dupe command",
|
||||
"",
|
||||
"<white>Options: <gray>REPLICATOR, INVENTORY, CHEST, MENU",
|
||||
"",
|
||||
"<yellow>Current: <white>" + getConfig().defaultDupeGui,
|
||||
"",
|
||||
"<yellow>▶ <white>Click to cycle options"
|
||||
))
|
||||
.build(), (g, e) -> cycleDefaultGui(player))
|
||||
|
||||
// Final Command Regex
|
||||
.item(13, ItemBuilder.create(Material.BARRIER)
|
||||
.displayName("<red><bold>Final Command Regex</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Regex patterns for commands that",
|
||||
"<gray>are blocked when holding FINAL items",
|
||||
"",
|
||||
"<yellow>Patterns: <white>" + getConfig().finalCommandRegex.size(),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to manage patterns"
|
||||
))
|
||||
.build(), (g, e) -> openFinalCommandRegexGui(player))
|
||||
|
||||
// Global Rules Editor
|
||||
.item(14, ItemBuilder.create(Material.COMMAND_BLOCK)
|
||||
.displayName("<light_purple><bold>Global Rules Editor</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Advanced rule system for applying",
|
||||
"<gray>tags based on item properties",
|
||||
"",
|
||||
"<yellow>Rules: <white>" + getConfig().globalRules.size(),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to manage rules"
|
||||
))
|
||||
.build(), (g, e) -> openGlobalRulesGui(player))
|
||||
|
||||
// Tag Lore Settings
|
||||
.item(15, ItemBuilder.create(Material.NAME_TAG)
|
||||
.displayName("<yellow><bold>Tag Lore Settings</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Configure the lore text that",
|
||||
"<gray>appears on tagged items",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to configure lore"
|
||||
))
|
||||
.build(), (g, e) -> openTagLoreGui(player, g))
|
||||
|
||||
// Common Settings
|
||||
.item(22,ItemBuilder.create(Material.LIGHT)
|
||||
.displayName("<gold><bold>Common Config</bold>")
|
||||
.loreMiniMessage(
|
||||
"<gray>Generic plugin configuration",
|
||||
"<gray>Like plugin name and colors",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to modify"
|
||||
)
|
||||
.build(),
|
||||
(g,e) -> openCommonGui(player, g))
|
||||
|
||||
// Replicator Settings
|
||||
.item(30, ItemBuilder.create(Material.REPEATER)
|
||||
.displayName("<blue><bold>Replicator Settings</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Configure replicator GUI behavior",
|
||||
"",
|
||||
"<yellow>Refresh Delay: <white>" + getConfig().replicator.baseRefreshDelayTicks + " ticks",
|
||||
"<yellow>Input Cooldown: <white>" + getConfig().replicator.baseInputCooldownTicks + " ticks",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to configure"
|
||||
))
|
||||
.build(), (g, e) -> openReplicatorGui(player, g))
|
||||
|
||||
// Chest Settings
|
||||
.item(31, ItemBuilder.create(Material.CHEST)
|
||||
.displayName("<gold><bold>Chest GUI Settings</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Configure chest GUI behavior",
|
||||
"",
|
||||
"<yellow>Refresh Delay: <white>" + getConfig().chest.baseRefreshDelayTicks + " ticks",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to configure"
|
||||
))
|
||||
.build(), (g, e) -> openChestGui(player, g))
|
||||
|
||||
// Inventory Settings
|
||||
.item(32, ItemBuilder.create(Material.ENDER_CHEST)
|
||||
.displayName("<dark_purple><bold>Inventory GUI Settings</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Configure inventory GUI behavior",
|
||||
"",
|
||||
"<yellow>Refresh Delay: <white>" + getConfig().inventory.baseRefreshDelayTicks + " ticks",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to configure"
|
||||
))
|
||||
.build(), (g, e) -> openInventoryGui(player, g))
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
private void openCommonGui(Player player, QuickGui g) {
|
||||
new CommonConfigGui(manager).open(player);
|
||||
}
|
||||
|
||||
private void cycleDefaultGui(Player player) {
|
||||
String[] options = {"REPLICATOR", "INVENTORY", "CHEST", "MENU"};
|
||||
String current = getConfig().defaultDupeGui;
|
||||
|
||||
int currentIndex = Arrays.asList(options).indexOf(current);
|
||||
int nextIndex = (currentIndex + 1) % options.length;
|
||||
|
||||
getConfig().defaultDupeGui = options[nextIndex];
|
||||
getConfig().save();
|
||||
|
||||
infoAny(player, "Default GUI changed to: {0}", options[nextIndex]);
|
||||
open(player);
|
||||
}
|
||||
|
||||
private void openFinalCommandRegexGui(Player player) {
|
||||
new CommandRegexGui(manager).createGUI(player).open(player);
|
||||
}
|
||||
|
||||
private void openGlobalRulesGui(Player player) {
|
||||
manager.openGlobalRuleList(player);
|
||||
}
|
||||
|
||||
private void openTagLoreGui(Player player, QuickGui backGui) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<yellow><bold>Tag Lore Settings</bold>")
|
||||
.rows(1)
|
||||
.item(8, BACK(), (g, e) -> open(player))
|
||||
.fillEmpty(EMPTY())
|
||||
.build();
|
||||
|
||||
int slot = 0;
|
||||
for (ItemTag tag : ItemTag.values()) {
|
||||
String trueLore = getConfig().trueTagLore.get(tag);
|
||||
String falseLore = getConfig().falseTagLore.get(tag);
|
||||
|
||||
gui.updateItem(slot, ItemBuilder.create(getMaterialForTag(tag))
|
||||
.displayName("<" + getTagColor(tag) + "><bold>" + tag.getName() + " Tag Lore</bold>")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>True Lore: <white>" + (trueLore != null ? trueLore : "Not set"),
|
||||
"<gray>False Lore: <white>" + (falseLore != null ? falseLore : "Not set"),
|
||||
"",
|
||||
"<yellow>▶ <white>Left-click to edit true lore",
|
||||
"<yellow>▶ <white>Right-click to edit false lore"
|
||||
))
|
||||
.build(), (g, e) -> editTagLore(player, tag, e.isLeftClick(), g, backGui));
|
||||
|
||||
slot++;
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
private void editTagLore(Player player, ItemTag tag, boolean isTrue, QuickGui gui, QuickGui backGui) {
|
||||
String callbackId = "edit_lore_" + tag.name() + "_" + isTrue;
|
||||
|
||||
gui.updateItem(0, gui.getSlotItems().get(0));
|
||||
|
||||
QuickGui.GuiCallback callback = new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
if (isTrue) {
|
||||
getConfig().trueTagLore.put(tag, input);
|
||||
} else {
|
||||
getConfig().falseTagLore.put(tag, input);
|
||||
}
|
||||
getConfig().save();
|
||||
successAny(player, "Updated {0} {1} lore", tag.getName(), isTrue ? "true" : "false");
|
||||
openTagLoreGui(player, backGui);
|
||||
}
|
||||
};
|
||||
|
||||
// Create a temporary GUI with the callback
|
||||
QuickGui tempGui = QuickGui.create()
|
||||
.titleMini("<yellow>Editing " + tag.getName() + " Lore")
|
||||
.rows(1)
|
||||
.callback(callbackId, callback)
|
||||
.build();
|
||||
|
||||
getDupe().getGuiListener().requestChatInput(tempGui, player, callbackId,
|
||||
"<yellow>Enter the " + (isTrue ? "true" : "false") + " lore for " + tag.getName() + ":\n" +
|
||||
"<gray>Use MiniMessage format (e.g., <green>text</green>)\n" +
|
||||
"<white>Current: " + (isTrue ? getConfig().trueTagLore.get(tag) : getConfig().falseTagLore.get(tag)));
|
||||
}
|
||||
|
||||
private void openReplicatorGui(Player player, QuickGui backGui) {
|
||||
openTicksConfigGui(player, backGui, "Replicator", Material.REPEATER,
|
||||
getConfig().replicator.baseRefreshDelayTicks,
|
||||
getConfig().replicator.baseInputCooldownTicks,
|
||||
(refresh, input) -> {
|
||||
getConfig().replicator.baseRefreshDelayTicks = refresh;
|
||||
getConfig().replicator.baseInputCooldownTicks = input;
|
||||
});
|
||||
}
|
||||
|
||||
private void openChestGui(Player player, QuickGui backGui) {
|
||||
openTicksConfigGui(player, backGui, "Chest", Material.CHEST,
|
||||
getConfig().chest.baseRefreshDelayTicks,
|
||||
null,
|
||||
(refresh, input) -> {
|
||||
getConfig().chest.baseRefreshDelayTicks = refresh;
|
||||
});
|
||||
}
|
||||
|
||||
private void openInventoryGui(Player player, QuickGui backGui) {
|
||||
openTicksConfigGui(player, backGui, "Inventory", Material.ENDER_CHEST,
|
||||
getConfig().inventory.baseRefreshDelayTicks,
|
||||
null,
|
||||
(refresh, input) -> {
|
||||
getConfig().inventory.baseRefreshDelayTicks = refresh;
|
||||
});
|
||||
}
|
||||
|
||||
private void openTicksConfigGui(Player player, QuickGui backGui, String name, Material icon,
|
||||
int refreshTicks, Integer inputTicks, TicksConfigSetter setter) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<blue><bold>" + name + " Settings</bold>")
|
||||
.rows(1)
|
||||
.item(8, BACK(), (g, e) -> open(player))
|
||||
|
||||
.callback("refresh_ticks", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
try {
|
||||
int ticks = Integer.parseInt(input);
|
||||
if (ticks < 1) {
|
||||
errorAny(player, "Ticks must be at least 1!");
|
||||
return;
|
||||
}
|
||||
setter.setRefresh(ticks, inputTicks);
|
||||
getConfig().save();
|
||||
successAny(player, "Set refresh delay to {0} ticks", ticks);
|
||||
openTicksConfigGui(player, backGui, name, icon, ticks, inputTicks, setter);
|
||||
} catch (NumberFormatException ex) {
|
||||
errorAny(player, "Please input a valid number of ticks.");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
.callback("input_ticks", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
try {
|
||||
int ticks = Integer.parseInt(input);
|
||||
if (ticks < 1) {
|
||||
errorAny(player, "Ticks must be at least 1!");
|
||||
return;
|
||||
}
|
||||
setter.setRefresh(refreshTicks, ticks);
|
||||
getConfig().save();
|
||||
successAny(player, "Set input cooldown to {0} ticks", ticks);
|
||||
openTicksConfigGui(player, backGui, name, icon, refreshTicks, ticks, setter);
|
||||
} catch (NumberFormatException ex) {
|
||||
errorAny(player, "Please input a valid number of ticks.");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
.item(0, ItemBuilder.integerItem(icon, "<aqua><bold>Refresh Delay</bold>", List.of(
|
||||
"<gray>How many ticks between item",
|
||||
"<gray>replenishment in the GUI",
|
||||
"",
|
||||
"<yellow>Current: <white>" + refreshTicks + " ticks",
|
||||
"<gray>(" + (refreshTicks / 20.0) + " seconds)",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to modify"
|
||||
), refreshTicks), (g, e) ->
|
||||
getDupe().getGuiListener().requestChatInput(g, player, "refresh_ticks",
|
||||
"<aqua>Enter the refresh delay in ticks:\n<gray>20 ticks = 1 second\n\n<yellow>Current: <white>" + refreshTicks + " ticks"))
|
||||
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.build();
|
||||
|
||||
if (inputTicks != null) {
|
||||
gui.updateItem(1, ItemBuilder.integerItem(Material.CLOCK, "<green><bold>Input Cooldown</bold>", List.of(
|
||||
"<gray>How many ticks players must wait",
|
||||
"<gray>before changing the input again",
|
||||
"",
|
||||
"<yellow>Current: <white>" + inputTicks + " ticks",
|
||||
"<gray>(" + (inputTicks / 20.0) + " seconds)",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to modify"
|
||||
), inputTicks), (g, e) -> {
|
||||
getDupe().getGuiListener().requestChatInput(g, player, "input_ticks",
|
||||
"<green>Enter the input cooldown in ticks:\n<gray>20 ticks = 1 second\n\n<yellow>Current: <white>" + inputTicks + " ticks");
|
||||
});
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
private Material getMaterialForTag(ItemTag tag) {
|
||||
return switch (tag) {
|
||||
case UNIQUE -> Material.EMERALD_BLOCK;
|
||||
case FINAL -> Material.REDSTONE_BLOCK;
|
||||
case INFINITE -> Material.LAPIS_BLOCK;
|
||||
case PROTECTED -> Material.STRUCTURE_BLOCK;
|
||||
};
|
||||
}
|
||||
|
||||
private String getTagColor(ItemTag tag) {
|
||||
return switch (tag) {
|
||||
case UNIQUE -> "green";
|
||||
case FINAL -> "red";
|
||||
case INFINITE -> "blue";
|
||||
case PROTECTED -> "dark_purple";
|
||||
};
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface TicksConfigSetter {
|
||||
void setRefresh(int refreshTicks, Integer inputTicks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package me.trouper.dupealias.server.gui.admin.globalrule;
|
||||
|
||||
import me.trouper.alias.data.enums.ValidTrimMaterial;
|
||||
import me.trouper.alias.data.enums.ValidTrimPattern;
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.FormatUtils;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GlobalRuleArmorTrimEditor implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
private final GlobalRule rule;
|
||||
|
||||
public GlobalRuleArmorTrimEditor(AdminPanelManager manager, GlobalRule rule) {
|
||||
this.manager = manager;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
public void open(Player player) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#795548:#5d4037><bold>Armor Trim Criteria</bold></gradient>")
|
||||
.rows(4)
|
||||
.fillBorder(EMPTY(Material.BROWN_STAINED_GLASS_PANE))
|
||||
|
||||
// Back button
|
||||
.item(0, BACK(), (g, e) -> manager.openGlobalRuleEditor(player, rule))
|
||||
|
||||
// Pattern section
|
||||
.item(11, ItemBuilder.create(Material.NETHERITE_UPGRADE_SMITHING_TEMPLATE)
|
||||
.displayName("<gold><bold>Trim Patterns")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Select required armor",
|
||||
"<gray>trim patterns",
|
||||
"",
|
||||
"<white>Selected: <gold>" + rule.trimPatterns.size() + " patterns",
|
||||
!rule.trimPatterns.isEmpty() ? "<gray>" + rule.trimPatterns.stream()
|
||||
.limit(3)
|
||||
.map(FormatUtils::formatEnum)
|
||||
.collect(Collectors.joining(", ")) : "",
|
||||
rule.trimPatterns.size() > 3 ? "<gray>... and " + (rule.trimPatterns.size() - 3) + " more" : "",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to manage patterns"
|
||||
))
|
||||
.build(), (g, e) -> openPatternSelector(player))
|
||||
|
||||
// Material section
|
||||
.item(15, ItemBuilder.create(Material.COPPER_INGOT)
|
||||
.displayName("<aqua><bold>Trim Materials")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Select required armor",
|
||||
"<gray>trim materials",
|
||||
"",
|
||||
"<white>Selected: <aqua>" + rule.trimMaterials.size() + " materials",
|
||||
!rule.trimMaterials.isEmpty() ? "<gray>" + rule.trimMaterials.stream()
|
||||
.limit(3)
|
||||
.map(FormatUtils::formatEnum)
|
||||
.collect(Collectors.joining(", ")) : "",
|
||||
rule.trimMaterials.size() > 3 ? "<gray>... and " + (rule.trimMaterials.size() - 3) + " more" : "",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to manage materials"
|
||||
))
|
||||
.build(), (g, e) -> openMaterialSelector(player))
|
||||
|
||||
// Clear all button
|
||||
.item(22, ItemBuilder.create(Material.BARRIER)
|
||||
.displayName("<red><bold>Clear All Trim Requirements")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Remove all trim criteria",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to clear"
|
||||
))
|
||||
.build(), (g, e) -> {
|
||||
rule.trimPatterns.clear();
|
||||
rule.trimMaterials.clear();
|
||||
getConfig().save();
|
||||
successAny(player, "Cleared all armor trim requirements");
|
||||
open(player);
|
||||
})
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
private void openPatternSelector(Player player) {
|
||||
QuickGui.GuiBuilder builder = QuickGui.create()
|
||||
.titleMini("<gradient:#ffc107:#ff6f00><bold>Select Trim Patterns</bold></gradient>")
|
||||
.rows(6)
|
||||
.fillBorder(EMPTY(Material.ORANGE_STAINED_GLASS_PANE))
|
||||
.item(0, BACK(), (g, e) -> open(player));
|
||||
|
||||
List<ValidTrimPattern> patterns = Arrays.asList(ValidTrimPattern.values());
|
||||
int[] slots = {
|
||||
11, 12, 13, 14, 15,
|
||||
20, 21, 22, 23, 24,
|
||||
29, 30, 31, 32, 33,
|
||||
39, 40, 41
|
||||
};
|
||||
|
||||
for (int i = 0; i < patterns.size(); i++) {
|
||||
ValidTrimPattern pattern = patterns.get(i);
|
||||
boolean selected = rule.trimPatterns.contains(pattern);
|
||||
ItemBuilder item = ItemBuilder.create(pattern.getMaterial())
|
||||
.displayName((selected ? "<gold>" : "<gray>") + "<bold>" + pattern.name())
|
||||
.loreMiniMessage("<gray>Pattern: " + pattern.name(),"", selected ? "<white>Status: <gold>SELECTED" : "<white>Status: <gray>NOT SELECTED","","<yellow>▶ <white>Click to toggle")
|
||||
.hideAllFlags();
|
||||
|
||||
if (selected) item.enchant(Enchantment.MENDING);
|
||||
|
||||
builder.item(slots[i], item.build(), (g, e) -> {
|
||||
if (rule.trimPatterns.contains(pattern)) {
|
||||
rule.trimPatterns.remove(pattern);
|
||||
infoAny(player, "Removed {0} pattern requirement", pattern.name());
|
||||
} else {
|
||||
rule.trimPatterns.add(pattern);
|
||||
successAny(player, "Added {0} pattern requirement", pattern.name());
|
||||
}
|
||||
getConfig().save();
|
||||
openPatternSelector(player);
|
||||
});
|
||||
}
|
||||
|
||||
builder.fillEmpty(EMPTY()).build().open(player);
|
||||
}
|
||||
|
||||
private void openMaterialSelector(Player player) {
|
||||
QuickGui.GuiBuilder builder = QuickGui.create()
|
||||
.titleMini("<gradient:#00bcd4:#006064><bold>Select Trim Materials</bold></gradient>")
|
||||
.rows(5)
|
||||
.fillBorder(EMPTY(Material.CYAN_STAINED_GLASS_PANE))
|
||||
.item(0, BACK(), (g, e) -> open(player));
|
||||
|
||||
List<ValidTrimMaterial> materials = Arrays.asList(ValidTrimMaterial.values());
|
||||
|
||||
int[] slots = {
|
||||
11, 13, 15,
|
||||
20, 21, 22, 23, 24,
|
||||
29, 31, 33
|
||||
};
|
||||
|
||||
|
||||
for (int i = 0; i < materials.size(); i++) {
|
||||
ValidTrimMaterial material = materials.get(i);
|
||||
boolean selected = rule.trimMaterials.contains(material);
|
||||
|
||||
ItemBuilder item = ItemBuilder.create(materials.get(i).getMaterial())
|
||||
.displayName((selected ? "<aqua>" : "<gray>") + "<bold>" + material.name())
|
||||
.loreMiniMessage(
|
||||
"<gray>Material: " + material.name(),
|
||||
"",
|
||||
"<white>Status: " + (selected ? "<aqua>SELECTED" : "<gray>NOT SELECTED"),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to toggle"
|
||||
)
|
||||
.hideAllFlags();
|
||||
|
||||
if (selected) item.enchant(Enchantment.MENDING);
|
||||
|
||||
builder.item(slots[i], item.build(), (g, e) -> {
|
||||
if (rule.trimMaterials.contains(material)) {
|
||||
rule.trimMaterials.remove(material);
|
||||
infoAny(player, "Removed {0} material requirement", material.name());
|
||||
} else {
|
||||
rule.trimMaterials.add(material);
|
||||
successAny(player, "Added {0} material requirement", material.name());
|
||||
}
|
||||
getConfig().save();
|
||||
openMaterialSelector(player);
|
||||
});
|
||||
}
|
||||
|
||||
builder.fillEmpty(EMPTY()).build().open(player);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package me.trouper.dupealias.server.gui.admin.globalrule;
|
||||
|
||||
import me.trouper.alias.data.enums.ValidAttribute;
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.server.systems.gui.QuickPaginatedGUI;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class GlobalRuleAttributeEditor extends QuickPaginatedGUI<ValidAttribute> implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
private final GlobalRule rule;
|
||||
|
||||
public GlobalRuleAttributeEditor(AdminPanelManager manager, GlobalRule rule) {
|
||||
this.manager = manager;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle(Player player) {
|
||||
return "<gradient:#4caf50:#2e7d32><bold>Attribute Criteria</bold></gradient>";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ValidAttribute> getAllItems(Player player) {
|
||||
return Arrays.asList(ValidAttribute.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack createDisplayItem(ValidAttribute attribute) {
|
||||
Double value = rule.attributes.get(attribute);
|
||||
boolean hasAttribute = value != null;
|
||||
|
||||
ItemBuilder builder = ItemBuilder.create(hasAttribute ? Material.ENCHANTED_BOOK : Material.BOOK)
|
||||
.displayName((hasAttribute ? "<green>" : "<gray>") + "<bold>" + attribute.name())
|
||||
.loreMiniMessage("<gray>Attribute: " + attribute.name());
|
||||
|
||||
if (hasAttribute) {
|
||||
builder.loreMiniMessage(
|
||||
"",
|
||||
"<white>Required Value: <green>" + String.format("%.2f", value),
|
||||
"",
|
||||
"<yellow>▶ <white>Left-click to change value",
|
||||
"<yellow>▶ <white>Right-click to remove"
|
||||
);
|
||||
} else {
|
||||
builder.loreMiniMessage(
|
||||
"",
|
||||
"<gray>Not required",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to add requirement"
|
||||
);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleItemClick(Player player, ValidAttribute attribute, InventoryClickEvent event) {
|
||||
if (event.isRightClick() && rule.attributes.containsKey(attribute)) {
|
||||
rule.attributes.remove(attribute);
|
||||
getConfig().save();
|
||||
successAny(player, "Removed {0} requirement", attribute.name());
|
||||
createGUI(player).open(player);
|
||||
return;
|
||||
}
|
||||
|
||||
QuickGui inputGui = QuickGui.create()
|
||||
.titleMini("<gradient:#4caf50:#2e7d32><bold>Set Attribute Value</bold></gradient>")
|
||||
.rows(3)
|
||||
.item(13, ItemBuilder.create(Material.EXPERIENCE_BOTTLE)
|
||||
.displayName("<green><bold>" + attribute.name())
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Enter the minimum value",
|
||||
"<gray>required for this attribute.",
|
||||
"",
|
||||
"<white>Current: <green>" +
|
||||
(rule.attributes.containsKey(attribute) ? String.format("%.2f", rule.attributes.get(attribute)) : "Not set"),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to set value"
|
||||
))
|
||||
.build(), (g, e) -> getDupe().getGuiListener().requestChatInput(g, player, "attributeValue", "Enter minimum attribute value."))
|
||||
.item(22, BACK(), (g, e) -> createGUI(player).open(player)) // Back button.
|
||||
.fillEmpty(EMPTY())
|
||||
.callback("attributeValue", (gui, p, input, source) -> {
|
||||
try {
|
||||
double value = Double.parseDouble(input);
|
||||
rule.attributes.put(attribute, value);
|
||||
getConfig().save();
|
||||
successAny(player, "Set {0} requirement to {1}", attribute.name(), String.format("%.2f", value));
|
||||
} catch (NumberFormatException ex) {
|
||||
if (input.contains("cancel")) {
|
||||
successAny(player,"Canceled.");
|
||||
} else {
|
||||
errorAny(player, "Invalid number: {0}", input);
|
||||
getDupe().getGuiListener().requestChatInput(gui, player, "attributeValue", "Enter minimum attribute value.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
createGUI(player).open(player);
|
||||
})
|
||||
.build();
|
||||
inputGui.open(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addFilterItems(QuickGui.GuiBuilder filterGui, Player player, Set<String> filters) {
|
||||
filterGui.item(0, createFilterToggleItem("Selected Only", Material.LIME_DYE, filters.contains("S")),
|
||||
(gui, event) -> toggleFilter(player, "S"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean testFilter(Player player, ValidAttribute attribute, String filterKey) {
|
||||
return switch (filterKey) {
|
||||
case "S" -> rule.attributes.containsKey(attribute);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openBackGUI(Player player) {
|
||||
manager.openGlobalRuleEditor(player, rule);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package me.trouper.dupealias.server.gui.admin.globalrule;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GlobalRuleEditorGui implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
private final GlobalRule rule;
|
||||
|
||||
public GlobalRuleEditorGui(AdminPanelManager manager, GlobalRule rule) {
|
||||
this.manager = manager;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
public void open(Player player) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#ff6b6b:#ffa726><bold>Rule Editor</bold></gradient>")
|
||||
.rows(6)
|
||||
.fillBorder(EMPTY(Material.ORANGE_STAINED_GLASS_PANE))
|
||||
|
||||
// Back button
|
||||
.item(0, BACK(), (g, e) -> manager.openGlobalRuleList(player))
|
||||
|
||||
// Applied Tags Section
|
||||
.item(10, createTagItem(ItemTag.UNIQUE),
|
||||
(g, e) -> toggleTag(player, ItemTag.UNIQUE))
|
||||
.item(11, createTagItem(ItemTag.FINAL),
|
||||
(g, e) -> toggleTag(player, ItemTag.FINAL))
|
||||
.item(12, createTagItem(ItemTag.INFINITE),
|
||||
(g, e) -> toggleTag(player, ItemTag.INFINITE))
|
||||
.item(13, createTagItem(ItemTag.PROTECTED),
|
||||
(g, e) -> toggleTag(player, ItemTag.PROTECTED))
|
||||
|
||||
// Match Mode
|
||||
.item(16, createMatchModeItem(),
|
||||
(g, e) -> cycleMatchMode(player))
|
||||
|
||||
// Criteria Items
|
||||
.item(19, createCriteriaItem("Name Regex", Material.NAME_TAG,
|
||||
!rule.nameContainsRegex.isEmpty(), rule.nameContainsRegex),
|
||||
(g, e) -> manager.openNameCriteriaEditor(player, rule))
|
||||
|
||||
.item(20, createCriteriaItem("Lore Regex", Material.WRITABLE_BOOK,
|
||||
!rule.loreContainsRegex.isEmpty(), rule.loreContainsRegex),
|
||||
(g, e) -> manager.openLoreCriteriaEditor(player, rule))
|
||||
|
||||
.item(21, createCriteriaItem("Enchantments", Material.ENCHANTED_BOOK,
|
||||
!rule.enchantments.isEmpty(), rule.enchantments.size() + " enchants"),
|
||||
(g, e) -> manager.openEnchantmentEditor(player, rule))
|
||||
|
||||
.item(22, createCriteriaItem("Attributes", Material.GOLDEN_APPLE,
|
||||
!rule.attributes.isEmpty(), rule.attributes.size() + " attributes"),
|
||||
(g, e) -> manager.openAttributeEditor(player, rule))
|
||||
|
||||
.item(23, createCriteriaItem("Item Flags", Material.WHITE_BANNER,
|
||||
!rule.itemFlags.isEmpty(), rule.itemFlags.size() + " flags"),
|
||||
(g, e) -> manager.openItemFlagEditor(player, rule))
|
||||
|
||||
.item(24, createCriteriaItem("Model Data", Material.COMPASS,
|
||||
!rule.legacyModelData.isEmpty(), rule.legacyModelData.size() + " values"),
|
||||
(g, e) -> manager.openModelDataEditor(player, rule))
|
||||
|
||||
.item(25, createCriteriaItem("Potion Effects", Material.POTION,
|
||||
!rule.potionEffects.isEmpty(), rule.potionEffects.size() + " effects"),
|
||||
(g, e) -> manager.openPotionEffectEditor(player, rule))
|
||||
|
||||
// Material Settings
|
||||
.item(30, createMaterialModeItem(),
|
||||
(g, e) -> cycleMaterialMode(player))
|
||||
|
||||
.item(31, createMaterialListItem(),
|
||||
(g, e) -> {
|
||||
if (rule.materialMode != GlobalRule.MaterialMatchMode.IGNORE) {
|
||||
manager.openMaterialSelector(player, rule);
|
||||
}
|
||||
})
|
||||
|
||||
// Armor Trim
|
||||
.item(32, createCriteriaItem("Armor Trim", Material.NETHERITE_CHESTPLATE,
|
||||
!rule.trimPatterns.isEmpty() || !rule.trimMaterials.isEmpty(),
|
||||
(rule.trimPatterns.size() + rule.trimMaterials.size()) + " selected"),
|
||||
(g, e) -> manager.openArmorTrimEditor(player, rule))
|
||||
|
||||
// Save button
|
||||
.item(40, ItemBuilder.create(Material.LIME_DYE)
|
||||
.displayName("<green><bold>Save & Return")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Save changes and return",
|
||||
"<gray>to the rule list",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to save"
|
||||
))
|
||||
.build(), (g, e) -> {
|
||||
getConfig().save();
|
||||
successAny(player, "Saved global rule");
|
||||
manager.openGlobalRuleList(player);
|
||||
})
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
private ItemStack createTagItem(ItemTag tag) {
|
||||
boolean active = rule.appliedTags.contains(tag);
|
||||
Material material = switch (tag) {
|
||||
case UNIQUE -> Material.EMERALD_BLOCK;
|
||||
case FINAL -> Material.REDSTONE_BLOCK;
|
||||
case INFINITE -> Material.LAPIS_BLOCK;
|
||||
case PROTECTED -> Material.STRUCTURE_BLOCK;
|
||||
};
|
||||
|
||||
return ItemBuilder.create(material)
|
||||
.displayName("<" + (active ? "green" : "gray") + "><bold>" + tag.getName() + " Tag")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>" + tag.getDesc(),
|
||||
"",
|
||||
"<white>Status: " + (active ? "<green>ACTIVE" : "<red>INACTIVE"),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to toggle"
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
private ItemStack createMatchModeItem() {
|
||||
return ItemBuilder.create(Material.COMPARATOR)
|
||||
.displayName("<yellow><bold>Match Mode: " + rule.matchMode.name())
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Determines how multiple criteria",
|
||||
"<gray>are evaluated together",
|
||||
"",
|
||||
"<white>Current: <yellow>" + rule.matchMode.name(),
|
||||
"",
|
||||
"<gray>• AND: All criteria must match",
|
||||
"<gray>• OR: Any criteria must match",
|
||||
"<gray>• NAND: Not all criteria match",
|
||||
"<gray>• XOR: Exactly one criteria matches",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to cycle"
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
private ItemStack createCriteriaItem(String name, Material material, boolean hasValue, String preview) {
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("<gray>Configure " + name.toLowerCase() + " criteria");
|
||||
lore.add("");
|
||||
|
||||
if (hasValue) {
|
||||
lore.add("<white>Current: <green>" + preview);
|
||||
} else {
|
||||
lore.add("<white>Current: <red>Not set");
|
||||
}
|
||||
|
||||
lore.add("");
|
||||
lore.add("<yellow>▶ <white>Click to edit");
|
||||
|
||||
return ItemBuilder.create(material)
|
||||
.displayName("<white><bold>" + name)
|
||||
.loreMiniMessage(lore)
|
||||
.build();
|
||||
}
|
||||
|
||||
private ItemStack createMaterialModeItem() {
|
||||
return ItemBuilder.create(Material.GRASS_BLOCK)
|
||||
.displayName("<yellow><bold>Material Mode: " + rule.materialMode.name())
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Control which materials this",
|
||||
"<gray>rule applies to",
|
||||
"",
|
||||
"<white>Current: <yellow>" + rule.materialMode.name(),
|
||||
"",
|
||||
"<gray>• IGNORE: Applies to all materials",
|
||||
"<gray>• WHITELIST: Only listed materials",
|
||||
"<gray>• BLACKLIST: Exclude listed materials",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to cycle"
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
private ItemStack createMaterialListItem() {
|
||||
if (rule.materialMode == GlobalRule.MaterialMatchMode.IGNORE) {
|
||||
return ItemBuilder.create(Material.GRAY_DYE)
|
||||
.displayName("<gray><bold>Material List")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Set material mode to",
|
||||
"<gray>WHITELIST or BLACKLIST",
|
||||
"<gray>to configure materials"
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("<gray>Manage materials for this rule");
|
||||
lore.add("");
|
||||
lore.add("<white>Selected: <yellow>" + rule.effectedMaterials.size() + " materials");
|
||||
|
||||
if (!rule.effectedMaterials.isEmpty()) {
|
||||
lore.add("");
|
||||
List<String> materialNames = rule.effectedMaterials.stream()
|
||||
.limit(5)
|
||||
.map(mat -> mat.name())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (String mat : materialNames) {
|
||||
lore.add("<gray>• " + mat);
|
||||
}
|
||||
|
||||
if (rule.effectedMaterials.size() > 5) {
|
||||
lore.add("<gray>... and " + (rule.effectedMaterials.size() - 5) + " more");
|
||||
}
|
||||
}
|
||||
|
||||
lore.add("");
|
||||
lore.add("<yellow>▶ <white>Click to manage");
|
||||
|
||||
return ItemBuilder.create(Material.CHEST)
|
||||
.displayName("<white><bold>Material List")
|
||||
.loreMiniMessage(lore)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void toggleTag(Player player, ItemTag tag) {
|
||||
if (rule.appliedTags.contains(tag)) {
|
||||
rule.appliedTags.remove(tag);
|
||||
} else {
|
||||
rule.appliedTags.add(tag);
|
||||
}
|
||||
open(player);
|
||||
}
|
||||
|
||||
private void cycleMatchMode(Player player) {
|
||||
GlobalRule.MatchMode[] modes = GlobalRule.MatchMode.values();
|
||||
int current = rule.matchMode.ordinal();
|
||||
rule.matchMode = modes[(current + 1) % modes.length];
|
||||
open(player);
|
||||
}
|
||||
|
||||
private void cycleMaterialMode(Player player) {
|
||||
GlobalRule.MaterialMatchMode[] modes = GlobalRule.MaterialMatchMode.values();
|
||||
int current = rule.materialMode.ordinal();
|
||||
rule.materialMode = modes[(current + 1) % modes.length];
|
||||
|
||||
// Clear materials if switching to IGNORE
|
||||
if (rule.materialMode == GlobalRule.MaterialMatchMode.IGNORE) {
|
||||
rule.effectedMaterials.clear();
|
||||
}
|
||||
|
||||
open(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package me.trouper.dupealias.server.gui.admin.globalrule;
|
||||
|
||||
import me.trouper.alias.data.enums.ValidEnchantment;
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.server.systems.gui.QuickPaginatedGUI;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class GlobalRuleEnchantmentEditor extends QuickPaginatedGUI<ValidEnchantment> implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
private final GlobalRule rule;
|
||||
|
||||
public GlobalRuleEnchantmentEditor(AdminPanelManager manager, GlobalRule rule) {
|
||||
this.manager = manager;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle(Player player) {
|
||||
return "<gradient:#e91e63:#c2185b><bold>Enchantment Criteria</bold></gradient>";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ValidEnchantment> getAllItems(Player player) {
|
||||
return Arrays.asList(ValidEnchantment.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack createDisplayItem(ValidEnchantment enchant) {
|
||||
Integer level = rule.enchantments.get(enchant);
|
||||
boolean hasEnchant = level != null;
|
||||
|
||||
ItemBuilder builder = ItemBuilder.create(hasEnchant ? Material.ENCHANTED_BOOK : Material.BOOK)
|
||||
.displayName((hasEnchant ? "<light_purple>" : "<gray>") + "<bold>" + enchant.name())
|
||||
.loreMiniMessage("<gray>Enchantment: " + enchant.name());
|
||||
|
||||
if (hasEnchant) {
|
||||
builder.loreMiniMessage(
|
||||
"",
|
||||
"<white>Required Level: <light_purple>" + level,
|
||||
"",
|
||||
"<yellow>▶ <white>Left-click to change level",
|
||||
"<yellow>▶ <white>Right-click to remove"
|
||||
);
|
||||
} else {
|
||||
builder.loreMiniMessage(
|
||||
"",
|
||||
"<gray>Not required",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to add requirement"
|
||||
);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleItemClick(Player player, ValidEnchantment enchant, InventoryClickEvent event) {
|
||||
if (event.isRightClick() && rule.enchantments.containsKey(enchant)) {
|
||||
rule.enchantments.remove(enchant);
|
||||
getConfig().save();
|
||||
successAny(player, "Removed {0} requirement", enchant.name());
|
||||
createGUI(player).open(player);
|
||||
} else {
|
||||
QuickGui inputGui = QuickGui.create()
|
||||
.titleMini("<gradient:#e91e63:#c2185b><bold>Set Enchantment Level</bold></gradient>")
|
||||
.rows(3)
|
||||
.item(13, ItemBuilder.create(Material.EXPERIENCE_BOTTLE)
|
||||
.displayName("<light_purple><bold>" + enchant.name())
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Enter the minimum level",
|
||||
"<gray>required for this enchantment",
|
||||
"",
|
||||
"<white>Current: <light_purple>" +
|
||||
(rule.enchantments.containsKey(enchant) ? rule.enchantments.get(enchant) : "Not set"),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to set level"
|
||||
))
|
||||
.build(), (g, e) -> getDupe().getGuiListener().requestChatInput(g,player, "enchantLevel","Enter minimum enchant level starting at 1."))
|
||||
.item(22, BACK(), (g, e) -> createGUI(player).open(player))
|
||||
.fillEmpty(EMPTY())
|
||||
.callback("enchantLevel", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
try {
|
||||
int level = Integer.parseInt(input);
|
||||
if (level < 1) {
|
||||
errorAny(player, "Level must be at least 1");
|
||||
} else {
|
||||
rule.enchantments.put(enchant, level);
|
||||
getConfig().save();
|
||||
successAny(player, "Set {0} requirement to level {1}", enchant.name(), level);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
errorAny(player, "Invalid number: {0}", input);
|
||||
}
|
||||
createGUI(player).open(player);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
inputGui.open(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addFilterItems(QuickGui.GuiBuilder filterGui, Player player, Set<String> filters) {
|
||||
filterGui.item(0, createFilterToggleItem("Selected Only", Material.LIME_DYE, filters.contains("S")),
|
||||
(gui, event) -> toggleFilter(player, "S"));
|
||||
filterGui.item(1, createFilterToggleItem("Weapon", Material.DIAMOND_SWORD, filters.contains("W")),
|
||||
(gui, event) -> toggleFilter(player, "W"));
|
||||
filterGui.item(2, createFilterToggleItem("Tool", Material.DIAMOND_PICKAXE, filters.contains("T")),
|
||||
(gui, event) -> toggleFilter(player, "T"));
|
||||
filterGui.item(3, createFilterToggleItem("Armor", Material.DIAMOND_CHESTPLATE, filters.contains("A")),
|
||||
(gui, event) -> toggleFilter(player, "A"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean testFilter(Player player, ValidEnchantment enchant, String filterKey) {
|
||||
return switch (filterKey) {
|
||||
case "S" -> rule.enchantments.containsKey(enchant);
|
||||
case "W" -> enchant.name().contains("SHARPNESS") || enchant.name().contains("SMITE") ||
|
||||
enchant.name().contains("BANE") || enchant.name().contains("KNOCKBACK") ||
|
||||
enchant.name().contains("FIRE_ASPECT") || enchant.name().contains("LOOTING") ||
|
||||
enchant.name().contains("SWEEPING");
|
||||
case "T" -> enchant.name().contains("EFFICIENCY") || enchant.name().contains("SILK_TOUCH") ||
|
||||
enchant.name().contains("UNBREAKING") || enchant.name().contains("FORTUNE");
|
||||
case "A" -> enchant.name().contains("PROTECTION") || enchant.name().contains("THORNS") ||
|
||||
enchant.name().contains("RESPIRATION") || enchant.name().contains("AQUA_AFFINITY") ||
|
||||
enchant.name().contains("FEATHER_FALLING") || enchant.name().contains("DEPTH_STRIDER") ||
|
||||
enchant.name().contains("FROST_WALKER");
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openBackGUI(Player player) {
|
||||
manager.openGlobalRuleEditor(player, rule);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package me.trouper.dupealias.server.gui.admin.globalrule;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class GlobalRuleItemFlagEditor implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
private final GlobalRule rule;
|
||||
|
||||
public GlobalRuleItemFlagEditor(AdminPanelManager manager, GlobalRule rule) {
|
||||
this.manager = manager;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void open(Player player) {
|
||||
QuickGui gui = QuickGui.create()
|
||||
.titleMini("<gradient:#ff9800:#f57c00><bold>Item Flag Criteria</bold></gradient>")
|
||||
.rows(4)
|
||||
.fillBorder(EMPTY(Material.ORANGE_STAINED_GLASS_PANE))
|
||||
|
||||
// Back button
|
||||
.item(0, BACK(), (g, e) -> manager.openGlobalRuleEditor(player, rule))
|
||||
|
||||
// Item flags
|
||||
.item(10, createFlagItem(ItemFlag.HIDE_ENCHANTS),
|
||||
(g, e) -> toggleFlag(player, ItemFlag.HIDE_ENCHANTS))
|
||||
.item(11, createFlagItem(ItemFlag.HIDE_ATTRIBUTES),
|
||||
(g, e) -> toggleFlag(player, ItemFlag.HIDE_ATTRIBUTES))
|
||||
.item(12, createFlagItem(ItemFlag.HIDE_UNBREAKABLE),
|
||||
(g, e) -> toggleFlag(player, ItemFlag.HIDE_UNBREAKABLE))
|
||||
.item(13, createFlagItem(ItemFlag.HIDE_DESTROYS),
|
||||
(g, e) -> toggleFlag(player, ItemFlag.HIDE_DESTROYS))
|
||||
.item(14, createFlagItem(ItemFlag.HIDE_PLACED_ON),
|
||||
(g, e) -> toggleFlag(player, ItemFlag.HIDE_PLACED_ON))
|
||||
.item(15, createFlagItem(ItemFlag.HIDE_ADDITIONAL_TOOLTIP),
|
||||
(g, e) -> toggleFlag(player, ItemFlag.HIDE_ADDITIONAL_TOOLTIP))
|
||||
.item(16, createFlagItem(ItemFlag.HIDE_DYE),
|
||||
(g, e) -> toggleFlag(player, ItemFlag.HIDE_DYE))
|
||||
.item(19, createFlagItem(ItemFlag.HIDE_ARMOR_TRIM),
|
||||
(g, e) -> toggleFlag(player, ItemFlag.HIDE_ARMOR_TRIM))
|
||||
.item(20, createFlagItem(ItemFlag.HIDE_STORED_ENCHANTS),
|
||||
(g, e) -> toggleFlag(player, ItemFlag.HIDE_STORED_ENCHANTS))
|
||||
|
||||
// Clear all button
|
||||
.item(22, ItemBuilder.create(Material.BARRIER)
|
||||
.displayName("<red><bold>Clear All Flags")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Remove all flag requirements",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to clear"
|
||||
))
|
||||
.build(), (g, e) -> {
|
||||
rule.itemFlags.clear();
|
||||
getConfig().save();
|
||||
successAny(player, "Cleared all item flag requirements");
|
||||
open(player);
|
||||
})
|
||||
|
||||
.fillEmpty(EMPTY())
|
||||
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
|
||||
.build();
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
|
||||
private ItemStack createFlagItem(ItemFlag flag) {
|
||||
boolean required = rule.itemFlags.contains(flag);
|
||||
|
||||
return ItemBuilder.create(required ? Material.WHITE_BANNER : Material.LIME_BANNER)
|
||||
.displayName((required ? "<yellow>" : "<gray>") + "<bold>" + flag.name())
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>" + getFlagDescription(flag),
|
||||
"",
|
||||
"<white>Status: " + (required ? "<yellow>REQUIRED" : "<gray>NOT REQUIRED"),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to toggle"
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getFlagDescription(ItemFlag flag) {
|
||||
return switch (flag) {
|
||||
case HIDE_ENCHANTS -> "Hides enchantments from item tooltip";
|
||||
case HIDE_ATTRIBUTES -> "Hides attribute modifiers";
|
||||
case HIDE_UNBREAKABLE -> "Hides the unbreakable tag";
|
||||
case HIDE_DESTROYS -> "Hides what blocks can be destroyed";
|
||||
case HIDE_PLACED_ON -> "Hides what blocks can be placed on";
|
||||
case HIDE_ADDITIONAL_TOOLTIP -> "Hides additional tooltip info";
|
||||
case HIDE_DYE -> "Hides dye color from items";
|
||||
case HIDE_ARMOR_TRIM -> "Hides armor trim information";
|
||||
case HIDE_STORED_ENCHANTS -> "Hides stored enchantments (books)";
|
||||
};
|
||||
}
|
||||
|
||||
private void toggleFlag(Player player, ItemFlag flag) {
|
||||
if (rule.itemFlags.contains(flag)) {
|
||||
rule.itemFlags.remove(flag);
|
||||
infoAny(player, "Removed {0} requirement", flag.name());
|
||||
} else {
|
||||
rule.itemFlags.add(flag);
|
||||
successAny(player, "Added {0} requirement", flag.name());
|
||||
}
|
||||
getConfig().save();
|
||||
open(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package me.trouper.dupealias.server.gui.admin.globalrule;
|
||||
|
||||
import me.trouper.alias.data.enums.ValidTrimMaterial;
|
||||
import me.trouper.alias.data.enums.ValidTrimPattern;
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.server.systems.gui.QuickPaginatedGUI;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.ItemTag;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GlobalRuleListGui extends QuickPaginatedGUI<GlobalRule> implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
|
||||
public GlobalRuleListGui(AdminPanelManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle(Player player) {
|
||||
return "<gradient:#ff6b6b:#ffa726><bold>Global Rules Manager</bold></gradient>";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<GlobalRule> getAllItems(Player player) {
|
||||
return new ArrayList<>(getConfig().globalRules);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack createDisplayItem(GlobalRule rule) {
|
||||
List<String> lore = new ArrayList<>();
|
||||
|
||||
// Display applied tags
|
||||
if (!rule.appliedTags.isEmpty()) {
|
||||
lore.add("<white>Applied Tags:");
|
||||
for (ItemTag tag : rule.appliedTags) {
|
||||
lore.add("<gray>• <" + manager.getTagColor(tag) + ">" + tag.getName());
|
||||
}
|
||||
} else {
|
||||
lore.add("<red>No tags applied");
|
||||
}
|
||||
|
||||
lore.add("");
|
||||
lore.add("<white>Match Mode: <yellow>" + rule.matchMode.name());
|
||||
lore.add("<white>Criteria Count: <yellow>" + rule.getCriteriaCount());
|
||||
|
||||
// Show material mode if applicable
|
||||
if (rule.materialMode != GlobalRule.MaterialMatchMode.IGNORE) {
|
||||
lore.add("<white>Material Mode: <yellow>" + rule.materialMode.name());
|
||||
if (!rule.effectedMaterials.isEmpty()) {
|
||||
lore.add("<white>Materials: <yellow>" + rule.effectedMaterials.size() + " selected");
|
||||
}
|
||||
}
|
||||
|
||||
// Quick preview of criteria
|
||||
List<String> criteria = new ArrayList<>();
|
||||
if (!rule.nameContainsRegex.isEmpty()) criteria.add("Name");
|
||||
if (!rule.loreContainsRegex.isEmpty()) criteria.add("Lore");
|
||||
if (!rule.enchantments.isEmpty()) criteria.add("Enchants");
|
||||
if (!rule.attributes.isEmpty()) criteria.add("Attributes");
|
||||
if (!rule.itemFlags.isEmpty()) criteria.add("Flags");
|
||||
if (!rule.legacyModelData.isEmpty()) criteria.add("Model Data");
|
||||
if (!rule.potionEffects.isEmpty()) criteria.add("Potion Effects");
|
||||
if (!rule.trimPatterns.isEmpty() || !rule.trimMaterials.isEmpty()) criteria.add("Armor Trim");
|
||||
|
||||
if (!criteria.isEmpty()) {
|
||||
lore.add("<white>Criteria: <gray>" + String.join(", ", criteria));
|
||||
}
|
||||
|
||||
lore.add("");
|
||||
lore.add("<yellow>▶ <white>Left-click to edit");
|
||||
lore.add("<yellow>▶ <white>Right-click to delete");
|
||||
lore.add("<yellow>▶ <white>Shift-click to duplicate");
|
||||
|
||||
// Determine icon based on tags or criteria
|
||||
Material icon = Material.WRITTEN_BOOK;
|
||||
if (rule.appliedTags.contains(ItemTag.UNIQUE)) icon = Material.EMERALD;
|
||||
else if (rule.appliedTags.contains(ItemTag.FINAL)) icon = Material.REDSTONE;
|
||||
else if (rule.appliedTags.contains(ItemTag.INFINITE)) icon = Material.LAPIS_LAZULI;
|
||||
else if (rule.appliedTags.contains(ItemTag.PROTECTED)) icon = Material.NETHER_STAR;
|
||||
else if (!rule.enchantments.isEmpty()) icon = Material.ENCHANTED_BOOK;
|
||||
else if (!rule.potionEffects.isEmpty()) icon = Material.POTION;
|
||||
else if (!rule.trimPatterns.isEmpty() || !rule.trimMaterials.isEmpty()) icon = Material.NETHERITE_CHESTPLATE;
|
||||
|
||||
String tagNames = rule.appliedTags.stream()
|
||||
.map(ItemTag::getName)
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
return ItemBuilder.create(icon)
|
||||
.displayName("<white><bold>Rule #" + (getConfig().globalRules.indexOf(rule) + 1) +
|
||||
(tagNames.isEmpty() ? "" : " <gray>(" + tagNames + ")"))
|
||||
.loreMiniMessage(lore)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleItemClick(Player player, GlobalRule rule, InventoryClickEvent event) {
|
||||
if (event.isRightClick()) {
|
||||
// Confirm deletion
|
||||
QuickGui.create()
|
||||
.titleMini("<red><bold>Delete Rule?")
|
||||
.rows(3)
|
||||
.item(12, ItemBuilder.create(Material.RED_CONCRETE)
|
||||
.displayName("<red><bold>Delete Rule")
|
||||
.loreMiniMessage("<gray>This action cannot be undone!")
|
||||
.build(), (gui, e) -> {
|
||||
getConfig().globalRules.remove(rule);
|
||||
getConfig().save();
|
||||
successAny(player, "Deleted global rule");
|
||||
createGUI(player).open(player);
|
||||
})
|
||||
.item(14, ItemBuilder.create(Material.GREEN_CONCRETE)
|
||||
.displayName("<green><bold>Cancel")
|
||||
.build(), (gui, e) -> createGUI(player).open(player))
|
||||
.fillEmpty(EMPTY())
|
||||
.build()
|
||||
.open(player);
|
||||
} else if (event.isShiftClick()) {
|
||||
// Duplicate rule
|
||||
GlobalRule duplicate = new GlobalRule();
|
||||
|
||||
// Copy all fieldsx
|
||||
duplicate.matchMode = rule.matchMode;
|
||||
duplicate.materialMode = rule.materialMode;
|
||||
duplicate.effectedMaterials = EnumSet.copyOf(rule.effectedMaterials);
|
||||
duplicate.nameContainsRegex = rule.nameContainsRegex;
|
||||
duplicate.loreContainsRegex = rule.loreContainsRegex;
|
||||
duplicate.legacyModelData = new HashSet<>(rule.legacyModelData);
|
||||
duplicate.itemFlags = EnumSet.copyOf(rule.itemFlags);
|
||||
duplicate.enchantments = new HashMap<>(rule.enchantments);
|
||||
duplicate.potionEffects = new HashMap<>(rule.potionEffects);
|
||||
duplicate.attributes = new HashMap<>(rule.attributes);
|
||||
duplicate.trimPatterns = rule.trimPatterns.isEmpty() ? EnumSet.noneOf(ValidTrimPattern.class) : EnumSet.copyOf(rule.trimPatterns);
|
||||
duplicate.trimMaterials = rule.trimMaterials.isEmpty() ? EnumSet.noneOf(ValidTrimMaterial.class) : EnumSet.copyOf(rule.trimMaterials);
|
||||
duplicate.appliedTags = EnumSet.copyOf(rule.appliedTags);
|
||||
|
||||
getConfig().globalRules.add(duplicate);
|
||||
getConfig().save();
|
||||
|
||||
successAny(player, "Duplicated global rule");
|
||||
createGUI(player).open(player);
|
||||
} else {
|
||||
// Edit rule
|
||||
manager.openGlobalRuleEditor(player, rule);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addFilterItems(QuickGui.GuiBuilder filterGui, Player player, Set<String> filters) {
|
||||
filterGui.item(0, createFilterToggleItem("Has UNIQUE", Material.EMERALD_BLOCK, filters.contains("U")),
|
||||
(gui, event) -> toggleFilter(player, "U"));
|
||||
filterGui.item(1, createFilterToggleItem("Has FINAL", Material.REDSTONE_BLOCK, filters.contains("F")),
|
||||
(gui, event) -> toggleFilter(player, "F"));
|
||||
filterGui.item(2, createFilterToggleItem("Has INFINITE", Material.LAPIS_BLOCK, filters.contains("I")),
|
||||
(gui, event) -> toggleFilter(player, "I"));
|
||||
filterGui.item(3, createFilterToggleItem("Has PROTECTED", Material.COMMAND_BLOCK, filters.contains("P")),
|
||||
(gui, event) -> toggleFilter(player, "P"));
|
||||
filterGui.item(5, createFilterToggleItem("Uses Materials", Material.GRASS_BLOCK, filters.contains("M")),
|
||||
(gui, event) -> toggleFilter(player, "M"));
|
||||
filterGui.item(6, createFilterToggleItem("Has Criteria", Material.COMPARATOR, filters.contains("C")),
|
||||
(gui, event) -> toggleFilter(player, "C"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean testFilter(Player player, GlobalRule rule, String filterKey) {
|
||||
return switch (filterKey) {
|
||||
case "U" -> rule.appliedTags.contains(ItemTag.UNIQUE);
|
||||
case "F" -> rule.appliedTags.contains(ItemTag.FINAL);
|
||||
case "I" -> rule.appliedTags.contains(ItemTag.INFINITE);
|
||||
case "P" -> rule.appliedTags.contains(ItemTag.PROTECTED);
|
||||
case "M" -> rule.materialMode != GlobalRule.MaterialMatchMode.IGNORE && !rule.effectedMaterials.isEmpty();
|
||||
case "C" -> rule.getCriteriaCount() > 0;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openBackGUI(Player player) {
|
||||
manager.openMainGui(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuickGui createGUI(Player player) {
|
||||
QuickGui gui = super.createGUI(player);
|
||||
gui.updateItem(51, ItemBuilder.create(Material.LIME_DYE)
|
||||
.displayName("<green><bold>+ Create New Rule")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Create a new global rule",
|
||||
"<gray>to apply tags to items",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to create"
|
||||
))
|
||||
.build(), (q, event) -> {
|
||||
GlobalRule newRule = new GlobalRule();
|
||||
getConfig().globalRules.add(newRule);
|
||||
getConfig().save();
|
||||
manager.openGlobalRuleEditor((Player) event.getWhoClicked(), newRule);
|
||||
});
|
||||
return gui;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package me.trouper.dupealias.server.gui.admin.globalrule;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.server.systems.gui.QuickPaginatedGUI;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class GlobalRuleMaterialSelector extends QuickPaginatedGUI<Material> implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
private final GlobalRule rule;
|
||||
|
||||
public GlobalRuleMaterialSelector(AdminPanelManager manager, GlobalRule rule) {
|
||||
this.manager = manager;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle(Player player) {
|
||||
return "<gradient:#4caf50:#388e3c><bold>Select Materials (" + rule.materialMode.name() + ")</bold></gradient>";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Material> getAllItems(Player player) {
|
||||
return Arrays.stream(Material.values())
|
||||
.filter(mat -> !mat.isLegacy() && !mat.isAir() && mat.isItem())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack createDisplayItem(Material material) {
|
||||
boolean selected = rule.effectedMaterials.contains(material);
|
||||
ItemBuilder builder = ItemBuilder.create(material)
|
||||
.displayName((selected ? "<green>" : "<gray>") + "<bold>" + material.name())
|
||||
.loreMiniMessage("<white>Material: <yellow>" + material.name());
|
||||
|
||||
if (selected) {
|
||||
builder.enchant(Enchantment.MENDING,1);
|
||||
builder.loreMiniMessage(
|
||||
"",
|
||||
"<green>✓ Selected",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to remove");
|
||||
} else {
|
||||
builder.loreMiniMessage(
|
||||
"",
|
||||
"<gray>Not selected",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to add");
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleItemClick(Player player, Material material, InventoryClickEvent event) {
|
||||
if (rule.effectedMaterials.contains(material)) {
|
||||
rule.effectedMaterials.remove(material);
|
||||
infoAny(player, "Removed {0} from material list", material.name());
|
||||
} else {
|
||||
rule.effectedMaterials.add(material);
|
||||
successAny(player, "Added {0} to material list", material.name());
|
||||
}
|
||||
|
||||
getConfig().save();
|
||||
createGUI(player).open(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addFilterItems(QuickGui.GuiBuilder filterGui, Player player, Set<String> filters) {
|
||||
filterGui.item(0, createFilterToggleItem("Selected Only", Material.LIME_DYE, filters.contains("S")),
|
||||
(gui, event) -> toggleFilter(player, "S"));
|
||||
filterGui.item(1, createFilterToggleItem("Blocks", Material.STONE, filters.contains("B")),
|
||||
(gui, event) -> toggleFilter(player, "B"));
|
||||
filterGui.item(2, createFilterToggleItem("Items", Material.STICK, filters.contains("I")),
|
||||
(gui, event) -> toggleFilter(player, "I"));
|
||||
filterGui.item(3, createFilterToggleItem("Tools", Material.DIAMOND_PICKAXE, filters.contains("T")),
|
||||
(gui, event) -> toggleFilter(player, "T"));
|
||||
filterGui.item(4, createFilterToggleItem("Armor", Material.DIAMOND_CHESTPLATE, filters.contains("A")),
|
||||
(gui, event) -> toggleFilter(player, "A"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean testFilter(Player player, Material material, String filterKey) {
|
||||
return switch (filterKey) {
|
||||
case "S" -> rule.effectedMaterials.contains(material);
|
||||
case "B" -> material.isBlock();
|
||||
case "I" -> !material.isBlock();
|
||||
case "T" -> material.name().contains("_AXE") || material.name().contains("_PICKAXE") ||
|
||||
material.name().contains("_SHOVEL") || material.name().contains("_HOE") ||
|
||||
material.name().contains("_SWORD");
|
||||
case "A" -> material.name().contains("_HELMET") || material.name().contains("_CHESTPLATE") ||
|
||||
material.name().contains("_LEGGINGS") || material.name().contains("_BOOTS");
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openBackGUI(Player player) {
|
||||
manager.openGlobalRuleEditor(player, rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuickGui createGUI(Player player) {
|
||||
QuickGui gui = super.createGUI(player);
|
||||
|
||||
gui.updateItem(47, ItemBuilder.create(Material.BARRIER)
|
||||
.displayName("<red><bold>Clear All")
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Remove all selected materials",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to clear"
|
||||
))
|
||||
.build(), (q, event) -> {
|
||||
rule.effectedMaterials.clear();
|
||||
getConfig().save();
|
||||
successAny(event.getWhoClicked(), "Cleared all materials");
|
||||
createGUI((Player) event.getWhoClicked()).open((Player) event.getWhoClicked());
|
||||
});
|
||||
|
||||
return gui;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package me.trouper.dupealias.server.gui.admin.globalrule;
|
||||
|
||||
import me.trouper.alias.data.enums.ValidPotionEffectType;
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.server.systems.gui.QuickPaginatedGUI;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.dupealias.DupeContext;
|
||||
import me.trouper.dupealias.data.GlobalRule;
|
||||
import me.trouper.dupealias.server.gui.CommonItems;
|
||||
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class GlobalRulePotionEffectEditor extends QuickPaginatedGUI<ValidPotionEffectType> implements DupeContext, CommonItems {
|
||||
|
||||
private final AdminPanelManager manager;
|
||||
private final GlobalRule rule;
|
||||
|
||||
public GlobalRulePotionEffectEditor(AdminPanelManager manager, GlobalRule rule) {
|
||||
this.manager = manager;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle(Player player) {
|
||||
return "<gradient:#9c27b0:#6a1b9a><bold>Potion Effect Criteria</bold></gradient>";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ValidPotionEffectType> getAllItems(Player player) {
|
||||
return Arrays.asList(ValidPotionEffectType.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack createDisplayItem(ValidPotionEffectType effect) {
|
||||
Integer amplifier = rule.potionEffects.get(effect);
|
||||
boolean hasEffect = amplifier != null;
|
||||
|
||||
Material icon = effect.name().contains("INSTANT") || effect.name().contains("HARM") ?
|
||||
Material.SPLASH_POTION : Material.POTION;
|
||||
|
||||
ItemBuilder builder = ItemBuilder.create(icon)
|
||||
.displayName((hasEffect ? "<light_purple>" : "<gray>") + "<bold>" + effect.name())
|
||||
.loreMiniMessage("<gray>Effect: " + effect.name());
|
||||
|
||||
if (hasEffect) {
|
||||
builder.loreMiniMessage(
|
||||
"",
|
||||
"<white>Required Amplifier: <light_purple>" + amplifier,
|
||||
"",
|
||||
"<yellow>▶ <white>Left-click to change amplifier",
|
||||
"<yellow>▶ <white>Right-click to remove"
|
||||
);
|
||||
} else {
|
||||
builder.loreMiniMessage(
|
||||
"",
|
||||
"<gray>Not required",
|
||||
"",
|
||||
"<yellow>▶ <white>Click to add requirement"
|
||||
);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleItemClick(Player player, ValidPotionEffectType effect, InventoryClickEvent event) {
|
||||
if (event.isRightClick() && rule.potionEffects.containsKey(effect)) {
|
||||
rule.potionEffects.remove(effect);
|
||||
getConfig().save();
|
||||
successAny(player, "Removed {0} requirement", effect.name());
|
||||
createGUI(player).open(player);
|
||||
} else {
|
||||
// Request amplifier input
|
||||
QuickGui inputGui = QuickGui.create()
|
||||
.titleMini("<gradient:#9c27b0:#6a1b9a><bold>Set Effect Amplifier</bold></gradient>")
|
||||
.rows(3)
|
||||
.item(13, ItemBuilder.create(Material.BREWING_STAND)
|
||||
.displayName("<light_purple><bold>" + effect.name())
|
||||
.loreMiniMessage(Arrays.asList(
|
||||
"<gray>Enter the minimum amplifier",
|
||||
"<gray>required for this effect",
|
||||
"<gray>(0 = Level I, 1 = Level II, etc.)",
|
||||
"",
|
||||
"<white>Current: <light_purple>" +
|
||||
(rule.potionEffects.containsKey(effect) ? rule.potionEffects.get(effect) : "Not set"),
|
||||
"",
|
||||
"<yellow>▶ <white>Click to set amplifier"
|
||||
))
|
||||
.build(), (g, e) -> getDupe().getGuiListener().requestChatInput(g,player, "effectAmplifier","Enter an integer to set the minimum effect amplifier. (Starting at 0)"))
|
||||
.item(22, BACK(), (g, e) -> createGUI(player).open(player))
|
||||
.fillEmpty(EMPTY())
|
||||
.callback("effectAmplifier", new QuickGui.GuiCallback() {
|
||||
@Override
|
||||
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
|
||||
try {
|
||||
int amplifier = Integer.parseInt(input);
|
||||
if (amplifier < 0) {
|
||||
errorAny(player, "Amplifier must be 0 or higher");
|
||||
} else {
|
||||
rule.potionEffects.put(effect, amplifier);
|
||||
getConfig().save();
|
||||
successAny(player, "Set {0} requirement to amplifier {1}", effect.name(), amplifier);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
errorAny(player, "Invalid number: {0}", input);
|
||||
}
|
||||
createGUI(player).open(player);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
inputGui.open(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addFilterItems(QuickGui.GuiBuilder filterGui, Player player, Set<String> filters) {
|
||||
filterGui.item(0, createFilterToggleItem("Selected Only", Material.LIME_DYE, filters.contains("S")),
|
||||
(gui, event) -> toggleFilter(player, "S"));
|
||||
filterGui.item(1, createFilterToggleItem("Beneficial", Material.GOLDEN_APPLE, filters.contains("B")),
|
||||
(gui, event) -> toggleFilter(player, "B"));
|
||||
filterGui.item(2, createFilterToggleItem("Harmful", Material.POISONOUS_POTATO, filters.contains("H")),
|
||||
(gui, event) -> toggleFilter(player, "H"));
|
||||
filterGui.item(3, createFilterToggleItem("Neutral", Material.MILK_BUCKET, filters.contains("N")),
|
||||
(gui, event) -> toggleFilter(player, "N"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean testFilter(Player player, ValidPotionEffectType effect, String filterKey) {
|
||||
return switch (filterKey) {
|
||||
case "S" -> rule.potionEffects.containsKey(effect);
|
||||
case "B" -> effect.name().contains("SPEED") || effect.name().contains("HASTE") ||
|
||||
effect.name().contains("STRENGTH") || effect.name().contains("INSTANT_HEALTH") ||
|
||||
effect.name().contains("JUMP") || effect.name().contains("REGENERATION") ||
|
||||
effect.name().contains("RESISTANCE") || effect.name().contains("FIRE_RESISTANCE") ||
|
||||
effect.name().contains("WATER_BREATHING") || effect.name().contains("INVISIBILITY") ||
|
||||
effect.name().contains("NIGHT_VISION") || effect.name().contains("HEALTH_BOOST") ||
|
||||
effect.name().contains("ABSORPTION") || effect.name().contains("SATURATION") ||
|
||||
effect.name().contains("LUCK") || effect.name().contains("CONDUIT") ||
|
||||
effect.name().contains("DOLPHINS") || effect.name().contains("HERO");
|
||||
case "H" -> effect.name().contains("SLOWNESS") || effect.name().contains("MINING_FATIGUE") ||
|
||||
effect.name().contains("INSTANT_DAMAGE") || effect.name().contains("NAUSEA") ||
|
||||
effect.name().contains("BLINDNESS") || effect.name().contains("HUNGER") ||
|
||||
effect.name().contains("WEAKNESS") || effect.name().contains("POISON") ||
|
||||
effect.name().contains("WITHER") || effect.name().contains("UNLUCK") ||
|
||||
effect.name().contains("BAD_OMEN") || effect.name().contains("DARKNESS");
|
||||
case "N" -> !testFilter(player, effect, "B") && !testFilter(player, effect, "H");
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openBackGUI(Player player) {
|
||||
manager.openGlobalRuleEditor(player, rule);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ public abstract class AbstractDupeGui<T extends AbstractDupeSession> implements
|
||||
protected abstract T createSession(Player player);
|
||||
|
||||
public T getSession(Player player) {
|
||||
sessions.entrySet().removeIf(entry -> entry.getValue().isClosed());
|
||||
return sessions.computeIfAbsent(player.getUniqueId(), uuid -> createSession(player));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ public abstract class AbstractDupeSession implements DupeContext {
|
||||
|
||||
protected abstract void tick();
|
||||
|
||||
protected abstract long getTickDelay(Player player);
|
||||
|
||||
private void startTicking() {
|
||||
if (replicationTask != null && !replicationTask.isCancelled()) {
|
||||
replicationTask.cancel();
|
||||
@@ -42,7 +40,7 @@ public abstract class AbstractDupeSession implements DupeContext {
|
||||
}
|
||||
tick();
|
||||
}
|
||||
}.runTaskTimer(getPlugin(), 0, getTickDelay(owner));
|
||||
}.runTaskTimer(getPlugin(), 0, 1);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
||||
@@ -6,6 +6,9 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
|
||||
|
||||
@Override
|
||||
@@ -13,10 +16,21 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
|
||||
return new ChestSession(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChestSession getSession(Player player) {
|
||||
ChestSession session = super.getSession(player);
|
||||
session.setDelayTicks(getDupe().getPermissionValue(player, "dupealias.gui.chest.refresh.", getConfig().chest.baseRefreshDelayTicks));
|
||||
session.open();
|
||||
return session;
|
||||
}
|
||||
|
||||
public class ChestSession extends AbstractDupeSession {
|
||||
private Map<Integer, ItemDelayInfo> itemDelays = new HashMap<>();
|
||||
private int delayTicks;
|
||||
|
||||
public ChestSession(Player owner) {
|
||||
super(owner, "<gradient:#cc22ff:#cc99ff><bold>DUPE CHEST</gradient>", 6);
|
||||
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.chest.refresh.", getConfig().chest.baseRefreshDelayTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -24,12 +38,19 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
|
||||
return QuickGui.create()
|
||||
.titleMini(title)
|
||||
.rows(rows)
|
||||
.onGlobalClick((g, e) -> e.setCancelled(
|
||||
shouldBlockClick(e.getCursor()) ||
|
||||
.onGlobalClick((g, e) -> {
|
||||
boolean shouldCancel = shouldBlockClick(e.getCursor()) ||
|
||||
shouldBlockClick(e.getCurrentItem()) ||
|
||||
(e.getClickedInventory() != null && shouldBlockClick(e.getClickedInventory().getItem(e.getSlot())))
|
||||
))
|
||||
(e.getClickedInventory() != null && shouldBlockClick(e.getClickedInventory().getItem(e.getSlot())));
|
||||
|
||||
if (!shouldCancel && e.getClickedInventory() != null) {
|
||||
resetItemDelay(e.getSlot());
|
||||
}
|
||||
|
||||
e.setCancelled(shouldCancel);
|
||||
})
|
||||
.allowDrag()
|
||||
.clickSound(null,0,0)
|
||||
.onCreate((g, i) -> populateInventory(i))
|
||||
.onClose((g, e) -> close())
|
||||
.build();
|
||||
@@ -40,12 +61,42 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
|
||||
populateInventory(getGui().getInventory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getTickDelay(Player player) {
|
||||
return 1;
|
||||
private void resetItemDelay(int slot) {
|
||||
ItemDelayInfo info = itemDelays.get(slot);
|
||||
if (info != null) {
|
||||
info.currentTicks = 0;
|
||||
info.ready = false;
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack getDelayedItem(int slot, ItemStack sourceItem) {
|
||||
if (sourceItem == null) {
|
||||
itemDelays.remove(slot);
|
||||
return createPopulatedItem(null,1);
|
||||
}
|
||||
|
||||
ItemDelayInfo info = itemDelays.get(slot);
|
||||
if (info == null) {
|
||||
info = new ItemDelayInfo(sourceItem.clone());
|
||||
itemDelays.put(slot, info);
|
||||
}
|
||||
|
||||
if (!sourceItem.isSimilar(info.originalItem)) {
|
||||
info = new ItemDelayInfo(sourceItem.clone());
|
||||
itemDelays.put(slot, info);
|
||||
}
|
||||
|
||||
if (!info.ready) {
|
||||
info.currentTicks++;
|
||||
double progress = Math.min(1.0, (double) info.currentTicks / delayTicks);
|
||||
if (info.currentTicks >= delayTicks) {
|
||||
info.ready = true;
|
||||
}
|
||||
return createPopulatedItem(sourceItem, progress);
|
||||
}
|
||||
|
||||
return createPopulatedItem(sourceItem, 1.0);
|
||||
}
|
||||
|
||||
private void populateInventory(Inventory inv) {
|
||||
for (int row = 0; row < 6; row++) {
|
||||
@@ -55,7 +106,22 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
|
||||
int leftIndex = rowStart + col;
|
||||
int rightIndex = rowStart + 8 - col;
|
||||
ItemStack leftItem = inv.getItem(leftIndex);
|
||||
inv.setItem(rightIndex, createPopulatedItem(leftItem));
|
||||
inv.setItem(rightIndex, getDelayedItem(rightIndex, leftItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setDelayTicks(int delayTicks) {
|
||||
this.delayTicks = delayTicks;
|
||||
}
|
||||
|
||||
private static class ItemDelayInfo {
|
||||
ItemStack originalItem;
|
||||
int currentTicks = 0;
|
||||
boolean ready = false;
|
||||
|
||||
ItemDelayInfo(ItemStack item) {
|
||||
this.originalItem = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class DupeGui implements DupeContext, CommonItems {
|
||||
|
||||
public final ReplicatorGui replicatorGui = new ReplicatorGui();
|
||||
public final DupeReplicatorGui replicatorGui = new DupeReplicatorGui();
|
||||
public final DupeInventoryGui inventoryGui = new DupeInventoryGui();
|
||||
public final DupeChestGui chestGui = new DupeChestGui();
|
||||
|
||||
@@ -81,7 +81,7 @@ public class DupeGui implements DupeContext, CommonItems {
|
||||
abstractDupeGui.getSession(player).getGui().open(player);
|
||||
} else {
|
||||
getVerbose().send("Creating new session for {0}",player.getName());
|
||||
player.openInventory(abstractDupeGui.createSession(player).open());
|
||||
player.openInventory(abstractDupeGui.getSession(player).open());
|
||||
}
|
||||
} else {
|
||||
player.closeInventory();
|
||||
|
||||
@@ -4,6 +4,10 @@ import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EntityEquipment;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.InventorySession> {
|
||||
|
||||
@@ -12,10 +16,21 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
|
||||
return new InventorySession(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InventorySession getSession(Player player) {
|
||||
InventorySession session = super.getSession(player);
|
||||
session.setDelayTicks(getDupe().getPermissionValue(player, "dupealias.gui.inventory.refresh.", getConfig().inventory.baseRefreshDelayTicks));
|
||||
session.open();
|
||||
return session;
|
||||
}
|
||||
|
||||
public class InventorySession extends AbstractDupeSession {
|
||||
private Map<Integer, ItemDelayInfo> itemDelays = new HashMap<>();
|
||||
private int delayTicks;
|
||||
|
||||
public InventorySession(Player owner) {
|
||||
super(owner, "<gradient:#cc22ff:#cc99ff><bold>YOUR INVENTORY</gradient>", 6);
|
||||
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.inventory.refresh.", getConfig().inventory.baseRefreshDelayTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,12 +38,19 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
|
||||
return QuickGui.create()
|
||||
.titleMini(title)
|
||||
.rows(rows)
|
||||
.onGlobalClick((g, e) -> e.setCancelled(
|
||||
shouldBlockClick(e.getCursor()) ||
|
||||
.onGlobalClick((g, e) -> {
|
||||
boolean shouldCancel = shouldBlockClick(e.getCursor()) ||
|
||||
shouldBlockClick(e.getCurrentItem()) ||
|
||||
(e.getClickedInventory() != null && shouldBlockClick(e.getClickedInventory().getItem(e.getSlot())))
|
||||
))
|
||||
(e.getClickedInventory() != null && shouldBlockClick(e.getClickedInventory().getItem(e.getSlot())));
|
||||
|
||||
if (!shouldCancel && e.getClickedInventory() != null) {
|
||||
resetItemDelay(e.getSlot());
|
||||
}
|
||||
|
||||
e.setCancelled(shouldCancel);
|
||||
})
|
||||
.allowDrag()
|
||||
.clickSound(null,0,0)
|
||||
.onCreate((g, i) -> populateInventory(getOwner(), i))
|
||||
.onClose((g, e) -> close())
|
||||
.build();
|
||||
@@ -39,35 +61,80 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
|
||||
populateInventory(getOwner(), getGui().getInventory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getTickDelay(Player player) {
|
||||
return 1;
|
||||
private void resetItemDelay(int slot) {
|
||||
ItemDelayInfo info = itemDelays.get(slot);
|
||||
if (info != null) {
|
||||
info.currentTicks = 0;
|
||||
info.ready = false;
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack getDelayedItem(int slot, ItemStack sourceItem) {
|
||||
if (sourceItem == null) {
|
||||
itemDelays.remove(slot);
|
||||
return createPopulatedItem(null,1);
|
||||
}
|
||||
|
||||
ItemDelayInfo info = itemDelays.get(slot);
|
||||
if (info == null) {
|
||||
info = new ItemDelayInfo(sourceItem.clone());
|
||||
itemDelays.put(slot, info);
|
||||
}
|
||||
|
||||
if (!sourceItem.isSimilar(info.originalItem)) {
|
||||
info = new ItemDelayInfo(sourceItem.clone());
|
||||
itemDelays.put(slot, info);
|
||||
}
|
||||
|
||||
if (!info.ready) {
|
||||
info.currentTicks++;
|
||||
double progress = Math.min(1.0, (double) info.currentTicks / delayTicks);
|
||||
if (info.currentTicks >= delayTicks) {
|
||||
info.ready = true;
|
||||
}
|
||||
return createPopulatedItem(sourceItem, progress);
|
||||
}
|
||||
|
||||
return createPopulatedItem(sourceItem, 1.0);
|
||||
}
|
||||
|
||||
private void populateInventory(Player player, Inventory inv) {
|
||||
EntityEquipment equipment = player.getEquipment();
|
||||
for (int i = 0; i < 18; i++) {
|
||||
inv.setItem(i, EMPTY());
|
||||
}
|
||||
|
||||
inv.setItem(0, createPopulatedItem(equipment.getHelmet()));
|
||||
inv.setItem(1, createPopulatedItem(equipment.getChestplate()));
|
||||
inv.setItem(2, createPopulatedItem(equipment.getLeggings()));
|
||||
inv.setItem(3, createPopulatedItem(equipment.getBoots()));
|
||||
inv.setItem(6, createPopulatedItem(equipment.getItemInOffHand()));
|
||||
inv.setItem(0, getDelayedItem(0, player.getInventory().getHelmet()));
|
||||
inv.setItem(1, getDelayedItem(1, player.getInventory().getChestplate()));
|
||||
inv.setItem(2, getDelayedItem(2, player.getInventory().getLeggings()));
|
||||
inv.setItem(3, getDelayedItem(3, player.getInventory().getBoots()));
|
||||
inv.setItem(6, getDelayedItem(6, player.getInventory().getItemInOffHand()));
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
inv.setItem(i + 18, createPopulatedItem(player.getInventory().getItem(i)));
|
||||
inv.setItem(i + 18, getDelayedItem(i + 18, player.getInventory().getItem(i)));
|
||||
}
|
||||
for (int i = 27; i < 36; i++) {
|
||||
inv.setItem(i, createPopulatedItem(player.getInventory().getItem(i)));
|
||||
inv.setItem(i, getDelayedItem(i, player.getInventory().getItem(i)));
|
||||
}
|
||||
for (int i = 36; i < 45; i++) {
|
||||
inv.setItem(i, createPopulatedItem(player.getInventory().getItem(i - 18)));
|
||||
inv.setItem(i, getDelayedItem(i, player.getInventory().getItem(i - 18)));
|
||||
}
|
||||
for (int i = 45; i < 54; i++) {
|
||||
inv.setItem(i, createPopulatedItem(player.getInventory().getItem(i - 36)));
|
||||
inv.setItem(i, getDelayedItem(i, player.getInventory().getItem(i - 36)));
|
||||
}
|
||||
}
|
||||
|
||||
public void setDelayTicks(int delayTicks) {
|
||||
this.delayTicks = delayTicks;
|
||||
}
|
||||
|
||||
private static class ItemDelayInfo {
|
||||
ItemStack originalItem;
|
||||
int currentTicks = 0;
|
||||
boolean ready = false;
|
||||
|
||||
ItemDelayInfo(ItemStack item) {
|
||||
this.originalItem = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package me.trouper.dupealias.server.gui.dupe;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.FormatUtils;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.alias.utils.SoundPlayer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class DupeReplicatorGui extends AbstractDupeGui<DupeReplicatorGui.ReplicatorSession> {
|
||||
|
||||
private final int[] inputRing = {1, 2, 3, 12, 21, 20, 19, 10};
|
||||
private final int[] outputRing = {5, 6, 7, 16, 25, 24, 23, 14};
|
||||
private final ItemStack emptyLightBlue = EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE);
|
||||
private final ItemStack emptyCyan = EMPTY(Material.CYAN_STAINED_GLASS_PANE);
|
||||
private final ItemStack emptyWhite = EMPTY(Material.WHITE_STAINED_GLASS_PANE);
|
||||
private final ItemStack emptyBlue = EMPTY(Material.BLUE_STAINED_GLASS_PANE);
|
||||
|
||||
@Override
|
||||
protected ReplicatorSession createSession(Player player) {
|
||||
return new ReplicatorSession(player, player.getInventory().getItemInMainHand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplicatorSession getSession(Player player) {
|
||||
ReplicatorSession session = super.getSession(player);
|
||||
session.setDelayTicks(getDupe().getPermissionValue(player, "dupealias.gui.replicator.refresh.", getConfig().replicator.baseRefreshDelayTicks));
|
||||
session.setCooldownTicks(getDupe().getPermissionValue(player, "dupealias.gui.replicator.cooldown.", getConfig().replicator.baseInputCooldownTicks));
|
||||
session.open();
|
||||
return session;
|
||||
}
|
||||
|
||||
public class ReplicatorSession extends AbstractDupeSession {
|
||||
private ItemStack input;
|
||||
private int timer = 0;
|
||||
private int delayTicks;
|
||||
private int currentDelayTicks = 0;
|
||||
private int cooldownTicks;
|
||||
private int currentCooldownTicks = 0;
|
||||
private boolean ready = false;
|
||||
|
||||
public ReplicatorSession(Player owner, ItemStack input) {
|
||||
super(owner, "<gradient:#cc22ff:#cc99ff><bold>REPLICATOR</gradient>", 3);
|
||||
getVerbose().send("Creating a new replicator with input of {0}", input.getType().name());
|
||||
setInput(input);
|
||||
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.replicator.refresh.", getConfig().replicator.baseRefreshDelayTicks);
|
||||
this.cooldownTicks = getDupe().getPermissionValue(owner, "dupealias.gui.replicator.cooldown.", getConfig().replicator.baseInputCooldownTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuickGui buildGui(String title, int rows) {
|
||||
return QuickGui.create()
|
||||
.titleMini(title)
|
||||
.rows(rows)
|
||||
.fillSlots(EMPTY(Material.BLACK_STAINED_GLASS_PANE), null, 0, 9, 18, 4, 13, 22, 8, 17, 26) // Background
|
||||
.fillSlots(emptyLightBlue, null, inputRing)
|
||||
.fillSlots(EMPTY(Material.BLUE_STAINED_GLASS_PANE), null, outputRing)
|
||||
.item(15, createPopulatedItem(null,1))
|
||||
.onGlobalClick((g, e) -> {
|
||||
if (e.getClickedInventory() != null && e.getSlot() == 15) {
|
||||
ItemStack clicked = e.getCurrentItem();
|
||||
ItemStack cursor = e.getCursor();
|
||||
ItemStack slot = e.getClickedInventory().getItem(e.getSlot());
|
||||
if (shouldBlockClick(clicked) || shouldBlockClick(cursor) || shouldBlockClick(slot)) {
|
||||
e.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
ready = false;
|
||||
currentDelayTicks = 0;
|
||||
SoundPlayer.play(getOwner(), Sound.ENTITY_ITEM_PICKUP, 0.8F, 1.2F);
|
||||
|
||||
e.setCancelled(false);
|
||||
return;
|
||||
}
|
||||
if (e.getSlot() == 11) {
|
||||
if (currentCooldownTicks > 0) {
|
||||
SoundPlayer.play(getOwner(),Sound.BLOCK_NOTE_BLOCK_BASS);
|
||||
return;
|
||||
} else {
|
||||
currentCooldownTicks = cooldownTicks;
|
||||
}
|
||||
|
||||
Inventory inv = getGui().getInventory();
|
||||
ItemStack cursor = e.getCursor();
|
||||
if (cursor == null || cursor.getType() == Material.AIR) {
|
||||
setInput(new ItemStack(Material.AIR));
|
||||
SoundPlayer.play(getOwner(),Sound.ITEM_BUNDLE_REMOVE_ONE);
|
||||
getOwner().stopSound(Sound.BLOCK_BEACON_AMBIENT);
|
||||
deactivateRings(inv);
|
||||
} else {
|
||||
if (setInput(cursor)) {
|
||||
SoundPlayer.play(getOwner(),Sound.ITEM_BUNDLE_INSERT);
|
||||
activateRings(inv);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.clickSound(null,0,0)
|
||||
.onClose((g, e) -> close())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tick() {
|
||||
timer++;
|
||||
|
||||
Inventory inv = getGui().getInventory();
|
||||
|
||||
if (timer % 2 == 0) for (int i = 0; i < outputRing.length; i++) {
|
||||
int currentSlot = outputRing[i];
|
||||
int nextSlot = outputRing[(i+1) % outputRing.length];
|
||||
ItemStack currentItem = inv.getItem(currentSlot);
|
||||
if (currentItem != null && currentItem.isSimilar(emptyLightBlue)) {
|
||||
inv.setItem(currentSlot,emptyCyan);
|
||||
inv.setItem(nextSlot,emptyLightBlue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ItemStack output = inv.getItem(15);
|
||||
|
||||
if (currentCooldownTicks > 1) {
|
||||
currentCooldownTicks--;
|
||||
inv.setItem(11,createInputItem(null, (double) currentCooldownTicks / (double) cooldownTicks));
|
||||
} else if (currentCooldownTicks == 1) {
|
||||
currentCooldownTicks--;
|
||||
getGui().getInventory().setItem(11, createInputItem(input, 1));
|
||||
SoundPlayer.play(getOwner(), Sound.BLOCK_AMETHYST_BLOCK_RESONATE, 1, 0.8F);
|
||||
}
|
||||
|
||||
if (input == null || input.getType() == Material.AIR) {
|
||||
if (output != null && !output.isSimilar(createPopulatedItem(null,1))) {
|
||||
inv.setItem(15, createPopulatedItem(null,1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer % 20 == 0) {
|
||||
SoundPlayer.play(getOwner(), Sound.BLOCK_BEACON_AMBIENT, 0.5F, 1.2F);
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
currentDelayTicks++;
|
||||
double progress = Math.min(1.0, (double) currentDelayTicks / delayTicks);
|
||||
inv.setItem(15, createPopulatedItem(input, progress));
|
||||
if (currentDelayTicks >= delayTicks) {
|
||||
ready = true;
|
||||
SoundPlayer.play(getOwner(), Sound.BLOCK_AMETHYST_BLOCK_RESONATE, 1, 0.8F);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.isSimilar(output)) return;
|
||||
|
||||
inv.setItem(15, createPopulatedItem(input, 1));
|
||||
}
|
||||
|
||||
public boolean setInput(ItemStack newInput) {
|
||||
if (getDupe().isUnique(newInput)) {
|
||||
SoundPlayer.play(getOwner(), Sound.ENTITY_VILLAGER_NO, 1, 0.8F);
|
||||
warningAny(getOwner(), "Your {0} is or contains a unique item!", FormatUtils.formatEnum(newInput.getType()));
|
||||
getGui().getInventory().setItem(11, createInputItem(this.input, 1));
|
||||
return false;
|
||||
}
|
||||
this.input = newInput.clone();
|
||||
getGui().getInventory().setItem(11, createInputItem(this.input, 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setDelayTicks(int delayTicks) {
|
||||
this.delayTicks = delayTicks;
|
||||
}
|
||||
|
||||
public void setCooldownTicks(int cooldownTicks) {
|
||||
this.cooldownTicks = cooldownTicks;
|
||||
}
|
||||
|
||||
private void activateRings(Inventory inv) {
|
||||
for (int i : inputRing) {
|
||||
inv.setItem(i,emptyWhite);
|
||||
}
|
||||
for (int i : outputRing) {
|
||||
inv.setItem(i,emptyCyan);
|
||||
}
|
||||
inv.setItem(outputRing[0], emptyLightBlue);
|
||||
}
|
||||
|
||||
private void deactivateRings(Inventory inv) {
|
||||
for (int i : inputRing) {
|
||||
inv.setItem(i, emptyLightBlue);
|
||||
}
|
||||
for (int i : outputRing) {
|
||||
inv.setItem(i,emptyBlue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack createInputItem(ItemStack input, double cooldownProgress) {
|
||||
if (cooldownProgress < 1) {
|
||||
return ItemBuilder.of(EMPTY(Material.BARRIER))
|
||||
.displayName("<gold>Replicator Input")
|
||||
.loreComponent(getTextSystem().createProgressBar(cooldownProgress, '|',30, TextColor.color(0xFF895A),TextColor.color(0x6F6F6F)))
|
||||
.loreComponent(Component.text("Replicator input on cooldown.", NamedTextColor.DARK_RED))
|
||||
.build();
|
||||
}
|
||||
if (input == null || input.getType() == Material.AIR) {
|
||||
return ItemBuilder.headOfTexture("http://textures.minecraft.net/texture/86bd920b402815ad89018df82977be9f7ea19e799ecf016f7f0da4ab47ca23c5")
|
||||
.displayName("<gold>Replicator Input")
|
||||
.loreMiniMessage("<gray>No item selected.")
|
||||
.loreMiniMessage("<dark_red>Drag an item into this slot.")
|
||||
.build();
|
||||
} else {
|
||||
return ItemBuilder.headOfTexture("http://textures.minecraft.net/texture/32d250f5336449b32bfe990bdfd307a1b39ae5ca07e9a1593b1bb6ed33ec14ba")
|
||||
.displayName("<gold>Replicator Input")
|
||||
.loreMiniMessage("<white>Set Item: " + FormatUtils.formatEnum(input.getType()))
|
||||
.loreMiniMessage("<dark_green>Replication Ready!")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
package me.trouper.dupealias.server.gui.dupe;
|
||||
|
||||
import me.trouper.alias.server.systems.gui.QuickGui;
|
||||
import me.trouper.alias.utils.FormatUtils;
|
||||
import me.trouper.alias.utils.ItemBuilder;
|
||||
import me.trouper.alias.utils.SoundPlayer;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReplicatorGui extends AbstractDupeGui<ReplicatorGui.ReplicatorSession> {
|
||||
|
||||
@Override
|
||||
protected ReplicatorSession createSession(Player player) {
|
||||
return new ReplicatorSession(player, player.getInventory().getItemInMainHand());
|
||||
}
|
||||
|
||||
public class ReplicatorSession extends AbstractDupeSession {
|
||||
private ItemStack input;
|
||||
private int ticks = 0;
|
||||
|
||||
public ReplicatorSession(Player owner, ItemStack input) {
|
||||
super(owner, "<gradient:#cc22ff:#cc99ff><bold>REPLICATOR</gradient>", 3);
|
||||
getVerbose().send("Creating a new replicator with input of {0}", input.getType().name());
|
||||
setInput(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuickGui buildGui(String title, int rows) {
|
||||
return QuickGui.create()
|
||||
.titleMini(title)
|
||||
.rows(rows)
|
||||
.fillSlots(EMPTY(Material.BLACK_STAINED_GLASS_PANE), null, 0, 9, 18, 4, 13, 22, 8, 17, 26) // Background
|
||||
.fillSlots(EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE), null, 1, 2, 3, 10, 12, 19, 20, 21) // Input ring
|
||||
.fillSlots(EMPTY(Material.BLUE_STAINED_GLASS_PANE), null, 5, 6, 7, 14, 16, 23, 24, 25) // Replication ring
|
||||
.item(15, new ItemStack(Material.AIR))
|
||||
.onGlobalClick((g, e) -> {
|
||||
if (e.getSlot() == 15) {
|
||||
e.setCancelled(false);
|
||||
return;
|
||||
}
|
||||
if (e.getSlot() == 11) {
|
||||
Inventory inv = getGui().getInventory();
|
||||
ItemStack cursor = e.getCursor();
|
||||
if (cursor == null || cursor.getType() == Material.AIR) {
|
||||
setInput(new ItemStack(Material.AIR));
|
||||
SoundPlayer.play(getOwner(),Sound.ITEM_BUNDLE_REMOVE_ONE);
|
||||
getOwner().stopSound(Sound.BLOCK_BEACON_AMBIENT);
|
||||
deactivateRings(inv);
|
||||
} else {
|
||||
if (setInput(cursor)) {
|
||||
SoundPlayer.play(getOwner(),Sound.ITEM_BUNDLE_INSERT);
|
||||
activateRings(inv);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.onClose((g, e) -> close())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getTickDelay(Player player) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tick() {
|
||||
ticks++;
|
||||
Inventory inv = getGui().getInventory();
|
||||
ItemStack output = inv.getItem(15);
|
||||
|
||||
if (input == null || input.getType() == Material.AIR) {
|
||||
if (output != null && output.getType() != Material.AIR) {
|
||||
inv.setItem(15, new ItemStack(Material.AIR));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ticks % 20 == 0) {
|
||||
SoundPlayer.play(getOwner(), Sound.BLOCK_BEACON_AMBIENT, 0.5F, 1.2F);
|
||||
}
|
||||
|
||||
if (input.isSimilar(output)) return;
|
||||
|
||||
inv.setItem(15, createPopulatedItem(input));
|
||||
SoundPlayer.play(getOwner(), Sound.BLOCK_AMETHYST_BLOCK_RESONATE, 1, 0.8F);
|
||||
}
|
||||
|
||||
public boolean setInput(ItemStack newInput) {
|
||||
if (getDupe().isUnique(newInput)) {
|
||||
SoundPlayer.play(getOwner(), Sound.ENTITY_VILLAGER_NO, 1, 0.8F);
|
||||
warningAny(getOwner(), "Your {0} is or contains a unique item!", FormatUtils.formatEnum(newInput.getType()));
|
||||
return false;
|
||||
}
|
||||
this.input = newInput.clone();
|
||||
getGui().getInventory().setItem(11, createInputItem(this.input));
|
||||
return true;
|
||||
}
|
||||
|
||||
public ItemStack getInput() {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack createInputItem(ItemStack input) {
|
||||
if (input == null || input.getType() == Material.AIR) {
|
||||
return ItemBuilder.headOfTexture("http://textures.minecraft.net/texture/86bd920b402815ad89018df82977be9f7ea19e799ecf016f7f0da4ab47ca23c5")
|
||||
.displayName("<gold>Replicator Input")
|
||||
.loreMiniMessage("<gray>No item selected.")
|
||||
.loreMiniMessage("<dark_red>Drag an item into this slot.")
|
||||
.build();
|
||||
} else {
|
||||
return ItemBuilder.headOfTexture("http://textures.minecraft.net/texture/32d250f5336449b32bfe990bdfd307a1b39ae5ca07e9a1593b1bb6ed33ec14ba")
|
||||
.displayName("<gold>Replicator Input")
|
||||
.loreMiniMessage("<white>Item: " + FormatUtils.formatEnum(input.getType()))
|
||||
.loreMiniMessage("<dark_green>Replication Ready!")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private void activateRings(Inventory inv) {
|
||||
inv.setItem(1, EMPTY(Material.WHITE_STAINED_GLASS_PANE));
|
||||
inv.setItem(2, EMPTY(Material.WHITE_STAINED_GLASS_PANE));
|
||||
inv.setItem(3, EMPTY(Material.WHITE_STAINED_GLASS_PANE));
|
||||
inv.setItem(10, EMPTY(Material.WHITE_STAINED_GLASS_PANE));
|
||||
inv.setItem(12, EMPTY(Material.WHITE_STAINED_GLASS_PANE));
|
||||
inv.setItem(19, EMPTY(Material.WHITE_STAINED_GLASS_PANE));
|
||||
inv.setItem(20, EMPTY(Material.WHITE_STAINED_GLASS_PANE));
|
||||
inv.setItem(21, EMPTY(Material.WHITE_STAINED_GLASS_PANE));
|
||||
|
||||
inv.setItem(5, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(6, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(7, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(14, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(16, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(23, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(24, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(25, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
}
|
||||
|
||||
private void deactivateRings(Inventory inv) {
|
||||
inv.setItem(1, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(2, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(3, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(10, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(12, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(19, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(20, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(21, EMPTY(Material.LIGHT_BLUE_STAINED_GLASS_PANE));
|
||||
|
||||
inv.setItem(5, EMPTY(Material.BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(6, EMPTY(Material.BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(7, EMPTY(Material.BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(14, EMPTY(Material.BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(16, EMPTY(Material.BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(23, EMPTY(Material.BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(24, EMPTY(Material.BLUE_STAINED_GLASS_PANE));
|
||||
inv.setItem(25, EMPTY(Material.BLUE_STAINED_GLASS_PANE));
|
||||
}
|
||||
|
||||
public class ReplicatorConfig {
|
||||
final int baseRefreshDelayTicks = 1;
|
||||
final int baseInputCooldownTicks = 20;
|
||||
final Map<String,Integer> permissionRefreshDelayTicks = new HashMap<>();
|
||||
final Map<String,Integer> permissionInputCooldownTicks = new HashMap<>();
|
||||
}
|
||||
}
|
||||
@@ -22,15 +22,18 @@ permissions:
|
||||
default: op
|
||||
dupealias.final.bypass:
|
||||
description: Allows the bypassing of final item restrictions
|
||||
default: op
|
||||
dupealias.protected.bypass:
|
||||
description: Allows the bypassing of protected item restrictions
|
||||
default: op
|
||||
dupealias.unique.bypass:
|
||||
description: Allows the duping of unique items
|
||||
default: op
|
||||
dupealias.dupe:
|
||||
description: Allows duplication of items through the command.
|
||||
default: true
|
||||
children:
|
||||
dupealias.dupe.cooldownbypass: op
|
||||
dupealias.dupe.cooldownbypass: false
|
||||
dupealias.dupe.cooldownbypass:
|
||||
description: Bypass the cooldown for /dupe
|
||||
default: op
|
||||
@@ -48,14 +51,16 @@ permissions:
|
||||
description: The gui which lets you shift click copies of a single item.
|
||||
default: true
|
||||
children:
|
||||
dupalias.gui.replicator.keep: op
|
||||
dupealias.gui.replicator.keep: false # Controls if a player should keep their previous replicator session with items in it. This does not persist across reboots.
|
||||
dupealias.gui.replicator.refresh.integerhere: false # Controls the time it will take a duplicated item to refill or refresh in the GUI. Always takes the lowest number on a permission holder.
|
||||
dupealias.gui.inventory:
|
||||
description: The gui which shows your inventory and armor on top.
|
||||
default: true
|
||||
children:
|
||||
dupalias.gui.inventory.keep: op
|
||||
dupealias.gui.inventory.refresh.integerhere: false # Controls the time it will take a duplicated item to refill or refresh in the GUI. Always takes the lowest number on a permission holder.
|
||||
dupealias.gui.chest:
|
||||
description: A gui which items can be put in and taken out as copies.
|
||||
default: true
|
||||
children:
|
||||
dupalias.gui.chest.keep: op
|
||||
dupealias.gui.chest.keep: false # Controls if a player should keep their previous chest session with items in it. This does not persist across reboots.
|
||||
dupealias.gui.chest.refresh.integerhere: false # Controls the time it will take a duplicated item to refill or refresh in the GUI. Always takes the lowest number on a permission holder.
|
||||
Reference in New Issue
Block a user