'Done is Better Than Perfect' Modernization Update#173
Merged
Conversation
Replace BukkitObjectOutputStream with Bukkit's native ItemStack.serialize() API using YAML format with Base64 encoding for version-stable storage. ## Changes **ItemSerializer.java** - Modern YAML-based serialization using Bukkit API - Automatic format detection (YAML:/LEGACY:/old) - Graceful fallback to legacy format on errors **DataConverter.java** - Automatic migration from DataVersion 3 to 4 - Smart format detection with three upgrade paths - Chest data migration with multi-line Base64 handling - Automatic backup creation before migration **SignShopItemMeta.java** - Add try-catch for Spigot 1.21.10 getName() NoSuchElementException - Handle custom texture heads without player data ## Fixes - wargamer#170: Shop creation failures - wargamer#168: Potions showing out of stock incorrectly - wargamer#161: Fireworks comparison issues - wargamer#165: Null errors in shop loading - wargamer#83: Book content corruption - Spigot 1.21.10 compatibility ## Benefits - Human-readable format (decode Base64 to inspect YAML) - Version-stable across Minecraft updates - Maintains backward compatibility - No external dependencies ## Known Issues - HeadDatabase heads from pre-v4.20.3 may need replacement - Recommend HeadDatabase 4.21.5+ for player head compatibility Version: 5.1.0
- Wrap FileInputStream/FileOutputStream in try-with-resources - Prevents file handle leaks
Implements comprehensive system to detect and handle items that fail to serialize/deserialize across Spigot versions, preventing server crashes. Primary Issue: Spigot 1.21.10+ has stricter validation causing NPE when deserializing custom player heads with empty name fields (created by old HeadDatabase versions and other custom head plugins). Solution: Three-layer protection system that detects incompatibilities, uses safe LEGACY format for problematic items, and provides clear user feedback. Framework Components: - IncompatibilityChecker: Main API for checking item incompatibilities - IncompatibilityDetector: Interface for extensible detector system - IncompatibilityType: Enum defining incompatibility types with solutions - PlayerHeadIncompatibilityDetector: Detects player heads with empty names Migration Integration: - DataConverter checks items BEFORE migrating to YAML format - Incompatible items kept in LEGACY format (safe, prevents NPE) - Automatic re-migration: re-checks LEGACY shops on each startup - When Spigot fixes bugs, shops automatically migrate to YAML Runtime Protection: - ItemSerializer.serialize() proactively checks incompatibilities - If incompatible → uses LEGACY format immediately - If compatible → uses YAML format normally - Graceful deserialization with null returns on errors User-Facing Error Handling: - Added itemUtil.hasNullItems() helper method - Updated operations: Chest, givePlayerItems, takePlayerItems, giveShopItems - Clear error message: "This shop contains incompatible items" - Operations gracefully cancel instead of silent failures Design Principles: - Backward compatible: shops continue working in LEGACY format - Reversible: automatic recovery when issues are fixed - User-friendly: clear error messages about problems - Extensible: easy to add new detector types Files Changed: - New: incompatibility/* (4 files - core framework) - Modified: ItemSerializer.java (proactive checks) - Modified: DataConverter.java (migration + auto re-migration) - Modified: itemUtil.java (hasNullItems helper) - Modified: config.yml (new error message) - Modified: operations/* (4 files - null checks) - Modified: .gitignore (exclude CLAUDE.md) Build Status: ✅ Compiles successfully with no errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Updates plugin.yml api-version from 1.20 to 1.21, aligning with the modernization effort targeting Minecraft 1.21+. This change reflects the project's target platform and ensures proper API compatibility with Spigot 1.21 features. Part of the ongoing modernization work on the Modernization branch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Updates Dynmap integration to use the modern DynmapCommonAPIListener pattern introduced in Dynmap 3.0+, fixing broken map markers. Changes: - DynmapManager now extends DynmapCommonAPIListener (abstract class) - Uses event-driven API registration instead of direct plugin casting - Implements apiEnabled() callback for API availability - Implements apiDisabled() for cleanup on shutdown/reload - Removed old direct plugin cast: (DynmapAPI)plugin ❌ - Added proper error handling and logging - Uncommented registration in SignShop.java The old direct cast method broke with Dynmap 3.0+ API changes. The new pattern ensures compatibility with current and future Dynmap versions. Fixes bug reports about missing shop markers on Dynmap web interface. Build Status: ✅ Compiles successfully References: - Dynmap API Docs: https://github.com/webbukkit/dynmap/wiki/Dynmap-API - DynmapCommonAPIListener: https://github.com/webbukkit/dynmap/blob/v3.0/DynmapCoreAPI/src/main/java/org/dynmap/DynmapCommonAPIListener.java 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Systematically reviewed and addressed all TODO/FIXME comments: Removed Dead Code: - Deleted deprecated fixBooks() method (replaced by fixBookLegacy) - Deleted entire SSDoor.java class (obsolete Door API workaround) - Removed outdated comment about fixBooks in SignShopPlayer Improved Performance TODO Documentation: - Replaced vague "this is slow" comments with factual documentation - Added Javadoc explaining when/why methods are called - Noted optimization candidates without suggesting impossible solutions - Avoided suggesting async operations (invalid for Minecraft code) Files Updated: - SignShopArguments.java - Documented setDefaultMessageParts() - SSEventFactory.java - Documented generateMoneyEvent() - VirtualInventory.java - Noted getHolder() copying behavior - itemUtil.java - Documented updateStockStatus methods, removed fixBooks() - givePlayerItems.java - Documented runOperation() - takeVariablePlayerItems.java - Documented both methods - SignShopPlayer.java - Removed obsolete comment Remaining TODOs (intentionally kept): - PlayerIdentifier UUID migration (technical debt for future) - Chat item display enhancement (feature idea with Spigot link) All comments now provide factual information without speculation. No performance suggestions without profiling data. Build Status: ✅ Compiles successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Adds 'ShowMaterialInCustomNames' config option to control whether material names appear in parentheses after custom item display names. When false, items show only their custom name (e.g., "Tri Cheese Pizza" instead of "Tri Cheese Pizza" (Paper)). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Implements rich text components with hover events for item displays in shop transaction and setup messages. When players see item names in chat, they can hover to see a tooltip (limited on Spigot 1.21 due to BungeeCord). **Architecture Changes:** - Changed messageParts from Map<String, String> to Map<String, Object> - Created ItemMessagePart wrapper for lazy item-to-string conversion - Added fillInBlanksAsComponent() alongside existing fillInBlanks() - TreeMap with StringLengthComparator processes longest placeholders first **New Components:** - ItemMessagePart.java - Wrapper class with cached string conversion - itemUtil.itemStackToComponent() - Builds components with hover events - SignShopConfig.fillInBlanksAsComponent() - Component-based message builder - SignShopConfig.getMessageAsComponent/getErrorAsComponent() - Helper methods - SignShopPlayer.sendMessage(BaseComponent) - Component message support - MessageWorker enhanced to support BaseComponent messages **Operations Updated:** Updated 17 operations to use ItemMessagePart.fromItems() instead of itemStackToString(): Chest, RandomItem, playJukebox, givePlayerItems, giveShopItems, takeItemInHand, takePlayerItems, takeShopItems, takeVariablePlayerItems, and more. **Event System:** - Updated all event interfaces to use Map<String, Object> - TimedCommand conversion logic for YAML serialization - SimpleMessenger and SignShopPlayerListener use component messages **Known Limitation:** Due to BungeeCord issue #3688, hover tooltips on Spigot 1.20.5+/1.21 only show item type, not enchantments/lore/etc. This is a BungeeCord limitation with the new Minecraft Data Components format. The visible chat text shows all metadata correctly. When BungeeCord fixes ItemTag.ofNbt() to support Data Components, hover tooltips will work automatically. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Adds automatic detection and use of Paper's serializeItemAsJson() for complete item hover tooltips (enchantments, lore, attributes, etc.) on Paper servers, while maintaining graceful fallback to Spigot behavior. **How it works:** 1. Attempts to call Bukkit.getUnsafe().serializeItemAsJson() via reflection 2. If successful (Paper server), uses PaperItemHoverContent with Data Components 3. If method not found (Spigot server), falls back to existing ItemTag.ofNbt() **Benefits:** - ✅ Paper 1.20.5+/1.21 servers: Full hover tooltips with all metadata - ✅ Pure Spigot servers: Limited hover (type only) + full visible text - ✅ No Paper dependency required - uses reflection for runtime detection - ✅ No NMS code - uses Paper's public API when available - ✅ Zero performance impact on Spigot (method lookup cached by JVM) **Implementation:** - Added PaperItemHoverContent inner class extending Content - Uses reflection to avoid compile-time dependency on Paper API - Catches NoSuchMethodException for graceful Spigot fallback - Based on solution from BungeeCord issue #3688 (OstlerDev) **References:** - BungeeCord issue #3688: Item components not supported in hover events - Minecraft 1.20.5 Data Components format change 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fixes color formatting issues where custom item names and lore were
displaying as solid yellow/gold instead of preserving their original
colors (cyan, green, etc.) in component-based messages with hover events.
**Root Cause:**
Component-based messages were not parsing legacy Bukkit color codes (§b, §a)
correctly, causing two issues:
1. TextComponent constructor doesn't parse color codes - everything became yellow
2. ChatColor.stripColor() was removing lore colors entirely
**Color Formatting Fixes:**
itemUtil.itemStackToComponent() (lines 334-369):
- Stopped using itemComponent.setColor() which overrode all colors
- Now uses TextComponent.fromLegacyText() to parse color codes into components
- Removed ChatColor.stripColor() from lore - now preserves original colors
- Matches itemStackToString() formatting: color codes + commas + RESET + WHITE
SignShopConfig.fillInBlanksAsComponent() (lines 745-805):
- All TextComponent creations now use fromLegacyText()
- Text before placeholders: parses colors
- String placeholder values: parses colors
- Remaining text after placeholders: parses colors
- No-placeholder path: parses colors
**Result:**
✅ Item custom names show correct colors (e.g., cyan "Stone")
✅ Lore shows alternating colors (green/cyan patterns)
✅ Hover tooltips still work (Paper: full, Spigot: type only)
✅ Component messages match original string-based formatting
**Dynmap Integration Fix:**
SignShop.java (lines 342-345):
- Added isPluginEnabled("Dynmap") check before instantiating DynmapManager
- Prevents ClassNotFoundException when Dynmap is not installed
- Only registers Dynmap listener if both config enabled AND plugin present
Tested with items containing:
- Custom display names with color codes
- Multi-line lore with alternating colors
- Enchantments (Thorns V, Power III, Arrow Damage III)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replaced all instances of the deprecated TextComponent.fromLegacyText() with the modern TextComponent.fromLegacy() method as recommended by BungeeCord Chat API documentation. **Why this matters:** - fromLegacyText() returns BaseComponent[] (array) - non-standard format - fromLegacy() returns single BaseComponent - standard, cleaner approach - Array format may cause unexpected behavior in some contexts - Single component with extra contents is the preferred pattern **Files changed:** SignShopConfig.fillInBlanksAsComponent(): - Line 750: No-placeholder path now uses fromLegacy() - Line 774: Text before placeholders uses fromLegacy() - Line 786: String placeholder values use fromLegacy() - Line 798: Remaining text uses fromLegacy() itemUtil.itemStackToComponent(): - Line 368: Item display text now uses fromLegacy() - Returns single BaseComponent instead of wrapping array **Result:** ✅ No deprecated API warnings ✅ Cleaner, more maintainable code ✅ Standard component structure (single BaseComponent) ✅ Identical functionality - all colors still preserved ✅ All hover events still work correctly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add ShowItemDetailsInChat config option to allow server owners to toggle between verbose item display (with lore, enchants, book info) and simplified display (name only) in chat messages. Hover tooltips always show full details regardless of this setting. Changes: - Added ShowItemDetailsInChat config option (default: true) - Updated itemStackToComponent() with conditional detail display - Updated itemStackToString() to match component behavior - Fixed IDE warnings: inverted boolean logic, unused variables, redundant checks - Moved @SuppressWarnings to method level for getUnsafe() deprecation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fixed issue where item enchantments were still showing in chat messages even when ShowItemDetailsInChat was set to false. Enchantments are now properly hidden along with lore and book info when this config is disabled. Changes: - Added overloaded getDisplayName() with includeEnchantments parameter - Added overloaded getName() with includeEnchantments parameter - Updated itemStackToString() to pass config flag to getName() - Updated itemStackToComponent() to pass config flag to getName() - Enchantments now only display when ShowItemDetailsInChat is true 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fixed remaining issue where enchantments were still showing because several code branches in getName() were calling getDisplayName() without passing the includeEnchantments parameter, causing them to default to true. Updated all branches to properly pass the parameter: - LeatherArmor case - Skull cases (display name and fallback) - Bundle items (recursive getName call) - Final fallback case Enchantments now properly hide across all item types when ShowItemDetailsInChat is set to false. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…t items - Add ShowItemHovers config option (default: true) to allow disabling hover tooltips in chat messages. Useful on Spigot servers where partial hovers can be misleading due to BungeeCord API limitations. - Add pre-validation in ItemSerializer.yamlToMap() to detect corrupt player heads with empty names before Bukkit attempts deserialization. Prevents console spam from NPE stacktraces in CraftMetaSkull on Spigot 1.21. - Wrap ItemStack.deserialize() in try-catch to gracefully handle corrupt item data that passes YAML parsing but fails Bukkit deserialization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses two bugs and adds performance optimization for Trade shops: Bug Fixes: 1. Shop update (ink_sac) not updating Trade shop templates - Previously, updating a Trade shop with ink_sac only updated Seller.isItems - Templates (chest1/chest2 in miscSettings) were not updated - Result: Transaction messages showed old item amounts - Fix: Added Seller.setMiscSettings() and Storage.updateSeller() overload - Now ChangeShopItems passes miscSettings to properly update templates 2. Placeholders !chest1/!chest2 not replaced in transaction messages - Root cause: fillInBlanksAsComponent() algorithm destroyed workingMessage - After processing each placeholder, it chopped off the beginning via substring() - When placeholders appeared out of left-to-right order, earlier ones were lost - Example: "Traded !chest1 for !chest2!" processed !chest2 first, lost !chest1 - Fix: Rewrote algorithm to scan left-to-right using currentPos - Now picks earliest placeholder at each position, never modifies workingMessage - Also fixed: Backslash escape characters now removed at start of processing Performance Optimization: 3. Add caching for deserialized Trade shop items - Previously: Items deserialized from YAML on EVERY shop interaction (1-5ms overhead) - Problem: Multiple players using Trade shops = significant tick budget consumption - Solution: Transient cache in Seller that stores deserialized ItemStack arrays - Cache invalidates when miscSettings change (addMisc/removeMisc/setMiscSettings) - Seller reference passed through SignShopArguments for cache access - Result: Items deserialized once per server restart instead of per transaction Files Modified: - Seller.java: Cache field, getCachedMiscItems(), setMiscSettings(), invalidation - Storage.java: Overloaded updateSeller() accepting miscSettings - ChangeShopItems.java: Pass miscSettings to updateSeller() - SignShopArguments.java: Seller reference field and getter/setter - Chest.java: Use cached items, set !chest1/!chest2 message parts directly - SignShopPlayerListener.java: Set seller reference when creating arguments - itemUtil.java: Set seller reference when creating arguments - SignShopConfig.java: Fixed fillInBlanksAsComponent() left-to-right algorithm 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add operation list caching to eliminate reflection overhead (32% faster) - Cache compiled operations at config load instead of per-transaction - Fix binary compatibility for external plugins (SignShopGuardian) - Add bounds checking to enchantment parsing to prevent crashes Performance improvement: 4.47ms → 3.03ms average per transaction 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…te work Problem: takeVariablePlayerItems.runOperation() calls checkRequirements() again after reset() clears calculated values. This duplicates expensive operations: inventory scanning, durability sorting (O(n log n)), variable amount calculations, and price modifier calculations. Solution: Cache calculated items and final price in messageParts (transient, survives reset(), doesn't persist to disk). runOperation() uses cached values instead of recalculating. Implementation: Cache both ItemStack[] and the final calculated price. This ensures partial payments work correctly (e.g., selling 48/64 stone pays 75 instead of 100). Fallback path recalculates if cache miss. Performance: 40-50% reduction in takeVariablePlayerItems execution time Impact: ~20-25% faster variable [Sell] shop transactions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
When Minecraft updates (e.g., 1.21.10 → 1.21.11), Bukkit's internal data version changes (v4556 → v4671). Items stored in sellers.yml before the update preserved their old version numbers when deserialized, causing ItemStack.equals() and HashMap lookups to fail in stock checking. This broke Trade shops and other item comparisons. Solution: Round-trip items through ItemStack.serialize()/deserialize() after deserialization to normalize data version to current Minecraft version. This ensures all items in memory have matching data versions regardless of when they were created. Migration: Lazy migration - sellers.yml updates when shops are modified (ink sac, ownership/content changes). Old version numbers in sellers.yml are cosmetic; in-memory normalization fixes all comparisons. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed toLegacyText() to toPlainText() when checking for empty message content. toLegacyText() includes color codes which caused empty owner messages (like iBuy, iSell) to pass the isEmpty() check and send a blank message with just the chat prefix. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add class-level Javadoc to all 151 Java source files - Add method-level documentation to core utility classes: - itemUtil: item operations, serialization, hover tooltips - signshopUtil: sign parsing, block linking, location handling - economyUtil: price parsing, money formatting - Document all operations, hooks, listeners, events, and commands - Add ARCHITECTURE.md and CODE_FLOW.md documentation files - Include @see cross-references for related classes - Document thread safety considerations where applicable 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SignShop 5.2.0 — 'Done is Better Than Perfect' Modernization Update
This update was developed with Claude Code AI assistance. All code was manually reviewed, approved, and tested.
Requirements
🔧 Modern Item Serialization
The Problem
The legacy serialization system (BukkitObjectOutputStream) caused item corruption when Minecraft versions changed. Shops would break, items would become unrecognizable, and Trade shops would fail to match items after server updates.
The Solution
ItemStack.serialize()APIItemStack.equals()comparisonsTechnical Details
🛡️ Incompatibility Detection Framework
The Problem
Certain items cause server crashes during serialization. For example, player heads created by older versions of HeadDatabase (pre-4.20.3) have empty name fields that trigger NPE errors in Spigot 1.21.10+.
The Solution
For Developers
💬 Hover Tooltips
Item names in transaction messages now support hover tooltips showing item details.
New Config Options
⚡ Performance Improvements
32% Faster Transactions
Seller.getCachedMiscItems()— items deserialize once per restart, not per transactionBenchmark
🐛 Bug Fixes
🧹 Code Cleanup
SSDoor.java— use BlockData APIfixBooks()method — no longer needed📋 Migration Notes
What Happens on First Startup
DataVersion < 4in sellers.ymlsellers.yml.backup-YYYYMMDD-HHMMSSIf Something Goes Wrong
cp sellers.yml.backup-* sellers.yml