Bulk Item Gui, Drop on Death, Admin Panel Updates, and started documenting it.

This commit is contained in:
wolf
2025-07-26 00:26:29 -05:00
parent 2817161c11
commit cb223bbe0b
27 changed files with 1060 additions and 296 deletions

724
Documentation.md Normal file
View File

@@ -0,0 +1,724 @@
# DupeAlias - An Advanced Dupe Plugin
**Make your server stand out by switching to DupeAlias, a powerful dupe plugin with niche features for unique servers.**
---
## 🌟 Why Choose DupeAlias?
DupeAlias isn't just another dupe plugin - it's a complete ecosystem for managing item behavior on your server. Whether you're running a creative build server, a unique survival experience, or a custom game mode. DupeAlias gives you unprecedented control over how items behave.
### ✨ Key Features
**🎯 Smart Item Tagging System**
- **UNIQUE** - Prevent specific items from being duplicated
- **FINAL** - Lock items against any modifications
- **INFINITE** - Create truly infinite resources that never run out
- **PROTECTED** - Make items completely inert and unusable
**🔧 Advanced Global Rules Engine**
- Create complex rules based on item properties
- Match by material, enchantments, name patterns, lore, and more
- Support for armor trims, potion effects, attributes, and custom model data
- Flexible matching modes (AND, OR, NAND, XOR)
**🖥️ Multiple Duplication Interfaces**
- **Replicator GUI** - Single-item duplication with visual feedback
- **Chest GUI** - Multi-item container-style duplication
- **Inventory GUI** - Mirror your entire inventory for easy access
- **Menu GUI** - Central hub for all duplication options
**⚡ Performance & Customization**
- Per-permission refresh rates and cooldowns
- Extensive configuration options
- Session persistence (optional)
- Beautiful, modern GUIs with progress indicators
---
## 🎮 Perfect For These Server Types
- **Creative Servers** - Give builders infinite blocks while protecting special items
- **Survival+** - Create unique economies with controlled item flow
- **Minigames** - Provide infinite consumables while preventing exploitation
- **RPG Servers** - Protect quest items and create unbreakable gear
- **Prison Servers** - Control contraband while allowing resource flow
---
## 📸 Screenshots
*[Image: Main admin panel showing the clean, modern interface with gradient backgrounds and intuitive navigation]*
*[Image: Global rules editor displaying the complex criteria system with material selection, enchantment matching, and tag application]*
*[Image: Replicator GUI in action showing the animated rings, progress bars, and real-time item duplication]*
*[Image: Held item management interface demonstrating individual tag application with conflict warnings]*
*[Image: Configuration menu showcasing the extensive customization options and color-coded settings]*
---
## ⚙️ Quick Setup
1. Drop `DupeAlias.jar` into your plugins folder
2. Restart your server
3. Use `/da` to open the admin panel
4. Configure your global rules and permissions
5. Let your players use `/dupe` to start duplicating!
---
## 🔑 Permissions Overview
- `dupealias.admin` - Access to admin panel and configuration
- `dupealias.dupe` - Basic duplication command access
- `dupealias.gui.*` - Access to specific GUI types
- `dupealias.*.bypass` - Bypass tag restrictions (use carefully!)
- Permission-based refresh rates: `dupealias.gui.replicator.refresh.1`
---
## 💡 Advanced Use Cases
**Crate Key Protection**
Create a global rule that makes all items containing "key" in their name both UNIQUE and PROTECTED, preventing duplication and accidental use.
**Infinite Building Materials**
Set up INFINITE tags on common building blocks, giving your builders unlimited resources while maintaining server economy balance.
**Quest Item Security**
Use FINAL tags on story items to prevent players from renaming or modifying important quest objects.
**Admin Tool Management**
Combine PROTECTED and UNIQUE tags to create admin-only items that can't be duplicated or used by regular players.
---
## 🛠️ Developer-Friendly
Built on the robust Alias Development Framework with:
- Clean, documented API
- Event-driven architecture
- Extensive configuration options
- JSON-based data storage
- Full backward compatibility
---
## 📋 Requirements
- **Minecraft Version**: 1.21+
- **Server Software**: Paper, Purpur, or compatible
- **Java Version**: 17+
---
## 🤝 Support & Updates
DupeAlias is actively maintained with regular updates and feature additions. Join our community for support, suggestions, and to see what's coming next!
**Get DupeAlias today and revolutionize how items work on your server!**
---
# DupeAlias Documentation
## Table of Contents
1. [Installation & Setup](#installation--setup)
2. [Core Concepts](#core-concepts)
3. [Item Tags System](#item-tags-system)
4. [Global Rules Engine](#global-rules-engine)
5. [Duplication GUIs](#duplication-guis)
6. [Permissions System](#permissions-system)
7. [Configuration](#configuration)
8. [Commands](#commands)
9. [Common Scenarios](#common-scenarios)
10. [Troubleshooting](#troubleshooting)
---
## Installation & Setup
### Prerequisites
- Minecraft 1.21 or higher
- Paper, Purpur, or compatible server software
- Java 17 or higher
### Installation Steps
1. Download the DupeAlias JAR file
2. Place it in your server's `plugins/` directory
3. Restart your server
4. The plugin will generate default configuration files in `plugins/DupeAlias/`
### First-Time Configuration
1. Join your server as an operator
2. Run `/da` to open the admin panel
3. Navigate to Configuration → Common Config to customize colors and branding
4. Set up your first global rules or start tagging items manually
---
## Core Concepts
### Item Tags
DupeAlias uses four primary tags that can be applied to items:
- **UNIQUE**: Prevents the item from being duplicated
- **FINAL**: Prevents the item from being modified in any way
- **INFINITE**: Keeps the item at maximum stack size (99)
- **PROTECTED**: Prevents the item from being used, consumed, or crafted with
### Tag Priority System
Individual item tags **always override** global rules. This allows for fine-grained control where you can set global rules for item types while making exceptions for specific items.
### Global Rules vs Individual Tags
- **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.
---
## Item Tags System
### UNIQUE Tag
**Purpose**: Prevents item duplication through intended methods
**Use Cases**:
- Crate keys and special tokens
- Rare items and rewards
- Admin-only equipment
- Currency items
**Conflicts**: Cannot be combined with INFINITE (creates a logical paradox)
**Example**: A crate key that should never be duplicated
```
Individual: Right-click key in hand → Apply UNIQUE tag
Global: All items with "key" in name → Apply UNIQUE tag
```
### FINAL Tag
**Purpose**: Prevents any modification to the item
**Blocks**:
- Renaming items
- Enchanting items
- Repairing items
- Anvil operations
- Any metadata changes
**Use Cases**:
- Quest items with specific names
- Rank kits that shouldn't be modified
- Event rewards with special formatting
**Example**: A quest sword that must keep its name
```
Individual: Apply FINAL tag to specific sword
Global: All items with "Quest" in lore → Apply FINAL tag
```
### INFINITE Tag
**Purpose**: Maintains maximum stack size and refills items
**Behavior**:
- Sets item stack to 99 (max stackable amount)
- Refills automatically after use
- Works with blocks, consumables, and projectiles
**Use Cases**:
- Creative-style building materials
- Infinite arrows for archery ranges
- Unlimited consumables for events
**Conflicts**: Cannot be combined with UNIQUE or PROTECTED
**Example**: Infinite building blocks for creative areas
```
Individual: Apply INFINITE to held stone blocks
Global: All concrete blocks → Apply INFINITE tag
```
### PROTECTED Tag
**Purpose**: Makes items completely inert and unusable
**Blocks**:
- Using items (right-click)
- Consuming food/potions
- Placing blocks
- Attacking with weapons
- Crafting with the item
- Trading with villagers
**Use Cases**:
- Display items
- Coupons and vouchers
- Decorative rewards
- Placeholder items
**Conflicts**: Cannot be combined with INFINITE
**Example**: A decorative trophy that can't be used
```
Individual: Apply PROTECTED to specific trophy
Global: All items with "Display" in name → Apply PROTECTED tag
```
---
## Global Rules Engine
### Creating Global Rules
1. Open admin panel with `/da`
2. Navigate to **Global Rules**
3. Click **+ Create New Rule**
4. Configure criteria and applied tags
5. Save the rule
### Rule Components
#### Applied Tags
Select which tags this rule will apply to matching items. Multiple tags can be selected for a single rule.
#### Match Mode
- **AND**: All criteria must match
- **OR**: Any criteria must match
- **NAND**: Not all criteria match
- **XOR**: Exactly one criteria matches
#### Material Matching
- **IGNORE**: Apply to all materials
- **WHITELIST**: Only apply to selected materials
- **BLACKLIST**: Apply to all except selected materials
### Criteria Types
#### Name Regex
Match items based on display name patterns using regular expressions.
```
Example: ".*[Kk]ey.*" matches any item with "key" or "Key" in the name
```
#### Lore Regex
Match items based on lore content using regular expressions.
```
Example: ".*Special.*" matches items with "Special" anywhere in lore
```
#### Enchantments
Match items that have specific enchantments at minimum levels.
```
Example: Sharpness V → matches items with Sharpness 5 or higher
```
#### Attributes
Match items with specific attribute modifiers.
```
Example: Attack Damage ≥ 10.0 → matches weapons with high damage
```
#### Potion Effects
Match potions/foods with specific effects and amplifiers.
```
Example: Strength II → matches items giving Strength 2 or higher
```
#### Model Data
Match items with specific custom model data values.
```
Example: 12345 → matches items with CustomModelData: 12345
```
#### Item Flags
Match items with specific visibility flags.
```
Example: HIDE_ENCHANTS → matches items that hide enchantments
```
#### Armor Trim
Match armor pieces with specific trim patterns or materials.
```
Example: Silence Pattern + Gold Material → matches gold silence trim armor
```
### Example Rules
#### Protect All Crate Keys
```
Applied Tags: UNIQUE, PROTECTED, FINAL
Match Mode: OR
Name Regex: ".*[Kk]ey.*"
Lore Regex: ".*[Cc]rate.*"
```
#### Make Netherite Gear Unmodifiable
```
Applied Tags: FINAL
Match Mode: AND
Material Mode: WHITELIST
Materials: NETHERITE_SWORD, NETHERITE_AXE, NETHERITE_PICKAXE, etc.
```
#### Infinite Creative Blocks
```
Applied Tags: INFINITE
Match Mode: AND
Material Mode: WHITELIST Materials: STONE, DIRT, WOOD, CONCRETE variants
```
---
## Duplication GUIs
### Replicator GUI
**Access**: `/dupe replicator` or through main menu
**Features**:
- Single-item focused duplication
- Animated visual feedback
- Input cooldown system
- Progress bars for item refresh
**How to Use**:
1. Open the GUI
2. Drag an item into the left input slot
3. Take copies from the output slot
4. Input refreshes after cooldown period
**Best For**: Quick duplication of single item types
### Chest GUI
**Access**: `/dupe chest` or through main menu
**Features**:
- Multi-item container interface
- 4 input columns, 4 output columns
- Individual item refresh timers
- Session persistence (if enabled)
**How to Use**:
1. Open the GUI
2. Place items in the left 4 columns
3. Take duplicated copies from the right 4 columns
4. Items refresh based on configured delays
**Best For**: Bulk duplication of multiple item types
### Inventory GUI
**Access**: `/dupe inventory` or through main menu
**Features**:
- Mirror of your actual inventory
- Includes armor slots and offhand
- Real-time synchronization
- Individual slot refresh timers
**How to Use**:
1. Open the GUI
2. Your inventory is mirrored in the interface
3. Take copies of any items you're carrying
4. GUI updates as you change your inventory
**Best For**: Easy access to copies of everything you're carrying
### Menu GUI
**Access**: `/dupe gui` or `/dupe` (if set as default)
**Features**:
- Central hub for all GUI types
- Permission-based access control
- Clean navigation interface
**How to Use**:
1. Open the main menu
2. Click on the GUI type you want to use
3. Access is controlled by permissions
---
## Permissions System
### Core Permissions
#### Admin Access
```yaml
dupealias.admin: true # Access to admin panel and configuration
```
#### Basic Duplication
```yaml
dupealias.dupe: true # Access to /dupe command
dupealias.dupe.cooldown.<integer>: false # Cooldown for command duping (milliseconds)
```
#### GUI Access
```yaml
dupealias.gui: true # Access to all GUIs
dupealias.gui.replicator: true # Access to replicator GUI
dupealias.gui.inventory: true # Access to inventory GUI dupealias.gui.chest: true # Access to chest GUI
```
### Advanced Permissions
#### Session Persistence
```yaml
dupealias.gui.replicator.keep: false # Keep replicator items on close
dupealias.gui.chest.keep: false # Keep chest items on close
dupealias.gui.chest.keepondeath: false # Keep chest items on death
```
#### Permission Based Refresh Rates
```yaml
dupealias.gui.replicator.refresh.<integer>: false # Ticks of refresh cooldown
dupealias.gui.replicator.cooldown.<integer>: false # Ticks of re-input cooldown
dupealias.gui.inventory.refresh.<integer>: false # Ticks of refresh cooldown
dupealias.gui.chest.refresh.<integer>: false # Ticks of refresh cooldown
```
#### Tag Bypasses (Use Carefully!)
```yaml
dupealias.unique.bypass: false # Can dupe UNIQUE items
dupealias.final.bypass: false # Can modify FINAL items
dupealias.protected.bypass: false # Can use PROTECTED items
```
#### Special Permissions
```yaml
dupealias.infinite: true # Can use INFINITE items
```
### Permission Hierarchy
The plugin uses a "lowest value wins" system for numeric permissions. If a player has both `refresh.20` and `refresh.5`, they will get the 5-tick refresh cooldown.
### Example Permission Sets
#### VIP Player
```yaml
groups:
vip:
permissions:
- dupealias.dupe - dupealias.gui - dupealias.gui.replicator.refresh.5 # Faster refresh
- dupealias.gui.replicator.cooldown.10 # Shorter cooldown
- dupealias.dupe.cooldown.0 # No command cooldown
```
#### Staff Member
```yaml
groups:
staff:
permissions: - dupealias.admin # Full admin access
- dupealias.gui.replicator.refresh.1 # Instant refresh
- dupealias.gui.*.keep # Session persistence
- dupealias.final.bypass # Can modify final items
```
---
## Configuration
### Main Configuration (`config.json`)
#### Duplication Settings
```json
{
"dupeCooldownMillis": 1000, // Command cooldown in milliseconds
"defaultDupeGui": "REPLICATOR" // Default GUI (REPLICATOR/INVENTORY/CHEST/MENU)
}
```
#### GUI Refresh Rates
```json
{
"replicator": {
"baseRefreshDelayTicks": 1, // Base item refresh delay
"baseInputCooldownTicks": 20 // Base input change cooldown
}, "chest": {
"baseRefreshDelayTicks": 1 // Base item refresh delay
},
"inventory": {
"baseRefreshDelayTicks": 1 // Base item refresh delay
}}
```
#### Command Blocking
```json
{
"finalCommandRegex": [
"\"(?:itemname|iname)\"gmi", // Block item naming commands "\"(?:itemlore|lore)\"gmi" // Block lore modification commands ]}
```
#### Tag Lore Customization (MiniMessage)
```json
{
"trueTagLore": {
"UNIQUE": "<dark_blue><bold>|</bold><blue> Unique",
"FINAL": "<dark_red><bold>|</bold><red> Final",
"INFINITE": "<dark_green><bold>|</bold><green> Infinite",
"PROTECTED": "<dark_purple><bold>|</bold><light_purple> Protected"
},
"falseTagLore": {
"UNIQUE": "<dark_blue><bold>|</bold><blue> Dupeable",
"FINAL": "<dark_red><bold>|</bold><red> Mutable",
"INFINITE": "<dark_green><bold>|</bold><green> Finite",
"PROTECTED": "<dark_purple><bold>|</bold><light_purple> Unprotected"
}
}
```
### Common Configuration (`common.json`)
#### Visual Customization
```json
{
"mainColor": 11184895, // Primary color (hex: AAAAFF)
"secondaryColor": 909055, // Secondary color (hex: 00DDFF)
"pluginName": "DupeAlias", // Display name
"flatPrefix": "&9DupeAlias> &7", // Legacy chat prefix
"flat": false // Use legacy formatting
}
```
#### Debug Settings
```json
{
"debugMode": false, // Enable debug output
"debuggerExclusions": [] // Methods to exclude from debug
}
```
---
## Commands
### Administrative Commands
#### `/dupealias` (Aliases: `/da`)
**Permission**: `dupealias.admin`
**Description**: Main administrative command
**Subcommands**:
- `/da` - Open admin panel GUI
- `/da gui` - Open admin panel GUI
- `/da debug toggle` - Toggle debug mode
- `/da debug exclude <method>` - Exclude method from debugging
- `/da debug include <method>` - Include method in debugging
#### Tag Management
- `/da tag <tag> [material|global] [remove]` - Manage item tags
- `/da tag <tag>` - Tag held item
- `/da tag <tag> remove` - Remove tag from held item
- `/da tag <tag> false` - Set tag to false on held item
- `/da tag <tag> global` - Create global rule for held item material
- `/da tag <tag> <material>` - Create global rule for specific material
- `/da tag <tag> <material> remove` - Remove global rule
#### Rule Management
- `/da rule create <tag>` - Create new global rule
- `/da rule list` - List all global rules
- `/da rule remove <index>` - Remove rule by index
- `/da rule info <index>` - Show detailed rule information
### Player Commands
#### `/dupe`
**Permission**: `dupealias.dupe`
**Description**: Main duplication command
**Usage**:
- `/dupe` - Duplicate held item once OR open default GUI
- `/dupe <amount>` - Duplicate held item multiple times
- `/dupe gui` - Open main GUI menu
- `/dupe replicator` - Open replicator GUI
- `/dupe inventory` - Open inventory GUI
- `/dupe chest` - Open chest GUI
## Troubleshooting
### Common Issues
#### "You cannot dupe unique items"
**Cause**: Item has UNIQUE tag or matches global rule
**Solution**:
- Check `/da` → Held Item Actions to see current tags
- Review global rules that might be applying UNIQUE tag
- Use `/da tag UNIQUE remove` to remove individual tag
#### Items not refreshing in GUI
**Cause**: Long refresh delay or permission issues
**Solution**:
- Check player has appropriate GUI permissions
- Verify refresh rate permissions (lower numbers = faster)
- Ensure player isn't hitting cooldown limits
#### "You cannot modify final items"
**Cause**: Item has FINAL tag blocking modifications
**Solution**:
- Check item tags in admin panel
- Use `/da tag FINAL remove` if needed
- Grant `dupealias.final.bypass` permission for admins
#### Global rules not applying
**Cause**: Rule criteria not matching or individual tags overriding
**Solution**:
- Test rule criteria with `/da rule info <index>`
- Check match mode (AND vs OR)
- Remember individual tags override global rules
#### GUI won't open
**Cause**: Missing permissions or plugin conflicts
**Solution**:
- Verify player has `dupealias.gui` permission
- Check for inventory plugin conflicts
- Ensure player inventory isn't full
### Debug Mode
Enable debug mode to troubleshoot issues:
```
/da debug toggle
```
This will show detailed information about:
- Tag checking processes
- Global rule matching
- Permission calculations
- GUI state changes
Exclude noisy methods:
```
/da debug exclude <method_name>
```
### Performance Considerations
#### Large Player Counts
- Use session persistence sparingly
- Monitor server TPS with `/tps`
#### Complex Global Rules
- Avoid overly complex regex patterns
- Use material whitelisting instead of complex criteria when possible
- Limit the number of active global rules
#### GUI Optimization
- Set appropriate refresh rates based on server performance
- Consider disabling session persistence for busy servers
- Use cooldowns to prevent spam
---
### Getting Help
If you encounter issues not covered in this documentation:
1. Enable debug mode and check console logs
2. Test with a minimal permission set
3. Verify your global rules are correctly configured
4. Check for conflicts with other plugins
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,2 +1 @@
match based on certain features such as enchant, trim, lore string, or name. Bulk item tag adder
on death, drop any items stored in chest GUI.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

0
gradlew vendored Normal file → Executable file
View File

View File

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

View File

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

View File

@@ -1,81 +0,0 @@
package me.trouper.dupealias.data;
import me.trouper.alias.utils.ItemSimilarity;
import me.trouper.dupealias.server.ItemTag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.ByteArrayInputStream;
import java.util.*;
public class ItemCapture {
private String serializedItem;
private double similarityThreshold;
private final ItemSimilarity.SimilarityConfiguration configuration;
private final Map<ItemTag, Boolean> tags;
public ItemCapture() {
this.similarityThreshold = 1;
this.configuration = new ItemSimilarity.SimilarityConfiguration();
this.tags = new HashMap<>();
}
public ItemCapture(ItemStack stack) {
this.serializedItem = serialize(stack);
this.similarityThreshold = 1;
this.configuration = new ItemSimilarity.SimilarityConfiguration();
this.tags = new HashMap<>();
}
private String serialize(ItemStack itemStack) {
try {
return Base64.getEncoder().encodeToString(itemStack.serializeAsBytes());
} catch (Exception e) {
throw new IllegalStateException("Unable to serialize ItemStack", e);
}
}
private ItemStack deserialize(String serializedItemStack) {
try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(serializedItemStack));
byte[] itemBytes = inputStream.readAllBytes();
return ItemStack.deserializeBytes(itemBytes);
} catch (Exception e) {
throw new IllegalStateException("Unable to deserialize ItemStack", e);
}
}
public Map<ItemTag, Boolean> getTags() {
return tags;
}
public ItemStack getStack() {
return deserialize(serializedItem);
}
public ItemMeta getMeta() {
return getStack().getItemMeta();
}
public boolean matches(ItemStack item) {
if (similarityThreshold >= 1) return item.isSimilar(getStack());
return similarityThreshold <= similarityTo(item);
}
public double similarityTo(ItemStack item) {
return ItemSimilarity.calculateSimilarity(item,getStack());
}
public double getThreshold() {
return similarityThreshold;
}
public ItemSimilarity.SimilarityConfiguration getConfiguration() {
return configuration;
}
public void setThreshold(double similarityThreshold) {
this.similarityThreshold = similarityThreshold;
}
}

View File

@@ -4,7 +4,6 @@ import me.trouper.alias.data.JsonSerializable;
import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.data.GlobalRule; import me.trouper.dupealias.data.GlobalRule;
import me.trouper.dupealias.server.ItemTag; import me.trouper.dupealias.server.ItemTag;
import org.bukkit.Material;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
@@ -15,7 +14,7 @@ public class DupeConfig implements JsonSerializable<DupeConfig>, DupeContext {
return new File(getInstance().getDataFolder(),"config.json"); return new File(getInstance().getDataFolder(),"config.json");
} }
public long dupeCooldownMillis = 1000; public int baseDupeCooldownMillis = 1000;
public String defaultDupeGui = "REPLICATOR"; public String defaultDupeGui = "REPLICATOR";

View File

@@ -1,41 +0,0 @@
package me.trouper.dupealias.data.files;
import me.trouper.alias.data.JsonSerializable;
import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.data.ItemCapture;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.util.*;
public class NBTStorage implements JsonSerializable<NBTStorage>, DupeContext {
@Override
public File getFile() {
return new File(getInstance().getDataFolder(), ".nbtstorage.json");
}
public List<ItemCapture> captures = new ArrayList<>();
public ItemCapture getCapture(ItemStack input) {
if (getNbtStorage().captures.isEmpty()) return null;
ItemCapture match = null;
double closest = -1;
for (ItemCapture capture : getNbtStorage().captures) {
boolean isSimilar = capture.getStack().isSimilar(input);
if (isSimilar) return capture;
double threshold = capture.getThreshold();
if (threshold >= 1) continue; // Don't bother calculating similarity if the item isn't similar.
double sim = capture.similarityTo(input);
if (sim >= threshold && sim >= closest) {
closest = sim;
match = capture;
}
}
if (closest == -1) return null;
return match;
}
}

View File

@@ -3,7 +3,6 @@ package me.trouper.dupealias.server;
import me.trouper.alias.utils.ItemBuilder; import me.trouper.alias.utils.ItemBuilder;
import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.data.GlobalRule; import me.trouper.dupealias.data.GlobalRule;
import me.trouper.dupealias.data.ItemCapture;
import me.trouper.dupealias.server.functions.UniqueCheck; import me.trouper.dupealias.server.functions.UniqueCheck;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
@@ -19,35 +18,8 @@ import java.util.*;
public class DupeManager implements DupeContext { public class DupeManager implements DupeContext {
/**
* @return false if the item was modified.
*/
public boolean verifyTag(ItemStack item) {
if (getNbtStorage().captures.isEmpty()) return true;
ItemCapture capture = getNbtStorage().getCapture(item);
if (capture == null) return true;
boolean modified = false;
for (Map.Entry<ItemTag, Boolean> tagEntry : capture.getTags().entrySet()) {
ItemTag tag = tagEntry.getKey();
boolean value = tagEntry.getValue();
boolean set = hasIndividualTag(item,tag);
if (set && checkIndividualTag(item,tag) == value) {
continue;
} else if (!set) {
setTag(item,tagEntry.getKey(),value);
} else {
removeTag(item,tag);
setTag(item,tag,value);
}
modified = true;
}
return !modified;
}
public boolean isUnique(ItemStack item) { public boolean isUnique(ItemStack item) {
if (item == null || item.isEmpty()) return false;
return !new UniqueCheck().passes(item); return !new UniqueCheck().passes(item);
} }
@@ -186,7 +158,7 @@ public class DupeManager implements DupeContext {
public boolean removeTag(ItemStack item, ItemTag tag) { public boolean removeTag(ItemStack item, ItemTag tag) {
ItemBuilder builder = ItemBuilder.of(item); ItemBuilder builder = ItemBuilder.of(item);
if (hasIndividualTag(item, tag) && !checkIndividualTag(item, tag)) return false; if (!hasIndividualTag(item, tag)) return false;
builder.modifyMeta(itemMeta->{ builder.modifyMeta(itemMeta->{
if (itemMeta.hasLore()) { if (itemMeta.hasLore()) {

View File

@@ -183,7 +183,7 @@ public class AdminCommand implements QuickCommand, DupeContext {
GlobalRule rule = getConfig().globalRules.get(i); GlobalRule rule = getConfig().globalRules.get(i);
StringBuilder tagList = new StringBuilder(); StringBuilder tagList = new StringBuilder();
for (ItemTag tag : rule.appliedTags) { for (ItemTag tag : rule.appliedTags) {
if (tagList.length() > 0) tagList.append(", "); if (!tagList.isEmpty()) tagList.append(", ");
tagList.append(tag.getName()); tagList.append(tag.getName());
} }
infoAny(sender, " #{0}: Tags: {1}, Match Mode: {2}, Material Mode: {3}", infoAny(sender, " #{0}: Tags: {1}, Match Mode: {2}, Material Mode: {3}",
@@ -209,7 +209,7 @@ public class AdminCommand implements QuickCommand, DupeContext {
StringBuilder tagList = new StringBuilder(); StringBuilder tagList = new StringBuilder();
for (ItemTag tag : removedRule.appliedTags) { for (ItemTag tag : removedRule.appliedTags) {
if (tagList.length() > 0) tagList.append(", "); if (!tagList.isEmpty()) tagList.append(", ");
tagList.append(tag.getName()); tagList.append(tag.getName());
} }

View File

@@ -17,7 +17,6 @@ import java.util.UUID;
@CommandRegistry( @CommandRegistry(
value = "dupe", value = "dupe",
permission = @Permission(value = "dupealias.dupe", message = "You do not have permission to duplicate items."),
usage = "/dupe [integer|gui]", usage = "/dupe [integer|gui]",
printStackTrace = true, printStackTrace = true,
blocksAllowed = false, blocksAllowed = false,
@@ -25,21 +24,15 @@ import java.util.UUID;
) )
public class DupeCommand implements QuickCommand, DupeContext { public class DupeCommand implements QuickCommand, DupeContext {
private final DupeGui dupeGui = new DupeGui(); public final DupeGui dupeGui = new DupeGui();
private final Cooldown<UUID> dupeCooldown = new Cooldown<>(); private final Cooldown<UUID> dupeCooldown = new Cooldown<>();
@Override @Override
public void handleCommand(CommandSender sender, Command command, String label, Args args) { public void handleCommand(CommandSender sender, Command command, String label, Args args) {
Player player = (Player) sender; Player player = (Player) sender;
if (!player.hasPermission("dupealias.dupe.cooldownbypass") && dupeCooldown.isOnCooldown(player.getUniqueId())) {
warningAny(player,"You can run /dupe again in {0}.", dupeCooldown.formatLong(player.getUniqueId()));
return;
}
if (args.isEmpty()) { if (args.isEmpty()) {
if (dupeHeld(player,1)) { if (!verifyDupe(player,1)) {
dupeCooldown.setCooldown(player.getUniqueId(), getConfig().dupeCooldownMillis);
} else {
dupeGui.openDefaultGui(player); dupeGui.openDefaultGui(player);
} }
return; return;
@@ -66,9 +59,7 @@ public class DupeCommand implements QuickCommand, DupeContext {
try { try {
int amount = args.get(0).toInt(); int amount = args.get(0).toInt();
if (dupeHeld(player,amount)) { if (!verifyDupe(player,amount)) {
dupeCooldown.setCooldown(player.getUniqueId(), getConfig().dupeCooldownMillis);
} else {
dupeGui.openDefaultGui(player); dupeGui.openDefaultGui(player);
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@@ -83,16 +74,50 @@ public class DupeCommand implements QuickCommand, DupeContext {
); );
} }
private boolean dupeHeld(Player player, int amount) { private boolean verifyDupe(Player player, int amount) {
ItemStack inHand = player.getInventory().getItemInMainHand(); if (!player.hasPermission("dupealias.dupe")) {
if (getDupe().isUnique(inHand)) { warningAny(player,"You are not allowed to dupe via commands.");
warningAny(player,"Your {0} is or contains a unique item that cannot be duped!", inHand.getType()); return false;
}
if (dupeCooldown.isOnCooldown(player.getUniqueId())) {
warningAny(player,"You can command dupe again in {0}.", dupeCooldown.formatLong(player.getUniqueId()));
return false; return false;
} }
if (inHand.isEmpty()) return false;
int baseCount = inHand.getAmount(); int playerMax = getDupe().getPermissionValue(player,"dupealias.dupe.limit.",Integer.MAX_VALUE);
int maxPerStack = inHand.getMaxStackSize(); if (amount > playerMax) {
warningAny(player,"Your maximum permitted dupe amplifier is {0}!", playerMax);
return false;
}
ItemStack toDupe = player.getInventory().getItemInMainHand();
ItemStack offHand = player.getInventory().getItemInOffHand();
if (toDupe.isEmpty() && offHand.isEmpty()) {
warningAny(player,"You must hold an item to duplicate it with commands.");
return false;
}
if (toDupe.isEmpty() || getDupe().isUnique(toDupe)) {
if (getDupe().isUnique(offHand)) {
warningAny(player,"Your {0} is or contains a unique item that cannot be duped!", toDupe.getType());
return false;
} else {
toDupe = offHand;
}
}
dupeStack(player,toDupe,amount);
int playerCooldown = getDupe().getPermissionValue(player,"dupealias.dupe.cooldown.",getConfig().baseDupeCooldownMillis);
dupeCooldown.setCooldown(player.getUniqueId(), playerCooldown);
return true;
}
private void dupeStack(Player player, ItemStack heldStack, int amount) {
int baseCount = heldStack.getAmount();
int maxPerStack = heldStack.getMaxStackSize();
for (int i = 0; i <= amount - 1; i++) { for (int i = 0; i <= amount - 1; i++) {
int remaining = baseCount * (1 << i); int remaining = baseCount * (1 << i);
@@ -101,18 +126,17 @@ public class DupeCommand implements QuickCommand, DupeContext {
int stackAmt = Math.min(remaining, maxPerStack); int stackAmt = Math.min(remaining, maxPerStack);
remaining -= stackAmt; remaining -= stackAmt;
ItemStack batch = inHand.clone(); ItemStack batch = heldStack.clone();
batch.setAmount(stackAmt); batch.setAmount(stackAmt);
if (!player.getInventory().addItem(batch).isEmpty()) { if (!player.getInventory().addItem(batch).isEmpty()) {
infoAny(player,"Your inventory is now full."); infoAny(player,"Your inventory is now full.");
return true; return;
} }
} }
} }
int totalGiven = baseCount * ((1 << amount) - 1); int totalGiven = baseCount * ((1 << amount) - 1);
successAny(player,"You have duplicated {0} items!", totalGiven); successAny(player,"You have duplicated {0} items!", totalGiven);
return true;
} }
} }

View File

@@ -0,0 +1,29 @@
package me.trouper.dupealias.server.events;
import me.trouper.alias.server.events.QuickListener;
import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.server.commands.DupeCommand;
import me.trouper.dupealias.server.gui.dupe.DupeGui;
import me.trouper.dupealias.server.gui.dupe.sub.DupeChestGui;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class DeathEvent implements QuickListener, DupeContext {
@EventHandler
public void onDeath(PlayerDeathEvent e) {
Player p = e.getPlayer();
if (p.hasPermission("dupealias.gui.chest.keepondeath")) return;
DupeGui gui = getContext().getAutoRegistrar().getQuickCommand(DupeCommand.class).dupeGui;
DupeChestGui.ChestSession session = gui.chestGui.getSession(p);
if (session == null) return;
List<ItemStack> items = session.getInputItems();
e.getDrops().addAll(items);
}
}

View File

@@ -25,6 +25,10 @@ public class AdminPanelManager implements DupeContext, CommonItems {
public void openHeldItemGui(Player player) { public void openHeldItemGui(Player player) {
new HeldItemGui(this).open(player); new HeldItemGui(this).open(player);
} }
public void openBulkTagGui(Player player) {
new BulkTagGui(this).open(player);
}
public void openHelpGui(Player player) { public void openHelpGui(Player player) {
new HelpGui(this).open(player); new HelpGui(this).open(player);
@@ -289,28 +293,9 @@ public class AdminPanelManager implements DupeContext, CommonItems {
.build(); .build();
} }
public ItemStack createPreviewItem(ItemStack stack) {
if (stack.getType().isAir()) {
return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE)
.displayName("<gray><bold>No Item Held</bold>")
.loreMiniMessage(Arrays.asList(
"<gray>Hold an item to see",
"<gray>its current tag status",
"",
"<yellow>💡 <white>Hold an item and reopen this GUI"
))
.build();
}
return ItemBuilder.create(stack.getType())
.displayName("<white><bold>Currently Held: " + stack.getType().name() + "</bold>")
.loreMiniMessage(getItemTagStatus(stack))
.build();
}
public List<String> getItemTagStatus(ItemStack item) { public List<String> getItemTagStatus(ItemStack item) {
List<String> lore = new ArrayList<>(); List<String> lore = new ArrayList<>();
lore.add("<white>Item: <yellow>" + item.getType().name()); lore.add("<white>Held Item: <yellow>" + item.getType().name());
lore.add(""); lore.add("");
List<String> individualTags = new ArrayList<>(); List<String> individualTags = new ArrayList<>();

View File

@@ -0,0 +1,137 @@
package me.trouper.dupealias.server.gui.admin;
import me.trouper.alias.server.systems.gui.QuickGui;
import me.trouper.alias.utils.ItemBuilder;
import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.server.ItemTag;
import me.trouper.dupealias.server.gui.CommonItems;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.Arrays;
public class BulkTagGui implements DupeContext, CommonItems {
private final AdminPanelManager manager;
public BulkTagGui(AdminPanelManager manager) {
this.manager = manager;
}
public void open(Player player) {
QuickGui gui = QuickGui.create()
.titleMini("<gradient:#9b59b6:#8e44ad><bold>Bulk Tagging Menu</bold></gradient>")
.allowDrag()
.onGlobalClick((g,e)->{
if (e.getSlot() >= 45) return;
e.setCancelled(false);
})
.size(54)
.fillSlots(EMPTY(),null,49,51,52)
.item(53,BACK(),(g,e)->manager.openMainGui(player))
.item(45, ItemBuilder.create(Material.EMERALD)
.displayName("<green><bold>UNIQUE Tag</bold>")
.loreMiniMessage(Arrays.asList(
"<gray>Makes all items in the gui",
"<red>unable to be duplicated",
"",
"<yellow>▶ <white>Left click to apply tag",
"<yellow>▶ <white>Right click to remove tag",
"<yellow>▶ <white>Shift click to set tag to false"
))
.build(),
(g,e) -> handleTag(g.getInventory(),ItemTag.UNIQUE,e.getClick())
)
.item(46, ItemBuilder.create(Material.BARRIER)
.displayName("<red><bold>FINAL Tag</bold>")
.loreMiniMessage(Arrays.asList(
"<gray>Makes all items in the gui",
"<red>unable to be modified",
"",
"<yellow>▶ <white>Left click to apply tag",
"<yellow>▶ <white>Right click to remove tag",
"<yellow>▶ <white>Shift click to set tag to false"
))
.build(),
(g,e) -> handleTag(g.getInventory(),ItemTag.FINAL,e.getClick()))
.item(47, ItemBuilder.create(Material.WATER_BUCKET)
.displayName("<blue><bold>INFINITE Tag</bold>")
.loreMiniMessage(Arrays.asList(
"<gray>Makes all items in the gui",
"<green>always have max stack size",
"",
"<yellow>▶ <white>Left click to apply tag",
"<yellow>▶ <white>Right click to remove tag",
"<yellow>▶ <white>Shift click to set tag to false"
))
.build(),
(g,e) -> handleTag(g.getInventory(),ItemTag.INFINITE,e.getClick()))
.item(48, ItemBuilder.create(Material.STRUCTURE_VOID)
.displayName("<dark_purple><bold>PROTECTED Tag</bold>")
.loreMiniMessage(Arrays.asList(
"<gray>Makes all items in the gui",
"<red>not able to be manually created",
"",
"<yellow>▶ <white>Left click to apply tag",
"<yellow>▶ <white>Right click to remove tag",
"<yellow>▶ <white>Shift click to set tag to false"
))
.build(),
(g,e) -> handleTag(g.getInventory(),ItemTag.PROTECTED,e.getClick()))
.item(50, ItemBuilder.create(Material.TNT)
.displayName("<dark_red><bold>Remove All Tags</bold>")
.loreMiniMessage(Arrays.asList(
"<gray>Removes all tags from",
"<gray>all items in the gui",
"",
"<red>⚠ <white>This cannot be undone!",
"<yellow>▶ <white>Click to remove tags"
))
.build(),
(g, e) -> removeTags(g.getInventory()))
.build();
gui.open(player);
}
private void handleTag(Inventory bulkGui, ItemTag tag, ClickType click) {
switch (click) {
case LEFT -> tagItems(bulkGui,tag,true);
case RIGHT -> removeTag(bulkGui,tag);
case SHIFT_LEFT, SHIFT_RIGHT -> tagItems(bulkGui,tag,false);
}
}
private void removeTags(Inventory bulkGui) {
for (ItemTag value : ItemTag.values()) {
removeTag(bulkGui,value);
}
}
private void removeTag(Inventory bulkGui, ItemTag tag) {
for (int i = 0; i < 45; i++) {
ItemStack toTag = bulkGui.getItem(i);
if (toTag == null || toTag.isEmpty()) continue;
getDupe().removeTag(toTag,tag);
}
}
private void tagItems(Inventory bulkGui, ItemTag tag, boolean value) {
for (int i = 0; i < 45; i++) {
ItemStack toTag = bulkGui.getItem(i);
if (toTag == null || toTag.isEmpty()) continue;
getDupe().setTag(toTag,tag,value);
}
}
}

View File

@@ -187,22 +187,24 @@ public class HelpGui implements CommonItems {
.loreMiniMessage(Arrays.asList( .loreMiniMessage(Arrays.asList(
"<white>Access Permissions:", "<white>Access Permissions:",
"<green>The root permission node is <bold>dupealias", "<green>The root permission node is <bold>dupealias",
"<gray>• <white>.dupe<gray> - Use /dupe command", "<gray>• <white>.dupe<gray> - Use command duping",
"<gray>• <white>.dupe.cooldown.<n><gray> - Sets the command dupe cooldown",
"<gray>• <white>.dupe.limit.<n><gray> - Sets the command dupe amplifier limit",
"<gray>• <white>.gui<gray> - Access duplication GUI", "<gray>• <white>.gui<gray> - Access duplication GUI",
"", "",
"<white>Dupe GUI & Sessions:", "<white>Dupe GUI & Sessions:",
"<gray>• <white>.gui.<type>.refresh.<n><gray> - GUI refill time", "<gray>• <white>.gui.<type>.refresh.<n><gray> - GUI refill time",
"<gray>• <white>.gui.<type>.keep<gray> - Retain items in GUI session", "<gray>• <white>.gui.<type>.keep<gray> - Retain items in GUI session",
"<gray>• <white>.gui.replicator<gray> - Shift-click duplicate", "<gray>• <white>.gui.replicator<gray> - Shift-click duplicate",
"<gray>• <white>.gui.replicator.cooldown<gray> - Item input cooldown", "<gray>• <white>.gui.replicator.cooldown.<n><gray> - Item input cooldown",
"<gray>• <white>.gui.inventory<gray> - View personal inventory", "<gray>• <white>.gui.inventory<gray> - View personal inventory",
"<gray>• <white>.gui.chest<gray> - Dupe via container", "<gray>• <white>.gui.chest<gray> - Dupe via container",
"<gray>• <white>.gui.chest.keepondeath<gray> - Don't drop items on death",
"", "",
"<white>Bypass Permissions:", "<white>Bypass Permissions:",
"<gray>• <white>.unique.bypass<gray> - Dupe unique items", "<gray>• <white>.unique.bypass<gray> - Dupe unique items",
"<gray>• <white>.final.bypass<gray> - Modify final items", "<gray>• <white>.final.bypass<gray> - Modify final items",
"<gray>• <white>.protected.bypass<gray> - Use protected items", "<gray>• <white>.protected.bypass<gray> - Use protected items",
"<gray>• <white>.dupe.cooldownbypass<gray> - Skip /dupe cooldown",
"", "",
"<white>Other:", "<white>Other:",
"<gray>• <white>.infinite<gray> - Use infinite-tagged items", "<gray>• <white>.infinite<gray> - Use infinite-tagged items",

View File

@@ -7,6 +7,10 @@ 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;
import org.bukkit.inventory.ItemStack;
import java.util.Arrays;
import java.util.List;
public class MainAdminGui implements CommonItems { public class MainAdminGui implements CommonItems {
@@ -21,18 +25,6 @@ public class MainAdminGui implements CommonItems {
.titleMini("<gradient:#6b6bff:#9999ff><bold>DupeAlias Admin Panel</gradient>") .titleMini("<gradient:#6b6bff:#9999ff><bold>DupeAlias Admin Panel</gradient>")
.rows(5) .rows(5)
.item(11, ItemBuilder.create(player.getInventory().getItemInMainHand().isEmpty() ? Material.BARRIER : Material.DIAMOND_SWORD)
.displayName("<gradient:#4ecdc4:#45b7d1><bold>Held Item Actions</bold></gradient>")
.loreMiniMessage(
"<gray>Manage tags for the item",
"<gray>you're currently holding",
"",
"<yellow>▶ <white>Click to open menu"
)
.hideAllFlags()
.build(),
(q, event) -> manager.openHeldItemGui(player))
.item(13, ItemBuilder.create(Material.BOOKSHELF) .item(13, ItemBuilder.create(Material.BOOKSHELF)
.displayName("<gradient:#ff6b6b:#ffa726><bold>Global Rules</bold></gradient>") .displayName("<gradient:#ff6b6b:#ffa726><bold>Global Rules</bold></gradient>")
.loreMiniMessage( .loreMiniMessage(
@@ -57,6 +49,17 @@ public class MainAdminGui implements CommonItems {
.build(), .build(),
(q, event) -> manager.openHelpGui(player)) (q, event) -> manager.openHelpGui(player))
.item(11, ItemBuilder.create(Material.CHEST)
.displayName("<gradient:#4ecdc4:#45b7d1><bold>Bulk Item Actions</bold></gradient>")
.loreMiniMessage(
"<gray>Manage tags for multiple",
"<gray>items at a time",
"",
"<yellow>▶ <white>Click to open menu"
)
.build(),
(q,event) -> manager.openBulkTagGui(player))
.item(29, ItemBuilder.create(Material.COMPARATOR) .item(29, ItemBuilder.create(Material.COMPARATOR)
.displayName("<gradient:#ff6bff:#ffa7ff><bold>Configuration</bold></gradient>") .displayName("<gradient:#ff6bff:#ffa7ff><bold>Configuration</bold></gradient>")
.loreMiniMessage( .loreMiniMessage(
@@ -67,7 +70,8 @@ public class MainAdminGui implements CommonItems {
) )
.build(), (q,event) -> manager.openConfigGui(player)) .build(), (q,event) -> manager.openConfigGui(player))
.item(31, manager.createPreviewItem(player.getInventory().getItemInMainHand())) .item(31, createPreviewItem(player.getInventory().getItemInMainHand()),
(q,event) -> manager.openHeldItemGui(player))
.item(33, ItemBuilder.create(Material.DIAMOND) .item(33, ItemBuilder.create(Material.DIAMOND)
.displayName("<#AAAAFF><bold>Dupe<#00DDFF>Alias</bold> <white>Credits") .displayName("<#AAAAFF><bold>Dupe<#00DDFF>Alias</bold> <white>Credits")
@@ -87,4 +91,32 @@ public class MainAdminGui implements CommonItems {
gui.open(player); gui.open(player);
} }
private ItemStack createPreviewItem(ItemStack stack) {
if (stack.getType().isAir()) {
return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE)
.displayName("<gray><bold>No Item Held</bold>")
.loreMiniMessage(Arrays.asList(
"<gray>Hold an item to see",
"<gray>its current tag status",
"",
"<yellow>💡 <white>Hold an item and reopen this GUI"
))
.build();
}
List<String> lore = manager.getItemTagStatus(stack);
lore.addAll(List.of(
"",
"<gray>Manage tags for the item",
"<gray>you're currently holding",
"",
"<yellow>▶ <white>Click to open menu"
));
return ItemBuilder.create(stack.getType())
.displayName("<gradient:#4ecdc4:#45b7d1><bold>Held Item Actions</bold></gradient>")
.loreMiniMessage(lore)
.build();
}
} }

View File

@@ -6,6 +6,8 @@ import me.trouper.dupealias.DupeContext;
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.AdminPanelManager; import me.trouper.dupealias.server.gui.admin.AdminPanelManager;
import me.trouper.dupealias.server.gui.admin.config.sub.CommandRegexGui;
import me.trouper.dupealias.server.gui.admin.config.sub.CommonConfigGui;
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;
@@ -28,21 +30,19 @@ public class ConfigGui implements DupeContext, CommonItems {
.rows(5) .rows(5)
.clickSound(Sound.UI_BUTTON_CLICK, 0.5f, 1.0f) .clickSound(Sound.UI_BUTTON_CLICK, 0.5f, 1.0f)
// Back button
.item(0, BACK(), (g, e) -> manager.openMainGui(player)) .item(0, BACK(), (g, e) -> manager.openMainGui(player))
// Dupe Cooldown
.callback("dupe_cooldown", new QuickGui.GuiCallback() { .callback("dupe_cooldown", new QuickGui.GuiCallback() {
@Override @Override
public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) { public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) {
try { try {
long millis = Long.parseLong(input); int millis = Integer.parseInt(input);
if (millis < 0) { if (millis < 0) {
errorAny(player, "Cooldown cannot be negative!"); errorAny(player, "Cooldown cannot be negative!");
return; return;
} }
infoAny(player, "You have set the dupe cooldown to {0}ms.", input); infoAny(player, "You have set the dupe cooldown to {0}ms.", input);
getConfig().dupeCooldownMillis = millis; getConfig().baseDupeCooldownMillis = millis;
getConfig().save(); getConfig().save();
open(player); open(player);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
@@ -52,17 +52,17 @@ public class ConfigGui implements DupeContext, CommonItems {
} }
}) })
.item(11, ItemBuilder.integerItem(Material.DIAMOND, "<aqua><bold>Dupe Command Cooldown</bold>", List.of( .item(11, ItemBuilder.integerItem(Material.DIAMOND, "<aqua><bold>Dupe Command Cooldown</bold>", List.of(
"<gray>How long players have to wait", "<gray>The base delay for command duping.",
"<gray>before running the /dupe command again.", "<gray>use the permission dupealias.dupe.cooldown.<n>",
"<gray>to configure cooldowns per-player",
"", "",
"<yellow>Current: <white>" + getConfig().dupeCooldownMillis + "ms", "<yellow>Current: <white>" + getConfig().baseDupeCooldownMillis + "ms",
"", "",
"<yellow>▶ <white>Click to modify" "<yellow>▶ <white>Click to modify"
), (int) getConfig().dupeCooldownMillis), (g, e) -> ), (int) getConfig().baseDupeCooldownMillis), (g, e) ->
getDupe().getGuiListener().requestChatInput(g, player, "dupe_cooldown", getDupe().getGuiListener().requestChatInput(g, player, "dupe_cooldown",
"<aqua>Insert a long value of Milliseconds.\n<gray> 1000ms = 1 Second\n\n<yellow>Current value: <white>" + getConfig().dupeCooldownMillis + "ms")) "<aqua>Insert a long value of Milliseconds.\n<gray> 1000ms = 1 Second\n\n<yellow>Current value: <white>" + getConfig().baseDupeCooldownMillis + "ms"))
// Default Dupe GUI
.item(12, ItemBuilder.create(Material.CHEST) .item(12, ItemBuilder.create(Material.CHEST)
.displayName("<green><bold>Default Dupe GUI</bold>") .displayName("<green><bold>Default Dupe GUI</bold>")
.loreMiniMessage(Arrays.asList( .loreMiniMessage(Arrays.asList(
@@ -77,7 +77,6 @@ public class ConfigGui implements DupeContext, CommonItems {
)) ))
.build(), (g, e) -> cycleDefaultGui(player)) .build(), (g, e) -> cycleDefaultGui(player))
// Final Command Regex
.item(13, ItemBuilder.create(Material.BARRIER) .item(13, ItemBuilder.create(Material.BARRIER)
.displayName("<red><bold>Final Command Regex</bold>") .displayName("<red><bold>Final Command Regex</bold>")
.loreMiniMessage(Arrays.asList( .loreMiniMessage(Arrays.asList(
@@ -90,7 +89,6 @@ public class ConfigGui implements DupeContext, CommonItems {
)) ))
.build(), (g, e) -> openFinalCommandRegexGui(player)) .build(), (g, e) -> openFinalCommandRegexGui(player))
// Global Rules Editor
.item(14, ItemBuilder.create(Material.COMMAND_BLOCK) .item(14, ItemBuilder.create(Material.COMMAND_BLOCK)
.displayName("<light_purple><bold>Global Rules Editor</bold>") .displayName("<light_purple><bold>Global Rules Editor</bold>")
.loreMiniMessage(Arrays.asList( .loreMiniMessage(Arrays.asList(
@@ -103,7 +101,6 @@ public class ConfigGui implements DupeContext, CommonItems {
)) ))
.build(), (g, e) -> openGlobalRulesGui(player)) .build(), (g, e) -> openGlobalRulesGui(player))
// Tag Lore Settings
.item(15, ItemBuilder.create(Material.NAME_TAG) .item(15, ItemBuilder.create(Material.NAME_TAG)
.displayName("<yellow><bold>Tag Lore Settings</bold>") .displayName("<yellow><bold>Tag Lore Settings</bold>")
.loreMiniMessage(Arrays.asList( .loreMiniMessage(Arrays.asList(
@@ -114,7 +111,6 @@ public class ConfigGui implements DupeContext, CommonItems {
)) ))
.build(), (g, e) -> openTagLoreGui(player, g)) .build(), (g, e) -> openTagLoreGui(player, g))
// Common Settings
.item(22,ItemBuilder.create(Material.LIGHT) .item(22,ItemBuilder.create(Material.LIGHT)
.displayName("<gold><bold>Common Config</bold>") .displayName("<gold><bold>Common Config</bold>")
.loreMiniMessage( .loreMiniMessage(
@@ -126,7 +122,6 @@ public class ConfigGui implements DupeContext, CommonItems {
.build(), .build(),
(g,e) -> openCommonGui(player, g)) (g,e) -> openCommonGui(player, g))
// Replicator Settings
.item(30, ItemBuilder.create(Material.REPEATER) .item(30, ItemBuilder.create(Material.REPEATER)
.displayName("<blue><bold>Replicator Settings</bold>") .displayName("<blue><bold>Replicator Settings</bold>")
.loreMiniMessage(Arrays.asList( .loreMiniMessage(Arrays.asList(
@@ -139,7 +134,6 @@ public class ConfigGui implements DupeContext, CommonItems {
)) ))
.build(), (g, e) -> openReplicatorGui(player, g)) .build(), (g, e) -> openReplicatorGui(player, g))
// Chest Settings
.item(31, ItemBuilder.create(Material.CHEST) .item(31, ItemBuilder.create(Material.CHEST)
.displayName("<gold><bold>Chest GUI Settings</bold>") .displayName("<gold><bold>Chest GUI Settings</bold>")
.loreMiniMessage(Arrays.asList( .loreMiniMessage(Arrays.asList(
@@ -151,7 +145,6 @@ public class ConfigGui implements DupeContext, CommonItems {
)) ))
.build(), (g, e) -> openChestGui(player, g)) .build(), (g, e) -> openChestGui(player, g))
// Inventory Settings
.item(32, ItemBuilder.create(Material.ENDER_CHEST) .item(32, ItemBuilder.create(Material.ENDER_CHEST)
.displayName("<dark_purple><bold>Inventory GUI Settings</bold>") .displayName("<dark_purple><bold>Inventory GUI Settings</bold>")
.loreMiniMessage(Arrays.asList( .loreMiniMessage(Arrays.asList(

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.admin.config; package me.trouper.dupealias.server.gui.admin.config.sub;
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;

View File

@@ -1,8 +1,7 @@
package me.trouper.dupealias.server.gui.admin.config; package me.trouper.dupealias.server.gui.admin.config.sub;
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.DupeAlias;
import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.data.files.CommonConfig; import me.trouper.dupealias.data.files.CommonConfig;
import me.trouper.dupealias.server.gui.CommonItems; import me.trouper.dupealias.server.gui.CommonItems;
@@ -13,54 +12,34 @@ import org.bukkit.entity.Player;
import java.util.List; import java.util.List;
/**
* A GUI for modifying the settings within the common.json file.
* This class provides an interface for changing general plugin settings
* such as colors, prefixes, and display names.
*/
public class CommonConfigGui implements DupeContext, CommonItems { public class CommonConfigGui implements DupeContext, CommonItems {
private final AdminPanelManager manager; private final AdminPanelManager manager;
/**
* Constructs a new CommonConfigGui.
*
* @param manager The AdminPanelManager to handle navigation.
*/
public CommonConfigGui(AdminPanelManager manager) { public CommonConfigGui(AdminPanelManager manager) {
this.manager = manager; this.manager = manager;
} }
/**
* Opens the Common Config GUI for a player.
*
* @param player The player to open the GUI for.
*/
public void open(Player player) { public void open(Player player) {
// Retrieve the current common configuration instance.
CommonConfig config = getCommonConfig(); CommonConfig config = getCommonConfig();
// Create the GUI using the QuickGui builder.
QuickGui gui = QuickGui.create() QuickGui gui = QuickGui.create()
.titleMini("<dark_aqua><bold>Common Config</bold></dark_aqua>") .titleMini("<dark_aqua><bold>Common Config</bold></dark_aqua>")
.defaultTimeout(30000) .defaultTimeout(30000)
.rows(3) .rows(3)
.clickSound(Sound.UI_BUTTON_CLICK, 0.5f, 1.0f) .clickSound(Sound.UI_BUTTON_CLICK, 0.5f, 1.0f)
// Back button to return to the main admin panel.
.item(0, BACK(), (g, e) -> manager.openMainGui(player)) .item(0, BACK(), (g, e) -> manager.openMainGui(player))
// Item and callback for modifying the main color.
.callback("main_color", (g, p, input, source) -> { .callback("main_color", (g, p, input, source) -> {
try { try {
// Parse the hex string into an integer color value.
int color = Integer.parseInt(input.replace("#", ""), 16); int color = Integer.parseInt(input.replace("#", ""), 16);
config.mainColor = color; config.mainColor = color;
config.save(); config.save();
// Reload the common settings in the core to apply changes immediately.
getInstance().getCommon().update(config.generateCommon()); getInstance().getCommon().update(config.generateCommon());
successAny(p, "Main color set to <#{0}>#{0}</#{0}>.", Integer.toHexString(color)); successAny(p, "Main color set to <#{0}>#{0}</#{0}>.", Integer.toHexString(color));
open(p); // Re-open the GUI to show the updated value. open(p);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
errorAny(p, "Invalid hex color format. Use RRGGBB (e.g., AAAAFF)."); errorAny(p, "Invalid hex color format. Use RRGGBB (e.g., AAAAFF).");
} }
@@ -74,11 +53,10 @@ public class CommonConfigGui implements DupeContext, CommonItems {
"", "",
"<yellow>▶ <white>Click to modify" "<yellow>▶ <white>Click to modify"
)).build(), (g, e) -> )).build(), (g, e) ->
// Request chat input from the player.
getDupe().getGuiListener().requestChatInput(g, player, "main_color", getDupe().getGuiListener().requestChatInput(g, player, "main_color",
"<blue>Enter a hex color code for the main color.\n<gray>Example: AAAAFF")) "<blue>Enter a hex color code for the main color.\n<gray>Example: AAAAFF"))
// Item and callback for modifying the secondary color.
.callback("secondary_color", (g, p, input, source) -> { .callback("secondary_color", (g, p, input, source) -> {
try { try {
int color = Integer.parseInt(input.replace("#", ""), 16); int color = Integer.parseInt(input.replace("#", ""), 16);
@@ -103,7 +81,6 @@ public class CommonConfigGui implements DupeContext, CommonItems {
getDupe().getGuiListener().requestChatInput(g, player, "secondary_color", getDupe().getGuiListener().requestChatInput(g, player, "secondary_color",
"<aqua>Enter a hex color code for the secondary color.\n<gray>Example: 00DDFF")) "<aqua>Enter a hex color code for the secondary color.\n<gray>Example: 00DDFF"))
// Item and callback for modifying the plugin name.
.callback("plugin_name", (g, p, input, source) -> { .callback("plugin_name", (g, p, input, source) -> {
config.pluginName = input; config.pluginName = input;
config.save(); config.save();
@@ -123,7 +100,6 @@ public class CommonConfigGui implements DupeContext, CommonItems {
getDupe().getGuiListener().requestChatInput(g, player, "plugin_name", getDupe().getGuiListener().requestChatInput(g, player, "plugin_name",
"<green>Enter the new plugin name.")) "<green>Enter the new plugin name."))
// Item and callback for modifying the flat prefix.
.callback("flat_prefix", (g, p, input, source) -> { .callback("flat_prefix", (g, p, input, source) -> {
config.flatPrefix = input; config.flatPrefix = input;
config.save(); config.save();
@@ -144,7 +120,6 @@ 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 and click handler for toggling flat mode.
.item(14, ItemBuilder.create(config.flat ? Material.LIME_DYE : Material.GRAY_DYE) .item(14, 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(
@@ -159,14 +134,12 @@ public class CommonConfigGui implements DupeContext, CommonItems {
config.save(); config.save();
getInstance().getCommon().update(config.generateCommon()); getInstance().getCommon().update(config.generateCommon());
infoAny(player, "Flat mode set to: {0}", config.flat); infoAny(player, "Flat mode set to: {0}", config.flat);
open(player); // Re-open to update the item's appearance. open(player);
}) })
// Fill the rest of the GUI with empty panes.
.fillEmpty(EMPTY()) .fillEmpty(EMPTY())
.build(); .build();
// Display the GUI to the player.
gui.open(player); gui.open(player);
} }
} }

View File

@@ -4,6 +4,10 @@ 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.server.gui.CommonItems; import me.trouper.dupealias.server.gui.CommonItems;
import me.trouper.dupealias.server.gui.dupe.sub.AbstractDupeGui;
import me.trouper.dupealias.server.gui.dupe.sub.DupeChestGui;
import me.trouper.dupealias.server.gui.dupe.sub.DupeInventoryGui;
import me.trouper.dupealias.server.gui.dupe.sub.DupeReplicatorGui;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
@@ -18,6 +22,11 @@ public class DupeGui implements DupeContext, CommonItems {
public final DupeChestGui chestGui = new DupeChestGui(); public final DupeChestGui chestGui = new DupeChestGui();
public void openMainGui(Player player) { public void openMainGui(Player player) {
if (!player.hasPermission("dupealias.gui")) {
warningAny(player,"You do not have permission to use the main dupe gui.");
return;
}
QuickGui gui = QuickGui.create() QuickGui gui = QuickGui.create()
.rows(5) .rows(5)
.titleMini("<aqua><bold>Available GUIs") .titleMini("<aqua><bold>Available GUIs")
@@ -85,7 +94,7 @@ public class DupeGui implements DupeContext, CommonItems {
} }
} else { } else {
player.closeInventory(); player.closeInventory();
warningAny(player,"You do not have permission to use that GUI!"); warningAny(player,"You do not have permission to use that GUI.");
} }
} }

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.dupe; package me.trouper.dupealias.server.gui.dupe.sub;
import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.DupeContext;
import me.trouper.dupealias.server.gui.CommonItems; import me.trouper.dupealias.server.gui.CommonItems;

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.dupe; package me.trouper.dupealias.server.gui.dupe.sub;
import me.trouper.alias.server.systems.gui.QuickGui; import me.trouper.alias.server.systems.gui.QuickGui;
import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.DupeContext;

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.dupe; package me.trouper.dupealias.server.gui.dupe.sub;
import me.trouper.alias.server.systems.gui.QuickGui; import me.trouper.alias.server.systems.gui.QuickGui;
import org.bukkit.Material; import org.bukkit.Material;
@@ -6,7 +6,9 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> { public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
@@ -61,6 +63,23 @@ public class DupeChestGui extends AbstractDupeGui<DupeChestGui.ChestSession> {
populateInventory(getGui().getInventory()); populateInventory(getGui().getInventory());
} }
public List<ItemStack> getInputItems() {
List<ItemStack> items = new ArrayList<>();
Inventory inv = getGui().getInventory();
for (int row = 0; row < 6; row++) {
int rowStart = row * 9;
for (int col = 0; col < 4; col++) {
int index = rowStart + col;
ItemStack item = inv.getItem(index);
if (item == null || item.isEmpty()) continue;
items.add(item);
}
}
return items;
}
private void resetItemDelay(int slot) { private void resetItemDelay(int slot) {
ItemDelayInfo info = itemDelays.get(slot); ItemDelayInfo info = itemDelays.get(slot);
if (info != null) { if (info != null) {

View File

@@ -1,8 +1,7 @@
package me.trouper.dupealias.server.gui.dupe; package me.trouper.dupealias.server.gui.dupe.sub;
import me.trouper.alias.server.systems.gui.QuickGui; import me.trouper.alias.server.systems.gui.QuickGui;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;

View File

@@ -1,4 +1,4 @@
package me.trouper.dupealias.server.gui.dupe; package me.trouper.dupealias.server.gui.dupe.sub;
import me.trouper.alias.server.systems.gui.QuickGui; import me.trouper.alias.server.systems.gui.QuickGui;
import me.trouper.alias.utils.FormatUtils; import me.trouper.alias.utils.FormatUtils;

View File

@@ -14,12 +14,14 @@ commands:
- da - da
dupe: dupe:
description: A command to duplicate items. description: A command to duplicate items.
permission: dupealias.dupe
usage: /dupe [gui|<integer>] usage: /dupe [gui|<integer>]
permissions: permissions:
dupealias.admin: dupealias.admin:
description: Allows access to the /dupealias admin command. description: Allows access to the /dupealias admin command.
default: op default: op
dupealias.infinite:
description: Allows the use of items tagged as "infinite".
default: true
dupealias.final.bypass: dupealias.final.bypass:
description: Allows the bypassing of final item restrictions description: Allows the bypassing of final item restrictions
default: op default: op
@@ -30,37 +32,33 @@ permissions:
description: Allows the duping of unique items description: Allows the duping of unique items
default: op default: op
dupealias.dupe: dupealias.dupe:
description: Allows duplication of items through the command. 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.cooldownbypass: false dupealias.dupe.cooldown.integerhere: false # Controls the cooldown time in ticks it will take to command dupe again.
dupealias.dupe.cooldownbypass: dupealias.dupe.limit.integerhere: false # Controls the integer argument's limit for exponential (2^n) duping.
description: Bypass the cooldown for /dupe
default: op
dupealias.infinite:
description: Allows the use of items tagged as "infinite".
default: true
dupealias.gui: dupealias.gui:
description: Allows access to the full dupe GUI. description: Allows access to the main dupe GUI selector. Players do not need this permission to access subsequent GUIs through the dupe command arguments.
default: true default: true
children: children:
dupealias.gui.replicator: true dupealias.gui.replicator: true
dupealias.gui.inventory: true dupealias.gui.inventory: true
dupealias.gui.chest: true dupealias.gui.chest: true
dupealias.gui.replicator: dupealias.gui.replicator:
description: The gui which lets you shift click copies of a single item. description: An animated GUI which replicates a single input item.
default: true default: true
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 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.inventory: dupealias.gui.inventory:
description: The gui which shows your inventory and armor on top. description: The gui which shows your inventory and armor on top.
default: true default: true
children: children:
dupealias.gui.inventory.refresh.integerhere: false # Controls the time it will take a duplicated item to refill or refresh in the GUI. Always takes the lowest number on a permission holder. dupealias.gui.inventory.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.chest: dupealias.gui.chest:
description: A gui which items can be put in and taken out as copies. description: A gui which items can be put in and taken out as copies.
default: true default: true
children: children:
dupealias.gui.chest.keep: false # Controls if a player should keep their previous chest session with items in it. This does not persist across reboots. dupealias.gui.chest.keep: false # Controls if a player should keep their previous chest session with items in it. This does not persist across reboots.
dupealias.gui.chest.refresh.integerhere: false # Controls the time it will take a duplicated item to refill or refresh in the GUI. Always takes the lowest number on a permission holder. dupealias.gui.chest.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.chest.keepondeath: false # Controls if a player should keep their chest GUI when they die. This only overrides the globally configured value if it is granted as true.