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
Add MiniMessage support
New Crowdin updates
There was a problem hiding this comment.
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.
🧩 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.
🧩 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.
🧩 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.
🧩 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.
🧩 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.
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.
If you: - Have /tptoggle disabled - Attempt to teleport to coordinates - Use /tp <YourName> 0 100 0 instead of /tp 0 100 0 It would fail.
Vanilla /kill doesn't distinguish permissions for self & other.
I suppose it isn't *really* necessary, given the logic of the command, but since Lew added it to TabCompleterSpeed I figured it should actually work.
Fix /tp failure, Add Sat and Exhaus to feedSelf(), Add /kill, Fix "both" not being a recognized type in /speed
|
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/main/java/net/lewmc/essence/stats/CommandSpeed.java (2)
42-64:⚠️ Potential issue | 🟡 MinorRouting logic looks correct but has an inherent ambiguity with player names.
The dispatch logic cleanly separates self vs. other by checking whether
args[1]/args[2]is a speed-mode keyword. However, if a player happens to be named "fly", "walk", or "both", the command will always route tospeedSelfinstead ofspeedOther. This is a very unlikely edge case in practice but worth documenting.
128-164:⚠️ Potential issue | 🔴 CriticalBug: Target player
pis never notified — all messages go to the command sender.In every messaging pair within
speedOther, bothmsg.send(...)andmsg.sendTo(cs, ...)deliver to the command sendercs(sincemsgwas constructed withcs). The target playerpnever receives any notification about their speed being changed.Compare with the correct pattern in
CommandFeed.feedOther(lines 82–87) andCommandFly.flyOtherfrom the relevant snippets:
msg.send(...)→ notifiescs(the sender)msg.sendTo(p, ...)→ notifiesp(the target)The "byother" messages should be sent to
p, and the "other" messages should be sent tocs. Currently both go tocs.🐛 Proposed fix — swap message targets across all 6 pairs
if (args.length == 3 && args[2].equalsIgnoreCase("fly")) { p.setFlySpeed(0.1F); - msg.send("speed", "resetflybyother", new String[]{ cs.getName() }); - msg.sendTo(cs, "speed", "resetflyother", new String[]{ p.getName() }); + msg.sendTo(p, "speed", "resetflybyother", new String[]{ cs.getName() }); + msg.send("speed", "resetflyother", new String[]{ p.getName() }); } else if (args.length == 3 && args[2].equalsIgnoreCase("walk")) { p.setWalkSpeed(0.2F); - msg.send("speed", "resetwalkbyother", new String[]{ cs.getName() }); - msg.sendTo(cs, "speed", "resetwalkother", new String[]{ p.getName() }); + msg.sendTo(p, "speed", "resetwalkbyother", new String[]{ cs.getName() }); + msg.send("speed", "resetwalkother", new String[]{ p.getName() }); } else { p.setWalkSpeed(0.2F); p.setFlySpeed(0.1F); - msg.send("speed", "resetbyother", new String[]{ cs.getName() }); - msg.sendTo(cs, "speed", "resetother", new String[]{ p.getName() }); + msg.sendTo(p, "speed", "resetbyother", new String[]{ cs.getName() }); + msg.send("speed", "resetother", new String[]{ p.getName() }); } } else { float speed = Float.parseFloat(args[0]); @@ .. if (args.length == 3 && args[2].equalsIgnoreCase("fly")) { p.setFlySpeed(speed/10); - msg.send("speed", "setflybyother", new String[]{cs.getName(), args[0]}); - msg.sendTo(cs, "speed", "setflyother", new String[]{args[1],args[0]}); + msg.sendTo(p, "speed", "setflybyother", new String[]{cs.getName(), args[0]}); + msg.send("speed", "setflyother", new String[]{args[1],args[0]}); } else if (args.length == 3 && args[2].equalsIgnoreCase("walk")) { p.setWalkSpeed(speed/10); - msg.send("speed", "setwalkbyother", new String[]{cs.getName(), args[0]}); - msg.sendTo(cs, "speed", "setwalkother", new String[]{args[1],args[0]}); + msg.sendTo(p, "speed", "setwalkbyother", new String[]{cs.getName(), args[0]}); + msg.send("speed", "setwalkother", new String[]{args[1],args[0]}); } else { p.setWalkSpeed(speed/10); p.setFlySpeed(speed/10); - msg.send("speed", "setbyother", new String[]{cs.getName(), args[0]}); - msg.sendTo(cs, "speed", "setother", new String[]{args[1],args[0]}); + msg.sendTo(p, "speed", "setbyother", new String[]{cs.getName(), args[0]}); + msg.send("speed", "setother", new String[]{args[1],args[0]}); }src/main/java/net/lewmc/essence/teleportation/tp/CommandTeleport.java (1)
100-107:⚠️ Potential issue | 🟠 MajorAdd null check before calling
teleportToggleCheckat line 102.When a console sender executes
/tp <selector> <x> <y> <z>, theplayervariable remains null (lines 52–54). SinceisSelfis false for console (line 67), the condition at line 102 passesnullas therequesterargument toteleportToggleCheck(). Inside that method,nullis then passed toUtilPermission, which will throw aNullPointerExceptionwhen the permission check is evaluated.Guard with a null check:
Proposed fix
- if (!isSelf && !tp.teleportToggleCheck(player, t)) { + if (!isSelf && player != null && !tp.teleportToggleCheck(player, t)) {
🤖 Fix all issues with AI agents
In `@src/main/java/net/lewmc/essence/stats/CommandKill.java`:
- Around line 62-66: The killSelf method currently calls p.setHealth(0) before
sending the UtilMessage, which can synchronously fire PlayerDeathEvent and
disrupt messaging; change CommandKill.killSelf so it calls msg.send("kill",
"beenkilled") before p.setHealth(0), matching the order used in killOther and
ensuring the message is delivered prior to any death event handlers.
- Around line 68-75: Update the Javadoc in CommandKill to correct the typo and
clarify parameter roles: change "PermisionHandler" to "PermissionHandler",
update the `@param` for "cs" to indicate it is the command sender (not the
target), and note that the target player is resolved from "args" (leave `@param`
for "args" describing it as the command arguments used to determine the target).
Retain references to the existing parameters perms, cs, msg, and args so the
Javadoc matches the method signature.
In `@src/main/java/net/lewmc/essence/teleportation/tp/CommandTeleport.java`:
- Around line 188-193: The code calls Bukkit.getOfflinePlayer(args[0]) twice
which can trigger duplicate blocking Mojang lookups; capture the OfflinePlayer
once in a local variable (e.g., OfflinePlayer target =
Bukkit.getOfflinePlayer(args[0])) and use that variable for the
hasPlayedBefore() check and to obtain the UUID (UUID uuid =
target.getUniqueId()); update the conditional and subsequent UUID assignment to
reference the new local variable (methods: Bukkit.getOfflinePlayer,
hasPlayedBefore, getUniqueId; surrounding context: CommandTeleport run logic and
flib.getScheduler call).
In `@src/main/resources/language/en-GB.yml`:
- Line 102: The heal help string for the key "heal" currently mistakenly uses
the word "Feed"; update the value of the "heal" entry (the heal YAML key) to
replace "Feed yourself or another." with "Heal yourself or another." so the help
text correctly describes /heal in src/main/resources/language/en-GB.yml.
- Line 77: Update the tp message string (key: tp) to correct the typo
"coordomate" to "coordinate" so the placeholder reads "<name/coordinate>" and
the message becomes "<gold>[Essence] <yellow>/tp <name/coordinate> - Teleport.".
- Line 235: The team home notfound message (key: teamhome.notfound / the
"notfound" entry shown) incorrectly directs users to "/homes"; update the string
to reference "/thomes" so it points to the correct command (e.g., change the
message text to "Use /thomes for a list of homes."). Ensure you edit the same
"notfound" entry under the teamhome section so it matches the regular
home.notfound pattern but with /thomes.
🧹 Nitpick comments (4)
src/main/resources/plugin.yml (1)
420-421: Note:lightning.otheris directly underessence.environment.*— noessence.environment.other.*group exists.The stats module separates self vs. other permissions via
essence.stats.*andessence.stats.other.*, allowing admins to grant self-use without granting power over others. Here,essence.environment.lightning.otheris a direct child ofessence.environment.*, so granting the wildcard automatically includes the "other" permission.This is fine if intentional (environment has no other
.othervariants yet), but worth noting for consistency if more.otherenvironment permissions are added later.Also applies to: 430-433
src/main/resources/language/en-GB.yml (2)
357-362: Minor inconsistency:kill.killed(line 361) is missing a trailing period.Nearly every other message in this file ends with a period (e.g.,
kill.beenkilled,kill.killedby). This one doesn't.📝 Proposed fix
- killed: "<dark_green>[Essence] <green>You killed <1>" + killed: "<dark_green>[Essence] <green>You killed <1>."
491-491: Nit:<usage>is a misleading placeholder name in the usage string for/sudo.The literal text
<usage>in/sudo <player> <usage>reads as a placeholder indicator, but in this MiniMessage context it could also be confused with a tag. Consider using<command>to match the actual intent (the command to force-run).📝 Proposed fix
- usage: "<dark_red>[Essence] <red>Usage: /sudo <player> <usage>" + usage: "<dark_red>[Essence] <red>Usage: /sudo <player> <command>"src/main/java/net/lewmc/essence/stats/CommandSpeed.java (1)
123-180: Consider extracting the repeated fly/walk/both branching pattern.Both
speedSelfandspeedOtherrepeat the same 3-way branch (fly / walk / both) twice each (reset and set). A small helper that takes the target player, mode string, and speed value could consolidate this into a single place and reduce the chance of message-routing bugs like the one above.
| private boolean killSelf(Player p, UtilMessage msg) { | ||
| p.setHealth(0); | ||
| msg.send("kill", "beenkilled"); | ||
| return true; | ||
| } |
There was a problem hiding this comment.
Send the message before killing the player.
p.setHealth(0) fires PlayerDeathEvent synchronously, which can trigger listeners that interfere with messaging (e.g., respawn screens, third-party plugins). In killOther you already message first then kill — this should be consistent.
Proposed fix
private boolean killSelf(Player p, UtilMessage msg) {
- p.setHealth(0);
msg.send("kill", "beenkilled");
+ p.setHealth(0);
return true;
}🤖 Prompt for AI Agents
In `@src/main/java/net/lewmc/essence/stats/CommandKill.java` around lines 62 - 66,
The killSelf method currently calls p.setHealth(0) before sending the
UtilMessage, which can synchronously fire PlayerDeathEvent and disrupt
messaging; change CommandKill.killSelf so it calls msg.send("kill",
"beenkilled") before p.setHealth(0), matching the order used in killOther and
ensuring the message is delivered prior to any death event handlers.
| /** | ||
| * Kills another user. | ||
| * @param perms PermisionHandler - The permission system. | ||
| * @param cs CommandSender - The user to kill. | ||
| * @param msg MessageUtil - The messaging system. | ||
| * @param args String[] - List of command arguments. | ||
| * @return boolean - If the operation was successful | ||
| */ |
There was a problem hiding this comment.
Minor Javadoc inaccuracies.
- Line 70:
PermisionHandler→PermissionHandler(typo). - Line 72:
@param cs CommandSender - The user to kill.—csis the sender, not the target. The target is resolved fromargs.
Proposed fix
/**
* Kills another user.
- * `@param` perms PermisionHandler - The permission system.
- * `@param` cs CommandSender - The user to kill.
+ * `@param` perms PermissionHandler - The permission system.
+ * `@param` cs CommandSender - The user who sent the command.
* `@param` msg MessageUtil - The messaging system.
* `@param` args String[] - List of command arguments.
* `@return` boolean - If the operation was successful
*/🤖 Prompt for AI Agents
In `@src/main/java/net/lewmc/essence/stats/CommandKill.java` around lines 68 - 75,
Update the Javadoc in CommandKill to correct the typo and clarify parameter
roles: change "PermisionHandler" to "PermissionHandler", update the `@param` for
"cs" to indicate it is the command sender (not the target), and note that the
target player is resolved from "args" (leave `@param` for "args" describing it as
the command arguments used to determine the target). Retain references to the
existing parameters perms, cs, msg, and args so the Javadoc matches the method
signature.
| if (!Bukkit.getOfflinePlayer(args[0]).hasPlayedBefore()) { | ||
| flib.getScheduler().runAtEntity((Player) cs, t -> message.send("generic", "playernotfound")); | ||
| return; | ||
| } | ||
|
|
||
| UUID uuid = Bukkit.getOfflinePlayer(args[0]).getUniqueId(); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Redundant Bukkit.getOfflinePlayer(args[0]) call — may trigger duplicate Mojang API lookups.
Bukkit.getOfflinePlayer(String) is deprecated and can perform a blocking web request to resolve the name to a UUID. It's called once at line 188 and again at line 193. Store the result in a local variable.
♻️ Proposed fix
+ org.bukkit.OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(args[0]);
+ if (!offlinePlayer.hasPlayedBefore()) {
- if (!Bukkit.getOfflinePlayer(args[0]).hasPlayedBefore()) {
flib.getScheduler().runAtEntity((Player) cs, t -> message.send("generic", "playernotfound"));
return;
}
- UUID uuid = Bukkit.getOfflinePlayer(args[0]).getUniqueId();
+ UUID uuid = offlinePlayer.getUniqueId();🤖 Prompt for AI Agents
In `@src/main/java/net/lewmc/essence/teleportation/tp/CommandTeleport.java` around
lines 188 - 193, The code calls Bukkit.getOfflinePlayer(args[0]) twice which can
trigger duplicate blocking Mojang lookups; capture the OfflinePlayer once in a
local variable (e.g., OfflinePlayer target = Bukkit.getOfflinePlayer(args[0]))
and use that variable for the hasPlayedBefore() check and to obtain the UUID
(UUID uuid = target.getUniqueId()); update the conditional and subsequent UUID
assignment to reference the new local variable (methods:
Bukkit.getOfflinePlayer, hasPlayedBefore, getUniqueId; surrounding context:
CommandTeleport run logic and flib.getScheduler call).
| gma: "<gold>[Essence] <yellow>/gma - Switch to adventure mode." | ||
| gmsp: "<gold>[Essence] <yellow>/gmsp - Switch to spectator mode." | ||
| teleport: "<gold>[Essence] ---------- Essence Help - Teleport ---------" | ||
| tp: "<gold>[Essence] <yellow>/tp <name/coordomate> - Teleport." |
There was a problem hiding this comment.
Typo: "coordomate" → "coordinate".
📝 Proposed fix
- tp: "<gold>[Essence] <yellow>/tp <name/coordomate> - Teleport."
+ tp: "<gold>[Essence] <yellow>/tp <name/coordinate> - Teleport."🤖 Prompt for AI Agents
In `@src/main/resources/language/en-GB.yml` at line 77, Update the tp message
string (key: tp) to correct the typo "coordomate" to "coordinate" so the
placeholder reads "<name/coordinate>" and the message becomes "<gold>[Essence]
<yellow>/tp <name/coordinate> - Teleport.".
| near: "<gold>[Essence] <yellow>/near - See who's nearby." | ||
| stats: "<gold>[Essence] ----------- Essence Help - Stats -----------" | ||
| feed: "<gold>[Essence] <yellow>/feed [user] - Feed yourself or another." | ||
| heal: "<gold>[Essence] <yellow>/heal [user] - Feed yourself or another." |
There was a problem hiding this comment.
Copy-paste error: heal help text says "Feed" instead of "Heal".
This will display incorrect information to users when they view the help page for /heal.
📝 Proposed fix
- heal: "<gold>[Essence] <yellow>/heal [user] - Feed yourself or another."
+ heal: "<gold>[Essence] <yellow>/heal [user] - Heal yourself or another."📝 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.
| heal: "<gold>[Essence] <yellow>/heal [user] - Feed yourself or another." | |
| heal: "<gold>[Essence] <yellow>/heal [user] - Heal yourself or another." |
🤖 Prompt for AI Agents
In `@src/main/resources/language/en-GB.yml` at line 102, The heal help string for
the key "heal" currently mistakenly uses the word "Feed"; update the value of
the "heal" entry (the heal YAML key) to replace "Feed yourself or another." with
"Heal yourself or another." so the help text correctly describes /heal in
src/main/resources/language/en-GB.yml.
| alreadyexists: "§4[Essence] §cA team home with this name already exists." | ||
| created: "§2[Essence] §aCreated team home '{{1}}'." | ||
| hitlimit: "§4[Essence] §cYou can't create any more team homes." | ||
| notfound: "<dark_red>[Essence] <red>Team home <1> does not exist. Use /homes for a list of homes." |
There was a problem hiding this comment.
Team home notfound message directs users to /homes instead of /thomes.
The teamhome.notfound message tells the user to "Use /homes for a list of homes", but team homes are listed via /thomes. Compare with line 222 where the regular home.notfound correctly references /homes.
📝 Proposed 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."📝 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.
| 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." |
🤖 Prompt for AI Agents
In `@src/main/resources/language/en-GB.yml` at line 235, The team home notfound
message (key: teamhome.notfound / the "notfound" entry shown) incorrectly
directs users to "/homes"; update the string to reference "/thomes" so it points
to the correct command (e.g., change the message text to "Use /thomes for a list
of homes."). Ensure you edit the same "notfound" entry under the teamhome
section so it matches the regular home.notfound pattern but with /thomes.



Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Localization
Chores