Added README.md and updated the global rule guis.

This commit is contained in:
wolf
2025-07-29 13:22:20 -05:00
parent cb223bbe0b
commit 402df54dcf
36 changed files with 1124 additions and 639 deletions

View File

@@ -1,128 +1,3 @@
# DupeAlias - An Advanced Dupe Plugin
**Make your server stand out by switching to DupeAlias, a powerful dupe plugin with niche features for unique servers.**
---
## 🌟 Why Choose DupeAlias?
DupeAlias isn't just another dupe plugin - it's a complete ecosystem for managing item behavior on your server. Whether you're running a creative build server, a unique survival experience, or a custom game mode. DupeAlias gives you unprecedented control over how items behave.
### ✨ Key Features
**🎯 Smart Item Tagging System**
- **UNIQUE** - Prevent specific items from being duplicated
- **FINAL** - Lock items against any modifications
- **INFINITE** - Create truly infinite resources that never run out
- **PROTECTED** - Make items completely inert and unusable
**🔧 Advanced Global Rules Engine**
- Create complex rules based on item properties
- Match by material, enchantments, name patterns, lore, and more
- Support for armor trims, potion effects, attributes, and custom model data
- Flexible matching modes (AND, OR, NAND, XOR)
**🖥️ Multiple Duplication Interfaces**
- **Replicator GUI** - Single-item duplication with visual feedback
- **Chest GUI** - Multi-item container-style duplication
- **Inventory GUI** - Mirror your entire inventory for easy access
- **Menu GUI** - Central hub for all duplication options
**⚡ Performance & Customization**
- Per-permission refresh rates and cooldowns
- Extensive configuration options
- Session persistence (optional)
- Beautiful, modern GUIs with progress indicators
---
## 🎮 Perfect For These Server Types
- **Creative Servers** - Give builders infinite blocks while protecting special items
- **Survival+** - Create unique economies with controlled item flow
- **Minigames** - Provide infinite consumables while preventing exploitation
- **RPG Servers** - Protect quest items and create unbreakable gear
- **Prison Servers** - Control contraband while allowing resource flow
---
## 📸 Screenshots
*[Image: Main admin panel showing the clean, modern interface with gradient backgrounds and intuitive navigation]*
*[Image: Global rules editor displaying the complex criteria system with material selection, enchantment matching, and tag application]*
*[Image: Replicator GUI in action showing the animated rings, progress bars, and real-time item duplication]*
*[Image: Held item management interface demonstrating individual tag application with conflict warnings]*
*[Image: Configuration menu showcasing the extensive customization options and color-coded settings]*
---
## ⚙️ Quick Setup
1. Drop `DupeAlias.jar` into your plugins folder
2. Restart your server
3. Use `/da` to open the admin panel
4. Configure your global rules and permissions
5. Let your players use `/dupe` to start duplicating!
---
## 🔑 Permissions Overview
- `dupealias.admin` - Access to admin panel and configuration
- `dupealias.dupe` - Basic duplication command access
- `dupealias.gui.*` - Access to specific GUI types
- `dupealias.*.bypass` - Bypass tag restrictions (use carefully!)
- Permission-based refresh rates: `dupealias.gui.replicator.refresh.1`
---
## 💡 Advanced Use Cases
**Crate Key Protection**
Create a global rule that makes all items containing "key" in their name both UNIQUE and PROTECTED, preventing duplication and accidental use.
**Infinite Building Materials**
Set up INFINITE tags on common building blocks, giving your builders unlimited resources while maintaining server economy balance.
**Quest Item Security**
Use FINAL tags on story items to prevent players from renaming or modifying important quest objects.
**Admin Tool Management**
Combine PROTECTED and UNIQUE tags to create admin-only items that can't be duplicated or used by regular players.
---
## 🛠️ Developer-Friendly
Built on the robust Alias Development Framework with:
- Clean, documented API
- Event-driven architecture
- Extensive configuration options
- JSON-based data storage
- Full backward compatibility
---
## 📋 Requirements
- **Minecraft Version**: 1.21+
- **Server Software**: Paper, Purpur, or compatible
- **Java Version**: 17+
---
## 🤝 Support & Updates
DupeAlias is actively maintained with regular updates and feature additions. Join our community for support, suggestions, and to see what's coming next!
**Get DupeAlias today and revolutionize how items work on your server!**
---
# DupeAlias Documentation
## Table of Contents
@@ -135,17 +10,15 @@ DupeAlias is actively maintained with regular updates and feature additions. Joi
6. [Permissions System](#permissions-system)
7. [Configuration](#configuration)
8. [Commands](#commands)
9. [Common Scenarios](#common-scenarios)
10. [Troubleshooting](#troubleshooting)
9. [Troubleshooting](#troubleshooting)
---
## Installation & Setup
### Prerequisites
- Minecraft 1.21 or higher
- Paper, Purpur, or compatible server software
- Java 17 or higher
- Minecraft 1.21.5
- Paper server software
- Java 21 or higher
### Installation Steps
1. Download the DupeAlias JAR file
@@ -160,7 +33,6 @@ DupeAlias is actively maintained with regular updates and feature additions. Joi
4. Set up your first global rules or start tagging items manually
---
## Core Concepts
### Item Tags
@@ -179,7 +51,6 @@ Individual item tags **always override** global rules. This allows for fine-grai
- **Global Rules**: Server-wide rules that apply tags based on item properties like material, name, enchantments, etc.
---
## Item Tags System
### UNIQUE Tag
@@ -191,13 +62,9 @@ Individual item tags **always override** global rules. This allows for fine-grai
- Admin-only equipment
- Currency items
**Conflicts**: Cannot be combined with INFINITE (creates a logical paradox)
**Conflicts**: Cannot be combined with INFINITE.
**Example**: A crate key that should never be duplicated
```
Individual: Right-click key in hand → Apply UNIQUE tag
Global: All items with "key" in name → Apply UNIQUE tag
```
### FINAL Tag
**Purpose**: Prevents any modification to the item
@@ -214,12 +81,6 @@ Global: All items with "key" in name → Apply UNIQUE tag
- Rank kits that shouldn't be modified
- Event rewards with special formatting
**Example**: A quest sword that must keep its name
```
Individual: Apply FINAL tag to specific sword
Global: All items with "Quest" in lore → Apply FINAL tag
```
### INFINITE Tag
**Purpose**: Maintains maximum stack size and refills items
@@ -230,17 +91,11 @@ Global: All items with "Quest" in lore → Apply FINAL tag
**Use Cases**:
- Creative-style building materials
- Infinite arrows for archery ranges
- "Infinity" enchantment for tipped arrows
- Unlimited consumables for events
**Conflicts**: Cannot be combined with UNIQUE or PROTECTED
**Example**: Infinite building blocks for creative areas
```
Individual: Apply INFINITE to held stone blocks
Global: All concrete blocks → Apply INFINITE tag
```
### PROTECTED Tag
**Purpose**: Makes items completely inert and unusable
@@ -260,14 +115,7 @@ Global: All concrete blocks → Apply INFINITE tag
**Conflicts**: Cannot be combined with INFINITE
**Example**: A decorative trophy that can't be used
```
Individual: Apply PROTECTED to specific trophy
Global: All items with "Display" in name → Apply PROTECTED tag
```
---
## Global Rules Engine
### Creating Global Rules
@@ -289,92 +137,64 @@ Select which tags this rule will apply to matching items. Multiple tags can be s
- **NAND**: Not all criteria match
- **XOR**: Exactly one criteria matches
Note that if no criteria are selected, any material or ItemsAdder item will match.
#### Material Matching
- **IGNORE**: Apply to all materials
- **WHITELIST**: Only apply to selected materials
- **BLACKLIST**: Apply to all except selected materials
- **IGNORE**: Apply to all materials and ItemsAdder items
- **WHITELIST**: Only apply to selected materials and ItemsAdder items
- **BLACKLIST**: Apply to all except selected materials and ItemsAdder items
### Criteria Types
#### Name Regex
Match items based on display name patterns using regular expressions.
```
Example: ".*[Kk]ey.*" matches any item with "key" or "Key" in the name
```
``` Example: ".*[Kk]ey.*" matches any item with "key" or "Key" in the name ```
#### Lore Regex
Match items based on lore content using regular expressions.
```
Example: ".*Special.*" matches items with "Special" anywhere in lore
```
``` Example: ".*Special.*" matches items with "Special" anywhere in lore ```
#### Compound Tag Regex
Match an item based on its compound tag generated from `ItemStack#getAsComponentString()`
```Example: ".*(nutrition).*|.*(saturation).*" Matches any custom food item```
#### NBT Tag Regex
Match an item based on its NBT tag generated from `ItemStack#getAsString()`
```Example: .*{dupenotallowed: 1b}.* matches items from DupePlus's blacklist```
#### Enchantments
Match items that have specific enchantments at minimum levels.
```
Example: Sharpness V → matches items with Sharpness 5 or higher
```
``` Example: Sharpness V → matches items with Sharpness 5 or higher ```
#### Attributes
Match items with specific attribute modifiers.
```
Example: Attack Damage ≥ 10.0 → matches weapons with high damage
```
``` Example: Attack Damage ≥ 10.0 → matches weapons with high damage ```
#### Potion Effects
Match potions/foods with specific effects and amplifiers.
```
Example: Strength II → matches items giving Strength 2 or higher
```
``` Example: Strength II → matches items giving Strength 2 or higher ```
#### Model Data
Match items with specific custom model data values.
```
Example: 12345 → matches items with CustomModelData: 12345
```
``` Example: 12345 → matches items with CustomModelData: 12345 ```
#### Item Flags
Match items with specific visibility flags.
```
Example: HIDE_ENCHANTS → matches items that hide enchantments
```
``` Example: HIDE_ENCHANTS → matches items that hide enchantments ```
#### Armor Trim
Match armor pieces with specific trim patterns or materials.
```
Example: Silence Pattern + Gold Material → matches gold silence trim armor
```
``` Example: Silence Pattern + Gold Material → matches gold silence trim armor ```
### Example Rules
#### Protect All Crate Keys
```
Applied Tags: UNIQUE, PROTECTED, FINAL
Match Mode: OR
Name Regex: ".*[Kk]ey.*"
Lore Regex: ".*[Cc]rate.*"
```
``` Applied Tags: UNIQUE, PROTECTED, FINAL Match Mode: OR NBT Tag Regex: ".*excellentcrates.*" Lore Regex: ".*[Cc]rate.*" ```
#### Make Netherite Gear Unmodifiable
```
Applied Tags: FINAL
Match Mode: AND
Material Mode: WHITELIST
Materials: NETHERITE_SWORD, NETHERITE_AXE, NETHERITE_PICKAXE, etc.
```
``` Applied Tags: FINAL Match Mode: AND Material Mode: WHITELIST Materials: NETHERITE_SWORD, NETHERITE_AXE, NETHERITE_PICKAXE, etc. ```
#### Infinite Creative Blocks
```
Applied Tags: INFINITE
Match Mode: AND
Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
```
``` Applied Tags: INFINITE Match Mode: AND Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants ```
---
## Duplication GUIs
### Replicator GUI
**Access**: `/dupe replicator` or through main menu
**Permissions**:
- `dupealias.gui.replicator`: Main Access
- `dupealias.gui.replicator.cooldown.<integer>`: Sets the input swap cooldown time (milliseconds).
- `dupealias.gui.replicator.refresh.<integer>`: Sets the amount of ticks it takes for the output item to restock.
- `dupealias.gui.replicator.keep`: Determines if the player should keep the items in the chest when they close it.
**Features**:
- Single-item focused duplication
@@ -392,12 +212,17 @@ Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
### Chest GUI
**Access**: `/dupe chest` or through main menu
**Permissions**:
- `dupealias.gui.chest`: Main Access
- `dupealias.gui.chest.cooldown.<integer>`: Sets the input swap cooldown time (milliseconds).
- `dupealias.gui.chest.keep`: Determines if the player should keep the items in the chest when they close it.
- `dupealias.gui.chest.keepondeath`: If set to false, all items in the chest will be dropped on the ground on death.
**Features**:
- Multi-item container interface
- 4 input columns, 4 output columns
- Individual item refresh timers
- Session persistence (if enabled)
- Session persistence (doesn't persist over reboots)
**How to Use**:
1. Open the GUI
@@ -405,10 +230,13 @@ Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
3. Take duplicated copies from the right 4 columns
4. Items refresh based on configured delays
**Best For**: Bulk duplication of multiple item types
**Best For**: Duplication of items you'll commonly need through session persistence.
### Inventory GUI
**Access**: `/dupe inventory` or through main menu
**Permissions**:
- `dupealias.gui.chest`: Main Access
- `dupealias.gui.chest.refresh.<integer>`: Sets the amount of ticks for the output items to restock
**Features**:
- Mirror of your actual inventory
@@ -426,6 +254,7 @@ Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
### Menu GUI
**Access**: `/dupe gui` or `/dupe` (if set as default)
**Permission**: `dupealias.gui` If granted, all other GUIs will be granted too unless specifically set to false.
**Features**:
- Central hub for all GUI types
@@ -438,7 +267,6 @@ Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
3. Access is controlled by permissions
---
## Permissions System
### Core Permissions
@@ -447,20 +275,18 @@ Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
```yaml
dupealias.admin: true # Access to admin panel and configuration
```
#### Basic Duplication
```yaml
dupealias.dupe: true # Access to /dupe command
dupealias.dupe.cooldown.<integer>: false # Cooldown for command duping (milliseconds)
```
#### GUI Access
```yaml
dupealias.gui: true # Access to all GUIs
dupealias.gui.replicator: true # Access to replicator GUI
dupealias.gui.inventory: true # Access to inventory GUI dupealias.gui.chest: true # Access to chest GUI
dupealias.gui.inventory: true # Access to inventory GUI
dupealias.gui.chest: true # Access to chest GUI
```
### Advanced Permissions
#### Session Persistence
@@ -469,7 +295,6 @@ dupealias.gui.replicator.keep: false # Keep replicator items on close
dupealias.gui.chest.keep: false # Keep chest items on close
dupealias.gui.chest.keepondeath: false # Keep chest items on death
```
#### Permission Based Refresh Rates
```yaml
dupealias.gui.replicator.refresh.<integer>: false # Ticks of refresh cooldown
@@ -477,22 +302,19 @@ dupealias.gui.replicator.cooldown.<integer>: false # Ticks of re-input coold
dupealias.gui.inventory.refresh.<integer>: false # Ticks of refresh cooldown
dupealias.gui.chest.refresh.<integer>: false # Ticks of refresh cooldown
```
#### Tag Bypasses (Use Carefully!)
```yaml
dupealias.unique.bypass: false # Can dupe UNIQUE items
dupealias.final.bypass: false # Can modify FINAL items
dupealias.protected.bypass: false # Can use PROTECTED items
```
#### Special Permissions
```yaml
dupealias.infinite: true # Can use INFINITE items
```
### Permission Hierarchy
The plugin uses a "lowest value wins" system for numeric permissions. If a player has both `refresh.20` and `refresh.5`, they will get the 5-tick refresh cooldown.
The plugin generally uses a "lowest value wins" system for numeric permissions. If a player has both `refresh.20` and `refresh.5`, they will get the 5-tick refresh cooldown. There is an exception to this rule however. For the permission `dupealias.dupe.limit.<integer>`, it will take the highest value given. This is because I am lazy and did not add a default limit for non ranked players.
### Example Permission Sets
@@ -501,23 +323,23 @@ The plugin uses a "lowest value wins" system for numeric permissions. If a playe
groups:
vip:
permissions:
- dupealias.dupe - dupealias.gui - dupealias.gui.replicator.refresh.5 # Faster refresh
- dupealias.dupe
- dupealias.gui
- dupealias.gui.replicator.refresh.5 # Faster refresh
- dupealias.gui.replicator.cooldown.10 # Shorter cooldown
- dupealias.dupe.cooldown.0 # No command cooldown
```
#### Staff Member
```yaml
groups:
staff:
permissions: - dupealias.admin # Full admin access
permissions:
- dupealias.admin # Full admin access
- dupealias.gui.replicator.refresh.1 # Instant refresh
- dupealias.gui.*.keep # Session persistence
- dupealias.final.bypass # Can modify final items
```
---
## Configuration
### Main Configuration (`config.json`)
@@ -529,29 +351,36 @@ groups:
"defaultDupeGui": "REPLICATOR" // Default GUI (REPLICATOR/INVENTORY/CHEST/MENU)
}
```
#### GUI Refresh Rates
```json
{
"replicator": {
"baseRefreshDelayTicks": 1, // Base item refresh delay
"baseInputCooldownTicks": 20 // Base input change cooldown
}, "chest": {
},
"chest": {
"baseRefreshDelayTicks": 1 // Base item refresh delay
},
"inventory": {
"baseRefreshDelayTicks": 1 // Base item refresh delay
}}
}
}
```
#### Command Blocking
```json
{
"finalCommandRegex": [
"\"(?:itemname|iname)\"gmi", // Block item naming commands "\"(?:itemlore|lore)\"gmi" // Block lore modification commands ]}
"\"(?:itemname|iname)\"gmi", // Block item naming commands
"\"(?:itemlore|lore)\"gmi" // Block lore modification commands
]
}
```
#### Tag Lore Customization (MiniMessage)
```json
{
"trueTagLore": {
@@ -567,11 +396,13 @@ groups:
"PROTECTED": "<dark_purple><bold>|</bold><light_purple> Unprotected"
}
}
```
### Common Configuration (`common.json`)
#### Visual Customization
```json
{
"mainColor": 11184895, // Primary color (hex: AAAAFF)
@@ -580,18 +411,19 @@ groups:
"flatPrefix": "&9DupeAlias> &7", // Legacy chat prefix
"flat": false // Use legacy formatting
}
```
#### Debug Settings
```json
{
"debugMode": false, // Enable debug output
"debuggerExclusions": [] // Methods to exclude from debug
}
```
---
## Commands
### Administrative Commands
@@ -678,26 +510,19 @@ groups:
### Debug Mode
Enable debug mode to troubleshoot issues:
```
/da debug toggle
```
Enable debug mode to troubleshoot issues: ``` /da debug toggle ```
This will show detailed information about:
- Tag checking processes
- Global rule matching
- Permission calculations
- GUI state changes
Exclude noisy methods:
```
/da debug exclude <method_name>
```
Exclude noisy methods: ``` /da debug exclude <method_name> ```
### Performance Considerations
#### Large Player Counts
- Use session persistence sparingly
- Don't use GUI refresh or input cooldowns.
- Monitor server TPS with `/tps`
#### Complex Global Rules
@@ -706,19 +531,17 @@ Exclude noisy methods:
- Limit the number of active global rules
#### GUI Optimization
- Set appropriate refresh rates based on server performance
- Consider disabling session persistence for busy servers
- Use cooldowns to prevent spam
---
### Getting Help
If you encounter issues not covered in this documentation:
1. Enable debug mode and check console logs
2. Test with a minimal permission set
3. Verify your global rules are correctly configured
4. Check for conflicts with other plugins
2. Verify your global rules are correctly configured
3. Check for conflicts with other plugins
4. Join the [Alias Development](https://trouper.me/alias) discord
Remember that individual item tags always override global rules, and bypass permissions should be used carefully as they can compromise your server's item security system.

View File

@@ -1 +1 @@
Bulk item tag adder
Messages and Language Configuration File

87
README.md Normal file
View File

@@ -0,0 +1,87 @@
# DupeAlias - An Advanced Dupe Plugin
**Make your server stand out by switching to DupeAlias, a powerful dupe plugin with niche features for unique servers.**
---
## Why Choose DupeAlias?
DupeAlias gives you unprecedented control over how items can be copied or duplicated on your server. Configurable through a sleek in-game GUI, DupeAlias provides a friction-free administration experience.
### Key Features
**Smart Item Tagging System**
- **UNIQUE** - Prevent specific items from being duplicated
- **FINAL** - Lock items against any modifications
- **INFINITE** - Create stacks that never run out
- **PROTECTED** - Make items completely inert and unusable
**Advanced Global Rules Engine**
- Create complex rules based on item properties
- Match by material, enchantments, name patterns, lore, attributes, and more
- Support for ItemsAdder
- Flexible matching modes (AND, OR, NAND, XOR)
**Multiple Duplication Interfaces**
- **Replicator GUI** - Single-item duplication with visual feedback
- **Chest GUI** - Multi-item container-style duplication
- **Inventory GUI** - Mirror your entire inventory for easy access
- **Menu GUI** - Central hub for all duplication options
**Performance & Customization**
- Per-permission refresh rates and cooldowns
- Extensive configuration options
- Session persistence (optional)
- Beautiful, modern GUIs with progress indicators
---
## Perfect For These Server Types
- **Survival/Creative** - Give players infinite building blocks while protecting valuable items
- **Dupe Servers** - Create economies in an otherwise chaotic server class
- **Minigames** - Provide infinite consumables while preventing exploitation
- **RPG Servers** - Protect quest items and create unbreakable gear (You don't *have* to use the dupe feature!)
---
## Screenshots
The main Admin Panel, click on the green book for a quick guide on how to use the plugin.
![Main Admin Gui](images/maingui.gif)
The Replicator GUI is a great choice for the default dupe GUI. It is clean, simple, and you have good control over how its used.
![Replicator Dupe Gui](images/replicator.gif)
The Global Rule Editor is designed to be intuitive to use, with quick buttons for changing match modes and toggling tags.
![Rule Manager Gui](images/rules.gif)
Want to know how an item is processed? Head over to the help GUI and hover on you held item's icon. An explanation will be generated for you.
![Conflict Detection](images/conflicts.gif)
---
## Quick Setup
1. Drop `DupeAlias.jar` into your plugins folder
2. Restart your server
3. Use `/da` to open the admin panel
4. Configure your global rules and permissions
5. Let your players use `/dupe` to start duplicating!
---
## Permissions Overview
- `dupealias.admin` - Access to admin panel and configuration
- `dupealias.dupe` - Basic duplication command access
- `dupealias.gui.*` - Access to specific GUI types
- `dupealias.*.bypass` - Bypass tag restrictions (use carefully!)
- Permission-based refresh rates: `dupealias.gui.replicator.refresh.1`
---
## Use Cases
**Crate Key Protection** Create a global rule that makes all items containing `"excellentcrates:crate_key.id"` in their NBT tag, UNIQUE, FINAL, and PROTECTED, preventing duplication and accidental use.
**Infinite Building Materials** Set up INFINITE tags on common building blocks, giving your players unlimited resources for creative purposes.
**Rank Kit Exclusivity** Combine both FINAL and UNIQUE tags on rank crates to prevent ranked players from modifying and redistributing exclusive branded kits.
**Admin Tool Management** Add the UNIQUE tag to admin-only items so they cannot be duplicated by regular players.
---
## Requirements
- **Minecraft Version**: 1.21.5
- **Server Software**: Paper
- **Java Version**: 21+
## Documentation
- [Documentation is available in Documentation.md](Documentation.md)

View File

@@ -16,11 +16,16 @@ java {
repositories {
mavenLocal()
maven {
name = "matteodev"
url = uri("https://maven.devs.beer/")
}
}
dependencies {
paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT")
implementation("me.trouper:alias:1.0-1.21.5-SNAPSHOT")
compileOnly("dev.lone:api-itemsadder:4.0.10")
}
tasks {

BIN
images/conflicts.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
images/maingui.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
images/replicator.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
images/rules.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

View File

@@ -2,6 +2,7 @@ package me.trouper.dupealias.data;
import me.trouper.alias.data.enums.*;
import me.trouper.alias.utils.misc.MapUtils;
import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.server.ItemTag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@@ -21,7 +22,7 @@ import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class GlobalRule {
public class GlobalRule implements DupeContext {
public enum MatchMode {
AND, OR, NAND, XOR
@@ -36,9 +37,12 @@ public class GlobalRule {
public MatchMode matchMode = MatchMode.AND;
public MaterialMatchMode materialMode = MaterialMatchMode.IGNORE;
public Set<Material> effectedMaterials = EnumSet.noneOf(Material.class);
public List<ItemsAdderItem> effectedItemsAdderMaterials = new ArrayList<>();
public String nameContainsRegex = "";
public String loreContainsRegex = "";
public String compoundTagContainsRegex = "";
public String nbtTagContainsRegex = "";
public Set<Integer> legacyModelData = new HashSet<>();
public Set<ItemFlag> itemFlags = EnumSet.noneOf(ItemFlag.class);
public Map<ValidEnchantment, Integer> enchantments = new HashMap<>();
@@ -51,14 +55,19 @@ public class GlobalRule {
@SuppressWarnings("deprecation")
public boolean doesMatch(ItemStack item) {
if (item == null || item.getType() == Material.AIR) return false;
if (item == null || item.isEmpty()) return false;
ItemsAdderItem iai = new ItemsAdderItem();
try {
iai = new ItemsAdderItem(item);
} catch (IllegalArgumentException ignored) {}
switch (materialMode) {
case WHITELIST -> {
if (!effectedMaterials.contains(item.getType())) return false;
if (!effectedMaterials.contains(item.getType()) && !effectedItemsAdderMaterials.contains(iai)) return false;
}
case BLACKLIST -> {
if (effectedMaterials.contains(item.getType())) return false;
if (effectedMaterials.contains(item.getType()) || effectedItemsAdderMaterials.contains(iai)) return false;
}
case IGNORE -> {}
}
@@ -66,6 +75,7 @@ public class GlobalRule {
ItemMeta meta = item.getItemMeta();
if (meta == null) return false;
List<Boolean> results = new ArrayList<>();
if (!nameContainsRegex.isEmpty()) {
@@ -82,6 +92,18 @@ public class GlobalRule {
results.add(found);
}
if (!compoundTagContainsRegex.isEmpty()) {
Pattern compoundPatten = safeCompileRegex(compoundTagContainsRegex);
String compound = meta.getAsComponentString();
results.add(compoundPatten.matcher(compound).find());
}
if (!nbtTagContainsRegex.isEmpty()) {
Pattern nbtPattern = safeCompileRegex(compoundTagContainsRegex);
String nbt = meta.getAsString();
results.add(nbtPattern.matcher(nbt).find());
}
if (!enchantments.isEmpty()) {
Map<Enchantment, Integer> itemEnchants = item.getEnchantments();
results.add(MapUtils.allValuesMatch(itemEnchants, enchantments.entrySet().stream().collect(Collectors.toMap(
@@ -135,6 +157,8 @@ public class GlobalRule {
int trueCount = (int) results.stream().filter(Boolean::booleanValue).count();
int total = results.size();
if (getCriteriaCount() == 0) return true;
return switch (matchMode) {
case AND -> trueCount == total;
case OR -> trueCount > 0;
@@ -156,11 +180,13 @@ public class GlobalRule {
int criteriaCount = 0;
if (!nameContainsRegex.isEmpty()) criteriaCount++;
if (!loreContainsRegex.isEmpty()) criteriaCount++;
if (!nbtTagContainsRegex.isEmpty()) criteriaCount++;
if (!compoundTagContainsRegex.isEmpty()) criteriaCount++;
if (!legacyModelData.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,90 @@
package me.trouper.dupealias.data;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.Map;
import java.util.Objects;
public class ItemsAdderItem {
public final String namespace;
public final String id;
private static final Gson GSON = new Gson();
public ItemsAdderItem() {
this.namespace = null;
this.id = null;
}
@SuppressWarnings("unchecked")
public ItemsAdderItem(ItemStack itemStack) throws IllegalArgumentException {
if (itemStack == null) {
throw new IllegalArgumentException("ItemStack provided cannot be null.");
}
ItemMeta itemMeta = itemStack.getItemMeta();
if (itemMeta == null) {
throw new IllegalArgumentException("ItemStack has no ItemMeta. It cannot be an ItemsAdder item.");
}
String itemMetaJson = itemMeta.getAsString();
if (itemMetaJson.trim().isEmpty()) {
throw new IllegalArgumentException("ItemMeta.getAsString() returned null or an empty string. No NBT data found.");
}
Map<String, Object> components;
try {
components = GSON.fromJson(itemMetaJson, Map.class);
} catch (JsonSyntaxException e) {
throw new IllegalArgumentException("Failed to parse ItemMeta JSON string: " + e.getMessage());
}
Object customDataObj = components.get("minecraft:custom_data");
if (!(customDataObj instanceof Map)) {
throw new IllegalArgumentException("Missing or invalid 'minecraft:custom_data' component in ItemStack NBT. Expected a JSON object.");
}
Map<String, Object> customData = (Map<String, Object>) customDataObj;
Object itemsAdderObj = customData.get("itemsadder");
if (!(itemsAdderObj instanceof Map)) {
throw new IllegalArgumentException("Missing or invalid 'itemsadder' object within 'minecraft:custom_data'. Expected a JSON object.");
}
Map<String, Object> itemsAdderData = (Map<String, Object>) itemsAdderObj;
Object namespaceObj = itemsAdderData.get("namespace");
Object idObj = itemsAdderData.get("id");
if (!(namespaceObj instanceof String) || ((String) namespaceObj).trim().isEmpty()) {
throw new IllegalArgumentException("Missing, invalid, or empty 'namespace' field in ItemsAdder custom data. Expected a non-empty string.");
}
this.namespace = (String) namespaceObj;
if (!(idObj instanceof String) || ((String) idObj).trim().isEmpty()) {
throw new IllegalArgumentException("Missing, invalid, or empty 'id' field in ItemsAdder custom data. Expected a non-empty string.");
}
this.id = (String) idObj;
}
@Override
public String toString() {
return "ItemsAdderItem{namespace='" + namespace + "', id='" + id + "'}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ItemsAdderItem that = (ItemsAdderItem) o;
return Objects.equals(namespace, that.namespace) &&
Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(namespace, id);
}
}

View File

@@ -6,7 +6,10 @@ import me.trouper.dupealias.data.GlobalRule;
import me.trouper.dupealias.server.ItemTag;
import java.io.File;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DupeConfig implements JsonSerializable<DupeConfig>, DupeContext {
@Override
@@ -37,6 +40,8 @@ public class DupeConfig implements JsonSerializable<DupeConfig>, DupeContext {
ItemTag.INFINITE, "<dark_green><bold>|</bold><green> Finite"
));
public boolean blockDupePlus = false;
public List<GlobalRule> globalRules = new ArrayList<>();
public Replicator replicator = new Replicator();

View File

@@ -14,7 +14,9 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.persistence.PersistentDataType;
import java.util.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class DupeManager implements DupeContext {
@@ -77,7 +79,9 @@ public class DupeManager implements DupeContext {
* Checks if any global rule applies this tag to the given item
*/
public boolean checkGlobalRuleTag(ItemStack input, ItemTag tag) {
getVerbose().send("Checking tag {0} on item {1}",tag,input.getType());
for (GlobalRule rule : getConfig().globalRules) {
getVerbose().send("Scanning rule with tags {0}",rule.appliedTags.toString());
if (rule.appliedTags.contains(tag) && rule.doesMatch(input)) {
return true;
}
@@ -122,12 +126,11 @@ public class DupeManager implements DupeContext {
* 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
return false;
}
}
@@ -236,8 +239,8 @@ public class DupeManager implements DupeContext {
throw new IllegalArgumentException("Invalid NameSpacedKey '%s'".formatted(key.value()));
}
public int getPermissionValue(Player player, String rootPermission, int fallback) {
int lowestCooldown = Integer.MAX_VALUE;
public int getPermissionValue(Player player, String rootPermission, int fallback, boolean takeHighest) {
int result = takeHighest ? Integer.MIN_VALUE : Integer.MAX_VALUE;
for (PermissionAttachmentInfo permInfo : player.getEffectivePermissions()) {
String perm = permInfo.getPermission();
@@ -246,13 +249,24 @@ public class DupeManager implements DupeContext {
String valueStr = perm.substring(rootPermission.length());
try {
int value = Integer.parseInt(valueStr);
if (value < lowestCooldown) {
lowestCooldown = value;
if (takeHighest) {
if (value > result) {
result = value;
}
} else {
if (value < result) {
result = value;
}
}
} catch (NumberFormatException ignored) {}
}
}
return (lowestCooldown == Integer.MAX_VALUE) ? fallback : lowestCooldown;
if (takeHighest) {
return (result == Integer.MIN_VALUE) ? fallback : result;
} else {
return (result == Integer.MAX_VALUE) ? fallback : result;
}
}
}

View File

@@ -7,6 +7,7 @@ import me.trouper.alias.server.commands.QuickCommand;
import me.trouper.alias.server.commands.completions.CompletionBuilder;
import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.data.GlobalRule;
import me.trouper.dupealias.data.ItemsAdderItem;
import me.trouper.dupealias.server.ItemTag;
import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
import me.trouper.dupealias.server.gui.admin.MainAdminGui;
@@ -49,6 +50,10 @@ public class AdminCommand implements QuickCommand, DupeContext {
handleRule(sender,args);
}
case "test" -> {
handleDebugTests(sender,args);
}
default -> {
errorAny(sender,"Invalid subcommand!");
}
@@ -93,13 +98,20 @@ public class AdminCommand implements QuickCommand, DupeContext {
)
).then(
b.arg("gui")
).then(
b.arg("test")
.then(
b.arg("nbt","component","itemsadder")
)
);
}
private void handleDebug(CommandSender sender, Args args) {
if (args.getSize() < 2) {
errorAny(sender, "Usage: debug <toggle|include|exclude>");
errorAny(sender, "Usage: debug <toggle|include|exclude|test>");
return;
}
@@ -141,6 +153,43 @@ public class AdminCommand implements QuickCommand, DupeContext {
successAny(sender, "Removed exclusion for {0} on the debugger.", exclusion);
}
case "test" -> handleDebugTests(sender, args);
}
}
private void handleDebugTests(CommandSender sender, Args args) {
if (args.getSize() < 2) {
errorAny(sender, "Usage: test <nbt|component>");
return;
}
final String sub = args.get(1).toString();
switch (sub) {
case "nbt" -> {
Player player = (Player) sender;
String tag = player.getInventory().getItemInMainHand().getItemMeta().getAsString();
infoAny(player,"The item you are holding has a visible NBT String of {0} to the plugin.", tag);
getInstance().getLogger().info(tag);
}
case "component" -> {
Player player = (Player) sender;
String component = player.getInventory().getItemInMainHand().getItemMeta().getAsComponentString();
infoAny(player,"The item you are holding has a visible Component String of {0} to the plugin.", component);
getInstance().getLogger().info(component);
}
case "itemsadder" -> {
Player player = (Player) sender;
ItemsAdderItem item;
try {
item = new ItemsAdderItem(player.getInventory().getItemInMainHand());
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
errorAny(player,"That item is not being detected as from ItemsAdder.");
return;
}
successAny(player,"Your item has an ID of {0} in the namespace of {1}.",item.id,item.namespace);
}
}
}

View File

@@ -2,7 +2,6 @@ package me.trouper.dupealias.server.commands;
import me.trouper.alias.server.commands.Args;
import me.trouper.alias.server.commands.CommandRegistry;
import me.trouper.alias.server.commands.Permission;
import me.trouper.alias.server.commands.QuickCommand;
import me.trouper.alias.server.commands.completions.CompletionBuilder;
import me.trouper.alias.utils.misc.Cooldown;
@@ -84,7 +83,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
return false;
}
int playerMax = getDupe().getPermissionValue(player,"dupealias.dupe.limit.",Integer.MAX_VALUE);
int playerMax = getDupe().getPermissionValue(player,"dupealias.dupe.limit.",Integer.MAX_VALUE,true);
if (amount > playerMax) {
warningAny(player,"Your maximum permitted dupe amplifier is {0}!", playerMax);
return false;
@@ -109,7 +108,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
dupeStack(player,toDupe,amount);
int playerCooldown = getDupe().getPermissionValue(player,"dupealias.dupe.cooldown.",getConfig().baseDupeCooldownMillis);
int playerCooldown = getDupe().getPermissionValue(player,"dupealias.dupe.cooldown.",getConfig().baseDupeCooldownMillis,false);
dupeCooldown.setCooldown(player.getUniqueId(), playerCooldown);
return true;

View File

@@ -2,17 +2,12 @@ package me.trouper.dupealias.server.functions;
import me.trouper.dupealias.server.ItemTag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
public class UniqueCheck implements Check<ItemStack> {
@Override
public boolean passes(ItemStack input) {
boolean globallyUnique = getDupe().checkGlobalRuleTag(input,ItemTag.UNIQUE);
boolean set = input.hasItemMeta() && input.getPersistentDataContainer().has(ItemTag.UNIQUE.getKey());
boolean individuallyUnique = Boolean.TRUE.equals(input.getPersistentDataContainer().get(ItemTag.UNIQUE.getKey(), PersistentDataType.BOOLEAN));
if (set && individuallyUnique) return false;
if (!set && globallyUnique) return false;
boolean isUnique = getDupe().checkEffectiveTag(input,ItemTag.UNIQUE);
if (isUnique) return false;
return new ItemInventoryCheck(this).passes(input);
}

View File

@@ -5,13 +5,9 @@ import me.trouper.alias.utils.ItemBuilder;
import me.trouper.dupealias.DupeAlias;
import me.trouper.dupealias.DupeContext;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.type.Light;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockStateMeta;
import org.bukkit.persistence.PersistentDataType;
public interface CommonItems extends DupeContext {

View File

@@ -1,20 +1,25 @@
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.data.ItemsAdderItem;
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 me.trouper.dupealias.server.gui.admin.globalrule.GlobalRuleEditorGui;
import me.trouper.dupealias.server.gui.admin.globalrule.GlobalRuleListGui;
import me.trouper.dupealias.server.gui.admin.globalrule.criteria.*;
import me.trouper.dupealias.server.gui.admin.globalrule.criteria.regex.*;
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;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class AdminPanelManager implements DupeContext, CommonItems {
@@ -50,73 +55,6 @@ public class AdminPanelManager implements DupeContext, CommonItems {
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);
}
@@ -125,44 +63,28 @@ public class AdminPanelManager implements DupeContext, CommonItems {
new GlobalRuleArmorTrimEditor(this, rule).open(player);
}
public void openNameCriteriaEditor(Player player, GlobalRule rule) {
new GlobalRuleNameEditor(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");
new GlobalRuleLoreEditor(this,rule).open(player);
}
})
.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);
public void openNbtCriteriaEditor(Player player, GlobalRule rule) {
new GlobalRuleNbtTagEditor(this,rule).open(player);
}
})
.build();
gui.open(player);
public void openCompoundCriteriaEditor(Player player, GlobalRule rule) {
new GlobalRuleCompoundTagEditor(this,rule).open(player);
}
public void openModelDataEditor(Player player, GlobalRule rule) {
new GlobalRuleModelDataEditor(this,rule).open(player);
}
public void openItemsAdderParser(Player player, GlobalRule rule) {
new GlobalRuleItemsAdderParser(this,rule).open(player);
}
public void openEnchantmentEditor(Player player, GlobalRule rule) {
@@ -177,51 +99,6 @@ public class AdminPanelManager implements DupeContext, CommonItems {
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)
@@ -230,6 +107,11 @@ public class AdminPanelManager implements DupeContext, CommonItems {
.build();
}
ItemsAdderItem iai = new ItemsAdderItem();
try {
iai = new ItemsAdderItem(item);
} catch (IllegalArgumentException ignored) {}
List<String> lore = new ArrayList<>();
lore.add("<white><bold>Held Item Explanation:</bold>");
@@ -266,6 +148,10 @@ public class AdminPanelManager implements DupeContext, CommonItems {
activeTags.add(ItemTag.UNIQUE);
}
if (!iai.equals(new ItemsAdderItem())) {
lore.add("<gray>• Is from ItemsAdder. (" + iai.namespace + ":" + iai.id + ")");
}
if (lore.size() == 1) {
lore.add("<gray>• No DupeAlias tags apply to this item");
}
@@ -338,6 +224,4 @@ public class AdminPanelManager implements DupeContext, CommonItems {
case PROTECTED -> "dark_purple";
};
}
}

View File

@@ -3,7 +3,6 @@ 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;

View File

@@ -44,7 +44,7 @@ public class CommonConfigGui implements DupeContext, CommonItems {
errorAny(p, "Invalid hex color format. Use RRGGBB (e.g., AAAAFF).");
}
})
.item(10, ItemBuilder.create(Material.BLUE_WOOL)
.item(11, ItemBuilder.create(Material.BLUE_WOOL)
.displayName("<blue><bold>Main Color</bold>")
.loreMiniMessage(List.of(
"<gray>The color for the message border.",
@@ -69,7 +69,7 @@ public class CommonConfigGui implements DupeContext, CommonItems {
errorAny(p, "Invalid hex color format. Use RRGGBB (e.g., 00DDFF).");
}
})
.item(11, ItemBuilder.create(Material.CYAN_WOOL)
.item(12, ItemBuilder.create(Material.CYAN_WOOL)
.displayName("<aqua><bold>Secondary Color</bold>")
.loreMiniMessage(List.of(
"<gray>The color used for the plugin's name.",
@@ -88,7 +88,7 @@ public class CommonConfigGui implements DupeContext, CommonItems {
successAny(p, "Plugin name set to: {0}", input);
open(p);
})
.item(12, ItemBuilder.create(Material.NAME_TAG)
.item(13, ItemBuilder.create(Material.NAME_TAG)
.displayName("<green><bold>Plugin Name</bold>")
.loreMiniMessage(List.of(
"<gray>The name of the plugin displayed in messages.",
@@ -107,7 +107,7 @@ public class CommonConfigGui implements DupeContext, CommonItems {
successAny(p, "Flat prefix set to: {0}", input);
open(p);
})
.item(13, ItemBuilder.create(Material.PAPER)
.item(14, ItemBuilder.create(Material.PAPER)
.displayName("<gray><bold>Flat Prefix</bold>")
.loreMiniMessage(List.of(
"<gray>The prefix used when 'flat' mode is enabled.",
@@ -120,7 +120,7 @@ public class CommonConfigGui implements DupeContext, CommonItems {
getDupe().getGuiListener().requestChatInput(g, player, "flat_prefix",
"<gray>Enter the new flat prefix."))
.item(14, ItemBuilder.create(config.flat ? Material.LIME_DYE : Material.GRAY_DYE)
.item(15, 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",

View File

@@ -15,7 +15,6 @@ 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 {
@@ -33,84 +32,75 @@ public class GlobalRuleEditorGui implements DupeContext, CommonItems {
.rows(6)
.fillBorder(EMPTY(Material.ORANGE_STAINED_GLASS_PANE))
// Back button
// Back button top-left
.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))
// Item Tags
.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))
// Material Matching
.item(16, createMaterialListItem(), (g, e) -> {
if (rule.materialMode != GlobalRule.MaterialMatchMode.IGNORE)
manager.openMaterialSelector(player, rule);
})
.item(25, createItemsAdderListItem(), (g, e) -> {
if (rule.materialMode != GlobalRule.MaterialMatchMode.IGNORE)
manager.openItemsAdderParser(player, rule);
})
.item(34, createMaterialModeItem(), (g, e) -> cycleMaterialMode(player))
// Criteria Items
.item(19, createCriteriaItem("Name Regex", Material.NAME_TAG,
// Criteria Match Mode
.item(43, createMatchModeItem(), (g, e) -> cycleMatchMode(player))
// Criteria Section
.item(28, 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,
.item(29, createCriteriaItem("Lore Regex", Material.WRITABLE_BOOK,
!rule.loreContainsRegex.isEmpty(), rule.loreContainsRegex),
(g, e) -> manager.openLoreCriteriaEditor(player, rule))
.item(21, createCriteriaItem("Enchantments", Material.ENCHANTED_BOOK,
.item(30, createCriteriaItem("NBT Regex", Material.BOOK,
!rule.nbtTagContainsRegex.isEmpty(), rule.nbtTagContainsRegex),
(g, e) -> manager.openNbtCriteriaEditor(player, rule))
.item(31, createCriteriaItem("Compound Regex", Material.BOOKSHELF,
!rule.compoundTagContainsRegex.isEmpty(), rule.compoundTagContainsRegex),
(g, e) -> manager.openCompoundCriteriaEditor(player, rule))
.item(32, createCriteriaItem("Model Data", Material.COMPASS,
!rule.legacyModelData.isEmpty(), rule.legacyModelData.size() + " values"),
(g, e) -> manager.openModelDataEditor(player, rule))
.item(37, 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,
.item(38, 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,
.item(39, 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,
.item(40, 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,
.item(41, 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) -> {
.onClose((g, e) -> {
getConfig().save();
successAny(player, "Saved global rule");
manager.openGlobalRuleList(player);
})
.fillEmpty(EMPTY())
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
.build();
@@ -118,6 +108,8 @@ public class GlobalRuleEditorGui implements DupeContext, CommonItems {
gui.open(player);
}
private ItemStack createTagItem(ItemTag tag) {
boolean active = rule.appliedTags.contains(tag);
Material material = switch (tag) {
@@ -217,8 +209,8 @@ public class GlobalRuleEditorGui implements DupeContext, CommonItems {
lore.add("");
List<String> materialNames = rule.effectedMaterials.stream()
.limit(5)
.map(mat -> mat.name())
.collect(Collectors.toList());
.map(Enum::name)
.toList();
for (String mat : materialNames) {
lore.add("<gray>• " + mat);
@@ -238,6 +230,51 @@ public class GlobalRuleEditorGui implements DupeContext, CommonItems {
.build();
}
private ItemStack createItemsAdderListItem() {
if (rule.materialMode == GlobalRule.MaterialMatchMode.IGNORE) {
return ItemBuilder.create(Material.GRAY_DYE)
.displayName("<gray><bold>ItemsAdder 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 custom materials for this rule");
lore.add("");
lore.add("<white>Selected: <yellow>" + rule.effectedItemsAdderMaterials.size() + " Custom Materials");
if (!rule.effectedMaterials.isEmpty()) {
lore.add("");
List<String> items = new ArrayList<>();
rule.effectedItemsAdderMaterials.stream()
.limit(5)
.forEach((iai)->{
items.add("<dark_green>" + iai.namespace + "<gray>:<green>" + iai.id);
});
for (String mat : items) {
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>ItemsAdder List")
.loreMiniMessage(lore)
.build();
}
private void toggleTag(Player player, ItemTag tag) {
if (rule.appliedTags.contains(tag)) {
rule.appliedTags.remove(tag);

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.admin.globalrule;
package me.trouper.dupealias.server.gui.admin.globalrule.criteria;
import me.trouper.alias.data.enums.ValidTrimMaterial;
import me.trouper.alias.data.enums.ValidTrimPattern;
@@ -13,7 +13,6 @@ 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;

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.admin.globalrule;
package me.trouper.dupealias.server.gui.admin.globalrule.criteria;
import me.trouper.alias.data.enums.ValidAttribute;
import me.trouper.alias.server.systems.gui.QuickGui;
@@ -13,7 +13,6 @@ 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;

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.admin.globalrule;
package me.trouper.dupealias.server.gui.admin.globalrule.criteria;
import me.trouper.alias.data.enums.ValidEnchantment;
import me.trouper.alias.server.systems.gui.QuickGui;
@@ -13,7 +13,9 @@ import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class GlobalRuleEnchantmentEditor extends QuickPaginatedGUI<ValidEnchantment> implements DupeContext, CommonItems {

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.admin.globalrule;
package me.trouper.dupealias.server.gui.admin.globalrule.criteria;
import me.trouper.alias.server.systems.gui.QuickGui;
import me.trouper.alias.utils.ItemBuilder;

View File

@@ -0,0 +1,121 @@
package me.trouper.dupealias.server.gui.admin.globalrule.criteria;
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.data.ItemsAdderItem;
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.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
public class GlobalRuleItemsAdderParser implements DupeContext, CommonItems {
private final AdminPanelManager manager;
private final GlobalRule rule;
public GlobalRuleItemsAdderParser(AdminPanelManager manager, GlobalRule rule) {
this.manager = manager;
this.rule = rule;
}
public void open(Player player) {
QuickGui gui = QuickGui.create()
.rows(6)
.titleMini("<gradient:#9b59b6:#8e44ad><bold>ItemsAdder Parsing Menu</bold></gradient>")
.allowDrag()
.onGlobalClick((g,e)->{
if (e.getSlot() >= 45) return;
e.setCancelled(false);
})
.fillSlots(EMPTY(),null,46,48,49,50,52)
.item(45, BACK(), (g,e) -> manager.openGlobalRuleEditor(player,rule))
.item(53, ItemBuilder.create(Material.LIGHT)
.displayName("<gold>Information")
.loreMiniMessage(
"<gray>Add custom items to this GUI and",
"<gray>click the green button to have",
"<gray>their namespaces parsed."
)
.build())
.item(51,ItemBuilder.create(Material.LIME_DYE)
.displayName("<green>Parse Items")
.loreMiniMessage(
"<gray>Click to add all items",
"<gray>to the ItemsAdder material list"
)
.build(),
(g,e) -> {
Inventory inv = g.getInventory();
for (int row = 0; row < 5; row++) {
for (int col = 0; col < 9; col++) {
int index = row * 9 + col;
ItemStack item = inv.getItem(index);
if (item == null || item.isEmpty() || !item.hasItemMeta()) continue;
ItemsAdderItem iai;
try {
iai = new ItemsAdderItem(item);
} catch (IllegalArgumentException ignored) {
continue;
}
if (rule.effectedItemsAdderMaterials.contains(iai)) continue;
rule.effectedItemsAdderMaterials.add(iai);
inv.setItem(index,new ItemStack(Material.AIR));
}
}
getConfig().save();
})
.item(47,generateItemsAdderListItem(rule),(g,e)->{
switch (e.getClick()) {
case SHIFT_RIGHT, SHIFT_LEFT -> {
rule.effectedItemsAdderMaterials.clear();
getConfig().save();
open(player);
}
}
})
.onClose((g,e)->{
getConfig().save();
})
.build();
gui.open(player);
}
public ItemStack generateItemsAdderListItem(GlobalRule rule) {
List<String> lore = new ArrayList<>();
ItemBuilder builder = ItemBuilder.create(Material.PAPER)
.displayName("<white><bold>Current Items");
lore.add("<yellow>Total: <bold>" + rule.effectedItemsAdderMaterials.size());
if (!rule.effectedMaterials.isEmpty()) {
lore.add("");
List<String> items = new ArrayList<>();
rule.effectedItemsAdderMaterials.stream()
.limit(5)
.forEach((iai)->{
items.add("<dark_green>" + iai.namespace + "<gray>:<green>" + iai.id);
});
for (String mat : items) {
lore.add("<gray>• " + mat);
}
if (rule.effectedMaterials.size() > 5) {
lore.add("<gray>... and " + (rule.effectedMaterials.size() - 5) + " more");
}
}
if (rule.effectedItemsAdderMaterials.size() > 5) lore.add("<gray>and %s more...".formatted(rule.effectedItemsAdderMaterials.size() - 5));
lore.add("<yellow>▶ <white>Shift Click to clear");
return builder.loreMiniMessage(lore).build();
}
}

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.admin.globalrule;
package me.trouper.dupealias.server.gui.admin.globalrule.criteria;
import me.trouper.alias.server.systems.gui.QuickGui;
import me.trouper.alias.server.systems.gui.QuickPaginatedGUI;
@@ -11,10 +11,11 @@ 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.*;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class GlobalRuleMaterialSelector extends QuickPaginatedGUI<Material> implements DupeContext, CommonItems {

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.admin.globalrule;
package me.trouper.dupealias.server.gui.admin.globalrule.criteria;
import me.trouper.alias.data.enums.ValidPotionEffectType;
import me.trouper.alias.server.systems.gui.QuickGui;
@@ -13,7 +13,9 @@ import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class GlobalRulePotionEffectEditor extends QuickPaginatedGUI<ValidPotionEffectType> implements DupeContext, CommonItems {

View File

@@ -0,0 +1,64 @@
package me.trouper.dupealias.server.gui.admin.globalrule.criteria.regex;
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.entity.Player;
import java.util.Arrays;
public class GlobalRuleCompoundTagEditor implements DupeContext, CommonItems {
private final AdminPanelManager manager;
private final GlobalRule rule;
public GlobalRuleCompoundTagEditor(AdminPanelManager manager, GlobalRule rule) {
this.manager = manager;
this.rule = rule;
}
public void open(Player player) {
QuickGui gui = QuickGui.create()
.titleMini("<gradient:#9c27b0:#7b1fa2><bold>Lore Contains</bold></gradient>")
.rows(3)
.item(0, BACK(), (g, e) -> manager.openGlobalRuleEditor(player, rule))
.callback("compoundPattern", new QuickGui.GuiCallback() {
@Override
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
rule.compoundTagContainsRegex = input;
getConfig().save();
successAny(player, "Set lore pattern to: " + input);
manager.openGlobalRuleEditor(player, rule);
}
})
.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 the item's compound tag",
"",
"<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.nbtTagContainsRegex = "";
getConfig().save();
successAny(player, "Cleared lore pattern");
manager.openGlobalRuleEditor(player, rule);
} else {
getDupe().getGuiListener().requestChatInput(g,player,"compoundPattern","Input a regex pattern for matching item compound tags.");
}
})
.fillEmpty(EMPTY())
.build();
gui.open(player);
}
}

View File

@@ -0,0 +1,64 @@
package me.trouper.dupealias.server.gui.admin.globalrule.criteria.regex;
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.entity.Player;
import java.util.Arrays;
public class GlobalRuleLoreEditor implements DupeContext, CommonItems {
private final AdminPanelManager manager;
private final GlobalRule rule;
public GlobalRuleLoreEditor(AdminPanelManager manager, GlobalRule rule) {
this.manager = manager;
this.rule = rule;
}
public void open(Player player) {
QuickGui gui = QuickGui.create()
.titleMini("<gradient:#9c27b0:#7b1fa2><bold>Lore Contains</bold></gradient>")
.rows(3)
.item(0, BACK(), (g, e) -> manager.openGlobalRuleEditor(player, rule))
.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);
manager.openGlobalRuleEditor(player, rule);
}
})
.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");
manager.openGlobalRuleEditor(player, rule);
} else {
getDupe().getGuiListener().requestChatInput(g,player,"lorePattern","Input a regex pattern for matching item lore (by line).");
}
})
.fillEmpty(EMPTY())
.build();
gui.open(player);
}
}

View File

@@ -0,0 +1,96 @@
package me.trouper.dupealias.server.gui.admin.globalrule.criteria.regex;
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.entity.Player;
import java.util.Arrays;
import java.util.stream.Collectors;
public class GlobalRuleModelDataEditor implements DupeContext, CommonItems {
private final AdminPanelManager manager;
private final GlobalRule rule;
public GlobalRuleModelDataEditor(AdminPanelManager manager, GlobalRule rule) {
this.manager = manager;
this.rule = rule;
}
public void open(Player player) {
QuickGui gui = QuickGui.create()
.titleMini("<gradient:#00bcd4:#0097a7><bold>Model Data Values</bold></gradient>")
.rows(5)
.item(0, BACK(), (g, e) -> manager.openGlobalRuleEditor(player, rule))
.fillSlots(EMPTY(Material.GRAY_STAINED_GLASS_PANE),null,28,29,30,31,32,33,34)
.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);
}
open(player);
}
})
.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");
open(player);
} else {
getDupe().getGuiListener().requestChatInput(g,player,"modelData","Input the the legacy model data ID number.");
}
})
.fillEmpty(EMPTY())
.build();
int slot = 28;
for (Integer value : rule.legacyModelData.stream().limit(7).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);
open(player);
});
}
gui.open(player);
}
}

View File

@@ -0,0 +1,63 @@
package me.trouper.dupealias.server.gui.admin.globalrule.criteria.regex;
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.entity.Player;
import java.util.Arrays;
public class GlobalRuleNameEditor implements DupeContext, CommonItems {
private final AdminPanelManager manager;
private final GlobalRule rule;
public GlobalRuleNameEditor(AdminPanelManager manager, GlobalRule rule) {
this.manager = manager;
this.rule = rule;
}
public void open(Player player) {
QuickGui gui = QuickGui.create()
.titleMini("<gradient:#ffeb3b:#ffc107><bold>Name Contains</bold></gradient>")
.rows(3)
.item(0, BACK(), (g, e) -> manager.openGlobalRuleEditor(player, rule))
.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);
manager.openGlobalRuleEditor(player, rule);
}
})
.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");
manager.openGlobalRuleEditor(player, rule);
} else {
getDupe().getGuiListener().requestChatInput(g,player,"namePattern","Input a regex pattern for matching item names.");
}
})
.fillEmpty(EMPTY())
.build();
gui.open(player);
}
}

View File

@@ -0,0 +1,64 @@
package me.trouper.dupealias.server.gui.admin.globalrule.criteria.regex;
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.entity.Player;
import java.util.Arrays;
public class GlobalRuleNbtTagEditor implements DupeContext, CommonItems {
private final AdminPanelManager manager;
private final GlobalRule rule;
public GlobalRuleNbtTagEditor(AdminPanelManager manager, GlobalRule rule) {
this.manager = manager;
this.rule = rule;
}
public void open(Player player) {
QuickGui gui = QuickGui.create()
.titleMini("<gradient:#9c27b0:#7b1fa2><bold>NBT Tag Contains</bold></gradient>")
.rows(3)
.item(0, BACK(), (g, e) -> manager.openGlobalRuleEditor(player, rule))
.callback("nbtPattern", new QuickGui.GuiCallback() {
@Override
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
rule.nbtTagContainsRegex = input;
getConfig().save();
successAny(player, "Set NBT pattern to: " + input);
manager.openGlobalRuleEditor(player, rule);
}
})
.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 mojangson NBT",
"",
"<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.nbtTagContainsRegex = "";
getConfig().save();
successAny(player, "Cleared lore pattern");
manager.openGlobalRuleEditor(player, rule);
} else {
getDupe().getGuiListener().requestChatInput(g,player,"nbtPattern","Input a regex pattern for matching nbt (mojangson).");
}
})
.fillEmpty(EMPTY())
.build();
gui.open(player);
}
}

View File

@@ -21,7 +21,7 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
@Override
public ChestSession getSession(Player player) {
ChestSession session = super.getSession(player);
session.setDelayTicks(getDupe().getPermissionValue(player, "dupealias.gui.chest.refresh.", getConfig().chest.baseRefreshDelayTicks));
session.setDelayTicks(getDupe().getPermissionValue(player, "dupealias.gui.chest.refresh.", getConfig().chest.baseRefreshDelayTicks,false));
session.open();
return session;
}
@@ -32,7 +32,7 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
public ChestSession(Player owner) {
super(owner, "<gradient:#cc22ff:#cc99ff><bold>DUPE CHEST</gradient>", 6);
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.chest.refresh.", getConfig().chest.baseRefreshDelayTicks);
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.chest.refresh.", getConfig().chest.baseRefreshDelayTicks,false);
}
@Override

View File

@@ -18,7 +18,7 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
@Override
public InventorySession getSession(Player player) {
InventorySession session = super.getSession(player);
session.setDelayTicks(getDupe().getPermissionValue(player, "dupealias.gui.inventory.refresh.", getConfig().inventory.baseRefreshDelayTicks));
session.setDelayTicks(getDupe().getPermissionValue(player, "dupealias.gui.inventory.refresh.", getConfig().inventory.baseRefreshDelayTicks,false));
session.open();
return session;
}
@@ -29,7 +29,7 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
public InventorySession(Player owner) {
super(owner, "<gradient:#cc22ff:#cc99ff><bold>YOUR INVENTORY</gradient>", 6);
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.inventory.refresh.", getConfig().inventory.baseRefreshDelayTicks);
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.inventory.refresh.", getConfig().inventory.baseRefreshDelayTicks,false);
}
@Override

View File

@@ -30,8 +30,8 @@ public class DupeReplicatorGui extends AbstractDupeGui<DupeReplicatorGui.Replica
@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.setDelayTicks(getDupe().getPermissionValue(player, "dupealias.gui.replicator.refresh.", getConfig().replicator.baseRefreshDelayTicks,false));
session.setCooldownTicks(getDupe().getPermissionValue(player, "dupealias.gui.replicator.cooldown.", getConfig().replicator.baseInputCooldownTicks,false));
session.open();
return session;
}
@@ -49,8 +49,8 @@ public class DupeReplicatorGui extends AbstractDupeGui<DupeReplicatorGui.Replica
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);
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.replicator.refresh.", getConfig().replicator.baseRefreshDelayTicks,false);
this.cooldownTicks = getDupe().getPermissionValue(owner, "dupealias.gui.replicator.cooldown.", getConfig().replicator.baseInputCooldownTicks,false);
}
@Override

View File

@@ -35,8 +35,8 @@ permissions:
description: Allows duplication of items through the command. Setting this to false results in the /dupe command always displaying a GUI.
default: true
children:
dupealias.dupe.cooldown.integerhere: false # Controls the cooldown time in ticks it will take to command dupe again.
dupealias.dupe.limit.integerhere: false # Controls the integer argument's limit for exponential (2^n) duping.
dupealias.dupe.cooldown.integerhere: false # Controls the cooldown time in milliseconds it will take to command dupe again. Always takes the lowest number on a permission holder.
dupealias.dupe.limit.integerhere: false # Controls the integer argument's limit for exponential (2^n) duping. Always takes the highest is number on a permission holder.
dupealias.gui:
description: Allows access to the main dupe GUI selector. Players do not need this permission to access subsequent GUIs through the dupe command arguments.
default: true
@@ -50,6 +50,7 @@ permissions:
children:
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 in ticks it will take a duplicated item to refill or refresh in the GUI. Always takes the lowest number on a permission holder.
dupealias.gui.replicator.cooldown.integerhere: false # Controls the time in milliseconds between input updates.
dupealias.gui.inventory:
description: The gui which shows your inventory and armor on top.
default: true