-
Notifications
You must be signed in to change notification settings - Fork 6
Essence 1.12.0 #355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Essence 1.12.0 #355
Conversation
Reset branch
New Crowdin updates
This is purely because of my players being confused by how Essences /speed works vs Essentials /speed, and thinking that 0 or 1 is default speed.
I really should look into rewriting /speed for de-duplication....
Fix & Improve /speed, fix NullPointerException in /spawn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/main/java/net/lewmc/essence/teleportation/tp/UtilTeleport.java (1)
202-213: Cross-world validation is a tautology and will never trigger.
targetWorldis assigned fromlocation.getWorld()on line 203, so the condition on line 207 comparestargetWorld.getName()to itself—it will always be equal, making this entire block dead code.If the intent is to detect world instance inconsistencies (e.g., stale world reference vs. reloaded world), consider comparing world instances by identity or validating that the world is still loaded:
🛠️ Suggested fix
// Cross-world teleportation safety check World targetWorld = location.getWorld(); World currentWorld = player.getWorld(); if (!targetWorld.equals(currentWorld)) { - // Validate target world instance validity - if (!targetWorld.getName().equals(location.getWorld().getName())) { - message.send("teleport","exception"); - this.log.severe("World mismatch detected during cross-world teleportation."); - this.log.severe("Details: {\"error\": \"WORLD_MISMATCH\", \"player\": \"" + player.getName() + "\", \"from\": \"" + currentWorld.getName() + "\", \"to\": \"" + targetWorld.getName() + "\"}."); - return; - } + // Validate target world is still loaded + if (Bukkit.getWorld(targetWorld.getName()) == null) { + message.send("teleport","exception"); + this.log.severe("Target world is no longer loaded."); + this.log.severe("Details: {\"error\": \"WORLD_UNLOADED\", \"player\": \"" + player.getName() + "\", \"from\": \"" + currentWorld.getName() + "\", \"to\": \"" + targetWorld.getName() + "\"}."); + return; + } }src/main/java/net/lewmc/essence/teleportation/tp/UtilTeleportRequest.java (1)
107-132: NPE risk when players go offline between request and acceptance.
getPlayer()returns null if the player is offline. Lines 113-114 and 120-121 callgetPlayer()without null checks, then immediately access.getLocation(), which will throw NPE if either player disconnected.🛠️ Proposed fix
String requesterName = tpaRequest[0]; Player requesterPlayer = this.plugin.getServer().getPlayer(requesterName); + Player requestedPlayer = this.plugin.getServer().getPlayer(requested); + + if (requesterPlayer == null || requestedPlayer == null) { + this.deleteFromRequested(requested); + return false; + } UtilTeleport tpu = new UtilTeleport(this.plugin); if (Objects.equals(tpaRequest[1], "true")) { tpu.doTeleport( - this.plugin.getServer().getPlayer(requested), - this.plugin.getServer().getPlayer(requesterName).getLocation(), + requestedPlayer, + requesterPlayer.getLocation(), (int) this.plugin.config.get("teleportation.requests.delay"), true ); } else { tpu.doTeleport( - this.plugin.getServer().getPlayer(requesterName), - this.plugin.getServer().getPlayer(requested).getLocation(), + requesterPlayer, + requestedPlayer.getLocation(), (int) this.plugin.config.get("teleportation.requests.delay"), true ); } - - if (requesterPlayer != null) { - new UtilMessage(this.plugin, requesterPlayer).send("teleport", "requestaccepted", new String[]{requested}); - } + + new UtilMessage(this.plugin, requesterPlayer).send("teleport", "requestaccepted", new String[]{requested}); this.deleteFromRequested(requested); return true;
🤖 Fix all issues with AI agents
In `@src/main/java/net/lewmc/essence/environment/CommandLightning.java`:
- Around line 34-41: The Javadoc for the command handler in class
CommandLightning incorrectly describes it as the "/bal command handler"; update
the comment to accurately describe the lightning command handler and its
parameters (e.g., change the description line to "/lightning command handler")
so the JavaDoc for the method (the onCommand handler in CommandLightning)
matches the actual functionality and arguments.
- Around line 17-23: The constructor Javadoc incorrectly references
"CommandWeather"; update the Javadoc for the CommandLightning constructor (the
public CommandLightning(Essence plugin) method) to reference the correct class
name (CommandLightning) and ensure the description and `@param` remain accurate
for the plugin parameter.
In `@src/main/java/net/lewmc/essence/environment/ModuleEnvironment.java`:
- Line 34: In ModuleEnvironment where the lightning command is registered (check
the cmd.isDisabled("lightning") block), replace the incorrect CommandPWeather
instantiation with the correct CommandLightning instantiation so the
runtimeCommand("lightning", ...) registers new CommandLightning((Essence)
plugin) instead of new CommandPWeather((Essence) plugin); ensure the alias
"smite" remains unchanged and remove any remaining references that instantiate
CommandPWeather for this registration.
In `@src/main/java/net/lewmc/essence/kit/UtilKit.java`:
- Around line 85-88: The loop in UtilKit.java that parses kit items currently
returns 4 on a single parse failure (when item == null), causing partial kit
delivery after the claim count increment earlier; change the control flow to
skip invalid items instead of aborting the entire method by replacing the early
"return 4" behavior with a "continue"-style skip so remaining valid items are
delivered (keep the existing warning via this.plugin.log.warn referencing
'object' and 'kit'), and verify the claim-count logic (the increment near line
71) remains correct for a full kit claim rather than on partial failure.
- Around line 77-80: The giveKit method in UtilKit returns 4 when item parsing
fails, but CommandKit (the caller of UtilKit.giveKit) only handles codes 0–3 so
players get no feedback; update CommandKit's kit-giving result handling to
include a case for return code 4 that sends a clear player-facing message about
item parse failure (e.g., "Kit contains invalid item names, please check server
logs") and ensure UtilKit.giveKit's Javadoc (around the existing return code
docs) is updated to document return code 4 and its meaning; locate the giveKit
method in UtilKit and the switch/if handling in CommandKit to add the new branch
and Javadoc update.
♻️ Duplicate comments (4)
src/main/java/net/lewmc/essence/core/UtilPlayer.java (1)
177-181: Storing InetSocketAddress object instead of string.Line 180 stores the
InetSocketAddressobject directly into the file. The memory cache correctly receives the string on line 181, but the file storage gets the raw object, which likely causes serialization issues.🐛 Proposed fix
if (p != null) { InetSocketAddress ip = p.getAddress(); if (ip != null) { - f.set(KEYS.USER_IP_ADDRESS.toString(), ip); - player.user.ipAddress = ip.getAddress().getHostAddress(); + String ipString = ip.getAddress().getHostAddress(); + f.set(KEYS.USER_IP_ADDRESS.toString(), ipString); + player.user.ipAddress = ipString; } else {src/main/java/net/lewmc/essence/teleportation/home/CommandSethome.java (1)
61-64: Null safety and substring matching issues remain unaddressed.The past review correctly identified:
getHomesList(p)may returnnull, causing an NPEindexOf(name)matches substrings—e.g., if a home named"myhome"exists, checking for"home"will incorrectly return a matchsrc/main/java/net/lewmc/essence/kit/UtilKit.java (1)
99-109: Enchantment parsing lacks defensive validation.The enchantment parsing has two potential runtime exceptions:
ArrayIndexOutOfBoundsExceptionif the enchantment string doesn't contain:(e.g.,"sharpness"instead of"sharpness:1")NumberFormatExceptionif the level isn't a valid integer (e.g.,"sharpness:max")Additionally,
NamespacedKey.fromString(e[0])expects formatnamespace:keybut enchantment names like"sharpness"lack a namespace. UseNamespacedKey.minecraft(e[0])for consistency withCommandEnchant.java.🛠️ Proposed fix
List<String> enchantments = kitData.getStringList("kits."+kit+".items."+object+".enchantments"); for (String enchantment : enchantments) { - String[] e = enchantment.split(":"); - Enchantment parsedEnchantment = Enchantment.getByKey(NamespacedKey.fromString(e[0])); - - if (parsedEnchantment != null) { - itemStack.addEnchantment(parsedEnchantment, Integer.parseInt(e[1])); - } else { + String[] e = enchantment.split(":", 2); + if (e.length != 2) { + this.plugin.log.warn("Unable to parse enchantment '"+enchantment+"' in kit '"+kit+"' - expected format 'name:level'"); + continue; + } + Enchantment parsedEnchantment = Enchantment.getByKey(NamespacedKey.minecraft(e[0].toLowerCase())); + if (parsedEnchantment == null) { this.plugin.log.warn("Unable to parse enchantment '"+enchantment+"' in kit '"+kit+"'"); + continue; + } + try { + int level = Integer.parseInt(e[1]); + itemStack.addEnchantment(parsedEnchantment, level); + } catch (NumberFormatException ex) { + this.plugin.log.warn("Unable to parse enchantment level '"+e[1]+"' in kit '"+kit+"'"); } }src/main/java/net/lewmc/essence/teleportation/tp/UtilTeleportRequest.java (1)
89-106: Array bounds and null-safety issues remain unaddressed.The expiry cleanup was added (line 103), but the previously flagged issues persist:
- ArrayIndexOutOfBoundsException: No check for
tpaRequest.length >= 3before accessingtpaRequest[2]- Uncaught NPE:
Objects.requireNonNull()throwsNullPointerException, notDateTimeException, so a null timestamp won't be caught- NPE on offline player:
getPlayer(requested)on line 102 can return null if the player went offline🛠️ Proposed fix
+ if (tpaRequest.length < 3 || tpaRequest[2] == null) { + this.plugin.log.warn("Unable to calculate cooldown, request timestamp missing or corrupted."); + this.deleteFromRequested(requested); + return false; + } + LocalDateTime rTime; try { - rTime = LocalDateTime.parse(Objects.requireNonNull(tpaRequest[2])); + rTime = LocalDateTime.parse(tpaRequest[2]); } catch (DateTimeException e) { this.plugin.log.warn("DateTimeException: "+e); this.plugin.log.warn("Unable to calculate cooldown, the field may be missing or corrupted."); + this.deleteFromRequested(requested); return false; } LocalDateTime cTime = LocalDateTime.now(); Duration elapsed = Duration.between(rTime, cTime); if (elapsed.getSeconds() > (int) this.plugin.config.get("teleportation.requests.expiry")) { - new UtilMessage(this.plugin, this.plugin.getServer().getPlayer(requested)).send("teleport", "requestexpired"); + Player requestedPlayer = this.plugin.getServer().getPlayer(requested); + if (requestedPlayer != null) { + new UtilMessage(this.plugin, requestedPlayer).send("teleport", "requestexpired"); + } this.deleteFromRequested(requested); return true; }
🧹 Nitpick comments (5)
src/main/java/net/lewmc/essence/core/UtilPlayer.java (1)
142-152: Redundant method calls can be consolidated.
Bukkit.getOfflinePlayer(uuid)is called twice (lines 143 and 147), andop.getPlayer()is called twice (lines 148-149). Consider caching the results.♻️ Suggested refactor
- Player p = null; - if (Bukkit.getOfflinePlayer(uuid).getName() == null) { + OfflinePlayer op = Bukkit.getOfflinePlayer(uuid); + if (op.getName() == null) { return false; } - OfflinePlayer op = Bukkit.getOfflinePlayer(uuid); - if (op.getPlayer() != null) { - p = op.getPlayer(); - } + Player p = op.getPlayer(); // null if offline f.load(f.playerDataFile(uuid));src/main/java/net/lewmc/essence/teleportation/home/CommandSethome.java (1)
49-49: RedundanttoLowerCase()calls.Since
nameis already lowercased on line 45, the calls to.toLowerCase()on lines 49 and 66 are unnecessary.♻️ Proposed fix
- if (new Security(this.plugin.foundryConfig).hasSpecialCharacters(name.toLowerCase())) { + if (new Security(this.plugin.foundryConfig).hasSpecialCharacters(name)) {- if (hu.create(name.toLowerCase(), p, p.getLocation())) { + if (hu.create(name, p, p.getLocation())) {Also applies to: 66-66
src/main/java/net/lewmc/essence/environment/CommandLightning.java (2)
51-56: Missing success feedback when striking lightning on target.After successfully striking lightning on the target player, no confirmation message is sent to the command sender. Consider adding a success message for consistency with other environment commands.
💡 Suggested improvement
Player target = this.plugin.getServer().getPlayer(args[0]); if (target != null) { target.getWorld().strikeLightningEffect(target.getLocation()); + msg.send("environment", "lightningstrike", new String[]{target.getName()}); } else { msg.send("generic", "playernotfound"); }Note: This requires adding the corresponding message key
lightningstriketo the language files.
62-65: Missing success feedback when striking lightning on self.Similar to the target case, no feedback is provided when the player strikes lightning on themselves.
💡 Suggested improvement
p.getWorld().strikeLightningEffect(p.getLocation()); + msg.send("environment", "lightningstrikeself"); } return true;src/main/java/net/lewmc/essence/kit/UtilKit.java (1)
91-97: Amount validation should respect material's max stack size.The hardcoded upper bound of 64 doesn't account for materials with smaller max stack sizes (e.g., eggs=16, ender pearls=16, tools=1). Setting an amount higher than the material's max stack size can cause unexpected behavior.
♻️ Proposed fix
ItemStack itemStack = new ItemStack(item); int amount = kitData.getInt("kits."+kit+".items."+object+".amount"); - if (amount > 0 && amount < 65) { + int maxStack = itemStack.getMaxStackSize(); + if (amount > 0 && amount <= maxStack) { itemStack.setAmount(amount); + } else if (amount > maxStack) { + itemStack.setAmount(maxStack); + this.plugin.log.warn("Item '"+object+"' in kit '"+kit+"', has amount '"+amount+"' exceeding max stack size '"+maxStack+"'. Clamped to max."); } else { itemStack.setAmount(1); this.plugin.log.warn("Item '"+object+"' in kit '"+kit+"', has an invalid amount '"+amount+"'. Must be between 1 and 64."); }
src/main/java/net/lewmc/essence/environment/ModuleEnvironment.java
Outdated
Show resolved
Hide resolved
Add MiniMessage support
New Crowdin updates
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/main/resources/language/bn-BD.yml (1)
4-6: Filename and metadata language mismatch.The file is named
bn-BD.yml(Bengali - Bangladesh), but the metadata declares the language as "British English" with author "LewMC". This appears to be a template or copy from the English locale. If this file is intended to serve as a fallback until Bengali translations are available, consider adding a comment to clarify this, or update the metadata to reflect the actual state.src/main/resources/language/ja-JP.yml (1)
4-6: Metadata inconsistency:languagesays "British English" but file isja-JP.yml.The metadata declares
language: British English, but this file is intended for Japanese localization (ja-JP.yml). This should be updated to reflect the correct language (e.g.,language: 日本語orlanguage: Japanese).Suggested fix
meta: - language: British English + language: 日本語 author: LewMCsrc/main/resources/language/pt-PT.yml (1)
233-245: Translate new pt-PT strings (or confirm intentional English fallback).These newly added messages are in English. For a pt-PT locale, please translate them or confirm that this file is intentionally serving as an English fallback.
Also applies to: 283-283, 305-322, 406-406
src/main/resources/language/tr-TR.yml (1)
4-6: Turkish locale file contains English content.The file is named
tr-TR.yml(Turkish) butmeta.languageis set to "British English" and all strings throughout the file are in English. This defeats the purpose of localization—Turkish users would see English text.Either:
- Translate the content to Turkish and update the metadata accordingly, or
- Remove this file until proper translations are available to avoid giving users a false impression that Turkish is supported.
src/main/resources/language/vi-VN.yml (1)
253-261: Addwarp.existskey for consistency withhomeandteamhomesections.Both
home(line 233) andteamhome(line 245) have anexistskey with placeholder for the entity name, butwarpis missing it. Add the key afteralreadyexists:Suggested addition after line 253
alreadyexists: "§4[Essence] §cA warp with this name already exists." + exists: "§4[Essence] §cA warp with the name '{{1}}' already exists." created: "§2[Essence] §aCreated warp '{{1}}'."
🤖 Fix all issues with AI agents
In `@src/main/java/net/lewmc/essence/chat/CommandReply.java`:
- Around line 3-5: The code in CommandReply is still calling
mm.deserialize(rawMsg, TagResolver.empty()), which allows MiniMessage built-in
tags via fallback; change the logic where formatting is disabled to construct a
plain text component instead of deserializing—replace mm.deserialize(rawMsg,
TagResolver.empty()) with Component.text(rawMsg) (or the equivalent plain text
construction) for the code path that checks the chat.allow-message-formatting
config so that neither custom nor built-in MiniMessage tags are parsed; update
any uses of variables mm, rawMsg, and TagResolver.empty() in that branch
accordingly.
In `@src/main/java/net/lewmc/essence/chat/EventPlayerChat.java`:
- Around line 48-59: The current code builds finalChatLine from
event.originalMessage(), bypassing any mutations passed into the renderer;
change the logic to compute messageContent and finalChatLine inside the
event.renderer lambda using the renderer's message parameter (the lambda's third
param, named message), e.g. serialize that message with
PlainTextComponentSerializer.plainText().serialize(message), then apply
mm.deserialize/Component.text and UtilPlaceholder.replaceAll with
Placeholder.component("user_message", messageContent) so the chat line respects
upstream modifications; keep references to UtilPlaceholder, mm.deserialize,
event.renderer, finalChatLine, and the config keys
"chat.allow-message-formatting" and "chat.name-format" when moving the code into
the lambda.
In `@src/main/java/net/lewmc/essence/core/UtilMessage.java`:
- Around line 73-87: The raw message returned by getMessage(...) contains legacy
§ color codes which MiniMessage.miniMessage().deserialize(...) won't parse, so
update UtilMessage.sendTo to convert legacy codes to MiniMessage tags before
deserializing (or ensure convertLegacyColors(...) is applied to language entries
at plugin initialization); specifically, take the String raw from getMessage,
run it through the existing convertLegacyColors(...) utility (or the equivalent
method on your plugin/class) to produce a converted string, and pass that
converted string into MiniMessage.miniMessage().deserialize(...) when building
the Component for sendTo.
In `@src/main/java/net/lewmc/essence/core/UtilPlaceholder.java`:
- Around line 46-48: The current approach in UtilPlaceholder.invokePAPI
serializes Components to plain text (via PlainTextComponentSerializer) which
strips MiniMessage styling; refactor invokePAPI to integrate PlaceholderAPI via
a MiniMessage TagResolver that calls PlaceholderAPI.setPlaceholders(player,
"%name%") and returns Placeholder.unparsed(result) (or resolves to a Component)
so placeholders are applied during MiniMessage deserialization and styling is
preserved; implement a TagResolver (e.g., "papi") and use
MiniMessage.miniMessage().deserialize(mmString, papiResolver) instead of
serializing to plain text — update any call sites that pass cs/Player to
invokePAPI accordingly.
In `@src/main/java/net/lewmc/essence/core/UtilUpdate.java`:
- Around line 459-505: In convertLegacyColors, the Scanner and PrintWriter use
platform default encoding and can corrupt non-ASCII chars like '§'; change the
Scanner(file) call to Scanner(file, "UTF-8") and the PrintWriter(file) call to
new PrintWriter(file, "UTF-8"), and update the surrounding exception handling to
account for UnsupportedEncodingException/IOException where needed (preserve the
existing FileNotFoundException handling and the log messages in this.log.info /
this.log.warn).
In `@src/main/java/net/lewmc/essence/kit/UtilKit.java`:
- Around line 113-121: Guard against missing/invalid config and catch
IllegalArgumentException: replace the unchecked cast of
this.plugin.config.get("kit.allow-unsafe-enchantments") by a safe boolean check
(e.g., Boolean.TRUE.equals(this.plugin.config.get(...)) or retrieving a default)
before choosing between itemStack.addUnsafeEnchantment(ench, level) and
itemStack.addEnchantment(ench, level); also extend the existing try/catch around
NumberFormatException to additionally catch IllegalArgumentException (and
optionally ClassCastException/NullPointerException if you don't use the safe
boolean check) so invalid enchantment levels or bad config types don't crash
when calling addEnchantment/addUnsafeEnchantment.
♻️ Duplicate comments (12)
src/main/resources/language/nl-NL.yml (7)
30-30: Typo: "negatiief" should be "negatief".This issue was previously flagged and still needs to be addressed.
206-206: Typo: "Noordern" should be "Noorden".This issue was previously flagged and still needs to be addressed.
281-281: Typo: Extra 'Y' character before "Je".This issue was previously flagged and still needs to be addressed.
365-365: Typo: Extra 'Y' character before "Je".This issue was previously flagged and still needs to be addressed.
475-475: Typo: "Werrld" should be "Wereld".This issue was previously flagged and still needs to be addressed.
491-491: Malformed placeholder and missing period.The placeholder
{1}should use double curly braces{{1}}to match the pattern used throughout the translation file. Also missing trailing period for consistency.🔤 Proposed fix
- invalidamount: "§4[Essence] §cKan geen hoeveelheid van {1} geven" + invalidamount: "§4[Essence] §cKan geen hoeveelheid van {{1}} geven."
406-406: Untranslated English string.The
notexistmessage is in English instead of Dutch. The placeholder{{1}}is now present (addressing the previous review comment), but the string still needs translation.🔤 Proposed fix
- notexist: "§4[Essence] §cThe requested world \"{{1}}\" either does not exist or could not be loaded." + notexist: "§4[Essence] §cDe opgevraagde wereld \"{{1}}\" bestaat niet of kon niet worden geladen."src/main/java/net/lewmc/essence/teleportation/tp/UtilTeleportRequest.java (1)
89-105: Guard against short/missing request payloads and clean up invalid entries.
tpaRequest[2]is accessed without length/null checks and onlyDateTimeExceptionis caught, so legacy or corrupted entries can triggerArrayIndexOutOfBoundsException/NullPointerExceptionand then persist in the map. Consider validating length/null and deleting invalid entries before returning.Proposed fix
- LocalDateTime rTime; - try { - rTime = LocalDateTime.parse(Objects.requireNonNull(tpaRequest[2])); - } catch (DateTimeException e) { + if (tpaRequest.length < 3 || tpaRequest[2] == null) { + this.plugin.log.warn("Unable to calculate cooldown, request timestamp missing or corrupted."); + this.deleteFromRequested(requested); + return false; + } + LocalDateTime rTime; + try { + rTime = LocalDateTime.parse(tpaRequest[2]); + } catch (DateTimeException e) { this.plugin.log.warn("DateTimeException: "+e); this.plugin.log.warn("Unable to calculate cooldown, the field may be missing or corrupted."); + this.deleteFromRequested(requested); return false; }src/main/java/net/lewmc/essence/kit/UtilKit.java (2)
32-36: Update Javadoc to document return code 4.The method can now return 4 (line 79) when the kit has no items, but this return code is not documented in the Javadoc.
/** * Gives the user referenced in the constructor the kit specified. * `@param` kit String - the kit to give the user. - * `@return` int - Result of operation. 0 = Successful, 1 = No Permission, 2 = Kit is missing, 3 = Claimed too many times. + * `@return` int - Result of operation. 0 = Successful, 1 = No Permission, 2 = Kit is missing, 3 = Claimed too many times, 4 = Kit has no items. */As noted in a previous review, ensure
CommandKit.javahandles return code 4 to provide user feedback.
107-111:Enchantment.getByKey()is also deprecated in Bukkit 1.21+.Per the previous review's research, both
getByName()andgetByKey()are deprecated. For 1.21+ compliance, useRegistry.ENCHANTMENT:Enchantment ench = Registry.ENCHANTMENT.get(NamespacedKey.minecraft(e[0].toLowerCase()));This requires importing
org.bukkit.Registry.Bukkit 1.21 Enchantment.getByKey deprecated Registry.ENCHANTMENTsrc/main/java/net/lewmc/essence/core/UtilUpdate.java (2)
339-376: Harden player-data migration against invalid filenames and key mismatches.
Line 350 can throw on non-UUID filenames, Line 354–356/363–365 read uppercase keys (data loss), Line 361 checks the parent section, andFilesis never closed.🐛 Proposed fix
- if (players != null) { - Files pf = new Files(this.plugin.foundryConfig, this.plugin); - for (File player : players) { - if (player.isFile()) { - pf.load(pf.playerDataFile(UUID.fromString(player.getName().replace(".yml","")))); + if (players != null) { + for (File player : players) { + if (player.isFile()) { + String fileName = player.getName().replace(".yml", ""); + UUID playerUuid; + try { + playerUuid = UUID.fromString(fileName); + } catch (IllegalArgumentException e) { + log.warn("Skipping invalid player data file: " + player.getName()); + continue; + } + Files pf = new Files(this.plugin.foundryConfig, this.plugin); + try { + pf.load(pf.playerDataFile(playerUuid)); if (pf.get("last-location.world") != null) { pf.set("location.last-known.world", pf.get("last-location.world")); - pf.set("location.last-known.x", pf.get("last-location.X")); - pf.set("location.last-known.y", pf.get("last-location.Y")); - pf.set("location.last-known.z", pf.get("last-location.Z")); + pf.set("location.last-known.x", pf.get("last-location.x")); + pf.set("location.last-known.y", pf.get("last-location.y")); + pf.set("location.last-known.z", pf.get("last-location.z")); pf.set("location.last-known.yaw", pf.get("last-location.yaw")); pf.set("location.last-known.pitch", pf.get("last-location.pitch")); } - if (pf.get("user.last-sleep-location") != null) { + if (pf.get("user.last-sleep-location.world") != null) { pf.set("location.last-sleep.world", pf.get("user.last-sleep-location.world")); - pf.set("location.last-sleep.x", pf.get("user.last-sleep-location.X")); - pf.set("location.last-sleep.y", pf.get("user.last-sleep-location.Y")); - pf.set("location.last-sleep.z", pf.get("user.last-sleep-location.Z")); + pf.set("location.last-sleep.x", pf.get("user.last-sleep-location.x")); + pf.set("location.last-sleep.y", pf.get("user.last-sleep-location.y")); + pf.set("location.last-sleep.z", pf.get("user.last-sleep-location.z")); pf.set("location.last-sleep.yaw", pf.get("user.last-sleep-location.yaw")); pf.set("location.last-sleep.pitch", pf.get("user.last-sleep-location.pitch")); } log.info("> Migrated " + player.getName()); pf.remove("last-location"); pf.remove("user.last-sleep-location"); pf.save(); + } finally { + pf.close(); + } } } }
431-444: Don’t silently drop spawns when the world isn’t loaded.
Line 436 can returnnull; the migration currently skips that spawn without any notice.💡 Suggested logging
World world = Bukkit.getWorld(spawnName); if (world != null) { UUID uid = world.getUID(); worldsFile.set("world." + uid + ".spawn.x", spawnsFile.getInt("spawn." + spawnName + ".X")); worldsFile.set("world." + uid + ".spawn.y", spawnsFile.getInt("spawn." + spawnName + ".Y")); worldsFile.set("world." + uid + ".spawn.z", spawnsFile.getInt("spawn." + spawnName + ".Z")); worldsFile.set("world." + uid + ".spawn.yaw", spawnsFile.getDouble("spawn." + spawnName + ".yaw")); worldsFile.set("world." + uid + ".spawn.pitch", spawnsFile.getDouble("spawn." + spawnName + ".pitch")); + } else { + log.warn("Could not migrate spawn for world '" + spawnName + "': world not found or failed to load."); }
🟡 Minor comments (16)
src/main/resources/language/en-GB.yml-235-235 (1)
235-235: Incorrect command reference: should direct to/thomesfor team homes.The
teamhome.notfoundmessage directs users to/homes, but since this concerns team homes, it should reference/thomesinstead.📝 Suggested fix
- notfound: "<dark_red>[Essence] <red>Team home <1> does not exist. Use /homes for a list of homes." + notfound: "<dark_red>[Essence] <red>Team home <1> does not exist. Use /thomes for a list of team homes."src/main/resources/language/en-GB.yml-102-102 (1)
102-102: Incorrect description: help text says "Feed" but this is the /heal command.Appears to be a copy-paste error from the
/feedhelp entry above.📝 Suggested fix
- heal: "<gold>[Essence] <yellow>/heal [user] - Feed yourself or another." + heal: "<gold>[Essence] <yellow>/heal [user] - Heal yourself or another."src/main/resources/language/en-GB.yml-77-77 (1)
77-77: Typo: "coordomate" should be "coordinate".📝 Suggested fix
- tp: "<gold>[Essence] <yellow>/tp <name/coordomate> - Teleport." + tp: "<gold>[Essence] <yellow>/tp <name/coordinate> - Teleport."src/main/resources/language/fr-FR.yml-406-406 (1)
406-406: Spawn message is untranslated.The
spawn.notexistmessage is in English. Consider translating to French:notexist: "§4[Essence] §cLe monde demandé \"{{1}}\" n'existe pas ou n'a pas pu être chargé."src/main/resources/language/fr-FR.yml-233-233 (1)
233-233: New translation keys are in English instead of French.The
home.exists,teamhome.exists, andteleport.requestexpiredmessages are in English but this is the French (fr-FR) localization file. These should be translated to French for users who have selected French as their language.Suggested French translations:
home.exists:"§4[Essence] §cUn home avec le nom '{{1}}' existe déjà."teamhome.exists:"§4[Essence] §cUn home d'équipe avec le nom '{{1}}' existe déjà."teleport.requestexpired:"§4[Essence] §cLa demande de téléportation a expiré."Also applies to: 245-245, 283-283
src/main/resources/language/fr-FR.yml-305-322 (1)
305-322: New speed command messages are untranslated.All new speed-related translation keys (
setwalk,setfly,setflyother,setwalkother,setflybyother,setwalkbyother,resetfly,resetwalk,resetflyother,resetwalkother,resetflybyother,resetwalkbyother, andusage) are in English. For consistency with the file's purpose as a French localization, these should be translated.Example translations:
setwalk:"§2[Essence] §aVotre vitesse de marche a été définie à {{1}}."setfly:"§2[Essence] §aVotre vitesse de vol a été définie à {{1}}."usage:"§4[Essence] §cUtilisation: /speed <montant> [utilisateur]"src/main/resources/language/es-ES.yml-233-233 (1)
233-233: Localize new strings to Spanish for consistency.Lines 233, 245, 283, and 305–322 add English text in the Spanish locale. This will surface mixed-language UX for es-ES users. Please translate these entries (or align with your fallback strategy).
Also applies to: 245-245, 283-283, 305-322
src/main/resources/language/de-DE.yml-305-322 (1)
305-322: Use<speed>placeholder for consistency.The help text elsewhere in this file uses
/speed <speed>, while this usage string now says<amount>. Consider aligning the placeholder for consistency.💬 Suggested tweak
- usage: "§4[Essence] §cUsage: /speed <amount> [user]" + usage: "§4[Essence] §cUsage: /speed <speed> [user]"src/main/resources/language/ja-JP.yml-233-233 (1)
233-233: New strings are in English instead of Japanese.The newly added localization strings in
ja-JP.yml(home.exists,teamhome.exists,teleport.requestexpired,speed.*variants,spawn.notexist) are all in English rather than Japanese. Since this is the Japanese locale file, these strings should contain Japanese translations for consistency with the localization strategy.Examples of strings needing translation:
- Line 233:
"A home with the name '{{1}}' already exists."→ Japanese translation needed- Line 283:
"The teleport request has expired."→ Japanese translation needed- Lines 305-322: Speed-related messages → Japanese translations needed
Verify that these placeholder English strings will be replaced with Japanese translations via Crowdin before release.
src/main/resources/language/nl-NL.yml-289-289 (1)
289-289: Typo: "Beweerg" should be "Beweeg".🔤 Proposed fix
- movetocancel: "§6[Essence] §eBeweerg om teleportatie te annuleren." + movetocancel: "§6[Essence] §eBeweeg om teleportatie te annuleren."src/main/resources/language/nl-NL.yml-260-260 (1)
260-260: Typo: "mee" should be "meer".The word "mee" should be "meer" (more) in this context.
🔤 Proposed fix
- hitlimit: "§4[Essence] §cJe kunt niet mee warp-punten maken." + hitlimit: "§4[Essence] §cJe kunt niet meer warp-punten maken."src/main/resources/language/nl-NL.yml-233-233 (1)
233-233: Untranslated English string.The
existskey contains English text instead of Dutch. This appears to duplicate line 229'salreadyexistsmessage.🔤 Proposed fix
- exists: "§4[Essence] §cA home with the name '{{1}}' already exists." + exists: "§4[Essence] §cEr bestaat al een home met de naam '{{1}}'."src/main/resources/language/nl-NL.yml-283-283 (1)
283-283: Untranslated English string.The
requestexpiredmessage is in English instead of Dutch.🔤 Proposed fix
- requestexpired: "§4[Essence] §cThe teleport request has expired." + requestexpired: "§4[Essence] §cHet teleportatieverzoek is verlopen."src/main/resources/language/nl-NL.yml-245-245 (1)
245-245: Untranslated English string.The
existskey contains English text instead of Dutch. This appears to duplicate line 242'salreadyexistsmessage.🔤 Proposed fix
- exists: "§4[Essence] §cA team home with the name '{{1}}' already exists." + exists: "§4[Essence] §cEr bestaat al een teamhome met de naam '{{1}}'."src/main/resources/language/nl-NL.yml-305-322 (1)
305-322: Multiple untranslated English strings in speed section.The following keys contain English text instead of Dutch translations:
setwalk,setfly(lines 305-306)setflyother,setwalkother(lines 308-309)setflybyother,setwalkbyother(lines 311-312)resetfly,resetwalk(lines 314-315)resetflyother,resetwalkother(lines 317-318)resetflybyother,resetwalkbyother,usage(lines 320-322)🔤 Proposed translations
- setwalk: "§2[Essence] §aSet your walking speed to {{1}}." - setfly: "§2[Essence] §aSet your flying speed to {{1}}." + setwalk: "§2[Essence] §aStel je loopsnelheid in op {{1}}." + setfly: "§2[Essence] §aStel je vliegsnelheid in op {{1}}." setother: "§2[Essence] §aStel {{1}}'s snelheid in op {{2}}." - setflyother: "§2[Essence] §aSet {{1}}'s flying speed to {{2}}." - setwalkother: "§2[Essence] §aSet {{1}}'s walking speed to {{2}}." + setflyother: "§2[Essence] §aStel {{1}}'s vliegsnelheid in op {{2}}." + setwalkother: "§2[Essence] §aStel {{1}}'s loopsnelheid in op {{2}}." setbyother: "§2[Essence] §a{{1}} stelde je snelheid in op {{2}}." - setflybyother: "§2[Essence] §a{{1}} set your flying speed to {{2}}." - setwalkbyother: "§2[Essence] §a{{1}} set your walking speed to {{2}}." + setflybyother: "§2[Essence] §a{{1}} stelde je vliegsnelheid in op {{2}}." + setwalkbyother: "§2[Essence] §a{{1}} stelde je loopsnelheid in op {{2}}." reset: "§2[Essence] §aJe snelheid is gereset." - resetfly: "§2[Essence] §aYour flying speed has been reset." - resetwalk: "§2[Essence] §aYour walking speed has been reset." + resetfly: "§2[Essence] §aJe vliegsnelheid is gereset." + resetwalk: "§2[Essence] §aJe loopsnelheid is gereset." resetother: "§2[Essence] §aResette {{1}}'s snelheid." - resetflyother: "§2[Essence] §aReset {{1}}'s flying speed." - resetwalkother: "§2[Essence] §aReset {{1}}'s walking speed." + resetflyother: "§2[Essence] §aResette {{1}}'s vliegsnelheid." + resetwalkother: "§2[Essence] §aResette {{1}}'s loopsnelheid." resetbyother: "§2[Essence] §a{{1}} resette je snelheid." - resetflybyother: "§2[Essence] §a{{1}} reset your flying speed." - resetwalkbyother: "§2[Essence] §a{{1}} reset your walking speed." - usage: "§4[Essence] §cUsage: /speed <amount> [user]" + resetflybyother: "§2[Essence] §a{{1}} resette je vliegsnelheid." + resetwalkbyother: "§2[Essence] §a{{1}} resette je loopsnelheid." + usage: "§4[Essence] §cGebruik: /speed <snelheid> [speler]"src/main/java/net/lewmc/essence/EssenceConfiguration.java-68-69 (1)
68-69: Fix grammar in the legacy-color warning.Minor typo in the log message.
✏️ Suggested edit
- this.plugin.log.warn("If you have any errors in the future, check you config file does not contain any legacy color codes (§ codes)."); + this.plugin.log.warn("If you have any errors in the future, check your config file does not contain any legacy color codes (§ codes).");
🧹 Nitpick comments (9)
src/main/java/net/lewmc/essence/core/CommandRules.java (1)
49-51: Consider using standard exception logging.
Arrays.toString(e.getStackTrace())produces a single-line, hard-to-read output. If the logging framework supports it, pass the exception directly for proper stack trace formatting, or log it in a more readable manner.♻️ Suggested improvement
- this.plugin.log.severe("Unable to display rules."); - this.plugin.log.severe(e.getMessage()); - this.plugin.log.severe(Arrays.toString(e.getStackTrace())); + this.plugin.log.severe("Unable to display rules: " + e.getMessage()); + e.printStackTrace();Or, if
this.plugin.logsupports logging with aThrowable:this.plugin.log.log(Level.SEVERE, "Unable to display rules.", e);src/main/resources/language/bn-BD.yml (2)
229-229: Potential redundancy betweenalreadyexistsandexistsmessages.Line 229 (
home.alreadyexists) and line 233 (home.exists) convey nearly identical messages. The newexistskey includes the home name placeholder{{1}}, making it more informative. Consider consolidating these into a single key or documenting when each should be used to avoid translator confusion.Also applies to: 233-233
242-242: Same redundancy ashomesection.
teamhome.alreadyexists(line 242) andteamhome.exists(line 245) have the same overlap. Theexistsvariant includes the name placeholder. Apply the same consolidation approach as suggested for thehomesection.Also applies to: 245-245
src/main/resources/language/fr-FR.yml (1)
1-6: Consider updating the file to indicate partial translation status.The file header encourages community translation contributions via Crowdin, which is good. However, many strings throughout this file (including the newly added ones) remain in English. If this is intentional as a fallback mechanism, the overall approach is acceptable. Just be aware that French-speaking users may see a mix of languages.
src/main/resources/language/ja-JP.yml (1)
1-2: Consider removing untranslated locale files or marking them as incomplete.The file references Crowdin for translations (line 2), but currently contains only English text. If Japanese translations aren't yet available, consider either:
- Removing this file until translations are ready (falling back to the default locale)
- Adding a comment indicating this locale is incomplete/pending translation
This prevents Japanese-locale users from expecting localized content when none is available.
src/main/resources/language/vi-VN.yml (1)
233-233: Untranslated string in Vietnamese locale file.This new key contains English text rather than Vietnamese translation. The same applies to all other new strings in this file. While this appears consistent with the existing file state (likely used as a template for Crowdin translation), ensure these strings are queued for translation.
src/main/resources/language/ko-KR.yml (1)
283-283: Consider Korean localization for the new message.If ko-KR is intended to be fully localized, this new string might need a Korean translation.
src/main/java/net/lewmc/essence/kit/UtilKit.java (1)
90-97: Consider clamping to material's max stack size.The validation ensures amount is 1-64, but some items have lower max stack sizes (e.g., tools = 1, ender pearls = 16). Setting amount higher than
getMaxStackSize()will still work but may be unexpected.ItemStack itemStack = new ItemStack(item); int amount = kitData.getInt("kits."+kit+".items."+object+".amount"); - if (amount > 0 && amount < 65) { + int maxStack = itemStack.getMaxStackSize(); + if (amount >= 1 && amount <= maxStack) { itemStack.setAmount(amount); } else { itemStack.setAmount(1); - this.plugin.log.warn("Item '"+object+"' in kit '"+kit+"', has an invalid amount '"+amount+"'. Must be between 1 and 64."); + this.plugin.log.warn("Item '"+object+"' in kit '"+kit+"', has an invalid amount '"+amount+"'. Must be between 1 and "+maxStack+"."); }src/main/java/net/lewmc/essence/core/UtilUpdate.java (1)
386-398: Guard against missing kits section before iterating.
IfgetKeys("kits", false)can returnnull, this loop will NPE during migration.♻️ Suggested hardening
- Set<String> keys = kf.getKeys("kits", false); - for (String key : keys) { + Set<String> keys = kf.getKeys("kits", false); + if (keys == null || keys.isEmpty()) { + log.info("> No kits found to migrate."); + } else { + for (String key : keys) { kf.set("kits." + key + ".name", "Kit"); kf.set("kits." + key + ".description", "A cool kit!"); List<String> items = kf.getStringList("kits." + key + ".items"); kf.set("kits." + key + ".items",null); for (String item : items) { kf.set("kits." + key + ".items."+item+".amount", 1); } log.info("> Migrated kit "+key); + } }
| import net.kyori.adventure.text.Component; | ||
| import net.kyori.adventure.text.minimessage.MiniMessage; | ||
| import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/main/java/net/lewmc/essence/chat/CommandReply.javaRepository: LewMC/Essence
Length of output: 3820
🌐 Web query:
MiniMessage TagResolver.empty() built-in tags parsing behavior
💡 Result:
TagResolver.empty() returns a tag resolver that never resolves any tags (it always returns null). If you use it as the MiniMessage tag resolver (or omit the standard resolver), built-in tags (colors, decorations, placeholders, etc.) will not be recognized and will be treated as literal text by the parser. [1][2]
Sources:
- TagResolver.empty() API (returns null for all resolves). [1]
- MiniMessage docs: custom tag resolvers / default (standard) tags and example showing missing tags become literal text. [2]
🌐 Web query:
MiniMessage mm.deserialize() default tags behavior vs TagResolver.empty()
💡 Result:
Short answer: They are different. Passing TagResolver.empty() to MiniMessage.deserialize() just supplies an extra resolver that resolves nothing; MiniMessage still uses its built‑in/default tag resolver afterward. deserialize(...) checks the supplied resolver(s) first, then falls back to the MiniMessage instance’s base (standard) tags. TagResolver.empty() simply returns null for all names — it does not disable the default tags. (jd.advntr.dev)
Citations:
chat.allow-message-formatting still allows MiniMessage built-in tags.
mm.deserialize(rawMsg, TagResolver.empty()) does not disable MiniMessage's default tag resolver—built-in tags are still parsed via fallback, so formatting can slip through even when the config disables it. Replace with Component.text(rawMsg) to prevent all tag parsing when formatting is off.
🔧 Suggested fix (disable tag parsing when formatting is off)
-import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
@@
-Component msgComponent = (boolean) this.plugin.config.get("chat.allow-message-formatting")
- ? mm.deserialize(rawMsg)
- : mm.deserialize(rawMsg, TagResolver.empty());
+Component msgComponent = (boolean) this.plugin.config.get("chat.allow-message-formatting")
+ ? mm.deserialize(rawMsg)
+ : Component.text(rawMsg);🤖 Prompt for AI Agents
In `@src/main/java/net/lewmc/essence/chat/CommandReply.java` around lines 3 - 5,
The code in CommandReply is still calling mm.deserialize(rawMsg,
TagResolver.empty()), which allows MiniMessage built-in tags via fallback;
change the logic where formatting is disabled to construct a plain text
component instead of deserializing—replace mm.deserialize(rawMsg,
TagResolver.empty()) with Component.text(rawMsg) (or the equivalent plain text
construction) for the code path that checks the chat.allow-message-formatting
config so that neither custom nor built-in MiniMessage tags are parsed; update
any uses of variables mm, rawMsg, and TagResolver.empty() in that branch
accordingly.
| String rawMsg = PlainTextComponentSerializer.plainText().serialize(event.originalMessage()); | ||
| Component messageContent = (boolean) this.plugin.config.get("chat.allow-message-formatting") | ||
| ? mm.deserialize(rawMsg) | ||
| : Component.text(rawMsg); | ||
|
|
||
| event.setMessage(msg); | ||
| event.setFormat(msg); | ||
| String format = this.plugin.config.get("chat.name-format") + " <user_message>"; | ||
|
|
||
| for (Player p : Bukkit.getServer().getOnlinePlayers()) { | ||
| List<String> ignoring = (List<String>) new UtilPlayer(this.plugin).getPlayer(p.getUniqueId(), UtilPlayer.KEYS.USER_IGNORING_PLAYERS); | ||
| if (!ignoring.contains(p.getUniqueId().toString())) { | ||
| p.sendMessage(msg); | ||
| } | ||
| } | ||
| event.setCancelled(true); | ||
| } | ||
| } | ||
| } | ||
| final Component finalChatLine = new UtilPlaceholder(this.plugin, player).replaceAll( | ||
| mm.deserialize(format, Placeholder.component("user_message", messageContent)) | ||
| ); | ||
|
|
||
| event.renderer((source, sourceDisplayName, message, viewer) -> finalChatLine); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, find and examine the exact file mentioned in the review
find . -type f -name "EventPlayerChat.java" | head -20Repository: LewMC/Essence
Length of output: 116
🏁 Script executed:
# Also check the repository structure to understand the project better
ls -laRepository: LewMC/Essence
Length of output: 970
🏁 Script executed:
# Read the EventPlayerChat.java file to see the actual code
cat -n src/main/java/net/lewmc/essence/chat/EventPlayerChat.javaRepository: LewMC/Essence
Length of output: 2708
🏁 Script executed:
# Check the Paper/Spigot dependency in pom.xml to understand the API version
cat pom.xml | grep -A 5 -B 5 "paper\|spigot" -iRepository: LewMC/Essence
Length of output: 843
🏁 Script executed:
# Search for other uses of AsyncChatEvent or event.renderer in the codebase
rg "AsyncChatEvent|event\.renderer" --type javaRepository: LewMC/Essence
Length of output: 527
🏁 Script executed:
# Let's check if there are any other plugins or configurations related to chat message handling
rg "originalMessage|message\.serialize|PlainTextComponentSerializer" --type java -B 2 -A 2Repository: LewMC/Essence
Length of output: 2254
🏁 Script executed:
# Check if there are any comments or documentation about chat handling
rg "chat|message|filter|moderat" --type java src/main/java/net/lewmc/essence/chat/ -iRepository: LewMC/Essence
Length of output: 9098
🌐 Web query:
Paper AsyncChatEvent API message parameter renderer documentation
💡 Result:
The AsyncChatEvent uses a ChatRenderer to produce the per-viewer Component. The renderer signature (lambda or ChatRenderer.render) is:
(Player source, Component sourceDisplayName, Component message, Audience viewer) -> Component
- source: the sending Player.
- sourceDisplayName: the already-rendered display name Component for the sender.
- message: the message Component that represents the chat content (i.e., the message being sent — already converted to a Component by the event).
- viewer: the Audience who will receive the rendered Component.
Return a Component to send to that viewer. See Paper’s Chat events guide and the AsyncChatEvent API docs for examples and the full API. [1][2]
Sources:
[1] PaperMC — Chat events (AsyncChatEvent / ChatRenderer examples).
[2] AsyncChatEvent JavaDoc (Paper API).
Use the mutated chat message in the renderer (avoid originalMessage bypass).
AsyncChatEvent distinguishes originalMessage (the initial message) from the message parameter passed to the renderer, which may be modified by upstream plugins or filters. Building a fixed finalChatLine from originalMessage bypasses any message mutations, sanitization, or moderation applied by other plugins and ignores the renderer's intended message parameter. Move message processing into the renderer lambda to respect changes made by the plugin chain.
🔧 Proposed fix (build from renderer message)
- String rawMsg = PlainTextComponentSerializer.plainText().serialize(event.originalMessage());
- Component messageContent = (boolean) this.plugin.config.get("chat.allow-message-formatting")
- ? mm.deserialize(rawMsg)
- : Component.text(rawMsg);
-
- String format = this.plugin.config.get("chat.name-format") + " <user_message>";
-
- final Component finalChatLine = new UtilPlaceholder(this.plugin, player).replaceAll(
- mm.deserialize(format, Placeholder.component("user_message", messageContent))
- );
-
- event.renderer((source, sourceDisplayName, message, viewer) -> finalChatLine);
+ String format = this.plugin.config.get("chat.name-format") + " <user_message>";
+ boolean allowFormatting = (boolean) this.plugin.config.get("chat.allow-message-formatting");
+
+ event.renderer((source, sourceDisplayName, message, viewer) -> {
+ String rawMsg = PlainTextComponentSerializer.plainText().serialize(message);
+ Component messageContent = allowFormatting
+ ? mm.deserialize(rawMsg)
+ : Component.text(rawMsg);
+ return new UtilPlaceholder(this.plugin, player).replaceAll(
+ mm.deserialize(format, Placeholder.component("user_message", messageContent))
+ );
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| String rawMsg = PlainTextComponentSerializer.plainText().serialize(event.originalMessage()); | |
| Component messageContent = (boolean) this.plugin.config.get("chat.allow-message-formatting") | |
| ? mm.deserialize(rawMsg) | |
| : Component.text(rawMsg); | |
| event.setMessage(msg); | |
| event.setFormat(msg); | |
| String format = this.plugin.config.get("chat.name-format") + " <user_message>"; | |
| for (Player p : Bukkit.getServer().getOnlinePlayers()) { | |
| List<String> ignoring = (List<String>) new UtilPlayer(this.plugin).getPlayer(p.getUniqueId(), UtilPlayer.KEYS.USER_IGNORING_PLAYERS); | |
| if (!ignoring.contains(p.getUniqueId().toString())) { | |
| p.sendMessage(msg); | |
| } | |
| } | |
| event.setCancelled(true); | |
| } | |
| } | |
| } | |
| final Component finalChatLine = new UtilPlaceholder(this.plugin, player).replaceAll( | |
| mm.deserialize(format, Placeholder.component("user_message", messageContent)) | |
| ); | |
| event.renderer((source, sourceDisplayName, message, viewer) -> finalChatLine); | |
| String format = this.plugin.config.get("chat.name-format") + " <user_message>"; | |
| boolean allowFormatting = (boolean) this.plugin.config.get("chat.allow-message-formatting"); | |
| event.renderer((source, sourceDisplayName, message, viewer) -> { | |
| String rawMsg = PlainTextComponentSerializer.plainText().serialize(message); | |
| Component messageContent = allowFormatting | |
| ? mm.deserialize(rawMsg) | |
| : Component.text(rawMsg); | |
| return new UtilPlaceholder(this.plugin, player).replaceAll( | |
| mm.deserialize(format, Placeholder.component("user_message", messageContent)) | |
| ); | |
| }); |
🤖 Prompt for AI Agents
In `@src/main/java/net/lewmc/essence/chat/EventPlayerChat.java` around lines 48 -
59, The current code builds finalChatLine from event.originalMessage(),
bypassing any mutations passed into the renderer; change the logic to compute
messageContent and finalChatLine inside the event.renderer lambda using the
renderer's message parameter (the lambda's third param, named message), e.g.
serialize that message with
PlainTextComponentSerializer.plainText().serialize(message), then apply
mm.deserialize/Component.text and UtilPlaceholder.replaceAll with
Placeholder.component("user_message", messageContent) so the chat line respects
upstream modifications; keep references to UtilPlaceholder, mm.deserialize,
event.renderer, finalChatLine, and the config keys
"chat.allow-message-formatting" and "chat.name-format" when moving the code into
the lambda.
| public void sendTo(CommandSender cs, String group, String msg, String[] replace) { | ||
| String message = this.getMessage(msg, group); | ||
| if (message != null) { | ||
| int i = 1; | ||
| for (String item : replace) { | ||
| message = message.replace("{{"+i+"}}", item); | ||
| i++; | ||
| } | ||
| cs.sendMessage(message); | ||
| } else { | ||
| cs.sendMessage(ChatColor.DARK_RED + "[Essence] " + ChatColor.RED + "Unable to send message to player, see console for more information."); | ||
| this.cs.sendMessage(ChatColor.DARK_RED + "[Essence] " + ChatColor.RED + "Unable to send message to player, see console for more information."); | ||
| new Logger(this.plugin.foundryConfig).warn("Unable to send message '"+group+"."+msg+"' to player, could not find key in en-GB.yml"); | ||
| String raw = this.getMessage(msg, group); | ||
| if (raw == null) { | ||
| this.sendError(cs, group, msg); | ||
| return; | ||
| } | ||
|
|
||
| List<TagResolver> resolvers = new ArrayList<>(); | ||
| for (int i = 0; i < replace.length; i++) { | ||
| resolvers.add(Placeholder.parsed(String.valueOf(i + 1), replace[i])); | ||
| } | ||
|
|
||
| Component component = MiniMessage.miniMessage().deserialize(raw, TagResolver.resolver(resolvers)); | ||
| cs.sendMessage(new UtilPlaceholder(this.plugin, cs).replaceAll(component)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, find language/message files
fd -e properties -e yml -e yaml -e json | head -20Repository: LewMC/Essence
Length of output: 770
🏁 Script executed:
# Search for files in common language/message directories
git ls-files | grep -i "lang\|message\|config" | head -30Repository: LewMC/Essence
Length of output: 840
🏁 Script executed:
# Look for the existence of § character in the codebase
rg "§" --type-listRepository: LewMC/Essence
Length of output: 446
🏁 Script executed:
# Check if there are any references to LegacyComponentSerializer or legacy code handling
rg "LegacyComponentSerializer|legacy.*Serializer"Repository: LewMC/Essence
Length of output: 39
🏁 Script executed:
# Search for MiniMessage usage patterns in the codebase
rg "MiniMessage" -A 3 -B 3Repository: LewMC/Essence
Length of output: 15864
🏁 Script executed:
# Check if language files contain § codes
rg "§" src/main/resources/language/Repository: LewMC/Essence
Length of output: 50370
🏁 Script executed:
# Look at the UtilUpdate.convertLegacyColors() method
rg -A 30 "public void convertLegacyColors"Repository: LewMC/Essence
Length of output: 3253
🏁 Script executed:
# Check the full UtilMessage.java file to understand the context
cat -n src/main/java/net/lewmc/essence/core/UtilMessage.java | head -100Repository: LewMC/Essence
Length of output: 4384
🏁 Script executed:
# Check imports and overall structure of UtilMessage.java
head -30 src/main/java/net/lewmc/essence/core/UtilMessage.javaRepository: LewMC/Essence
Length of output: 980
🏁 Script executed:
# Search for calls to convertLegacyColors()
rg "convertLegacyColors" -B 2 -A 2Repository: LewMC/Essence
Length of output: 1040
🏁 Script executed:
# Check EssenceConfiguration to see how language files are loaded
cat -n src/main/java/net/lewmc/essence/EssenceConfiguration.java | head -80Repository: LewMC/Essence
Length of output: 2937
🏁 Script executed:
# Search for getMessage method to understand how messages are retrieved
rg -A 10 "private.*getMessage|String getMessage"Repository: LewMC/Essence
Length of output: 737
Legacy § color codes in language files won't render under MiniMessage.
Language files (en-GB.yml, zh-CN.yml, tr-TR.yml, etc.) contain § codes (e.g., §6[Essence]), but getMessage() retrieves these and passes them directly to MiniMessage.miniMessage().deserialize() at line 85, which only parses MiniMessage tags like <gold>. Users will see raw codes instead of formatted text.
The codebase has a convertLegacyColors() utility that already converts legacy codes to MiniMessage tags, but it's only applied to config.yml during startup, not to language files. Either extend this conversion to language files during plugin initialization, or apply runtime conversion here (as suggested below).
🔧 Possible approach (runtime conversion)
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
...
- Component component = MiniMessage.miniMessage().deserialize(raw, TagResolver.resolver(resolvers));
+ String normalized = raw.contains("§")
+ ? MiniMessage.miniMessage().serialize(LegacyComponentSerializer.legacySection().deserialize(raw))
+ : raw;
+ Component component = MiniMessage.miniMessage().deserialize(normalized, TagResolver.resolver(resolvers));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public void sendTo(CommandSender cs, String group, String msg, String[] replace) { | |
| String message = this.getMessage(msg, group); | |
| if (message != null) { | |
| int i = 1; | |
| for (String item : replace) { | |
| message = message.replace("{{"+i+"}}", item); | |
| i++; | |
| } | |
| cs.sendMessage(message); | |
| } else { | |
| cs.sendMessage(ChatColor.DARK_RED + "[Essence] " + ChatColor.RED + "Unable to send message to player, see console for more information."); | |
| this.cs.sendMessage(ChatColor.DARK_RED + "[Essence] " + ChatColor.RED + "Unable to send message to player, see console for more information."); | |
| new Logger(this.plugin.foundryConfig).warn("Unable to send message '"+group+"."+msg+"' to player, could not find key in en-GB.yml"); | |
| String raw = this.getMessage(msg, group); | |
| if (raw == null) { | |
| this.sendError(cs, group, msg); | |
| return; | |
| } | |
| List<TagResolver> resolvers = new ArrayList<>(); | |
| for (int i = 0; i < replace.length; i++) { | |
| resolvers.add(Placeholder.parsed(String.valueOf(i + 1), replace[i])); | |
| } | |
| Component component = MiniMessage.miniMessage().deserialize(raw, TagResolver.resolver(resolvers)); | |
| cs.sendMessage(new UtilPlaceholder(this.plugin, cs).replaceAll(component)); | |
| } | |
| import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; | |
| public void sendTo(CommandSender cs, String group, String msg, String[] replace) { | |
| String raw = this.getMessage(msg, group); | |
| if (raw == null) { | |
| this.sendError(cs, group, msg); | |
| return; | |
| } | |
| List<TagResolver> resolvers = new ArrayList<>(); | |
| for (int i = 0; i < replace.length; i++) { | |
| resolvers.add(Placeholder.parsed(String.valueOf(i + 1), replace[i])); | |
| } | |
| String normalized = raw.contains("§") | |
| ? MiniMessage.miniMessage().serialize(LegacyComponentSerializer.legacySection().deserialize(raw)) | |
| : raw; | |
| Component component = MiniMessage.miniMessage().deserialize(normalized, TagResolver.resolver(resolvers)); | |
| cs.sendMessage(new UtilPlaceholder(this.plugin, cs).replaceAll(component)); | |
| } |
🤖 Prompt for AI Agents
In `@src/main/java/net/lewmc/essence/core/UtilMessage.java` around lines 73 - 87,
The raw message returned by getMessage(...) contains legacy § color codes which
MiniMessage.miniMessage().deserialize(...) won't parse, so update
UtilMessage.sendTo to convert legacy codes to MiniMessage tags before
deserializing (or ensure convertLegacyColors(...) is applied to language entries
at plugin initialization); specifically, take the String raw from getMessage,
run it through the existing convertLegacyColors(...) utility (or the equivalent
method on your plugin/class) to produce a converted string, and pass that
converted string into MiniMessage.miniMessage().deserialize(...) when building
the Component for sendTo.
| if (this.plugin.integrations.PAPIEnabled) { | ||
| if (this.cs instanceof Player) { | ||
| return new UtilPlaceholderAPI().invokePAPI((Player) this.cs, text); | ||
| } else { | ||
| return new UtilPlaceholderAPI().invokePAPI(null, text); | ||
| } | ||
| } else { | ||
| text = text.replace("%essence_version%", this.replaceSingle("version")); | ||
| text = text.replace("%essence_minecraft_version%", this.replaceSingle("minecraft_version")); | ||
| text = text.replace("%essence_time%", this.replaceSingle("time")); | ||
| text = text.replace("%essence_date%", this.replaceSingle("date")); | ||
| text = text.replace("%essence_datetime%", this.replaceSingle("datetime")); | ||
| text = text.replace("%essence_player%", this.replaceSingle("player")); | ||
| text = text.replace("%essence_username%", this.replaceSingle("username")); | ||
| text = text.replace("%essence_team%", this.replaceSingle("team_name")); | ||
| text = text.replace("%essence_team_name%", this.replaceSingle("team_name")); | ||
| text = text.replace("%essence_team_leader%", this.replaceSingle("team_leader")); | ||
| text = text.replace("%essence_team_prefix%", this.replaceSingle("team_prefix")); | ||
| text = text.replace("%essence_combined_prefix%", this.replaceSingle("combined_prefix")); | ||
| text = text.replace("%essence_player_prefix%", this.replaceSingle("player_prefix")); | ||
| text = text.replace("%essence_player_suffix%", this.replaceSingle("player_suffix")); | ||
| text = text.replace("%essence_balance%", this.replaceSingle("balance")); | ||
| return this.invokePAPI(this.cs instanceof Player p ? p : null, text); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/main/java/net/lewmc/essence/core/UtilPlaceholder.javaRepository: LewMC/Essence
Length of output: 5685
🏁 Script executed:
# Check if there are any tests or documentation about PAPI and MiniMessage integration
rg -i "placeholderapi|plaintext|minimessage" --type java -B 2 -A 2 | head -100Repository: LewMC/Essence
Length of output: 9046
🏁 Script executed:
# Search for how MiniMessage and PlaceholderAPI are used together elsewhere
rg "MiniMessage.*serialize" --type java -B 1 -A 1 | head -50Repository: LewMC/Essence
Length of output: 4454
🏁 Script executed:
# Check if there's any config or setup for PAPI in this codebase
fd -e yml -e yaml | xargs rg -l "papi\|placeholder" 2>/dev/nullRepository: LewMC/Essence
Length of output: 41
🌐 Web query:
PlaceholderAPI MiniMessage serialization compatibility preserve formatting
💡 Result:
Short answer: PlaceholderAPI’s percent-style placeholders are not natively compatible with MiniMessage serialization — to preserve full formatting you must resolve placeholders into Adventure Components (or use a MiniMessage-aware placeholder bridge) rather than inserting raw legacy/percent strings. Use either a MiniMessage TagResolver that runs PlaceholderAPI per-player, or a plugin/library that provides component-based placeholders (e.g. MiniPlaceholders) so the serializer preserves colors, decorations, events and avoids formatting “bleed.” See Adventure’s docs and examples and MiniPlaceholders docs. [1][2][3]
Practical tips
- Don’t inject raw %placeholders% into a MiniMessage string before parsing; instead parse MiniMessage and resolve placeholders to Components via a TagResolver or resolver that returns Component. That preserves hover/click/events and style resets. [1][2]
- If you must inject strings, prefer component-producing APIs (PlaceholderAPI expansions that expose Components or MiniMessage-aware expansions) or use MiniPlaceholders so placeholders are already Components when serialized. [2][3]
- Watch self-closing vs open tags: self-closing placeholder tags (or resolvers that return closed components) prevent formatting from bleeding into following text. Some plugins expose both self-closing and open variants (e.g., papi vs papi_open / papi_mini patterns). [2]
References
- Adventure FAQ: MiniMessage & PlaceholderAPI guidance (custom TagResolver example). [1]
- MiniPlaceholders (component-based placeholder API / plugin). [2]
- Example plugin docs showing papi/papi_mini / self-closing vs open placeholder behavior. [3]
🌐 Web query:
PlaceholderAPI setPlaceholders MiniMessage tags
💡 Result:
Short answer: MiniMessage doesn't natively expand PlaceholderAPI (PAPI) strings — you must add a custom TagResolver that calls PlaceholderAPI and injects the result into the MiniMessage parse. See Adventure's FAQ and MiniMessage dynamic placeholders for guidance. [1][2]
Minimal example (Paper/Spigot):
// create a TagResolver that resolves <papi:placeholder_name>
public TagResolver papiTag(Player player) {
return TagResolver.resolver(resolver -> {
resolver.parser("papi", (ctx, arg) -> {
String key = arg.value(); // e.g. "vault_eco_balance"
// call PlaceholderAPI to get the value for this player
String result = PlaceholderAPI.setPlaceholders(player, "%" + key + "%");
// return as an unparsed placeholder so MiniMessage does not re-parse tags inside it
return Placeholder.unparsed(result);
});
});
}
Usage:
String mm = "Balance: <papi:vault_eco_balance>";
Component comp = MiniMessage.miniMessage().deserialize(mm, papiTag(player));
Notes / tips:
- Use Placeholder.unparsed(...) if the PAPI value may contain MiniMessage tags you do not want parsed; use Placeholder.component(...) if you convert the PAPI output into a Component. [2]
- Some plugins provide built-in support (MiniPlaceholders / plugin-specific papi:... tags) so you can use those instead of writing your own resolver. [3]
- If PAPI values include legacy color codes (e.g. &a), you may need to convert them to components before inserting to preserve colors. Some plugins (or MiniPlaceholders) handle this. [1][3]
Sources:
- Adventure FAQ (MiniMessage + PlaceholderAPI guidance).
- Adventure — Dynamic Replacements / Placeholders (TagResolver, Placeholder.unparsed/component).
- MiniPlaceholders / plugin docs showing papi:... usage and plugins that add MiniMessage PAPI support.
PAPI path strips component styling — use proper MiniMessage-PlaceholderAPI integration.
PlainTextComponentSerializer removes all formatting. When PAPI is enabled, MiniMessage styling is lost and cannot be recovered.
The suggested fix of serializing to MiniMessage then passing to PlaceholderAPI (as plain string) has limitations: PlaceholderAPI processes percent-style placeholders as strings and may not properly preserve MiniMessage tag structure. Per Adventure's best practices, use one of these approaches:
- Custom TagResolver (recommended): Create a TagResolver that resolves
<papi:placeholder_name>tags by calling PlaceholderAPI and returning Components, preserving formatting throughout. - MiniPlaceholders plugin: Provides native MiniMessage-aware placeholders that maintain formatting.
- Placeholder.unparsed(): If using PlaceholderAPI in a resolver, wrap results with
Placeholder.unparsed()to prevent re-parsing tags.
Example TagResolver approach:
TagResolver papiResolver = TagResolver.resolver("papi", (ctx, arg) -> {
String result = PlaceholderAPI.setPlaceholders(player, "%" + arg.value() + "%");
return Placeholder.unparsed(result);
});
Component text = MiniMessage.miniMessage().deserialize(mmString, papiResolver);Refactor invokePAPI() to use a proper integration pattern rather than string-based serialization.
🤖 Prompt for AI Agents
In `@src/main/java/net/lewmc/essence/core/UtilPlaceholder.java` around lines 46 -
48, The current approach in UtilPlaceholder.invokePAPI serializes Components to
plain text (via PlainTextComponentSerializer) which strips MiniMessage styling;
refactor invokePAPI to integrate PlaceholderAPI via a MiniMessage TagResolver
that calls PlaceholderAPI.setPlaceholders(player, "%name%") and returns
Placeholder.unparsed(result) (or resolves to a Component) so placeholders are
applied during MiniMessage deserialization and styling is preserved; implement a
TagResolver (e.g., "papi") and use
MiniMessage.miniMessage().deserialize(mmString, papiResolver) instead of
serializing to plain text — update any call sites that pass cs/Player to
invokePAPI accordingly.
| public void convertLegacyColors(File file) { | ||
| boolean modified = false; | ||
| List<String> lines = new ArrayList<>(); | ||
|
|
||
| try (Scanner scanner = new Scanner(file)) { | ||
| while (scanner.hasNextLine()) { | ||
| String line = scanner.nextLine(); | ||
| if (line.contains("§")) { | ||
| line = line.replace("§0", "<black>") | ||
| .replace("§1", "<dark_blue>") | ||
| .replace("§2", "<dark_green>") | ||
| .replace("§3", "<dark_aqua>") | ||
| .replace("§4", "<dark_red>") | ||
| .replace("§5", "<dark_purple>") | ||
| .replace("§6", "<gold>") | ||
| .replace("§7", "<gray>") | ||
| .replace("§8", "<dark_gray>") | ||
| .replace("§9", "<blue>") | ||
| .replace("§a", "<green>") | ||
| .replace("§b", "<aqua>") | ||
| .replace("§c", "<red>") | ||
| .replace("§d", "<light_purple>") | ||
| .replace("§e", "<yellow>") | ||
| .replace("§f", "<white>") | ||
| .replace("§l", "<bold>") | ||
| .replace("§m", "<strikethrough>") | ||
| .replace("§n", "<underlined>") | ||
| .replace("§o", "<italic>") | ||
| .replace("§r", "<reset>"); | ||
| modified = true; | ||
| } | ||
| lines.add(line); | ||
| } | ||
| } catch (FileNotFoundException e) { | ||
| return; | ||
| } | ||
|
|
||
| if (modified) { | ||
| this.log.info("Converting legacy color codes in config.yml to MiniMessage..."); | ||
| try (PrintWriter writer = new PrintWriter(file)) { | ||
| for (String line : lines) { | ||
| writer.println(line); | ||
| } | ||
| } catch (IOException e) { | ||
| this.log.warn("Failed to save converted config.yml: " + e.getMessage()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and verify the target file exists
fd "UtilUpdate.java" --type fRepository: LewMC/Essence
Length of output: 109
🏁 Script executed:
# Check if the file contains the code at the specified lines
if [ -f "src/main/java/net/lewmc/essence/core/UtilUpdate.java" ]; then
wc -l "src/main/java/net/lewmc/essence/core/UtilUpdate.java"
echo "---"
sed -n '459,505p' "src/main/java/net/lewmc/essence/core/UtilUpdate.java"
fiRepository: LewMC/Essence
Length of output: 2227
🌐 Web query:
Java Scanner constructor charset parameter UTF-8 support
💡 Result:
Yes — Scanner supports specifying UTF-8. You can pass "UTF-8" to the String-based constructors (e.g. Scanner(InputStream, String) or Scanner(File, String)) — they throw IllegalArgumentException if the name isn’t known — and newer JDKs also provide overloads that accept a java.nio.charset.Charset (so you can use StandardCharsets.UTF_8). UTF-8 is one of Java’s required standard charsets. [1][2][3]
Sources:
- Scanner constructors (InputStream/File overloads; String and Charset variants). [1][2]
- Java standard charsets (includes UTF-8). [3]
🌐 Web query:
Java PrintWriter constructor charset parameter support
💡 Result:
Short answer: Yes — PrintWriter has constructors that accept a Charset (and ones that accept a charset name String). The Charset overloads were added in Java 10 (e.g. PrintWriter(OutputStream, boolean, Charset), PrintWriter(File, Charset), PrintWriter(String, Charset)). [1][2]
References:
- Oracle Java SE PrintWriter (JDK 11) — constructor list and "Since: 10". [1]
- Oracle Java SE PrintWriter (JDK 18/25 docs) showing PrintWriter(..., Charset) constructors. [2]
Use explicit UTF-8 charset when reading and writing files.
Scanner and PrintWriter default to platform encodings, which can corrupt config files containing non-ASCII characters like the section symbol (§) being processed here. Add charset parameter to both constructors: Scanner(file, "UTF-8") and PrintWriter(file, "UTF-8").
✅ Safer charset handling
- try (Scanner scanner = new Scanner(file)) {
+ try (Scanner scanner = new Scanner(file, "UTF-8")) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.contains("§")) {
line = line.replace("§0", "<black>")
.replace("§1", "<dark_blue>")
.replace("§2", "<dark_green>")
.replace("§3", "<dark_aqua>")
.replace("§4", "<dark_red>")
.replace("§5", "<dark_purple>")
.replace("§6", "<gold>")
.replace("§7", "<gray>")
.replace("§8", "<dark_gray>")
.replace("§9", "<blue>")
.replace("§a", "<green>")
.replace("§b", "<aqua>")
.replace("§c", "<red>")
.replace("§d", "<light_purple>")
.replace("§e", "<yellow>")
.replace("§f", "<white>")
.replace("§l", "<bold>")
.replace("§m", "<strikethrough>")
.replace("§n", "<underlined>")
.replace("§o", "<italic>")
.replace("§r", "<reset>");
modified = true;
}
lines.add(line);
}
} catch (FileNotFoundException e) {
return;
}
if (modified) {
this.log.info("Converting legacy color codes in config.yml to MiniMessage...");
- try (PrintWriter writer = new PrintWriter(file)) {
+ try (PrintWriter writer = new PrintWriter(file, "UTF-8")) {
for (String line : lines) {
writer.println(line);
}
} catch (IOException e) {
this.log.warn("Failed to save converted config.yml: " + e.getMessage());
}
}🤖 Prompt for AI Agents
In `@src/main/java/net/lewmc/essence/core/UtilUpdate.java` around lines 459 - 505,
In convertLegacyColors, the Scanner and PrintWriter use platform default
encoding and can corrupt non-ASCII chars like '§'; change the Scanner(file) call
to Scanner(file, "UTF-8") and the PrintWriter(file) call to new
PrintWriter(file, "UTF-8"), and update the surrounding exception handling to
account for UnsupportedEncodingException/IOException where needed (preserve the
existing FileNotFoundException handling and the log messages in this.log.info /
this.log.warn).
| try { | ||
| if ((boolean) this.plugin.config.get("kit.allow-unsafe-enchantments")) { | ||
| itemStack.addUnsafeEnchantment(ench, Integer.parseInt(e[1])); | ||
| } else { | ||
| itemStack.addEnchantment(ench, Integer.parseInt(e[1])); | ||
| } | ||
| } catch (NumberFormatException ex) { | ||
| this.plugin.log.warn("Invalid enchantment level '" + e[1] + "' for enchantment '" + e[0] + "' in kit '" + kit + "'"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against null/invalid config and catch IllegalArgumentException.
Two potential runtime issues:
-
Line 114: If
kit.allow-unsafe-enchantmentsis missing or not a boolean, the cast throwsNullPointerExceptionorClassCastException. -
Line 117:
addEnchantment()throwsIllegalArgumentExceptionif the level is outside the enchantment's valid range (e.g., Sharpness level 100 when unsafe enchantments are disabled). OnlyNumberFormatExceptionis caught.
🛠️ Proposed fix
try {
- if ((boolean) this.plugin.config.get("kit.allow-unsafe-enchantments")) {
+ int level = Integer.parseInt(e[1]);
+ boolean allowUnsafe = Boolean.TRUE.equals(this.plugin.config.get("kit.allow-unsafe-enchantments"));
+ if (allowUnsafe) {
- itemStack.addUnsafeEnchantment(ench, Integer.parseInt(e[1]));
+ itemStack.addUnsafeEnchantment(ench, level);
} else {
- itemStack.addEnchantment(ench, Integer.parseInt(e[1]));
+ itemStack.addEnchantment(ench, level);
}
- } catch (NumberFormatException ex) {
+ } catch (NumberFormatException | IllegalArgumentException ex) {
this.plugin.log.warn("Invalid enchantment level '" + e[1] + "' for enchantment '" + e[0] + "' in kit '" + kit + "'");
}🤖 Prompt for AI Agents
In `@src/main/java/net/lewmc/essence/kit/UtilKit.java` around lines 113 - 121,
Guard against missing/invalid config and catch IllegalArgumentException: replace
the unchecked cast of this.plugin.config.get("kit.allow-unsafe-enchantments") by
a safe boolean check (e.g., Boolean.TRUE.equals(this.plugin.config.get(...)) or
retrieving a default) before choosing between
itemStack.addUnsafeEnchantment(ench, level) and itemStack.addEnchantment(ench,
level); also extend the existing try/catch around NumberFormatException to
additionally catch IllegalArgumentException (and optionally
ClassCastException/NullPointerException if you don't use the safe boolean check)
so invalid enchantment levels or bad config types don't crash when calling
addEnchantment/addUnsafeEnchantment.
|



Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Localization
Chores
✏️ Tip: You can customize this high-level summary in your review settings.