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 # DupeAlias Documentation
## Table of Contents ## Table of Contents
@@ -135,17 +10,15 @@ DupeAlias is actively maintained with regular updates and feature additions. Joi
6. [Permissions System](#permissions-system) 6. [Permissions System](#permissions-system)
7. [Configuration](#configuration) 7. [Configuration](#configuration)
8. [Commands](#commands) 8. [Commands](#commands)
9. [Common Scenarios](#common-scenarios) 9. [Troubleshooting](#troubleshooting)
10. [Troubleshooting](#troubleshooting)
---
---
## Installation & Setup ## Installation & Setup
### Prerequisites ### Prerequisites
- Minecraft 1.21 or higher - Minecraft 1.21.5
- Paper, Purpur, or compatible server software - Paper server software
- Java 17 or higher - Java 21 or higher
### Installation Steps ### Installation Steps
1. Download the DupeAlias JAR file 1. Download the DupeAlias JAR file
@@ -159,8 +32,7 @@ DupeAlias is actively maintained with regular updates and feature additions. Joi
3. Navigate to Configuration → Common Config to customize colors and branding 3. Navigate to Configuration → Common Config to customize colors and branding
4. Set up your first global rules or start tagging items manually 4. Set up your first global rules or start tagging items manually
--- ---
## Core Concepts ## Core Concepts
### Item Tags ### Item Tags
@@ -178,8 +50,7 @@ Individual item tags **always override** global rules. This allows for fine-grai
- **Individual Tags**: Stored directly on the item's metadata, apply only to that specific item instance - **Individual Tags**: Stored directly on the item's metadata, apply only to that specific item instance
- **Global Rules**: Server-wide rules that apply tags based on item properties like material, name, enchantments, etc. - **Global Rules**: Server-wide rules that apply tags based on item properties like material, name, enchantments, etc.
--- ---
## Item Tags System ## Item Tags System
### UNIQUE Tag ### UNIQUE Tag
@@ -191,13 +62,9 @@ Individual item tags **always override** global rules. This allows for fine-grai
- Admin-only equipment - Admin-only equipment
- Currency items - 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 **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 ### FINAL Tag
**Purpose**: Prevents any modification to the item **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 - Rank kits that shouldn't be modified
- Event rewards with special formatting - 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 ### INFINITE Tag
**Purpose**: Maintains maximum stack size and refills items **Purpose**: Maintains maximum stack size and refills items
@@ -230,17 +91,11 @@ Global: All items with "Quest" in lore → Apply FINAL tag
**Use Cases**: **Use Cases**:
- Creative-style building materials - Creative-style building materials
- Infinite arrows for archery ranges - "Infinity" enchantment for tipped arrows
- Unlimited consumables for events - Unlimited consumables for events
**Conflicts**: Cannot be combined with UNIQUE or PROTECTED **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 ### PROTECTED Tag
**Purpose**: Makes items completely inert and unusable **Purpose**: Makes items completely inert and unusable
@@ -260,14 +115,7 @@ Global: All concrete blocks → Apply INFINITE tag
**Conflicts**: Cannot be combined with INFINITE **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 ## Global Rules Engine
### Creating Global Rules ### 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 - **NAND**: Not all criteria match
- **XOR**: Exactly one criteria matches - **XOR**: Exactly one criteria matches
Note that if no criteria are selected, any material or ItemsAdder item will match.
#### Material Matching #### Material Matching
- **IGNORE**: Apply to all materials - **IGNORE**: Apply to all materials and ItemsAdder items
- **WHITELIST**: Only apply to selected materials - **WHITELIST**: Only apply to selected materials and ItemsAdder items
- **BLACKLIST**: Apply to all except selected materials - **BLACKLIST**: Apply to all except selected materials and ItemsAdder items
### Criteria Types ### Criteria Types
#### Name Regex #### Name Regex
Match items based on display name patterns using regular expressions. 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 #### Lore Regex
Match items based on lore content using regular expressions. 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 #### Enchantments
Match items that have specific enchantments at minimum levels. 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 #### Attributes
Match items with specific attribute modifiers. 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 #### Potion Effects
Match potions/foods with specific effects and amplifiers. 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 #### Model Data
Match items with specific custom model data values. Match items with specific custom model data values.
``` ``` Example: 12345 → matches items with CustomModelData: 12345 ```
Example: 12345 → matches items with CustomModelData: 12345
```
#### Item Flags #### Item Flags
Match items with specific visibility 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 #### Armor Trim
Match armor pieces with specific trim patterns or materials. 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 ### Example Rules
#### Protect All Crate Keys #### Protect All Crate Keys
``` ``` Applied Tags: UNIQUE, PROTECTED, FINAL Match Mode: OR NBT Tag Regex: ".*excellentcrates.*" Lore Regex: ".*[Cc]rate.*" ```
Applied Tags: UNIQUE, PROTECTED, FINAL
Match Mode: OR
Name Regex: ".*[Kk]ey.*"
Lore Regex: ".*[Cc]rate.*"
```
#### Make Netherite Gear Unmodifiable #### 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 #### 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 ## Duplication GUIs
### Replicator GUI ### Replicator GUI
**Access**: `/dupe replicator` or through main menu **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**: **Features**:
- Single-item focused duplication - Single-item focused duplication
@@ -391,13 +211,18 @@ Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
**Best For**: Quick duplication of single item types **Best For**: Quick duplication of single item types
### Chest GUI ### Chest GUI
**Access**: `/dupe chest` or through main menu **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**: **Features**:
- Multi-item container interface - Multi-item container interface
- 4 input columns, 4 output columns - 4 input columns, 4 output columns
- Individual item refresh timers - Individual item refresh timers
- Session persistence (if enabled) - Session persistence (doesn't persist over reboots)
**How to Use**: **How to Use**:
1. Open the GUI 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 3. Take duplicated copies from the right 4 columns
4. Items refresh based on configured delays 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 ### Inventory GUI
**Access**: `/dupe inventory` or through main menu **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**: **Features**:
- Mirror of your actual inventory - Mirror of your actual inventory
@@ -425,7 +253,8 @@ Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
**Best For**: Easy access to copies of everything you're carrying **Best For**: Easy access to copies of everything you're carrying
### Menu GUI ### Menu GUI
**Access**: `/dupe gui` or `/dupe` (if set as default) **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**: **Features**:
- Central hub for all GUI types - Central hub for all GUI types
@@ -437,167 +266,170 @@ Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
2. Click on the GUI type you want to use 2. Click on the GUI type you want to use
3. Access is controlled by permissions 3. Access is controlled by permissions
--- ---
## Permissions System ## Permissions System
### Core Permissions ### Core Permissions
#### Admin Access #### Admin Access
```yaml ```yaml
dupealias.admin: true # Access to admin panel and configuration dupealias.admin: true # Access to admin panel and configuration
``` ```
#### Basic Duplication #### Basic Duplication
```yaml ```yaml
dupealias.dupe: true # Access to /dupe command dupealias.dupe: true # Access to /dupe command
dupealias.dupe.cooldown.<integer>: false # Cooldown for command duping (milliseconds) dupealias.dupe.cooldown.<integer>: false # Cooldown for command duping (milliseconds)
``` ```
#### GUI Access #### GUI Access
```yaml ```yaml
dupealias.gui: true # Access to all GUIs dupealias.gui: true # Access to all GUIs
dupealias.gui.replicator: true # Access to replicator GUI 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 ### Advanced Permissions
#### Session Persistence #### Session Persistence
```yaml ```yaml
dupealias.gui.replicator.keep: false # Keep replicator items on close dupealias.gui.replicator.keep: false # Keep replicator items on close
dupealias.gui.chest.keep: false # Keep chest items on close dupealias.gui.chest.keep: false # Keep chest items on close
dupealias.gui.chest.keepondeath: false # Keep chest items on death dupealias.gui.chest.keepondeath: false # Keep chest items on death
``` ```
#### Permission Based Refresh Rates #### Permission Based Refresh Rates
```yaml ```yaml
dupealias.gui.replicator.refresh.<integer>: false # Ticks of refresh cooldown dupealias.gui.replicator.refresh.<integer>: false # Ticks of refresh cooldown
dupealias.gui.replicator.cooldown.<integer>: false # Ticks of re-input cooldown dupealias.gui.replicator.cooldown.<integer>: false # Ticks of re-input cooldown
dupealias.gui.inventory.refresh.<integer>: false # Ticks of refresh cooldown dupealias.gui.inventory.refresh.<integer>: false # Ticks of refresh cooldown
dupealias.gui.chest.refresh.<integer>: false # Ticks of refresh cooldown dupealias.gui.chest.refresh.<integer>: false # Ticks of refresh cooldown
``` ```
#### Tag Bypasses (Use Carefully!) #### Tag Bypasses (Use Carefully!)
```yaml ```yaml
dupealias.unique.bypass: false # Can dupe UNIQUE items dupealias.unique.bypass: false # Can dupe UNIQUE items
dupealias.final.bypass: false # Can modify FINAL items dupealias.final.bypass: false # Can modify FINAL items
dupealias.protected.bypass: false # Can use PROTECTED items dupealias.protected.bypass: false # Can use PROTECTED items
``` ```
#### Special Permissions #### Special Permissions
```yaml ```yaml
dupealias.infinite: true # Can use INFINITE items dupealias.infinite: true # Can use INFINITE items
``` ```
### Permission Hierarchy ### 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 ### Example Permission Sets
#### VIP Player #### VIP Player
```yaml ```yaml
groups: groups:
vip: vip:
permissions: permissions:
- dupealias.dupe - dupealias.gui - dupealias.gui.replicator.refresh.5 # Faster refresh - dupealias.dupe
- dupealias.gui.replicator.cooldown.10 # Shorter cooldown - dupealias.gui
- dupealias.dupe.cooldown.0 # No command cooldown - dupealias.gui.replicator.refresh.5 # Faster refresh
``` - dupealias.gui.replicator.cooldown.10 # Shorter cooldown
- dupealias.dupe.cooldown.0 # No command cooldown
```
#### Staff Member #### Staff Member
```yaml ```yaml
groups: groups:
staff: staff:
permissions: - dupealias.admin # Full admin access permissions:
- dupealias.gui.replicator.refresh.1 # Instant refresh - dupealias.admin # Full admin access
- dupealias.gui.*.keep # Session persistence - dupealias.gui.replicator.refresh.1 # Instant refresh
- dupealias.final.bypass # Can modify final items - dupealias.gui.*.keep # Session persistence
``` - dupealias.final.bypass # Can modify final items
```
--- ---
## Configuration ## Configuration
### Main Configuration (`config.json`) ### Main Configuration (`config.json`)
#### Duplication Settings #### Duplication Settings
```json ```json
{ {
"dupeCooldownMillis": 1000, // Command cooldown in milliseconds "dupeCooldownMillis": 1000, // Command cooldown in milliseconds
"defaultDupeGui": "REPLICATOR" // Default GUI (REPLICATOR/INVENTORY/CHEST/MENU) "defaultDupeGui": "REPLICATOR" // Default GUI (REPLICATOR/INVENTORY/CHEST/MENU)
} }
``` ```
#### GUI Refresh Rates #### GUI Refresh Rates
```json ```json
{ {
"replicator": { "replicator": {
"baseRefreshDelayTicks": 1, // Base item refresh delay "baseRefreshDelayTicks": 1, // Base item refresh delay
"baseInputCooldownTicks": 20 // Base input change cooldown "baseInputCooldownTicks": 20 // Base input change cooldown
}, "chest": { },
"baseRefreshDelayTicks": 1 // Base item refresh delay "chest": {
}, "baseRefreshDelayTicks": 1 // Base item refresh delay
"inventory": { },
"baseRefreshDelayTicks": 1 // Base item refresh delay "inventory": {
}} "baseRefreshDelayTicks": 1 // Base item refresh delay
``` }
}
```
#### Command Blocking #### Command Blocking
```json
{ ```json
"finalCommandRegex": [ {
"\"(?:itemname|iname)\"gmi", // Block item naming commands "\"(?:itemlore|lore)\"gmi" // Block lore modification commands ]} "finalCommandRegex": [
``` "\"(?:itemname|iname)\"gmi", // Block item naming commands
"\"(?:itemlore|lore)\"gmi" // Block lore modification commands
]
}
```
#### Tag Lore Customization (MiniMessage) #### Tag Lore Customization (MiniMessage)
```json
{ ```json
"trueTagLore": { {
"UNIQUE": "<dark_blue><bold>|</bold><blue> Unique", "trueTagLore": {
"FINAL": "<dark_red><bold>|</bold><red> Final", "UNIQUE": "<dark_blue><bold>|</bold><blue> Unique",
"INFINITE": "<dark_green><bold>|</bold><green> Infinite", "FINAL": "<dark_red><bold>|</bold><red> Final",
"PROTECTED": "<dark_purple><bold>|</bold><light_purple> Protected" "INFINITE": "<dark_green><bold>|</bold><green> Infinite",
}, "PROTECTED": "<dark_purple><bold>|</bold><light_purple> Protected"
"falseTagLore": { },
"UNIQUE": "<dark_blue><bold>|</bold><blue> Dupeable", "falseTagLore": {
"FINAL": "<dark_red><bold>|</bold><red> Mutable", "UNIQUE": "<dark_blue><bold>|</bold><blue> Dupeable",
"INFINITE": "<dark_green><bold>|</bold><green> Finite", "FINAL": "<dark_red><bold>|</bold><red> Mutable",
"PROTECTED": "<dark_purple><bold>|</bold><light_purple> Unprotected" "INFINITE": "<dark_green><bold>|</bold><green> Finite",
} "PROTECTED": "<dark_purple><bold>|</bold><light_purple> Unprotected"
} }
``` }
```
### Common Configuration (`common.json`) ### Common Configuration (`common.json`)
#### Visual Customization #### Visual Customization
```json
{ ```json
"mainColor": 11184895, // Primary color (hex: AAAAFF) {
"secondaryColor": 909055, // Secondary color (hex: 00DDFF) "mainColor": 11184895, // Primary color (hex: AAAAFF)
"pluginName": "DupeAlias", // Display name "secondaryColor": 909055, // Secondary color (hex: 00DDFF)
"flatPrefix": "&9DupeAlias> &7", // Legacy chat prefix "pluginName": "DupeAlias", // Display name
"flat": false // Use legacy formatting "flatPrefix": "&9DupeAlias> &7", // Legacy chat prefix
} "flat": false // Use legacy formatting
``` }
```
#### Debug Settings #### Debug Settings
```json
{
"debugMode": false, // Enable debug output
"debuggerExclusions": [] // Methods to exclude from debug
}
```
---
```json
{
"debugMode": false, // Enable debug output
"debuggerExclusions": [] // Methods to exclude from debug
}
```
---
## Commands ## Commands
### Administrative Commands ### Administrative Commands
#### `/dupealias` (Aliases: `/da`) #### `/dupealias` (Aliases: `/da`)
**Permission**: `dupealias.admin` **Permission**: `dupealias.admin`
**Description**: Main administrative command **Description**: Main administrative command
**Subcommands**: **Subcommands**:
@@ -625,7 +457,7 @@ groups:
### Player Commands ### Player Commands
#### `/dupe` #### `/dupe`
**Permission**: `dupealias.dupe` **Permission**: `dupealias.dupe`
**Description**: Main duplication command **Description**: Main duplication command
**Usage**: **Usage**:
@@ -642,35 +474,35 @@ groups:
### Common Issues ### Common Issues
#### "You cannot dupe unique items" #### "You cannot dupe unique items"
**Cause**: Item has UNIQUE tag or matches global rule **Cause**: Item has UNIQUE tag or matches global rule
**Solution**: **Solution**:
- Check `/da` → Held Item Actions to see current tags - Check `/da` → Held Item Actions to see current tags
- Review global rules that might be applying UNIQUE tag - Review global rules that might be applying UNIQUE tag
- Use `/da tag UNIQUE remove` to remove individual tag - Use `/da tag UNIQUE remove` to remove individual tag
#### Items not refreshing in GUI #### Items not refreshing in GUI
**Cause**: Long refresh delay or permission issues **Cause**: Long refresh delay or permission issues
**Solution**: **Solution**:
- Check player has appropriate GUI permissions - Check player has appropriate GUI permissions
- Verify refresh rate permissions (lower numbers = faster) - Verify refresh rate permissions (lower numbers = faster)
- Ensure player isn't hitting cooldown limits - Ensure player isn't hitting cooldown limits
#### "You cannot modify final items" #### "You cannot modify final items"
**Cause**: Item has FINAL tag blocking modifications **Cause**: Item has FINAL tag blocking modifications
**Solution**: **Solution**:
- Check item tags in admin panel - Check item tags in admin panel
- Use `/da tag FINAL remove` if needed - Use `/da tag FINAL remove` if needed
- Grant `dupealias.final.bypass` permission for admins - Grant `dupealias.final.bypass` permission for admins
#### Global rules not applying #### Global rules not applying
**Cause**: Rule criteria not matching or individual tags overriding **Cause**: Rule criteria not matching or individual tags overriding
**Solution**: **Solution**:
- Test rule criteria with `/da rule info <index>` - Test rule criteria with `/da rule info <index>`
- Check match mode (AND vs OR) - Check match mode (AND vs OR)
- Remember individual tags override global rules - Remember individual tags override global rules
#### GUI won't open #### GUI won't open
**Cause**: Missing permissions or plugin conflicts **Cause**: Missing permissions or plugin conflicts
**Solution**: **Solution**:
- Verify player has `dupealias.gui` permission - Verify player has `dupealias.gui` permission
- Check for inventory plugin conflicts - Check for inventory plugin conflicts
@@ -678,26 +510,19 @@ groups:
### Debug Mode ### Debug Mode
Enable debug mode to troubleshoot issues: Enable debug mode to troubleshoot issues: ``` /da debug toggle ```
```
/da debug toggle
```
This will show detailed information about: This will show detailed information about:
- Tag checking processes - Tag checking processes
- Global rule matching - Global rule matching
- Permission calculations - Permission calculations
- GUI state changes - GUI state changes
Exclude noisy methods: Exclude noisy methods: ``` /da debug exclude <method_name> ```
```
/da debug exclude <method_name>
```
### Performance Considerations ### Performance Considerations
#### Large Player Counts #### Large Player Counts
- Use session persistence sparingly - Use session persistence sparingly
- Don't use GUI refresh or input cooldowns.
- Monitor server TPS with `/tps` - Monitor server TPS with `/tps`
#### Complex Global Rules #### Complex Global Rules
@@ -706,19 +531,17 @@ Exclude noisy methods:
- Limit the number of active global rules - Limit the number of active global rules
#### GUI Optimization #### GUI Optimization
- Set appropriate refresh rates based on server performance
- Consider disabling session persistence for busy servers - Consider disabling session persistence for busy servers
- Use cooldowns to prevent spam - Use cooldowns to prevent spam
--- ---
### Getting Help ### Getting Help
If you encounter issues not covered in this documentation: If you encounter issues not covered in this documentation:
1. Enable debug mode and check console logs 1. Enable debug mode and check console logs
2. Test with a minimal permission set 2. Verify your global rules are correctly configured
3. Verify your global rules are correctly configured 3. Check for conflicts with other plugins
4. 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. 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 { repositories {
mavenLocal() mavenLocal()
maven {
name = "matteodev"
url = uri("https://maven.devs.beer/")
}
} }
dependencies { dependencies {
paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT") paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT")
implementation("me.trouper:alias:1.0-1.21.5-SNAPSHOT") implementation("me.trouper:alias:1.0-1.21.5-SNAPSHOT")
compileOnly("dev.lone:api-itemsadder:4.0.10")
} }
tasks { 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.data.enums.*;
import me.trouper.alias.utils.misc.MapUtils; import me.trouper.alias.utils.misc.MapUtils;
import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.server.ItemTag; import me.trouper.dupealias.server.ItemTag;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@@ -21,7 +22,7 @@ import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class GlobalRule { public class GlobalRule implements DupeContext {
public enum MatchMode { public enum MatchMode {
AND, OR, NAND, XOR AND, OR, NAND, XOR
@@ -36,9 +37,12 @@ public class GlobalRule {
public MatchMode matchMode = MatchMode.AND; public MatchMode matchMode = MatchMode.AND;
public MaterialMatchMode materialMode = MaterialMatchMode.IGNORE; public MaterialMatchMode materialMode = MaterialMatchMode.IGNORE;
public Set<Material> effectedMaterials = EnumSet.noneOf(Material.class); public Set<Material> effectedMaterials = EnumSet.noneOf(Material.class);
public List<ItemsAdderItem> effectedItemsAdderMaterials = new ArrayList<>();
public String nameContainsRegex = ""; public String nameContainsRegex = "";
public String loreContainsRegex = ""; public String loreContainsRegex = "";
public String compoundTagContainsRegex = "";
public String nbtTagContainsRegex = "";
public Set<Integer> legacyModelData = new HashSet<>(); public Set<Integer> legacyModelData = new HashSet<>();
public Set<ItemFlag> itemFlags = EnumSet.noneOf(ItemFlag.class); public Set<ItemFlag> itemFlags = EnumSet.noneOf(ItemFlag.class);
public Map<ValidEnchantment, Integer> enchantments = new HashMap<>(); public Map<ValidEnchantment, Integer> enchantments = new HashMap<>();
@@ -51,14 +55,19 @@ public class GlobalRule {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public boolean doesMatch(ItemStack item) { 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) { switch (materialMode) {
case WHITELIST -> { case WHITELIST -> {
if (!effectedMaterials.contains(item.getType())) return false; if (!effectedMaterials.contains(item.getType()) && !effectedItemsAdderMaterials.contains(iai)) return false;
} }
case BLACKLIST -> { case BLACKLIST -> {
if (effectedMaterials.contains(item.getType())) return false; if (effectedMaterials.contains(item.getType()) || effectedItemsAdderMaterials.contains(iai)) return false;
} }
case IGNORE -> {} case IGNORE -> {}
} }
@@ -66,6 +75,7 @@ public class GlobalRule {
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (meta == null) return false; if (meta == null) return false;
List<Boolean> results = new ArrayList<>(); List<Boolean> results = new ArrayList<>();
if (!nameContainsRegex.isEmpty()) { if (!nameContainsRegex.isEmpty()) {
@@ -82,6 +92,18 @@ public class GlobalRule {
results.add(found); 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()) { if (!enchantments.isEmpty()) {
Map<Enchantment, Integer> itemEnchants = item.getEnchantments(); Map<Enchantment, Integer> itemEnchants = item.getEnchantments();
results.add(MapUtils.allValuesMatch(itemEnchants, enchantments.entrySet().stream().collect(Collectors.toMap( 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 trueCount = (int) results.stream().filter(Boolean::booleanValue).count();
int total = results.size(); int total = results.size();
if (getCriteriaCount() == 0) return true;
return switch (matchMode) { return switch (matchMode) {
case AND -> trueCount == total; case AND -> trueCount == total;
case OR -> trueCount > 0; case OR -> trueCount > 0;
@@ -156,11 +180,13 @@ public class GlobalRule {
int criteriaCount = 0; int criteriaCount = 0;
if (!nameContainsRegex.isEmpty()) criteriaCount++; if (!nameContainsRegex.isEmpty()) criteriaCount++;
if (!loreContainsRegex.isEmpty()) criteriaCount++; if (!loreContainsRegex.isEmpty()) criteriaCount++;
if (!nbtTagContainsRegex.isEmpty()) criteriaCount++;
if (!compoundTagContainsRegex.isEmpty()) criteriaCount++;
if (!legacyModelData.isEmpty()) criteriaCount++;
if (!enchantments.isEmpty()) criteriaCount++; if (!enchantments.isEmpty()) criteriaCount++;
if (!potionEffects.isEmpty()) criteriaCount++; if (!potionEffects.isEmpty()) criteriaCount++;
if (!attributes.isEmpty()) criteriaCount++; if (!attributes.isEmpty()) criteriaCount++;
if (!itemFlags.isEmpty()) criteriaCount++; if (!itemFlags.isEmpty()) criteriaCount++;
if (!legacyModelData.isEmpty()) criteriaCount++;
if (!trimPatterns.isEmpty()) criteriaCount++; if (!trimPatterns.isEmpty()) criteriaCount++;
if (!trimMaterials.isEmpty()) criteriaCount++; if (!trimMaterials.isEmpty()) criteriaCount++;
return 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 me.trouper.dupealias.server.ItemTag;
import java.io.File; 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 { public class DupeConfig implements JsonSerializable<DupeConfig>, DupeContext {
@Override @Override
@@ -36,6 +39,8 @@ public class DupeConfig implements JsonSerializable<DupeConfig>, DupeContext {
ItemTag.UNIQUE, "<dark_blue><bold>|</bold><blue> Dupeable", ItemTag.UNIQUE, "<dark_blue><bold>|</bold><blue> Dupeable",
ItemTag.INFINITE, "<dark_green><bold>|</bold><green> Finite" ItemTag.INFINITE, "<dark_green><bold>|</bold><green> Finite"
)); ));
public boolean blockDupePlus = false;
public List<GlobalRule> globalRules = new ArrayList<>(); public List<GlobalRule> globalRules = new ArrayList<>();

View File

@@ -14,7 +14,9 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.persistence.PersistentDataType; 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 { 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 * Checks if any global rule applies this tag to the given item
*/ */
public boolean checkGlobalRuleTag(ItemStack input, ItemTag tag) { public boolean checkGlobalRuleTag(ItemStack input, ItemTag tag) {
getVerbose().send("Checking tag {0} on item {1}",tag,input.getType());
for (GlobalRule rule : getConfig().globalRules) { for (GlobalRule rule : getConfig().globalRules) {
getVerbose().send("Scanning rule with tags {0}",rule.appliedTags.toString());
if (rule.appliedTags.contains(tag) && rule.doesMatch(input)) { if (rule.appliedTags.contains(tag) && rule.doesMatch(input)) {
return true; return true;
} }
@@ -122,12 +126,11 @@ public class DupeManager implements DupeContext {
* Adds a global rule for a specific material and tag * Adds a global rule for a specific material and tag
*/ */
public boolean addGlobalRuleForMaterial(Material material, ItemTag tag) { public boolean addGlobalRuleForMaterial(Material material, ItemTag tag) {
// Check if rule already exists
for (GlobalRule rule : getConfig().globalRules) { for (GlobalRule rule : getConfig().globalRules) {
if (rule.appliedTags.contains(tag) && if (rule.appliedTags.contains(tag) &&
rule.materialMode == GlobalRule.MaterialMatchMode.WHITELIST && rule.materialMode == GlobalRule.MaterialMatchMode.WHITELIST &&
rule.effectedMaterials.contains(material)) { 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())); throw new IllegalArgumentException("Invalid NameSpacedKey '%s'".formatted(key.value()));
} }
public int getPermissionValue(Player player, String rootPermission, int fallback) { public int getPermissionValue(Player player, String rootPermission, int fallback, boolean takeHighest) {
int lowestCooldown = Integer.MAX_VALUE; int result = takeHighest ? Integer.MIN_VALUE : Integer.MAX_VALUE;
for (PermissionAttachmentInfo permInfo : player.getEffectivePermissions()) { for (PermissionAttachmentInfo permInfo : player.getEffectivePermissions()) {
String perm = permInfo.getPermission(); String perm = permInfo.getPermission();
@@ -246,13 +249,24 @@ public class DupeManager implements DupeContext {
String valueStr = perm.substring(rootPermission.length()); String valueStr = perm.substring(rootPermission.length());
try { try {
int value = Integer.parseInt(valueStr); int value = Integer.parseInt(valueStr);
if (value < lowestCooldown) { if (takeHighest) {
lowestCooldown = value; if (value > result) {
result = value;
}
} else {
if (value < result) {
result = value;
}
} }
} catch (NumberFormatException ignored) {} } 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.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.data.GlobalRule;
import me.trouper.dupealias.data.ItemsAdderItem;
import me.trouper.dupealias.server.ItemTag; import me.trouper.dupealias.server.ItemTag;
import me.trouper.dupealias.server.gui.admin.AdminPanelManager; import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
import me.trouper.dupealias.server.gui.admin.MainAdminGui; import me.trouper.dupealias.server.gui.admin.MainAdminGui;
@@ -49,6 +50,10 @@ public class AdminCommand implements QuickCommand, DupeContext {
handleRule(sender,args); handleRule(sender,args);
} }
case "test" -> {
handleDebugTests(sender,args);
}
default -> { default -> {
errorAny(sender,"Invalid subcommand!"); errorAny(sender,"Invalid subcommand!");
} }
@@ -93,13 +98,20 @@ public class AdminCommand implements QuickCommand, DupeContext {
) )
).then( ).then(
b.arg("gui") b.arg("gui")
).then(
b.arg("test")
.then(
b.arg("nbt","component","itemsadder")
)
); );
} }
private void handleDebug(CommandSender sender, Args args) { private void handleDebug(CommandSender sender, Args args) {
if (args.getSize() < 2) { if (args.getSize() < 2) {
errorAny(sender, "Usage: debug <toggle|include|exclude>"); errorAny(sender, "Usage: debug <toggle|include|exclude|test>");
return; return;
} }
@@ -141,6 +153,43 @@ public class AdminCommand implements QuickCommand, DupeContext {
successAny(sender, "Removed exclusion for {0} on the debugger.", exclusion); 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.Args;
import me.trouper.alias.server.commands.CommandRegistry; 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.QuickCommand;
import me.trouper.alias.server.commands.completions.CompletionBuilder; import me.trouper.alias.server.commands.completions.CompletionBuilder;
import me.trouper.alias.utils.misc.Cooldown; import me.trouper.alias.utils.misc.Cooldown;
@@ -84,7 +83,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
return false; 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) { if (amount > playerMax) {
warningAny(player,"Your maximum permitted dupe amplifier is {0}!", playerMax); warningAny(player,"Your maximum permitted dupe amplifier is {0}!", playerMax);
return false; return false;
@@ -109,7 +108,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
dupeStack(player,toDupe,amount); 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); dupeCooldown.setCooldown(player.getUniqueId(), playerCooldown);
return true; return true;

View File

@@ -2,17 +2,12 @@ package me.trouper.dupealias.server.functions;
import me.trouper.dupealias.server.ItemTag; import me.trouper.dupealias.server.ItemTag;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
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().checkGlobalRuleTag(input,ItemTag.UNIQUE); boolean isUnique = getDupe().checkEffectiveTag(input,ItemTag.UNIQUE);
boolean set = input.hasItemMeta() && input.getPersistentDataContainer().has(ItemTag.UNIQUE.getKey()); if (isUnique) return false;
boolean individuallyUnique = Boolean.TRUE.equals(input.getPersistentDataContainer().get(ItemTag.UNIQUE.getKey(), PersistentDataType.BOOLEAN));
if (set && individuallyUnique) return false;
if (!set && globallyUnique) return false;
return new ItemInventoryCheck(this).passes(input); 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.DupeAlias;
import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.DupeContext;
import net.kyori.adventure.text.format.TextColor; 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 {

View File

@@ -1,20 +1,25 @@
package me.trouper.dupealias.server.gui.admin; package me.trouper.dupealias.server.gui.admin;
import me.trouper.alias.server.systems.gui.QuickGui;
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.GlobalRule;
import me.trouper.dupealias.data.ItemsAdderItem;
import me.trouper.dupealias.server.ItemTag; import me.trouper.dupealias.server.ItemTag;
import me.trouper.dupealias.server.gui.CommonItems; import me.trouper.dupealias.server.gui.CommonItems;
import me.trouper.dupealias.server.gui.admin.config.ConfigGui; 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.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import java.util.*; import java.util.ArrayList;
import java.util.stream.Collectors; import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class AdminPanelManager implements DupeContext, CommonItems { public class AdminPanelManager implements DupeContext, CommonItems {
@@ -50,73 +55,6 @@ public class AdminPanelManager implements DupeContext, CommonItems {
new GlobalRuleMaterialSelector(this, rule).createGUI(player).open(player); 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) { public void openPotionEffectEditor(Player player, GlobalRule rule) {
new GlobalRulePotionEffectEditor(this, rule).createGUI(player).open(player); new GlobalRulePotionEffectEditor(this, rule).createGUI(player).open(player);
} }
@@ -125,44 +63,28 @@ public class AdminPanelManager implements DupeContext, CommonItems {
new GlobalRuleArmorTrimEditor(this, rule).open(player); 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) { public void openLoreCriteriaEditor(Player player, GlobalRule rule) {
QuickGui gui = QuickGui.create() new GlobalRuleLoreEditor(this,rule).open(player);
.titleMini("<gradient:#9c27b0:#7b1fa2><bold>Lore Contains</bold></gradient>") }
.rows(3)
.item(13, ItemBuilder.create(Material.WRITABLE_BOOK) public void openNbtCriteriaEditor(Player player, GlobalRule rule) {
.displayName("<light_purple><bold>Current Pattern") new GlobalRuleNbtTagEditor(this,rule).open(player);
.loreMiniMessage(Arrays.asList( }
"<gray>Set a regex pattern to match",
"<gray>against item lore lines", public void openCompoundCriteriaEditor(Player player, GlobalRule rule) {
"", new GlobalRuleCompoundTagEditor(this,rule).open(player);
"<white>Current: <light_purple>" + (rule.loreContainsRegex.isEmpty() ? "Not set" : rule.loreContainsRegex), }
"",
"<yellow>▶ <white>Click to set pattern", public void openModelDataEditor(Player player, GlobalRule rule) {
"<yellow>▶ <white>Right-click to clear" new GlobalRuleModelDataEditor(this,rule).open(player);
)) }
.build(), (g, e) -> {
if (e.isRightClick()) { public void openItemsAdderParser(Player player, GlobalRule rule) {
rule.loreContainsRegex = ""; new GlobalRuleItemsAdderParser(this,rule).open(player);
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) { public void openEnchantmentEditor(Player player, GlobalRule rule) {
@@ -177,51 +99,6 @@ public class AdminPanelManager implements DupeContext, CommonItems {
new GlobalRuleItemFlagEditor(this, rule).open(player); 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) { public ItemStack createExplainedItem(ItemStack item) {
if (item == null || item.isEmpty()) { if (item == null || item.isEmpty()) {
return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE) return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE)
@@ -230,6 +107,11 @@ public class AdminPanelManager implements DupeContext, CommonItems {
.build(); .build();
} }
ItemsAdderItem iai = new ItemsAdderItem();
try {
iai = new ItemsAdderItem(item);
} catch (IllegalArgumentException ignored) {}
List<String> lore = new ArrayList<>(); List<String> lore = new ArrayList<>();
lore.add("<white><bold>Held Item Explanation:</bold>"); lore.add("<white><bold>Held Item Explanation:</bold>");
@@ -266,6 +148,10 @@ public class AdminPanelManager implements DupeContext, CommonItems {
activeTags.add(ItemTag.UNIQUE); activeTags.add(ItemTag.UNIQUE);
} }
if (!iai.equals(new ItemsAdderItem())) {
lore.add("<gray>• Is from ItemsAdder. (" + iai.namespace + ":" + iai.id + ")");
}
if (lore.size() == 1) { if (lore.size() == 1) {
lore.add("<gray>• No DupeAlias tags apply to this item"); lore.add("<gray>• No DupeAlias tags apply to this item");
} }
@@ -338,6 +224,4 @@ public class AdminPanelManager implements DupeContext, CommonItems {
case PROTECTED -> "dark_purple"; 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.server.systems.gui.QuickGui;
import me.trouper.alias.utils.ItemBuilder; import me.trouper.alias.utils.ItemBuilder;
import me.trouper.dupealias.server.gui.CommonItems; import me.trouper.dupealias.server.gui.CommonItems;
import me.trouper.dupealias.server.gui.admin.config.ConfigGui;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.entity.Player; 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)."); 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>") .displayName("<blue><bold>Main Color</bold>")
.loreMiniMessage(List.of( .loreMiniMessage(List.of(
"<gray>The color for the message border.", "<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)."); 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>") .displayName("<aqua><bold>Secondary Color</bold>")
.loreMiniMessage(List.of( .loreMiniMessage(List.of(
"<gray>The color used for the plugin's name.", "<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); successAny(p, "Plugin name set to: {0}", input);
open(p); open(p);
}) })
.item(12, ItemBuilder.create(Material.NAME_TAG) .item(13, ItemBuilder.create(Material.NAME_TAG)
.displayName("<green><bold>Plugin Name</bold>") .displayName("<green><bold>Plugin Name</bold>")
.loreMiniMessage(List.of( .loreMiniMessage(List.of(
"<gray>The name of the plugin displayed in messages.", "<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); successAny(p, "Flat prefix set to: {0}", input);
open(p); open(p);
}) })
.item(13, ItemBuilder.create(Material.PAPER) .item(14, ItemBuilder.create(Material.PAPER)
.displayName("<gray><bold>Flat Prefix</bold>") .displayName("<gray><bold>Flat Prefix</bold>")
.loreMiniMessage(List.of( .loreMiniMessage(List.of(
"<gray>The prefix used when 'flat' mode is enabled.", "<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", getDupe().getGuiListener().requestChatInput(g, player, "flat_prefix",
"<gray>Enter the new 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>") .displayName("<white><bold>Flat Mode</bold>")
.loreMiniMessage(List.of( .loreMiniMessage(List.of(
"<gray>If true, uses the simple flat message system", "<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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public class GlobalRuleEditorGui implements DupeContext, CommonItems { public class GlobalRuleEditorGui implements DupeContext, CommonItems {
@@ -33,84 +32,75 @@ public class GlobalRuleEditorGui implements DupeContext, CommonItems {
.rows(6) .rows(6)
.fillBorder(EMPTY(Material.ORANGE_STAINED_GLASS_PANE)) .fillBorder(EMPTY(Material.ORANGE_STAINED_GLASS_PANE))
// Back button // Back button top-left
.item(0, BACK(), (g, e) -> manager.openGlobalRuleList(player)) .item(0, BACK(), (g, e) -> manager.openGlobalRuleList(player))
// Applied Tags Section // Item Tags
.item(10, createTagItem(ItemTag.UNIQUE), .item(10, createTagItem(ItemTag.UNIQUE), (g, e) -> toggleTag(player, ItemTag.UNIQUE))
(g, e) -> toggleTag(player, ItemTag.UNIQUE)) .item(11, createTagItem(ItemTag.FINAL), (g, e) -> toggleTag(player, ItemTag.FINAL))
.item(11, createTagItem(ItemTag.FINAL), .item(12, createTagItem(ItemTag.INFINITE), (g, e) -> toggleTag(player, ItemTag.INFINITE))
(g, e) -> toggleTag(player, ItemTag.FINAL)) .item(13, createTagItem(ItemTag.PROTECTED), (g, e) -> toggleTag(player, ItemTag.PROTECTED))
.item(12, createTagItem(ItemTag.INFINITE),
(g, e) -> toggleTag(player, ItemTag.INFINITE))
.item(13, createTagItem(ItemTag.PROTECTED),
(g, e) -> toggleTag(player, ItemTag.PROTECTED))
// Match Mode // Material Matching
.item(16, createMatchModeItem(), .item(16, createMaterialListItem(), (g, e) -> {
(g, e) -> cycleMatchMode(player)) 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 // Criteria Match Mode
.item(19, createCriteriaItem("Name Regex", Material.NAME_TAG, .item(43, createMatchModeItem(), (g, e) -> cycleMatchMode(player))
// Criteria Section
.item(28, createCriteriaItem("Name Regex", Material.NAME_TAG,
!rule.nameContainsRegex.isEmpty(), rule.nameContainsRegex), !rule.nameContainsRegex.isEmpty(), rule.nameContainsRegex),
(g, e) -> manager.openNameCriteriaEditor(player, rule)) (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), !rule.loreContainsRegex.isEmpty(), rule.loreContainsRegex),
(g, e) -> manager.openLoreCriteriaEditor(player, rule)) (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"), !rule.enchantments.isEmpty(), rule.enchantments.size() + " enchants"),
(g, e) -> manager.openEnchantmentEditor(player, rule)) (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"), !rule.attributes.isEmpty(), rule.attributes.size() + " attributes"),
(g, e) -> manager.openAttributeEditor(player, rule)) (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"), !rule.itemFlags.isEmpty(), rule.itemFlags.size() + " flags"),
(g, e) -> manager.openItemFlagEditor(player, rule)) (g, e) -> manager.openItemFlagEditor(player, rule))
.item(24, createCriteriaItem("Model Data", Material.COMPASS, .item(40, createCriteriaItem("Potion Effects", Material.POTION,
!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"), !rule.potionEffects.isEmpty(), rule.potionEffects.size() + " effects"),
(g, e) -> manager.openPotionEffectEditor(player, rule)) (g, e) -> manager.openPotionEffectEditor(player, rule))
// Material Settings .item(41, createCriteriaItem("Armor Trim", Material.NETHERITE_CHESTPLATE,
.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.isEmpty() || !rule.trimMaterials.isEmpty(),
(rule.trimPatterns.size() + rule.trimMaterials.size()) + " selected"), (rule.trimPatterns.size() + rule.trimMaterials.size()) + " selected"),
(g, e) -> manager.openArmorTrimEditor(player, rule)) (g, e) -> manager.openArmorTrimEditor(player, rule))
// Save button .onClose((g, e) -> {
.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(); getConfig().save();
successAny(player, "Saved global rule"); successAny(player, "Saved global rule");
manager.openGlobalRuleList(player);
}) })
.fillEmpty(EMPTY()) .fillEmpty(EMPTY())
.clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f) .clickSound(Sound.UI_BUTTON_CLICK, 0.7f, 1.2f)
.build(); .build();
@@ -118,6 +108,8 @@ public class GlobalRuleEditorGui implements DupeContext, CommonItems {
gui.open(player); gui.open(player);
} }
private ItemStack createTagItem(ItemTag tag) { private ItemStack createTagItem(ItemTag tag) {
boolean active = rule.appliedTags.contains(tag); boolean active = rule.appliedTags.contains(tag);
Material material = switch (tag) { Material material = switch (tag) {
@@ -217,8 +209,8 @@ public class GlobalRuleEditorGui implements DupeContext, CommonItems {
lore.add(""); lore.add("");
List<String> materialNames = rule.effectedMaterials.stream() List<String> materialNames = rule.effectedMaterials.stream()
.limit(5) .limit(5)
.map(mat -> mat.name()) .map(Enum::name)
.collect(Collectors.toList()); .toList();
for (String mat : materialNames) { for (String mat : materialNames) {
lore.add("<gray>• " + mat); lore.add("<gray>• " + mat);
@@ -238,6 +230,51 @@ public class GlobalRuleEditorGui implements DupeContext, CommonItems {
.build(); .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) { private void toggleTag(Player player, ItemTag tag) {
if (rule.appliedTags.contains(tag)) { if (rule.appliedTags.contains(tag)) {
rule.appliedTags.remove(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.ValidTrimMaterial;
import me.trouper.alias.data.enums.ValidTrimPattern; import me.trouper.alias.data.enums.ValidTrimPattern;
@@ -13,7 +13,6 @@ import org.bukkit.Material;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFlag;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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.data.enums.ValidAttribute;
import me.trouper.alias.server.systems.gui.QuickGui; 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.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; 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.data.enums.ValidEnchantment;
import me.trouper.alias.server.systems.gui.QuickGui; 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.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack; 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 { 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.server.systems.gui.QuickGui;
import me.trouper.alias.utils.ItemBuilder; 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.QuickGui;
import me.trouper.alias.server.systems.gui.QuickPaginatedGUI; import me.trouper.alias.server.systems.gui.QuickPaginatedGUI;
@@ -11,10 +11,11 @@ import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack; 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 { 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.data.enums.ValidPotionEffectType;
import me.trouper.alias.server.systems.gui.QuickGui; 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.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack; 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 { 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 @Override
public ChestSession getSession(Player player) { public ChestSession getSession(Player player) {
ChestSession session = super.getSession(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(); session.open();
return session; return session;
} }
@@ -32,7 +32,7 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
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); this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.chest.refresh.", getConfig().chest.baseRefreshDelayTicks,false);
} }
@Override @Override

View File

@@ -18,7 +18,7 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
@Override @Override
public InventorySession getSession(Player player) { public InventorySession getSession(Player player) {
InventorySession session = super.getSession(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(); session.open();
return session; return session;
} }
@@ -29,7 +29,7 @@ public class DupeInventoryGui extends AbstractDupeGui<DupeInventoryGui.Inventory
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); this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.inventory.refresh.", getConfig().inventory.baseRefreshDelayTicks,false);
} }
@Override @Override

View File

@@ -30,8 +30,8 @@ public class DupeReplicatorGui extends AbstractDupeGui<DupeReplicatorGui.Replica
@Override @Override
public ReplicatorSession getSession(Player player) { public ReplicatorSession getSession(Player player) {
ReplicatorSession session = super.getSession(player); ReplicatorSession session = super.getSession(player);
session.setDelayTicks(getDupe().getPermissionValue(player, "dupealias.gui.replicator.refresh.", getConfig().replicator.baseRefreshDelayTicks)); 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)); session.setCooldownTicks(getDupe().getPermissionValue(player, "dupealias.gui.replicator.cooldown.", getConfig().replicator.baseInputCooldownTicks,false));
session.open(); session.open();
return session; return session;
} }
@@ -49,8 +49,8 @@ public class DupeReplicatorGui extends AbstractDupeGui<DupeReplicatorGui.Replica
super(owner, "<gradient:#cc22ff:#cc99ff><bold>REPLICATOR</gradient>", 3); super(owner, "<gradient:#cc22ff:#cc99ff><bold>REPLICATOR</gradient>", 3);
getVerbose().send("Creating a new replicator with input of {0}", input.getType().name()); getVerbose().send("Creating a new replicator with input of {0}", input.getType().name());
setInput(input); setInput(input);
this.delayTicks = getDupe().getPermissionValue(owner, "dupealias.gui.replicator.refresh.", getConfig().replicator.baseRefreshDelayTicks); 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); this.cooldownTicks = getDupe().getPermissionValue(owner, "dupealias.gui.replicator.cooldown.", getConfig().replicator.baseInputCooldownTicks,false);
} }
@Override @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. description: Allows duplication of items through the command. Setting this to false results in the /dupe command always displaying a GUI.
default: true default: true
children: children:
dupealias.dupe.cooldown.integerhere: false # Controls the cooldown time in ticks it will take to command dupe again. 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. 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: 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. 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 default: true
@@ -50,6 +50,7 @@ permissions:
children: 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.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.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: 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