Replaced global material with Global Rule

This commit is contained in:
2025-07-24 19:57:54 -05:00
parent e409815188
commit 2817161c11
49 changed files with 4120 additions and 1311 deletions

View File

@@ -1,36 +1,2 @@
Infinite Item - one that always stays at max stack size. match based on certain features such as enchant, trim, lore string, or name.
on death, drop any items stored in chest GUI.
| 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, its 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 projectiles `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. |

View File

@@ -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
View 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()
}
}

View File

@@ -0,0 +1,3 @@
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

10
gradlew vendored Executable file → Normal file
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -112,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # 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. # 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 # * 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. # treated as '${Hostname}' itself on the command line.
@@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.

6
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -68,11 +70,11 @@ goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=
@rem Execute Gradle @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 :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

View File

@@ -1 +0,0 @@
rootProject.name = 'DupeAlias'

5
settings.gradle.kts Normal file
View File

@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0"
}
rootProject.name = "DupeAlias"

View File

@@ -3,9 +3,9 @@ package me.trouper.dupealias;
import me.trouper.alias.AliasContext; import me.trouper.alias.AliasContext;
import me.trouper.alias.AliasContextProvider; import me.trouper.alias.AliasContextProvider;
import me.trouper.alias.data.Common; import me.trouper.alias.data.Common;
import me.trouper.dupealias.data.CommonConfig; import me.trouper.dupealias.data.files.CommonConfig;
import me.trouper.dupealias.data.DupeConfig; import me.trouper.dupealias.data.files.DupeConfig;
import me.trouper.dupealias.data.PlayerData; import me.trouper.dupealias.data.files.NBTStorage;
import me.trouper.dupealias.server.DupeManager; import me.trouper.dupealias.server.DupeManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@@ -29,7 +29,7 @@ public final class DupeAlias extends JavaPlugin {
alias.initialize(); alias.initialize();
alias.getDataManager().load(CommonConfig.class).save(); alias.getDataManager().load(CommonConfig.class).save();
alias.getDataManager().load(DupeConfig.class).save(); alias.getDataManager().load(DupeConfig.class).save();
alias.getDataManager().load(PlayerData.class).save(); alias.getDataManager().load(NBTStorage.class).save();
updateCommon(); updateCommon();
dupe = new DupeManager(); dupe = new DupeManager();
@@ -39,7 +39,7 @@ public final class DupeAlias extends JavaPlugin {
public void onDisable() { public void onDisable() {
alias.getDataManager().save(CommonConfig.class); alias.getDataManager().save(CommonConfig.class);
alias.getDataManager().save(DupeConfig.class); alias.getDataManager().save(DupeConfig.class);
alias.getDataManager().save(PlayerData.class); alias.getDataManager().save(NBTStorage.class);
alias.shutdown(); alias.shutdown();
} }

View File

@@ -2,8 +2,9 @@ package me.trouper.dupealias;
import me.trouper.alias.server.ContextAware; import me.trouper.alias.server.ContextAware;
import me.trouper.alias.server.events.listeners.GuiInputListener; import me.trouper.alias.server.events.listeners.GuiInputListener;
import me.trouper.dupealias.data.CommonConfig; import me.trouper.dupealias.data.files.CommonConfig;
import me.trouper.dupealias.data.DupeConfig; import me.trouper.dupealias.data.files.DupeConfig;
import me.trouper.dupealias.data.files.NBTStorage;
import me.trouper.dupealias.server.DupeManager; import me.trouper.dupealias.server.DupeManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@@ -24,6 +25,11 @@ public interface DupeContext extends ContextAware {
default DupeConfig getConfig() { default DupeConfig getConfig() {
return getDataManager().get(DupeConfig.class); return getDataManager().get(DupeConfig.class);
} }
default NBTStorage getNbtStorage() {
return getDataManager().get(NBTStorage.class);
}
default GuiInputListener getGuiListener() { default GuiInputListener getGuiListener() {
return getContext().getGuiInputListener(); return getContext().getGuiInputListener();
} }

View 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;
}
}

View 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;
}
}

View File

@@ -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");
}
}

View File

@@ -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.Common;
import me.trouper.alias.data.JsonSerializable; import me.trouper.alias.data.JsonSerializable;

View File

@@ -1,7 +1,8 @@
package me.trouper.dupealias.data; package me.trouper.dupealias.data.files;
import me.trouper.alias.data.JsonSerializable; import me.trouper.alias.data.JsonSerializable;
import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.data.GlobalRule;
import me.trouper.dupealias.server.ItemTag; import me.trouper.dupealias.server.ItemTag;
import org.bukkit.Material; import org.bukkit.Material;
@@ -23,13 +24,36 @@ public class DupeConfig implements JsonSerializable<DupeConfig>, DupeContext {
"\"(?:itemlore|lore|elore|ilore|eilore|eitemlore)\"gmi" "\"(?: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.PROTECTED, "<dark_purple><bold>|</bold><light_purple> Protected",
ItemTag.FINAL, "<dark_red><bold>|</bold><red> Final", ItemTag.FINAL, "<dark_red><bold>|</bold><red> Final",
ItemTag.UNIQUE, "<dark_blue><bold>|</bold><blue> Unique", ItemTag.UNIQUE, "<dark_blue><bold>|</bold><blue> Unique",
ItemTag.INFINITE, "<dark_green><bold>|</bold><green> Infinite" 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;
}
} }

View File

@@ -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;
}
}

View File

@@ -2,21 +2,51 @@ package me.trouper.dupealias.server;
import me.trouper.alias.utils.ItemBuilder; import me.trouper.alias.utils.ItemBuilder;
import me.trouper.dupealias.DupeContext; 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 me.trouper.dupealias.server.functions.UniqueCheck;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import java.util.HashSet; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class DupeManager implements DupeContext { 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) { public boolean isUnique(ItemStack item) {
return !new UniqueCheck().passes(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)); 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) { public boolean checkEffectiveTag(ItemStack input, ItemTag tag) {
if (tag == null || input == null) return false; if (tag == null || input == null) return false;
if (input.isEmpty()) return false; if (input.isEmpty()) return false;
boolean set = hasIndividualTag(input,tag); boolean set = hasIndividualTag(input,tag);
boolean global = getDupe().checkGlobalTag(input.getType(),tag); boolean individual = set && Boolean.TRUE.equals(input.getPersistentDataContainer().get(tag.getKey(), PersistentDataType.BOOLEAN));
boolean individual = Boolean.TRUE.equals(input.getPersistentDataContainer().get(tag.getKey(), PersistentDataType.BOOLEAN));
// Check individual tag first
if (set) return individual; 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<>()); * Gets all global rules that apply to a given material
boolean result = tags.add(tag); */
getConfig().globalMaterials.put(material,tags); public List<GlobalRule> getApplicableRules(Material material) {
getConfig().save(); return getConfig().globalRules.stream()
return result; .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); * Gets all global rules that apply to a specific item
getConfig().globalMaterials.put(material,tags); */
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(); 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) { public boolean addTag(ItemStack item, ItemTag tag) {
if (hasIndividualTag(item, tag) && getDupe().checkIndividualTag(item, tag)) return false; if (hasIndividualTag(item, tag) && getDupe().checkIndividualTag(item, tag)) return false;
ItemBuilder builder = ItemBuilder.of(item); ItemBuilder builder = ItemBuilder.of(item);
builder.loreMiniMessage(getConfig().tagLore.get(tag)); builder.loreMiniMessage(getConfig().trueTagLore.get(tag));
builder.modifyMeta(itemMeta -> { builder.modifyMeta(itemMeta -> {
itemMeta.getPersistentDataContainer().set(tag.getKey(), PersistentDataType.BOOLEAN, true); itemMeta.getPersistentDataContainer().set(tag.getKey(), PersistentDataType.BOOLEAN, true);
return itemMeta; return itemMeta;
}); });
ItemStack result = builder.buildAndGet(); ItemStack result = builder.buildAndGet();
item.setItemMeta(result.getItemMeta()); item.setItemMeta(result.getItemMeta());
return true; return true;
} }
public boolean removeTag(ItemStack item, ItemTag tag) { public boolean removeTag(ItemStack item, ItemTag tag) {
ItemBuilder builder = ItemBuilder.of(item); ItemBuilder builder = ItemBuilder.of(item);
if (hasIndividualTag(item, tag) && !checkIndividualTag(item, tag)) return false; if (hasIndividualTag(item, tag) && !checkIndividualTag(item, tag)) return false;
builder.modifyMeta(itemMeta->{
if (itemMeta.hasLore()) {
removeTagLore(itemMeta,tag);
}
return itemMeta;
});
try { try {
builder.modifyMeta(itemMeta -> { builder.modifyMeta(itemMeta -> {
itemMeta.getPersistentDataContainer().remove(tag.getKey()); 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; return itemMeta;
}); });
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
return false; return false;
} }
ItemStack result = builder.buildAndGet();
ItemStack result = builder.buildAndGet();
item.setItemMeta(result.getItemMeta()); item.setItemMeta(result.getItemMeta());
return true; return true;
} }
public void setTag(ItemStack item, ItemTag tag, boolean value) { public void setTag(ItemStack item, ItemTag tag, boolean value) {
ItemBuilder builder = ItemBuilder.of(item); 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 -> { builder.modifyMeta(itemMeta -> {
itemMeta.getPersistentDataContainer().set(tag.getKey(), PersistentDataType.BOOLEAN, value); itemMeta.getPersistentDataContainer().set(tag.getKey(), PersistentDataType.BOOLEAN, value);
return itemMeta; return itemMeta;
}); });
ItemStack result = builder.buildAndGet(); ItemStack result = builder.buildAndGet();
item.setItemMeta(result.getItemMeta()); 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) { public ItemTag getTag(NamespacedKey key) {
for (ItemTag value : ItemTag.values()) { for (ItemTag value : ItemTag.values()) {
if (value.getKey().equals(key)) return value; 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())); 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;
}
} }

View File

@@ -6,8 +6,10 @@ import me.trouper.alias.server.commands.Permission;
import me.trouper.alias.server.commands.QuickCommand; import me.trouper.alias.server.commands.QuickCommand;
import me.trouper.alias.server.commands.completions.CompletionBuilder; import me.trouper.alias.server.commands.completions.CompletionBuilder;
import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.data.GlobalRule;
import me.trouper.dupealias.server.ItemTag; 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.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -17,7 +19,7 @@ import org.bukkit.inventory.ItemStack;
@CommandRegistry( @CommandRegistry(
value = "dupealias", value = "dupealias",
permission = @Permission(value = "dupealias.admin", message = "Only server administrators can use this command."), 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, blocksAllowed = false,
printStackTrace = true printStackTrace = true
) )
@@ -43,6 +45,10 @@ public class AdminCommand implements QuickCommand, DupeContext {
handleTag(sender,args); handleTag(sender,args);
} }
case "rule" -> {
handleRule(sender,args);
}
default -> { default -> {
errorAny(sender,"Invalid subcommand!"); errorAny(sender,"Invalid subcommand!");
} }
@@ -69,6 +75,22 @@ public class AdminCommand implements QuickCommand, DupeContext {
b.arg("remove","false") 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( ).then(
b.arg("gui") 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) { private void handleTag(CommandSender sender, Args args) {
if (args.getSize() < 2) { 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; return;
} }
@@ -136,7 +270,7 @@ public class AdminCommand implements QuickCommand, DupeContext {
return; return;
} }
// gui tag <tag> // da tag <tag>
if (args.getSize() == 2) { if (args.getSize() == 2) {
if (!(sender instanceof Player player)) { 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'."); 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 // Argument 2
String subCommand = args.get(2).toString().toLowerCase(); String subCommand = args.get(2).toString().toLowerCase();
// gui tag <tag> remove|false // da tag <tag> remove|false
switch (subCommand) { switch (subCommand) {
case "remove" -> { case "remove" -> {
if (args.getSize() != 3) { if (args.getSize() != 3) {
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> remove"); errorAny(sender, "Invalid arguments. Usage: /da tag <tag> remove");
return; return;
} }
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
@@ -184,11 +318,11 @@ public class AdminCommand implements QuickCommand, DupeContext {
} }
case "false" -> { case "false" -> {
if (args.getSize() != 3) { if (args.getSize() != 3) {
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> remove"); errorAny(sender, "Invalid arguments. Usage: /da tag <tag> false");
return; return;
} }
if (!(sender instanceof Player player)) { 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; return;
} }
ItemStack heldItem = player.getInventory().getItemInMainHand(); ItemStack heldItem = player.getInventory().getItemInMainHand();
@@ -197,12 +331,12 @@ public class AdminCommand implements QuickCommand, DupeContext {
return; return;
} }
getDupe().setTag(heldItem, tag, false); 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; return;
} }
// gui tag <tag> global [remove] // da tag <tag> global [remove]
case "global" -> { case "global" -> {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
errorAny(sender, "The 'global' subcommand must be run by a 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()); boolean isRemove = args.getSize() > 3 && "remove".equalsIgnoreCase(args.get(3).toString());
if (isRemove) { if (isRemove) {
if (args.getSize() != 4) { 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; return;
} }
if (getDupe().removeGlobalTag(heldMaterial, tag)) { if (getDupe().removeGlobalRulesForMaterial(heldMaterial, tag)) {
successAny(sender, "Removed global tag {0} from all {1} items.", tag.getName(), heldMaterial); successAny(sender, "Removed global rules applying tag {0} to {1} items.", tag.getName(), heldMaterial);
} else { } 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 { } else {
if (args.getSize() != 3) { if (args.getSize() != 3) {
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> global"); errorAny(sender, "Invalid arguments. Usage: /da tag <tag> global");
return; return;
} }
if (getDupe().addGlobalTag(heldMaterial, tag)) { if (getDupe().addGlobalRuleForMaterial(heldMaterial, tag)) {
successAny(sender, "All {0} items are now globally tagged as {1} and {2}.", heldMaterial, tag.getName(), tag.getDesc()); successAny(sender, "Created global rule: all {0} items are now tagged as {1} and {2}.", heldMaterial, tag.getName(), tag.getDesc());
} else { } 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; return;
} }
} }
// gui tag <tag> <material> [remove] // da tag <tag> <material> [remove]
try { try {
Material material = args.get(2).toEnum(Material.class); Material material = args.get(2).toEnum(Material.class);
boolean isRemove = args.getSize() > 3 && "remove".equalsIgnoreCase(args.get(3).toString()); boolean isRemove = args.getSize() > 3 && "remove".equalsIgnoreCase(args.get(3).toString());
if (isRemove) { if (isRemove) {
if (args.getSize() != 4) { 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; return;
} }
if (getDupe().removeGlobalTag(material, tag)) { if (getDupe().removeGlobalRulesForMaterial(material, tag)) {
successAny(sender, "Removed global tag {0} from {1}.", tag.getName(), material); successAny(sender, "Removed global rules applying tag {0} to {1}.", tag.getName(), material);
} else { } 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 { } else {
if (args.getSize() != 3) { if (args.getSize() != 3) {
errorAny(sender, "Invalid arguments. Usage: /gui tag <tag> <material>"); errorAny(sender, "Invalid arguments. Usage: /da tag <tag> <material>");
return; return;
} }
getDupe().addGlobalTag(material, tag); if (getDupe().addGlobalRuleForMaterial(material, tag)) {
if (getDupe().addGlobalTag(material, tag)) { successAny(sender, "Created global rule: all {0} items are now tagged as {1} and {2}.", material, tag.getName(), tag.getDesc());
successAny(sender, "All {0} items are now tagged as {1} and {2}.", material, tag.getName(), tag.getDesc());
} else { } 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) { } catch (IllegalArgumentException e) {
@@ -275,7 +408,7 @@ public class AdminCommand implements QuickCommand, DupeContext {
public void openBaseGui(CommandSender sender) { public void openBaseGui(CommandSender sender) {
if (sender instanceof Player player) { if (sender instanceof Player player) {
new AdminGui().openMainGui(player); new MainAdminGui(new AdminPanelManager()).open(player);
} else { } else {
errorAny(sender, "Console may not open a GUI."); errorAny(sender, "Console may not open a GUI.");
} }

View File

@@ -37,7 +37,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
} }
if (args.isEmpty()) { if (args.isEmpty()) {
if (dupeHeld(player,0)) { if (dupeHeld(player,1)) {
dupeCooldown.setCooldown(player.getUniqueId(), getConfig().dupeCooldownMillis); dupeCooldown.setCooldown(player.getUniqueId(), getConfig().dupeCooldownMillis);
} else { } else {
dupeGui.openDefaultGui(player); dupeGui.openDefaultGui(player);
@@ -94,7 +94,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
int baseCount = inHand.getAmount(); int baseCount = inHand.getAmount();
int maxPerStack = inHand.getMaxStackSize(); int maxPerStack = inHand.getMaxStackSize();
for (int i = 0; i <= amount; i++) { for (int i = 0; i <= amount - 1; i++) {
int remaining = baseCount * (1 << i); int remaining = baseCount * (1 << i);
while (remaining > 0) { 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); successAny(player,"You have duplicated {0} items!", totalGiven);
return true; return true;
} }

View File

@@ -7,7 +7,7 @@ import org.bukkit.persistence.PersistentDataType;
public class UniqueCheck implements Check<ItemStack> { public class UniqueCheck implements Check<ItemStack> {
@Override @Override
public boolean passes(ItemStack input) { 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 set = input.hasItemMeta() && input.getPersistentDataContainer().has(ItemTag.UNIQUE.getKey());
boolean individuallyUnique = Boolean.TRUE.equals(input.getPersistentDataContainer().get(ItemTag.UNIQUE.getKey(), PersistentDataType.BOOLEAN)); boolean individuallyUnique = Boolean.TRUE.equals(input.getPersistentDataContainer().get(ItemTag.UNIQUE.getKey(), PersistentDataType.BOOLEAN));

View File

@@ -4,9 +4,14 @@ import me.trouper.alias.utils.FormatUtils;
import me.trouper.alias.utils.ItemBuilder; import me.trouper.alias.utils.ItemBuilder;
import me.trouper.dupealias.DupeAlias; import me.trouper.dupealias.DupeAlias;
import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.DupeContext;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey; 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.ItemStack;
import org.bukkit.inventory.meta.BlockStateMeta;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
public interface CommonItems extends DupeContext { 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))); && 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); if (item == null || item.isEmpty()) return EMPTY(Material.GRAY_STAINED_GLASS_PANE);
ItemStack clone = item.clone(); ItemStack clone = item.clone();
if (getDupe().isUnique(clone)) return ItemBuilder.of(EMPTY(Material.BARRIER)) if (getDupe().isUnique(clone)) return ItemBuilder.of(EMPTY(Material.BARRIER))

View File

@@ -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";
};
}
}

View File

@@ -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";
};
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -14,7 +14,6 @@ public abstract class AbstractDupeGui<T extends AbstractDupeSession> implements
protected abstract T createSession(Player player); protected abstract T createSession(Player player);
public T getSession(Player player) { public T getSession(Player player) {
sessions.entrySet().removeIf(entry -> entry.getValue().isClosed());
return sessions.computeIfAbsent(player.getUniqueId(), uuid -> createSession(player)); return sessions.computeIfAbsent(player.getUniqueId(), uuid -> createSession(player));
} }

View File

@@ -25,8 +25,6 @@ public abstract class AbstractDupeSession implements DupeContext {
protected abstract void tick(); protected abstract void tick();
protected abstract long getTickDelay(Player player);
private void startTicking() { private void startTicking() {
if (replicationTask != null && !replicationTask.isCancelled()) { if (replicationTask != null && !replicationTask.isCancelled()) {
replicationTask.cancel(); replicationTask.cancel();
@@ -42,7 +40,7 @@ public abstract class AbstractDupeSession implements DupeContext {
} }
tick(); tick();
} }
}.runTaskTimer(getPlugin(), 0, getTickDelay(owner)); }.runTaskTimer(getPlugin(), 0, 1);
} }
public void close() { public void close() {

View File

@@ -6,6 +6,9 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
import java.util.Map;
public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> { public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
@Override @Override
@@ -13,10 +16,21 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
return new ChestSession(player); 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 { public class ChestSession extends AbstractDupeSession {
private Map<Integer, ItemDelayInfo> itemDelays = new HashMap<>();
private int delayTicks;
public ChestSession(Player owner) { public ChestSession(Player owner) {
super(owner, "<gradient:#cc22ff:#cc99ff><bold>DUPE CHEST</gradient>", 6); super(owner, "<gradient:#cc22ff:#cc99ff><bold>DUPE CHEST</gradient>", 6);
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.chest.refresh.", getConfig().chest.baseRefreshDelayTicks);
} }
@Override @Override
@@ -24,12 +38,19 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
return QuickGui.create() return QuickGui.create()
.titleMini(title) .titleMini(title)
.rows(rows) .rows(rows)
.onGlobalClick((g, e) -> e.setCancelled( .onGlobalClick((g, e) -> {
shouldBlockClick(e.getCursor()) || boolean shouldCancel = shouldBlockClick(e.getCursor()) ||
shouldBlockClick(e.getCurrentItem()) || 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() .allowDrag()
.clickSound(null,0,0)
.onCreate((g, i) -> populateInventory(i)) .onCreate((g, i) -> populateInventory(i))
.onClose((g, e) -> close()) .onClose((g, e) -> close())
.build(); .build();
@@ -40,12 +61,42 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
populateInventory(getGui().getInventory()); populateInventory(getGui().getInventory());
} }
@Override private void resetItemDelay(int slot) {
protected long getTickDelay(Player player) { ItemDelayInfo info = itemDelays.get(slot);
return 1; 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) { private void populateInventory(Inventory inv) {
for (int row = 0; row < 6; row++) { for (int row = 0; row < 6; row++) {
@@ -55,7 +106,22 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
int leftIndex = rowStart + col; int leftIndex = rowStart + col;
int rightIndex = rowStart + 8 - col; int rightIndex = rowStart + 8 - col;
ItemStack leftItem = inv.getItem(leftIndex); 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;
} }
} }
} }

View File

@@ -13,7 +13,7 @@ import org.bukkit.inventory.ItemStack;
public class DupeGui implements DupeContext, CommonItems { 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 DupeInventoryGui inventoryGui = new DupeInventoryGui();
public final DupeChestGui chestGui = new DupeChestGui(); public final DupeChestGui chestGui = new DupeChestGui();
@@ -81,7 +81,7 @@ public class DupeGui implements DupeContext, CommonItems {
abstractDupeGui.getSession(player).getGui().open(player); abstractDupeGui.getSession(player).getGui().open(player);
} else { } else {
getVerbose().send("Creating new session for {0}",player.getName()); getVerbose().send("Creating new session for {0}",player.getName());
player.openInventory(abstractDupeGui.createSession(player).open()); player.openInventory(abstractDupeGui.getSession(player).open());
} }
} else { } else {
player.closeInventory(); player.closeInventory();

View File

@@ -4,6 +4,10 @@ import me.trouper.alias.server.systems.gui.QuickGui;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.Inventory; 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> { public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.InventorySession> {
@@ -12,10 +16,21 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
return new InventorySession(player); 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 { public class InventorySession extends AbstractDupeSession {
private Map<Integer, ItemDelayInfo> itemDelays = new HashMap<>();
private int delayTicks;
public InventorySession(Player owner) { public InventorySession(Player owner) {
super(owner, "<gradient:#cc22ff:#cc99ff><bold>YOUR INVENTORY</gradient>", 6); super(owner, "<gradient:#cc22ff:#cc99ff><bold>YOUR INVENTORY</gradient>", 6);
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.inventory.refresh.", getConfig().inventory.baseRefreshDelayTicks);
} }
@Override @Override
@@ -23,12 +38,19 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
return QuickGui.create() return QuickGui.create()
.titleMini(title) .titleMini(title)
.rows(rows) .rows(rows)
.onGlobalClick((g, e) -> e.setCancelled( .onGlobalClick((g, e) -> {
shouldBlockClick(e.getCursor()) || boolean shouldCancel = shouldBlockClick(e.getCursor()) ||
shouldBlockClick(e.getCurrentItem()) || 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() .allowDrag()
.clickSound(null,0,0)
.onCreate((g, i) -> populateInventory(getOwner(), i)) .onCreate((g, i) -> populateInventory(getOwner(), i))
.onClose((g, e) -> close()) .onClose((g, e) -> close())
.build(); .build();
@@ -39,35 +61,80 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
populateInventory(getOwner(), getGui().getInventory()); populateInventory(getOwner(), getGui().getInventory());
} }
@Override private void resetItemDelay(int slot) {
protected long getTickDelay(Player player) { ItemDelayInfo info = itemDelays.get(slot);
return 1; 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) { private void populateInventory(Player player, Inventory inv) {
EntityEquipment equipment = player.getEquipment();
for (int i = 0; i < 18; i++) { for (int i = 0; i < 18; i++) {
inv.setItem(i, EMPTY()); inv.setItem(i, EMPTY());
} }
inv.setItem(0, createPopulatedItem(equipment.getHelmet())); inv.setItem(0, getDelayedItem(0, player.getInventory().getHelmet()));
inv.setItem(1, createPopulatedItem(equipment.getChestplate())); inv.setItem(1, getDelayedItem(1, player.getInventory().getChestplate()));
inv.setItem(2, createPopulatedItem(equipment.getLeggings())); inv.setItem(2, getDelayedItem(2, player.getInventory().getLeggings()));
inv.setItem(3, createPopulatedItem(equipment.getBoots())); inv.setItem(3, getDelayedItem(3, player.getInventory().getBoots()));
inv.setItem(6, createPopulatedItem(equipment.getItemInOffHand())); inv.setItem(6, getDelayedItem(6, player.getInventory().getItemInOffHand()));
for (int i = 0; i < 9; i++) { 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++) { 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++) { 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++) { 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;
}
} }
} }
} }

View File

@@ -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();
}
}
}

View File

@@ -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<>();
}
}

View File

@@ -22,15 +22,18 @@ permissions:
default: op default: op
dupealias.final.bypass: dupealias.final.bypass:
description: Allows the bypassing of final item restrictions description: Allows the bypassing of final item restrictions
default: op
dupealias.protected.bypass: dupealias.protected.bypass:
description: Allows the bypassing of protected item restrictions description: Allows the bypassing of protected item restrictions
default: op
dupealias.unique.bypass: dupealias.unique.bypass:
description: Allows the duping of unique items description: Allows the duping of unique items
default: op
dupealias.dupe: dupealias.dupe:
description: Allows duplication of items through the command. description: Allows duplication of items through the command.
default: true default: true
children: children:
dupealias.dupe.cooldownbypass: op dupealias.dupe.cooldownbypass: false
dupealias.dupe.cooldownbypass: dupealias.dupe.cooldownbypass:
description: Bypass the cooldown for /dupe description: Bypass the cooldown for /dupe
default: op default: op
@@ -48,14 +51,16 @@ permissions:
description: The gui which lets you shift click copies of a single item. description: The gui which lets you shift click copies of a single item.
default: true default: true
children: 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: dupealias.gui.inventory:
description: The gui which shows your inventory and armor on top. description: The gui which shows your inventory and armor on top.
default: true default: true
children: 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: dupealias.gui.chest:
description: A gui which items can be put in and taken out as copies. description: A gui which items can be put in and taken out as copies.
default: true default: true
children: 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.