diff --git a/Documentation.md b/Documentation.md new file mode 100644 index 0000000..46ec1b0 --- /dev/null +++ b/Documentation.md @@ -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.: 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.: false # Ticks of refresh cooldown +dupealias.gui.replicator.cooldown.: false # Ticks of re-input cooldown +dupealias.gui.inventory.refresh.: false # Ticks of refresh cooldown +dupealias.gui.chest.refresh.: 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": "| Unique", + "FINAL": "| Final", + "INFINITE": "| Infinite", + "PROTECTED": "| Protected" + }, + "falseTagLore": { + "UNIQUE": "| Dupeable", + "FINAL": "| Mutable", + "INFINITE": "| Finite", + "PROTECTED": "| 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 ` - Exclude method from debugging +- `/da debug include ` - Include method in debugging + +#### Tag Management +- `/da tag [material|global] [remove]` - Manage item tags +- `/da tag ` - Tag held item +- `/da tag remove` - Remove tag from held item +- `/da tag false` - Set tag to false on held item +- `/da tag global` - Create global rule for held item material +- `/da tag ` - Create global rule for specific material +- `/da tag remove` - Remove global rule + +#### Rule Management +- `/da rule create ` - Create new global rule +- `/da rule list` - List all global rules +- `/da rule remove ` - Remove rule by index +- `/da rule info ` - 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 ` - 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 ` +- 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 +``` + +### 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. diff --git a/Ideas.md b/Ideas.md index 8f20256..29e3e75 100644 --- a/Ideas.md +++ b/Ideas.md @@ -1,2 +1 @@ -match based on certain features such as enchant, trim, lore string, or name. -on death, drop any items stored in chest GUI. \ No newline at end of file +Bulk item tag adder diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c8..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME 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 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/me/trouper/dupealias/DupeAlias.java b/src/main/java/me/trouper/dupealias/DupeAlias.java index a8ff1be..eb65afb 100644 --- a/src/main/java/me/trouper/dupealias/DupeAlias.java +++ b/src/main/java/me/trouper/dupealias/DupeAlias.java @@ -5,7 +5,6 @@ import me.trouper.alias.AliasContextProvider; import me.trouper.alias.data.Common; import me.trouper.dupealias.data.files.CommonConfig; import me.trouper.dupealias.data.files.DupeConfig; -import me.trouper.dupealias.data.files.NBTStorage; import me.trouper.dupealias.server.DupeManager; import org.bukkit.plugin.java.JavaPlugin; @@ -29,7 +28,6 @@ public final class DupeAlias extends JavaPlugin { alias.initialize(); alias.getDataManager().load(CommonConfig.class).save(); alias.getDataManager().load(DupeConfig.class).save(); - alias.getDataManager().load(NBTStorage.class).save(); updateCommon(); dupe = new DupeManager(); @@ -39,7 +37,6 @@ public final class DupeAlias extends JavaPlugin { public void onDisable() { alias.getDataManager().save(CommonConfig.class); alias.getDataManager().save(DupeConfig.class); - alias.getDataManager().save(NBTStorage.class); alias.shutdown(); } diff --git a/src/main/java/me/trouper/dupealias/DupeContext.java b/src/main/java/me/trouper/dupealias/DupeContext.java index 6d9e53a..866ce46 100644 --- a/src/main/java/me/trouper/dupealias/DupeContext.java +++ b/src/main/java/me/trouper/dupealias/DupeContext.java @@ -4,7 +4,6 @@ import me.trouper.alias.server.ContextAware; import me.trouper.alias.server.events.listeners.GuiInputListener; import me.trouper.dupealias.data.files.CommonConfig; import me.trouper.dupealias.data.files.DupeConfig; -import me.trouper.dupealias.data.files.NBTStorage; import me.trouper.dupealias.server.DupeManager; import org.bukkit.plugin.java.JavaPlugin; @@ -26,10 +25,6 @@ public interface DupeContext extends ContextAware { return getDataManager().get(DupeConfig.class); } - default NBTStorage getNbtStorage() { - return getDataManager().get(NBTStorage.class); - } - default GuiInputListener getGuiListener() { return getContext().getGuiInputListener(); } diff --git a/src/main/java/me/trouper/dupealias/data/ItemCapture.java b/src/main/java/me/trouper/dupealias/data/ItemCapture.java deleted file mode 100644 index c6a1300..0000000 --- a/src/main/java/me/trouper/dupealias/data/ItemCapture.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/src/main/java/me/trouper/dupealias/data/files/DupeConfig.java b/src/main/java/me/trouper/dupealias/data/files/DupeConfig.java index dfb0717..0305837 100644 --- a/src/main/java/me/trouper/dupealias/data/files/DupeConfig.java +++ b/src/main/java/me/trouper/dupealias/data/files/DupeConfig.java @@ -4,7 +4,6 @@ import me.trouper.alias.data.JsonSerializable; import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.data.GlobalRule; import me.trouper.dupealias.server.ItemTag; -import org.bukkit.Material; import java.io.File; import java.util.*; @@ -15,7 +14,7 @@ public class DupeConfig implements JsonSerializable, DupeContext { return new File(getInstance().getDataFolder(),"config.json"); } - public long dupeCooldownMillis = 1000; + public int baseDupeCooldownMillis = 1000; public String defaultDupeGui = "REPLICATOR"; diff --git a/src/main/java/me/trouper/dupealias/data/files/NBTStorage.java b/src/main/java/me/trouper/dupealias/data/files/NBTStorage.java deleted file mode 100644 index e0bdbeb..0000000 --- a/src/main/java/me/trouper/dupealias/data/files/NBTStorage.java +++ /dev/null @@ -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, DupeContext { - @Override - public File getFile() { - return new File(getInstance().getDataFolder(), ".nbtstorage.json"); - } - - public List 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; - } -} diff --git a/src/main/java/me/trouper/dupealias/server/DupeManager.java b/src/main/java/me/trouper/dupealias/server/DupeManager.java index 051ad33..8a3eaa5 100644 --- a/src/main/java/me/trouper/dupealias/server/DupeManager.java +++ b/src/main/java/me/trouper/dupealias/server/DupeManager.java @@ -3,7 +3,6 @@ package me.trouper.dupealias.server; import me.trouper.alias.utils.ItemBuilder; import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.data.GlobalRule; -import me.trouper.dupealias.data.ItemCapture; import me.trouper.dupealias.server.functions.UniqueCheck; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; @@ -19,35 +18,8 @@ import java.util.*; public class DupeManager implements DupeContext { - /** - * @return false if the item was modified. - */ - public boolean verifyTag(ItemStack item) { - if (getNbtStorage().captures.isEmpty()) return true; - ItemCapture capture = getNbtStorage().getCapture(item); - if (capture == null) return true; - boolean modified = false; - - for (Map.Entry 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) { + if (item == null || item.isEmpty()) return false; return !new UniqueCheck().passes(item); } @@ -186,7 +158,7 @@ public class DupeManager implements DupeContext { public boolean removeTag(ItemStack item, ItemTag tag) { ItemBuilder builder = ItemBuilder.of(item); - if (hasIndividualTag(item, tag) && !checkIndividualTag(item, tag)) return false; + if (!hasIndividualTag(item, tag)) return false; builder.modifyMeta(itemMeta->{ if (itemMeta.hasLore()) { diff --git a/src/main/java/me/trouper/dupealias/server/commands/AdminCommand.java b/src/main/java/me/trouper/dupealias/server/commands/AdminCommand.java index 0a72cfb..e827856 100644 --- a/src/main/java/me/trouper/dupealias/server/commands/AdminCommand.java +++ b/src/main/java/me/trouper/dupealias/server/commands/AdminCommand.java @@ -183,7 +183,7 @@ public class AdminCommand implements QuickCommand, DupeContext { GlobalRule rule = getConfig().globalRules.get(i); StringBuilder tagList = new StringBuilder(); for (ItemTag tag : rule.appliedTags) { - if (tagList.length() > 0) tagList.append(", "); + if (!tagList.isEmpty()) tagList.append(", "); tagList.append(tag.getName()); } 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(); for (ItemTag tag : removedRule.appliedTags) { - if (tagList.length() > 0) tagList.append(", "); + if (!tagList.isEmpty()) tagList.append(", "); tagList.append(tag.getName()); } diff --git a/src/main/java/me/trouper/dupealias/server/commands/DupeCommand.java b/src/main/java/me/trouper/dupealias/server/commands/DupeCommand.java index 5b9bd23..23c7531 100644 --- a/src/main/java/me/trouper/dupealias/server/commands/DupeCommand.java +++ b/src/main/java/me/trouper/dupealias/server/commands/DupeCommand.java @@ -17,7 +17,6 @@ import java.util.UUID; @CommandRegistry( value = "dupe", - permission = @Permission(value = "dupealias.dupe", message = "You do not have permission to duplicate items."), usage = "/dupe [integer|gui]", printStackTrace = true, blocksAllowed = false, @@ -25,21 +24,15 @@ import java.util.UUID; ) public class DupeCommand implements QuickCommand, DupeContext { - private final DupeGui dupeGui = new DupeGui(); + public final DupeGui dupeGui = new DupeGui(); private final Cooldown dupeCooldown = new Cooldown<>(); @Override public void handleCommand(CommandSender sender, Command command, String label, Args args) { 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 (dupeHeld(player,1)) { - dupeCooldown.setCooldown(player.getUniqueId(), getConfig().dupeCooldownMillis); - } else { + if (!verifyDupe(player,1)) { dupeGui.openDefaultGui(player); } return; @@ -66,9 +59,7 @@ public class DupeCommand implements QuickCommand, DupeContext { try { int amount = args.get(0).toInt(); - if (dupeHeld(player,amount)) { - dupeCooldown.setCooldown(player.getUniqueId(), getConfig().dupeCooldownMillis); - } else { + if (!verifyDupe(player,amount)) { dupeGui.openDefaultGui(player); } } catch (NumberFormatException e) { @@ -83,16 +74,50 @@ public class DupeCommand implements QuickCommand, DupeContext { ); } - private boolean dupeHeld(Player player, int amount) { - ItemStack inHand = player.getInventory().getItemInMainHand(); - if (getDupe().isUnique(inHand)) { - warningAny(player,"Your {0} is or contains a unique item that cannot be duped!", inHand.getType()); + private boolean verifyDupe(Player player, int amount) { + if (!player.hasPermission("dupealias.dupe")) { + warningAny(player,"You are not allowed to dupe via commands."); + return false; + } + if (dupeCooldown.isOnCooldown(player.getUniqueId())) { + warningAny(player,"You can command dupe again in {0}.", dupeCooldown.formatLong(player.getUniqueId())); return false; } - if (inHand.isEmpty()) return false; - int baseCount = inHand.getAmount(); - int maxPerStack = inHand.getMaxStackSize(); + int playerMax = getDupe().getPermissionValue(player,"dupealias.dupe.limit.",Integer.MAX_VALUE); + 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++) { int remaining = baseCount * (1 << i); @@ -101,18 +126,17 @@ public class DupeCommand implements QuickCommand, DupeContext { int stackAmt = Math.min(remaining, maxPerStack); remaining -= stackAmt; - ItemStack batch = inHand.clone(); + ItemStack batch = heldStack.clone(); batch.setAmount(stackAmt); if (!player.getInventory().addItem(batch).isEmpty()) { infoAny(player,"Your inventory is now full."); - return true; + return; } } } int totalGiven = baseCount * ((1 << amount) - 1); successAny(player,"You have duplicated {0} items!", totalGiven); - return true; } } diff --git a/src/main/java/me/trouper/dupealias/server/events/DeathEvent.java b/src/main/java/me/trouper/dupealias/server/events/DeathEvent.java new file mode 100644 index 0000000..3cc2b04 --- /dev/null +++ b/src/main/java/me/trouper/dupealias/server/events/DeathEvent.java @@ -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 items = session.getInputItems(); + e.getDrops().addAll(items); + } +} diff --git a/src/main/java/me/trouper/dupealias/server/gui/admin/AdminPanelManager.java b/src/main/java/me/trouper/dupealias/server/gui/admin/AdminPanelManager.java index 00b9841..92b6f87 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/admin/AdminPanelManager.java +++ b/src/main/java/me/trouper/dupealias/server/gui/admin/AdminPanelManager.java @@ -25,6 +25,10 @@ public class AdminPanelManager implements DupeContext, CommonItems { public void openHeldItemGui(Player player) { new HeldItemGui(this).open(player); } + + public void openBulkTagGui(Player player) { + new BulkTagGui(this).open(player); + } public void openHelpGui(Player player) { new HelpGui(this).open(player); @@ -289,28 +293,9 @@ public class AdminPanelManager implements DupeContext, CommonItems { .build(); } - public ItemStack createPreviewItem(ItemStack stack) { - if (stack.getType().isAir()) { - return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE) - .displayName("No Item Held") - .loreMiniMessage(Arrays.asList( - "Hold an item to see", - "its current tag status", - "", - "💡 Hold an item and reopen this GUI" - )) - .build(); - } - - return ItemBuilder.create(stack.getType()) - .displayName("Currently Held: " + stack.getType().name() + "") - .loreMiniMessage(getItemTagStatus(stack)) - .build(); - } - public List getItemTagStatus(ItemStack item) { List lore = new ArrayList<>(); - lore.add("Item: " + item.getType().name()); + lore.add("Held Item: " + item.getType().name()); lore.add(""); List individualTags = new ArrayList<>(); diff --git a/src/main/java/me/trouper/dupealias/server/gui/admin/BulkTagGui.java b/src/main/java/me/trouper/dupealias/server/gui/admin/BulkTagGui.java new file mode 100644 index 0000000..a07a337 --- /dev/null +++ b/src/main/java/me/trouper/dupealias/server/gui/admin/BulkTagGui.java @@ -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("Bulk Tagging Menu") + .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("UNIQUE Tag") + .loreMiniMessage(Arrays.asList( + "Makes all items in the gui", + "unable to be duplicated", + "", + "Left click to apply tag", + "Right click to remove tag", + "Shift click to set tag to false" + )) + .build(), + (g,e) -> handleTag(g.getInventory(),ItemTag.UNIQUE,e.getClick()) + ) + + .item(46, ItemBuilder.create(Material.BARRIER) + .displayName("FINAL Tag") + .loreMiniMessage(Arrays.asList( + "Makes all items in the gui", + "unable to be modified", + "", + "Left click to apply tag", + "Right click to remove tag", + "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("INFINITE Tag") + .loreMiniMessage(Arrays.asList( + "Makes all items in the gui", + "always have max stack size", + "", + "Left click to apply tag", + "Right click to remove tag", + "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("PROTECTED Tag") + .loreMiniMessage(Arrays.asList( + "Makes all items in the gui", + "not able to be manually created", + "", + "Left click to apply tag", + "Right click to remove tag", + "Shift click to set tag to false" + )) + .build(), + (g,e) -> handleTag(g.getInventory(),ItemTag.PROTECTED,e.getClick())) + + .item(50, ItemBuilder.create(Material.TNT) + .displayName("Remove All Tags") + .loreMiniMessage(Arrays.asList( + "Removes all tags from", + "all items in the gui", + "", + "This cannot be undone!", + "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); + } + } +} diff --git a/src/main/java/me/trouper/dupealias/server/gui/admin/HelpGui.java b/src/main/java/me/trouper/dupealias/server/gui/admin/HelpGui.java index 3091177..d0095e7 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/admin/HelpGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/admin/HelpGui.java @@ -187,22 +187,24 @@ public class HelpGui implements CommonItems { .loreMiniMessage(Arrays.asList( "Access Permissions:", "The root permission node is dupealias", - ".dupe - Use /dupe command", + ".dupe - Use command duping", + ".dupe.cooldown. - Sets the command dupe cooldown", + ".dupe.limit. - Sets the command dupe amplifier limit", ".gui - Access duplication GUI", "", "Dupe GUI & Sessions:", ".gui..refresh. - GUI refill time", ".gui..keep - Retain items in GUI session", ".gui.replicator - Shift-click duplicate", - ".gui.replicator.cooldown - Item input cooldown", + ".gui.replicator.cooldown. - Item input cooldown", ".gui.inventory - View personal inventory", ".gui.chest - Dupe via container", + ".gui.chest.keepondeath - Don't drop items on death", "", "Bypass Permissions:", ".unique.bypass - Dupe unique items", ".final.bypass - Modify final items", ".protected.bypass - Use protected items", - ".dupe.cooldownbypass - Skip /dupe cooldown", "", "Other:", ".infinite - Use infinite-tagged items", diff --git a/src/main/java/me/trouper/dupealias/server/gui/admin/MainAdminGui.java b/src/main/java/me/trouper/dupealias/server/gui/admin/MainAdminGui.java index fda009f..b3e80bd 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/admin/MainAdminGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/admin/MainAdminGui.java @@ -7,6 +7,10 @@ import me.trouper.dupealias.server.gui.admin.config.ConfigGui; import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.Arrays; +import java.util.List; public class MainAdminGui implements CommonItems { @@ -21,18 +25,6 @@ public class MainAdminGui implements CommonItems { .titleMini("DupeAlias Admin Panel") .rows(5) - .item(11, ItemBuilder.create(player.getInventory().getItemInMainHand().isEmpty() ? Material.BARRIER : Material.DIAMOND_SWORD) - .displayName("Held Item Actions") - .loreMiniMessage( - "Manage tags for the item", - "you're currently holding", - "", - "Click to open menu" - ) - .hideAllFlags() - .build(), - (q, event) -> manager.openHeldItemGui(player)) - .item(13, ItemBuilder.create(Material.BOOKSHELF) .displayName("Global Rules") .loreMiniMessage( @@ -57,6 +49,17 @@ public class MainAdminGui implements CommonItems { .build(), (q, event) -> manager.openHelpGui(player)) + .item(11, ItemBuilder.create(Material.CHEST) + .displayName("Bulk Item Actions") + .loreMiniMessage( + "Manage tags for multiple", + "items at a time", + "", + "Click to open menu" + ) + .build(), + (q,event) -> manager.openBulkTagGui(player)) + .item(29, ItemBuilder.create(Material.COMPARATOR) .displayName("Configuration") .loreMiniMessage( @@ -67,7 +70,8 @@ public class MainAdminGui implements CommonItems { ) .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) .displayName("<#AAAAFF>Dupe<#00DDFF>Alias Credits") @@ -87,4 +91,32 @@ public class MainAdminGui implements CommonItems { gui.open(player); } + + private ItemStack createPreviewItem(ItemStack stack) { + if (stack.getType().isAir()) { + return ItemBuilder.create(Material.GRAY_STAINED_GLASS_PANE) + .displayName("No Item Held") + .loreMiniMessage(Arrays.asList( + "Hold an item to see", + "its current tag status", + "", + "💡 Hold an item and reopen this GUI" + )) + .build(); + } + + List lore = manager.getItemTagStatus(stack); + lore.addAll(List.of( + "", + "Manage tags for the item", + "you're currently holding", + "", + "Click to open menu" + )); + + return ItemBuilder.create(stack.getType()) + .displayName("Held Item Actions") + .loreMiniMessage(lore) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/me/trouper/dupealias/server/gui/admin/config/ConfigGui.java b/src/main/java/me/trouper/dupealias/server/gui/admin/config/ConfigGui.java index dc9d266..84c1144 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/admin/config/ConfigGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/admin/config/ConfigGui.java @@ -6,6 +6,8 @@ import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.server.ItemTag; import me.trouper.dupealias.server.gui.CommonItems; import me.trouper.dupealias.server.gui.admin.AdminPanelManager; +import 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.Sound; import org.bukkit.entity.Player; @@ -28,21 +30,19 @@ public class ConfigGui implements DupeContext, CommonItems { .rows(5) .clickSound(Sound.UI_BUTTON_CLICK, 0.5f, 1.0f) - // Back button .item(0, BACK(), (g, e) -> manager.openMainGui(player)) - // Dupe Cooldown .callback("dupe_cooldown", new QuickGui.GuiCallback() { @Override public void onInput(QuickGui gui, Player player, String input, QuickGui.InputSource source) { try { - long millis = Long.parseLong(input); + int millis = Integer.parseInt(input); if (millis < 0) { errorAny(player, "Cooldown cannot be negative!"); return; } infoAny(player, "You have set the dupe cooldown to {0}ms.", input); - getConfig().dupeCooldownMillis = millis; + getConfig().baseDupeCooldownMillis = millis; getConfig().save(); open(player); } catch (NumberFormatException ex) { @@ -52,17 +52,17 @@ public class ConfigGui implements DupeContext, CommonItems { } }) .item(11, ItemBuilder.integerItem(Material.DIAMOND, "Dupe Command Cooldown", List.of( - "How long players have to wait", - "before running the /dupe command again.", + "The base delay for command duping.", + "use the permission dupealias.dupe.cooldown.", + "to configure cooldowns per-player", "", - "Current: " + getConfig().dupeCooldownMillis + "ms", + "Current: " + getConfig().baseDupeCooldownMillis + "ms", "", "Click to modify" - ), (int) getConfig().dupeCooldownMillis), (g, e) -> + ), (int) getConfig().baseDupeCooldownMillis), (g, e) -> getDupe().getGuiListener().requestChatInput(g, player, "dupe_cooldown", - "Insert a long value of Milliseconds.\n 1000ms = 1 Second\n\nCurrent value: " + getConfig().dupeCooldownMillis + "ms")) + "Insert a long value of Milliseconds.\n 1000ms = 1 Second\n\nCurrent value: " + getConfig().baseDupeCooldownMillis + "ms")) - // Default Dupe GUI .item(12, ItemBuilder.create(Material.CHEST) .displayName("Default Dupe GUI") .loreMiniMessage(Arrays.asList( @@ -77,7 +77,6 @@ public class ConfigGui implements DupeContext, CommonItems { )) .build(), (g, e) -> cycleDefaultGui(player)) - // Final Command Regex .item(13, ItemBuilder.create(Material.BARRIER) .displayName("Final Command Regex") .loreMiniMessage(Arrays.asList( @@ -90,7 +89,6 @@ public class ConfigGui implements DupeContext, CommonItems { )) .build(), (g, e) -> openFinalCommandRegexGui(player)) - // Global Rules Editor .item(14, ItemBuilder.create(Material.COMMAND_BLOCK) .displayName("Global Rules Editor") .loreMiniMessage(Arrays.asList( @@ -103,7 +101,6 @@ public class ConfigGui implements DupeContext, CommonItems { )) .build(), (g, e) -> openGlobalRulesGui(player)) - // Tag Lore Settings .item(15, ItemBuilder.create(Material.NAME_TAG) .displayName("Tag Lore Settings") .loreMiniMessage(Arrays.asList( @@ -114,7 +111,6 @@ public class ConfigGui implements DupeContext, CommonItems { )) .build(), (g, e) -> openTagLoreGui(player, g)) - // Common Settings .item(22,ItemBuilder.create(Material.LIGHT) .displayName("Common Config") .loreMiniMessage( @@ -126,7 +122,6 @@ public class ConfigGui implements DupeContext, CommonItems { .build(), (g,e) -> openCommonGui(player, g)) - // Replicator Settings .item(30, ItemBuilder.create(Material.REPEATER) .displayName("Replicator Settings") .loreMiniMessage(Arrays.asList( @@ -139,7 +134,6 @@ public class ConfigGui implements DupeContext, CommonItems { )) .build(), (g, e) -> openReplicatorGui(player, g)) - // Chest Settings .item(31, ItemBuilder.create(Material.CHEST) .displayName("Chest GUI Settings") .loreMiniMessage(Arrays.asList( @@ -151,7 +145,6 @@ public class ConfigGui implements DupeContext, CommonItems { )) .build(), (g, e) -> openChestGui(player, g)) - // Inventory Settings .item(32, ItemBuilder.create(Material.ENDER_CHEST) .displayName("Inventory GUI Settings") .loreMiniMessage(Arrays.asList( diff --git a/src/main/java/me/trouper/dupealias/server/gui/admin/config/CommandRegexGui.java b/src/main/java/me/trouper/dupealias/server/gui/admin/config/sub/CommandRegexGui.java similarity index 98% rename from src/main/java/me/trouper/dupealias/server/gui/admin/config/CommandRegexGui.java rename to src/main/java/me/trouper/dupealias/server/gui/admin/config/sub/CommandRegexGui.java index 1c2c8f8..3e9d3e7 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/admin/config/CommandRegexGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/admin/config/sub/CommandRegexGui.java @@ -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.QuickPaginatedGUI; diff --git a/src/main/java/me/trouper/dupealias/server/gui/admin/config/CommonConfigGui.java b/src/main/java/me/trouper/dupealias/server/gui/admin/config/sub/CommonConfigGui.java similarity index 82% rename from src/main/java/me/trouper/dupealias/server/gui/admin/config/CommonConfigGui.java rename to src/main/java/me/trouper/dupealias/server/gui/admin/config/sub/CommonConfigGui.java index 6d74ef2..62546b8 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/admin/config/CommonConfigGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/admin/config/sub/CommonConfigGui.java @@ -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.utils.ItemBuilder; -import me.trouper.dupealias.DupeAlias; import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.data.files.CommonConfig; import me.trouper.dupealias.server.gui.CommonItems; @@ -13,54 +12,34 @@ import org.bukkit.entity.Player; import java.util.List; -/** - * A GUI for modifying the settings within the common.json file. - * This class provides an interface for changing general plugin settings - * such as colors, prefixes, and display names. - */ public class CommonConfigGui implements DupeContext, CommonItems { private final AdminPanelManager manager; - /** - * Constructs a new CommonConfigGui. - * - * @param manager The AdminPanelManager to handle navigation. - */ public CommonConfigGui(AdminPanelManager manager) { this.manager = manager; } - /** - * Opens the Common Config GUI for a player. - * - * @param player The player to open the GUI for. - */ public void open(Player player) { - // Retrieve the current common configuration instance. CommonConfig config = getCommonConfig(); - // Create the GUI using the QuickGui builder. QuickGui gui = QuickGui.create() .titleMini("Common Config") .defaultTimeout(30000) .rows(3) .clickSound(Sound.UI_BUTTON_CLICK, 0.5f, 1.0f) - // Back button to return to the main admin panel. .item(0, BACK(), (g, e) -> manager.openMainGui(player)) - // Item and callback for modifying the main color. .callback("main_color", (g, p, input, source) -> { try { - // Parse the hex string into an integer color value. int color = Integer.parseInt(input.replace("#", ""), 16); config.mainColor = color; config.save(); - // Reload the common settings in the core to apply changes immediately. + getInstance().getCommon().update(config.generateCommon()); successAny(p, "Main color set to <#{0}>#{0}.", Integer.toHexString(color)); - open(p); // Re-open the GUI to show the updated value. + open(p); } catch (NumberFormatException ex) { errorAny(p, "Invalid hex color format. Use RRGGBB (e.g., AAAAFF)."); } @@ -74,11 +53,10 @@ public class CommonConfigGui implements DupeContext, CommonItems { "", "Click to modify" )).build(), (g, e) -> - // Request chat input from the player. + getDupe().getGuiListener().requestChatInput(g, player, "main_color", "Enter a hex color code for the main color.\nExample: AAAAFF")) - // Item and callback for modifying the secondary color. .callback("secondary_color", (g, p, input, source) -> { try { int color = Integer.parseInt(input.replace("#", ""), 16); @@ -103,7 +81,6 @@ public class CommonConfigGui implements DupeContext, CommonItems { getDupe().getGuiListener().requestChatInput(g, player, "secondary_color", "Enter a hex color code for the secondary color.\nExample: 00DDFF")) - // Item and callback for modifying the plugin name. .callback("plugin_name", (g, p, input, source) -> { config.pluginName = input; config.save(); @@ -123,7 +100,6 @@ public class CommonConfigGui implements DupeContext, CommonItems { getDupe().getGuiListener().requestChatInput(g, player, "plugin_name", "Enter the new plugin name.")) - // Item and callback for modifying the flat prefix. .callback("flat_prefix", (g, p, input, source) -> { config.flatPrefix = input; config.save(); @@ -144,7 +120,6 @@ public class CommonConfigGui implements DupeContext, CommonItems { getDupe().getGuiListener().requestChatInput(g, player, "flat_prefix", "Enter the new flat prefix.")) - // Item and click handler for toggling flat mode. .item(14, ItemBuilder.create(config.flat ? Material.LIME_DYE : Material.GRAY_DYE) .displayName("Flat Mode") .loreMiniMessage(List.of( @@ -159,14 +134,12 @@ public class CommonConfigGui implements DupeContext, CommonItems { config.save(); getInstance().getCommon().update(config.generateCommon()); infoAny(player, "Flat mode set to: {0}", config.flat); - open(player); // Re-open to update the item's appearance. + open(player); }) - // Fill the rest of the GUI with empty panes. .fillEmpty(EMPTY()) .build(); - // Display the GUI to the player. gui.open(player); } } diff --git a/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeGui.java b/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeGui.java index 5c391e5..0a8c738 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeGui.java @@ -4,6 +4,10 @@ import me.trouper.alias.server.systems.gui.QuickGui; import me.trouper.alias.utils.ItemBuilder; import me.trouper.dupealias.DupeContext; import me.trouper.dupealias.server.gui.CommonItems; +import 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.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; @@ -18,6 +22,11 @@ public class DupeGui implements DupeContext, CommonItems { public final DupeChestGui chestGui = new DupeChestGui(); 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() .rows(5) .titleMini("Available GUIs") @@ -85,7 +94,7 @@ public class DupeGui implements DupeContext, CommonItems { } } else { player.closeInventory(); - warningAny(player,"You do not have permission to use that GUI!"); + warningAny(player,"You do not have permission to use that GUI."); } } diff --git a/src/main/java/me/trouper/dupealias/server/gui/dupe/AbstractDupeGui.java b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/AbstractDupeGui.java similarity index 93% rename from src/main/java/me/trouper/dupealias/server/gui/dupe/AbstractDupeGui.java rename to src/main/java/me/trouper/dupealias/server/gui/dupe/sub/AbstractDupeGui.java index bbc9660..01cd1b4 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/dupe/AbstractDupeGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/AbstractDupeGui.java @@ -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.server.gui.CommonItems; diff --git a/src/main/java/me/trouper/dupealias/server/gui/dupe/AbstractDupeSession.java b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/AbstractDupeSession.java similarity index 97% rename from src/main/java/me/trouper/dupealias/server/gui/dupe/AbstractDupeSession.java rename to src/main/java/me/trouper/dupealias/server/gui/dupe/sub/AbstractDupeSession.java index 2dbea63..075b88f 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/dupe/AbstractDupeSession.java +++ b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/AbstractDupeSession.java @@ -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.dupealias.DupeContext; diff --git a/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeChestGui.java b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/DupeChestGui.java similarity index 86% rename from src/main/java/me/trouper/dupealias/server/gui/dupe/DupeChestGui.java rename to src/main/java/me/trouper/dupealias/server/gui/dupe/sub/DupeChestGui.java index 0c4f387..7d4e081 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeChestGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/DupeChestGui.java @@ -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 org.bukkit.Material; @@ -6,7 +6,9 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class DupeChestGui extends AbstractDupeGui { @@ -61,6 +63,23 @@ public class DupeChestGui extends AbstractDupeGui { populateInventory(getGui().getInventory()); } + public List getInputItems() { + List 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) { ItemDelayInfo info = itemDelays.get(slot); if (info != null) { diff --git a/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeInventoryGui.java b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/DupeInventoryGui.java similarity index 98% rename from src/main/java/me/trouper/dupealias/server/gui/dupe/DupeInventoryGui.java rename to src/main/java/me/trouper/dupealias/server/gui/dupe/sub/DupeInventoryGui.java index 5485db5..288b768 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeInventoryGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/DupeInventoryGui.java @@ -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 org.bukkit.entity.Player; -import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; diff --git a/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeReplicatorGui.java b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/DupeReplicatorGui.java similarity index 99% rename from src/main/java/me/trouper/dupealias/server/gui/dupe/DupeReplicatorGui.java rename to src/main/java/me/trouper/dupealias/server/gui/dupe/sub/DupeReplicatorGui.java index 9a9473d..b5c9216 100644 --- a/src/main/java/me/trouper/dupealias/server/gui/dupe/DupeReplicatorGui.java +++ b/src/main/java/me/trouper/dupealias/server/gui/dupe/sub/DupeReplicatorGui.java @@ -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.utils.FormatUtils; diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6049f2f..2cc0879 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -14,12 +14,14 @@ commands: - da dupe: description: A command to duplicate items. - permission: dupealias.dupe usage: /dupe [gui|] permissions: dupealias.admin: description: Allows access to the /dupealias admin command. default: op + dupealias.infinite: + description: Allows the use of items tagged as "infinite". + default: true dupealias.final.bypass: description: Allows the bypassing of final item restrictions default: op @@ -30,37 +32,33 @@ permissions: description: Allows the duping of unique items default: op 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 children: - dupealias.dupe.cooldownbypass: false - dupealias.dupe.cooldownbypass: - description: Bypass the cooldown for /dupe - default: op - dupealias.infinite: - description: Allows the use of items tagged as "infinite". - default: true + dupealias.dupe.cooldown.integerhere: false # Controls the cooldown time in ticks it will take to command dupe again. + dupealias.dupe.limit.integerhere: false # Controls the integer argument's limit for exponential (2^n) duping. dupealias.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 children: dupealias.gui.replicator: true dupealias.gui.inventory: true dupealias.gui.chest: true 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 children: dupealias.gui.replicator.keep: false # Controls if a player should keep their previous replicator session with items in it. This does not persist across reboots. - dupealias.gui.replicator.refresh.integerhere: false # Controls the time 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: description: The gui which shows your inventory and armor on top. default: true 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: description: A gui which items can be put in and taken out as copies. default: true 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.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. \ No newline at end of file + 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. \ No newline at end of file