From 66a80c6d8662fd5e8369a6023de1fc4d93a674c6 Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Thu, 24 Sep 2026 17:16:29 +0000 Subject: [PATCH 1/3] fix: keep unchargeable elements off items Schools that cannot take aura were still stamped onto artifacts and charges, and a colour list was printed as brackets in front of the name. Co-authored-by: Cursor --- .../magic/artifact/ArtifactLore.java | 18 +-- .../create/ArtifactCreateSession.java | 8 ++ .../artifact/generate/ArtifactRoller.java | 7 + .../artifact/shrine/ShrineChargeService.java | 5 +- .../net/tfminecraft/magic/charge/Charge.java | 9 +- .../tfminecraft/magic/charge/ChargeLore.java | 8 +- .../tfminecraft/magic/gear/WeaponLore.java | 9 +- .../magic/gear/WeaponRequirement.java | 4 + .../tfminecraft/magic/model/ElementDef.java | 53 ++++++- .../magic/model/ElementVisibility.java | 132 ++++++++++++++++++ .../net/tfminecraft/magic/util/MagicText.java | 33 +++++ src/main/resources/elements/elements.yml | 1 + 12 files changed, 267 insertions(+), 20 deletions(-) create mode 100644 src/main/java/net/tfminecraft/magic/model/ElementVisibility.java diff --git a/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java b/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java index b30257b..baf5e45 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java +++ b/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java @@ -17,6 +17,7 @@ import net.tfminecraft.magic.artifact.sacrifice.SacrificeImprintStore; import net.tfminecraft.magic.artifact.sacrifice.SacrificeRegistry; import net.tfminecraft.magic.model.ElementDef; +import net.tfminecraft.magic.model.ElementVisibility; import net.tfminecraft.magic.registry.ElementRegistry; import net.tfminecraft.magic.util.MagicNumbers; import net.tfminecraft.magic.util.MagicText; @@ -37,6 +38,7 @@ public static void apply(ItemStack stack) { if (artifact == null) { return; } + ElementVisibility.stripArtifact(artifact, primaryId(stack, artifact)); ItemMeta meta = stack.getItemMeta(); if (meta == null) { return; @@ -54,13 +56,12 @@ public static void apply(ItemStack stack) { double cap = artifact.getCap(element.getId()); double fill = artifact.getFill(element.getId()); double shown = ArtifactCareStore.usableFill(fill, cap, muffle); - block.add(MagicText.format( - element.getColor() - + element.getName() - + " {color:label_muted}" - + formatAmount(shown) - + " / " - + formatAmount(cap))); + block.add(MagicText.elementName(element) + + MagicText.format( + " {color:label_muted}" + + formatAmount(shown) + + " / " + + formatAmount(cap))); } int attuneOffset = block.size(); List attuneLines = buildAttuneLines(stack, artifact); @@ -455,7 +456,8 @@ private static List orderedAuraElements(ItemStack stack, Artifact ar String primary = primaryId(stack, artifact); List listed = new ArrayList<>(); for (ElementDef element : ElementRegistry.getAll()) { - if (artifact.getCap(element.getId()) > 0) { + if (artifact.getCap(element.getId()) > 0 + && ElementVisibility.shownOnArtifact(element.getId(), primary)) { listed.add(element); } } diff --git a/src/main/java/net/tfminecraft/magic/artifact/create/ArtifactCreateSession.java b/src/main/java/net/tfminecraft/magic/artifact/create/ArtifactCreateSession.java index 4f2bbea..bca2c03 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/create/ArtifactCreateSession.java +++ b/src/main/java/net/tfminecraft/magic/artifact/create/ArtifactCreateSession.java @@ -18,6 +18,7 @@ import net.tfminecraft.magic.artifact.generate.ArtifactAuraSlot; import net.tfminecraft.magic.artifact.generate.ArtifactItemBuilder; import net.tfminecraft.magic.artifact.generate.ArtifactRoll; +import net.tfminecraft.magic.model.ElementVisibility; import net.tfminecraft.magic.util.MagicNumbers; public final class ArtifactCreateSession { @@ -121,6 +122,10 @@ public boolean cycleElement(String elementId) { } ElementRole role = roleOf(elementId); if (role == ElementRole.OFF) { + if (primaryId != null && !primaryId.isBlank() + && !ElementVisibility.shownOnArtifact(elementId, primaryId)) { + return false; + } if (!ArtifactAffinityRegistry.compatibleWith(caps.keySet(), elementId)) { return false; } @@ -181,6 +186,9 @@ public ArtifactRoll toRoll() { if (primaryId.equals(entry.getKey()) || entry.getValue() == null || entry.getValue() <= 0) { continue; } + if (!ElementVisibility.shownOnArtifact(entry.getKey(), primaryId)) { + continue; + } slots.add(new ArtifactAuraSlot(entry.getKey(), entry.getValue())); } return ArtifactRoll.ok(primaryId, rarityId, slots); diff --git a/src/main/java/net/tfminecraft/magic/artifact/generate/ArtifactRoller.java b/src/main/java/net/tfminecraft/magic/artifact/generate/ArtifactRoller.java index cea2f50..d1ae531 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/generate/ArtifactRoller.java +++ b/src/main/java/net/tfminecraft/magic/artifact/generate/ArtifactRoller.java @@ -15,6 +15,7 @@ import net.tfminecraft.magic.artifact.config.ArtifactTypeRegistry; import net.tfminecraft.magic.artifact.config.CapRange; import net.tfminecraft.magic.artifact.path.ArtifactPathSpec; +import net.tfminecraft.magic.model.ElementVisibility; import net.tfminecraft.magic.util.MagicNumbers; public final class ArtifactRoller { @@ -68,6 +69,9 @@ public ArtifactRoll roll(String primaryId, String rarityId) { if (companion == null || !companion.isEnabled() || !companion.hasRarity(rarity.getId())) { continue; } + if (!ElementVisibility.shownOnArtifact(companion.getElementId(), type.getElementId())) { + continue; + } pool.add(companion); } @@ -139,6 +143,9 @@ private ArtifactRoll rollLockedPrimary(ArtifactPathSpec spec) { if (companion == null || !companion.isEnabled() || !companion.hasRarity(rarity.getId())) { continue; } + if (!ElementVisibility.shownOnArtifact(companion.getElementId(), type.getElementId())) { + continue; + } if (!ArtifactAffinityRegistry.compatibleWith(slotIds(slots), companion.getElementId())) { continue; } diff --git a/src/main/java/net/tfminecraft/magic/artifact/shrine/ShrineChargeService.java b/src/main/java/net/tfminecraft/magic/artifact/shrine/ShrineChargeService.java index c23a983..d2cbe5a 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/shrine/ShrineChargeService.java +++ b/src/main/java/net/tfminecraft/magic/artifact/shrine/ShrineChargeService.java @@ -141,9 +141,8 @@ public static AdminStart startAdmin(Player player, String elementId, double amou continue; } sawArtifact = true; - // Admin fill names the element outright, so a charge can take on one the - // shrine would never score. This is the only way to reach the elements - // that have scenery charging disabled. + // Admin fill names the element outright. Schools players cannot charge + // are refused here, the same as a shrine imprint. if (artifact instanceof Charge charge && charge.getCap(id) <= 0 && charge.imprintElement(item, id)) { charge.write(item); diff --git a/src/main/java/net/tfminecraft/magic/charge/Charge.java b/src/main/java/net/tfminecraft/magic/charge/Charge.java index 6ffdcf1..737346d 100644 --- a/src/main/java/net/tfminecraft/magic/charge/Charge.java +++ b/src/main/java/net/tfminecraft/magic/charge/Charge.java @@ -14,6 +14,7 @@ import net.tfminecraft.magic.artifact.aura.VesselKind; import net.tfminecraft.magic.artifact.shrine.ShrineScore; import net.tfminecraft.magic.artifact.shrine.ShrineElementScore; +import net.tfminecraft.magic.model.ElementVisibility; /** * Crafting consumable that gathers aura at a shrine and is spent at the mage station. @@ -87,7 +88,7 @@ public boolean isBlank() { } /** - * Takes on every element the shrine scores, capped at this charge's tier. + * Takes on every playable element the shrine scores, capped at this charge's tier. * * @return true when at least one element was imprinted */ @@ -107,6 +108,9 @@ public boolean imprint(ItemStack stack, ShrineScore score) { if (elementScore == null || elementScore.getMaxAura() <= 0) { continue; } + if (!ElementVisibility.shownOnCharge(entry.getKey())) { + continue; + } setCap(entry.getKey(), cap); any = true; if (elementScore.getMaxAura() > bestScore) { @@ -130,6 +134,9 @@ public boolean imprintElement(ItemStack stack, String elementId) { if (stack == null || elementId == null || elementId.isBlank() || cap <= 0) { return false; } + if (!ElementVisibility.shownOnCharge(elementId)) { + return false; + } setCap(elementId, cap); stampTier(stack); if (primary.isBlank()) { diff --git a/src/main/java/net/tfminecraft/magic/charge/ChargeLore.java b/src/main/java/net/tfminecraft/magic/charge/ChargeLore.java index a5b674d..27f97d5 100644 --- a/src/main/java/net/tfminecraft/magic/charge/ChargeLore.java +++ b/src/main/java/net/tfminecraft/magic/charge/ChargeLore.java @@ -10,6 +10,7 @@ import net.tfminecraft.magic.Cache; import net.tfminecraft.magic.model.ElementDef; +import net.tfminecraft.magic.model.ElementVisibility; import net.tfminecraft.magic.registry.ElementRegistry; import net.tfminecraft.magic.util.MagicText; @@ -38,6 +39,7 @@ public static void apply(ItemStack stack) { if (charge == null) { return; } + ElementVisibility.stripCharge(charge); ItemMeta meta = stack.getItemMeta(); if (meta == null) { return; @@ -54,8 +56,8 @@ public static void apply(ItemStack stack) { String numeral = TierBands.numeralFor(element.getId(), charge.getFill(element.getId())); // An imprinted element below the first band still lists, so the player can // see which elements the charge took on before it has gathered anything. - block.add(MagicText.format(element.getColor() + element.getName() - + " {color:label_muted}" + (numeral.isEmpty() ? "-" : numeral))); + block.add(MagicText.elementName(element) + + MagicText.format(" {color:label_muted}" + (numeral.isEmpty() ? "-" : numeral))); } } block.set(0, BEGIN + block.get(0)); @@ -71,7 +73,7 @@ private static List orderedElements(Charge charge) { String primary = charge.primaryElementId(); List listed = new ArrayList<>(); for (ElementDef element : ElementRegistry.getAll()) { - if (charge.getCap(element.getId()) > 0) { + if (charge.getCap(element.getId()) > 0 && ElementVisibility.shownOnCharge(element.getId())) { listed.add(element); } } diff --git a/src/main/java/net/tfminecraft/magic/gear/WeaponLore.java b/src/main/java/net/tfminecraft/magic/gear/WeaponLore.java index 58b9e04..5a7d4ad 100644 --- a/src/main/java/net/tfminecraft/magic/gear/WeaponLore.java +++ b/src/main/java/net/tfminecraft/magic/gear/WeaponLore.java @@ -10,6 +10,7 @@ import net.tfminecraft.magic.charge.TierBands; import net.tfminecraft.magic.model.ElementDef; +import net.tfminecraft.magic.model.ElementVisibility; import net.tfminecraft.magic.registry.ElementRegistry; import net.tfminecraft.magic.util.MagicText; @@ -31,6 +32,7 @@ public static void apply(ItemStack stack) { return; } WeaponRequirement requirement = WeaponRequirement.fromItem(stack); + ElementVisibility.stripCharge(requirement.aura()); ItemMeta meta = stack.getItemMeta(); if (meta == null) { return; @@ -44,13 +46,16 @@ public static void apply(ItemStack stack) { } else { block.add(MagicText.format("{color:label_muted}Resonance")); for (ElementDef element : ElementRegistry.getAll()) { + if (!ElementVisibility.shownOnCharge(element.getId())) { + continue; + } double fill = requirement.aura().getFill(element.getId()); if (requirement.aura().getCap(element.getId()) <= 0 && fill <= 0) { continue; } String numeral = TierBands.numeralFor(element.getId(), fill); - block.add(MagicText.format(element.getColor() + element.getName() - + " {color:label_muted}" + (numeral.isEmpty() ? "-" : numeral))); + block.add(MagicText.elementName(element) + + MagicText.format(" {color:label_muted}" + (numeral.isEmpty() ? "-" : numeral))); } } int rift = WeaponRift.get(stack); diff --git a/src/main/java/net/tfminecraft/magic/gear/WeaponRequirement.java b/src/main/java/net/tfminecraft/magic/gear/WeaponRequirement.java index feb40bf..47e16e7 100644 --- a/src/main/java/net/tfminecraft/magic/gear/WeaponRequirement.java +++ b/src/main/java/net/tfminecraft/magic/gear/WeaponRequirement.java @@ -9,6 +9,7 @@ import net.tfminecraft.magic.artifact.aura.AuraData; import net.tfminecraft.magic.charge.Charge; import net.tfminecraft.magic.charge.TierBands; +import net.tfminecraft.magic.model.ElementVisibility; public final class WeaponRequirement { @@ -64,6 +65,9 @@ public void mergeAmounts(Map amounts) { continue; } String elementId = entry.getKey(); + if (!ElementVisibility.shownOnCharge(elementId)) { + continue; + } double next = Math.max(aura.getFill(elementId), incoming); if (aura.getCap(elementId) < next) { aura.setCap(elementId, next); diff --git a/src/main/java/net/tfminecraft/magic/model/ElementDef.java b/src/main/java/net/tfminecraft/magic/model/ElementDef.java index 92cee91..6183a0a 100644 --- a/src/main/java/net/tfminecraft/magic/model/ElementDef.java +++ b/src/main/java/net/tfminecraft/magic/model/ElementDef.java @@ -1,5 +1,11 @@ package net.tfminecraft.magic.model; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + import org.bukkit.configuration.ConfigurationSection; import net.tfminecraft.magic.Cache; @@ -7,10 +13,12 @@ public final class ElementDef { + private static final Pattern HEX = Pattern.compile("#?[0-9a-fA-F]{6}"); + private final String id; private final String name; private final String icon; - private final String color; + private final List colors; private final int slot; private final double maxResonance; private final Double decayPerHourOverride; @@ -21,7 +29,7 @@ public ElementDef(String id, ConfigurationSection config) { this.id = id; this.name = config.getString("name", id); this.icon = config.getString("icon", "v.BARRIER"); - this.color = config.getString("color", "#ffffff"); + this.colors = readColors(config); this.slot = config.getInt("slot", -1); this.maxResonance = Math.max(1.0, config.getDouble("max_resonance", 100.0)); this.decayPerHourOverride = config.contains("decay_per_hour") @@ -47,8 +55,14 @@ public String getIcon() { return icon; } + /** First colour stop, {@code #rrggbb}. Used for particles and single-colour text. */ public String getColor() { - return color; + return colors.get(0); + } + + /** Colour stops in config order. One stop is a solid colour. Several form a gradient. */ + public List getColors() { + return colors; } public double getMaxResonance() { @@ -70,4 +84,37 @@ public int getSlot() { public KeyframeCurve getResonanceCurve() { return resonance; } + + /** + * {@code color} is either one {@code #rrggbb} or a list of stops. Reading the list + * with {@code getString} would stringify it to {@code [#rrggbb]} and print the brackets. + */ + private static List readColors(ConfigurationSection config) { + List parsed = new ArrayList<>(); + if (config.isList("color")) { + for (String entry : config.getStringList("color")) { + addColor(parsed, entry); + } + } else { + addColor(parsed, config.getString("color")); + } + if (parsed.isEmpty()) { + parsed.add("#ffffff"); + } + return Collections.unmodifiableList(parsed); + } + + private static void addColor(List parsed, String raw) { + if (raw == null) { + return; + } + String token = raw.trim(); + if (!HEX.matcher(token).matches()) { + return; + } + if (!token.startsWith("#")) { + token = "#" + token; + } + parsed.add(token.toLowerCase(Locale.ROOT)); + } } diff --git a/src/main/java/net/tfminecraft/magic/model/ElementVisibility.java b/src/main/java/net/tfminecraft/magic/model/ElementVisibility.java new file mode 100644 index 0000000..27c6476 --- /dev/null +++ b/src/main/java/net/tfminecraft/magic/model/ElementVisibility.java @@ -0,0 +1,132 @@ +package net.tfminecraft.magic.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import net.tfminecraft.magic.artifact.aura.AuraData; +import net.tfminecraft.magic.artifact.aura.AuraVessel; +import net.tfminecraft.magic.artifact.config.ArtifactTypeDef; +import net.tfminecraft.magic.artifact.config.ArtifactTypeRegistry; +import net.tfminecraft.magic.artifact.sacrifice.SacrificeElementDef; +import net.tfminecraft.magic.artifact.sacrifice.SacrificeRegistry; +import net.tfminecraft.magic.artifact.shrine.ShrineChargeService; +import net.tfminecraft.magic.artifact.shrine.ShrineElementDef; +import net.tfminecraft.magic.artifact.shrine.ShrineRegistry; + +/** + * Which elements may be written onto artifacts and enchanted charges. + * + *

An element is playable when players can actually put aura into it: scenery + * charging is on, or its sacrifice rite is enabled. Schools that are switched off + * (Shadowmancy, and the other inert schools) stay in the resonance menu but are + * not stamped onto items. An artifact still names its own primary, so a staff-made + * item of that school keeps its line. Disabled artifact types never appear. + */ +public final class ElementVisibility { + + private ElementVisibility() {} + + public static boolean typeEnabled(String elementId) { + ArtifactTypeDef type = typeOf(elementId); + return type == null || type.isEnabled(); + } + + /** + * True when a player can fill this element at a shrine or through an enabled rite. + * Missing shrine data counts as playable so a failed config load does not blank items. + */ + public static boolean playerCanCharge(String elementId) { + if (elementId == null || elementId.isBlank() || !typeEnabled(elementId)) { + return false; + } + ShrineElementDef shrine = ShrineRegistry.getById(elementId); + boolean sceneryOpen = shrine == null || shrine.isSceneryCharge(); + if (sceneryOpen && !ShrineChargeService.blocksVanillaCharge(elementId)) { + return true; + } + if (!SacrificeRegistry.isEnabled()) { + return false; + } + SacrificeElementDef rite = SacrificeRegistry.getById(elementId); + return rite != null && rite.isEnabled(); + } + + /** Charges only list elements a player can fill. */ + public static boolean shownOnCharge(String elementId) { + return playerCanCharge(elementId); + } + + /** + * Artifact lines list playable elements, plus the artifact's own primary even when + * that school cannot be filled from a shrine. + */ + public static boolean shownOnArtifact(String elementId, String primaryId) { + if (!typeEnabled(elementId)) { + return false; + } + if (playerCanCharge(elementId)) { + return true; + } + return primaryId != null && !primaryId.isBlank() && primaryId.equalsIgnoreCase(elementId); + } + + public static void stripCharge(AuraVessel vessel) { + if (vessel == null) { + return; + } + for (String elementId : copyIds(vessel.getCappedElementIds())) { + if (!shownOnCharge(elementId)) { + vessel.setCap(elementId, 0); + } + } + } + + public static void stripCharge(AuraData data) { + if (data == null) { + return; + } + for (String elementId : copyIds(data.getCappedElementIds())) { + if (!shownOnCharge(elementId)) { + data.setCap(elementId, 0); + } + } + } + + public static void stripArtifact(AuraVessel vessel, String primaryId) { + if (vessel == null) { + return; + } + for (String elementId : copyIds(vessel.getCappedElementIds())) { + if (!shownOnArtifact(elementId, primaryId)) { + vessel.setCap(elementId, 0); + } + } + } + + private static List copyIds(Set ids) { + return new ArrayList<>(ids); + } + + private static ArtifactTypeDef typeOf(String elementId) { + if (elementId == null || elementId.isBlank()) { + return null; + } + ArtifactTypeDef exact = ArtifactTypeRegistry.getById(elementId); + if (exact != null) { + return exact; + } + String id = elementId.trim().toLowerCase(Locale.ROOT); + exact = ArtifactTypeRegistry.getById(id); + if (exact != null) { + return exact; + } + for (ArtifactTypeDef type : ArtifactTypeRegistry.getAll()) { + if (type.getElementId() != null && type.getElementId().equalsIgnoreCase(id)) { + return type; + } + } + return null; + } +} diff --git a/src/main/java/net/tfminecraft/magic/util/MagicText.java b/src/main/java/net/tfminecraft/magic/util/MagicText.java index aa48d47..d2a6e03 100644 --- a/src/main/java/net/tfminecraft/magic/util/MagicText.java +++ b/src/main/java/net/tfminecraft/magic/util/MagicText.java @@ -1,12 +1,15 @@ package net.tfminecraft.magic.util; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bukkit.Color; +import net.md_5.bungee.api.ChatColor; import net.tfminecraft.tlibs.objects.api.subapi.StringFormatter; import net.tfminecraft.magic.GuiCache; +import net.tfminecraft.magic.model.ElementDef; /** * Formats all player-facing strings from config via TLibs {@link StringFormatter#formatHex}. @@ -30,6 +33,36 @@ public static String text(String colorKey, String plain) { return format(color(colorKey) + (plain != null ? plain : "")); } + /** + * Element name in its configured colour. A single stop is solid. Several stops + * run as a gradient. The name's own hex prefix is not repeated. + */ + public static String elementName(ElementDef element) { + if (element == null) { + return ""; + } + String plain = visibleName(element); + List colors = element.getColors(); + if (colors.size() > 1) { + return StringFormatter.applyColourGradient(plain, colors); + } + return format(element.getColor() + plain); + } + + private static String visibleName(ElementDef element) { + String raw = element.getName(); + if (raw == null || raw.isBlank()) { + raw = element.getId(); + } + @SuppressWarnings("deprecation") + String stripped = ChatColor.stripColor(raw); + if (stripped == null) { + stripped = raw; + } + stripped = stripped.replaceAll("(?i)#[0-9a-f]{6}", "").trim(); + return stripped.isEmpty() ? element.getId() : stripped; + } + /** * Piecewise RGB: 0 dark red, 0.5 yellow, 1 light green. Returns {@code #rrggbb}. */ diff --git a/src/main/resources/elements/elements.yml b/src/main/resources/elements/elements.yml index 04a0706..2ab77f4 100644 --- a/src/main/resources/elements/elements.yml +++ b/src/main/resources/elements/elements.yml @@ -1,4 +1,5 @@ # Magic element catalog. Each element has a fixed GUI slot (0-53). +# color is one "#rrggbb", or a list of hex stops for a gradient across the name. # Row 2: Spirit (20), Arcanum (22), Illusion (24) # Row 3 (third from bottom): Cerrith, Seithr, Oseni, Mitlan # Row 4 (second from bottom): Shadowmancy, Necromancy, Bloodmagic From b9ac6b7cbd40bc6f268e751c405b63f6a59b49c4 Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Thu, 24 Sep 2026 17:26:50 +0000 Subject: [PATCH 2/3] fix: hide unchargeable elements without erasing stored aura Lore omits schools players cannot fill, and the artifact editor drops those caps when the primary changes, while existing aura stays in the item. Co-authored-by: Cursor --- .../magic/artifact/ArtifactLore.java | 14 +++++-- .../create/ArtifactCreateSession.java | 8 ++++ .../tfminecraft/magic/charge/ChargeLore.java | 1 - .../tfminecraft/magic/gear/WeaponLore.java | 1 - .../magic/model/ElementVisibility.java | 42 ------------------- 5 files changed, 19 insertions(+), 47 deletions(-) diff --git a/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java b/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java index baf5e45..23d4d32 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java +++ b/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java @@ -38,7 +38,6 @@ public static void apply(ItemStack stack) { if (artifact == null) { return; } - ElementVisibility.stripArtifact(artifact, primaryId(stack, artifact)); ItemMeta meta = stack.getItemMeta(); if (meta == null) { return; @@ -271,7 +270,7 @@ private static ElementDef matchElement(String before) { ElementDef best = null; int bestLen = 0; for (ElementDef element : ElementRegistry.getAll()) { - String name = plain(element.getName()).toLowerCase(Locale.ROOT); + String name = loreName(element); if (name.isEmpty() || !lower.startsWith(name)) { continue; } @@ -425,7 +424,7 @@ private static boolean isFillLine(String line) { } String before = p.substring(0, slash).trim().toLowerCase(Locale.ROOT); for (ElementDef element : ElementRegistry.getAll()) { - String name = plain(element.getName()).toLowerCase(Locale.ROOT); + String name = loreName(element); if (!name.isEmpty() && before.startsWith(name)) { return true; } @@ -433,6 +432,15 @@ private static boolean isFillLine(String line) { return false; } + /** Same label {@link MagicText#elementName} writes, so a blank name still matches its id. */ + private static String loreName(ElementDef element) { + String name = plain(element.getName()).toLowerCase(Locale.ROOT); + if (!name.isEmpty()) { + return name; + } + return element.getId() == null ? "" : element.getId().toLowerCase(Locale.ROOT); + } + // Keep the existing legacy text representation, formatting, and exact-string comparisons. @SuppressWarnings("deprecation") private static String plain(String line) { diff --git a/src/main/java/net/tfminecraft/magic/artifact/create/ArtifactCreateSession.java b/src/main/java/net/tfminecraft/magic/artifact/create/ArtifactCreateSession.java index bca2c03..673449d 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/create/ArtifactCreateSession.java +++ b/src/main/java/net/tfminecraft/magic/artifact/create/ArtifactCreateSession.java @@ -248,6 +248,14 @@ private void assignPrimary(String next) { primaryId = next; if (next != null) { clampCapsToRarity(); + if (next.equals(primaryId)) { + for (String elementId : new ArrayList<>(caps.keySet())) { + if (!elementId.equals(next) + && !ElementVisibility.shownOnArtifact(elementId, next)) { + setCap(elementId, 0); + } + } + } } clearPreviewLock(); } diff --git a/src/main/java/net/tfminecraft/magic/charge/ChargeLore.java b/src/main/java/net/tfminecraft/magic/charge/ChargeLore.java index 27f97d5..1e9ef9a 100644 --- a/src/main/java/net/tfminecraft/magic/charge/ChargeLore.java +++ b/src/main/java/net/tfminecraft/magic/charge/ChargeLore.java @@ -39,7 +39,6 @@ public static void apply(ItemStack stack) { if (charge == null) { return; } - ElementVisibility.stripCharge(charge); ItemMeta meta = stack.getItemMeta(); if (meta == null) { return; diff --git a/src/main/java/net/tfminecraft/magic/gear/WeaponLore.java b/src/main/java/net/tfminecraft/magic/gear/WeaponLore.java index 5a7d4ad..c9cf5aa 100644 --- a/src/main/java/net/tfminecraft/magic/gear/WeaponLore.java +++ b/src/main/java/net/tfminecraft/magic/gear/WeaponLore.java @@ -32,7 +32,6 @@ public static void apply(ItemStack stack) { return; } WeaponRequirement requirement = WeaponRequirement.fromItem(stack); - ElementVisibility.stripCharge(requirement.aura()); ItemMeta meta = stack.getItemMeta(); if (meta == null) { return; diff --git a/src/main/java/net/tfminecraft/magic/model/ElementVisibility.java b/src/main/java/net/tfminecraft/magic/model/ElementVisibility.java index 27c6476..c3caad7 100644 --- a/src/main/java/net/tfminecraft/magic/model/ElementVisibility.java +++ b/src/main/java/net/tfminecraft/magic/model/ElementVisibility.java @@ -1,12 +1,7 @@ package net.tfminecraft.magic.model; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; -import java.util.Set; -import net.tfminecraft.magic.artifact.aura.AuraData; -import net.tfminecraft.magic.artifact.aura.AuraVessel; import net.tfminecraft.magic.artifact.config.ArtifactTypeDef; import net.tfminecraft.magic.artifact.config.ArtifactTypeRegistry; import net.tfminecraft.magic.artifact.sacrifice.SacrificeElementDef; @@ -72,43 +67,6 @@ public static boolean shownOnArtifact(String elementId, String primaryId) { return primaryId != null && !primaryId.isBlank() && primaryId.equalsIgnoreCase(elementId); } - public static void stripCharge(AuraVessel vessel) { - if (vessel == null) { - return; - } - for (String elementId : copyIds(vessel.getCappedElementIds())) { - if (!shownOnCharge(elementId)) { - vessel.setCap(elementId, 0); - } - } - } - - public static void stripCharge(AuraData data) { - if (data == null) { - return; - } - for (String elementId : copyIds(data.getCappedElementIds())) { - if (!shownOnCharge(elementId)) { - data.setCap(elementId, 0); - } - } - } - - public static void stripArtifact(AuraVessel vessel, String primaryId) { - if (vessel == null) { - return; - } - for (String elementId : copyIds(vessel.getCappedElementIds())) { - if (!shownOnArtifact(elementId, primaryId)) { - vessel.setCap(elementId, 0); - } - } - } - - private static List copyIds(Set ids) { - return new ArrayList<>(ids); - } - private static ArtifactTypeDef typeOf(String elementId) { if (elementId == null || elementId.isBlank()) { return null; From 19a8f35b538a22211e1054c28685641eb0eda794 Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Thu, 24 Sep 2026 17:40:22 +0000 Subject: [PATCH 3/3] fix: match aura lore labels only when the amount follows the name A short element id must not be treated as the start of an unrelated lore line. Co-authored-by: Cursor --- .../magic/artifact/ArtifactLore.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java b/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java index 23d4d32..5a115e1 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java +++ b/src/main/java/net/tfminecraft/magic/artifact/ArtifactLore.java @@ -271,7 +271,7 @@ private static ElementDef matchElement(String before) { int bestLen = 0; for (ElementDef element : ElementRegistry.getAll()) { String name = loreName(element); - if (name.isEmpty() || !lower.startsWith(name)) { + if (!auraLabel(lower, name)) { continue; } if (name.length() > bestLen) { @@ -425,13 +425,40 @@ private static boolean isFillLine(String line) { String before = p.substring(0, slash).trim().toLowerCase(Locale.ROOT); for (ElementDef element : ElementRegistry.getAll()) { String name = loreName(element); - if (!name.isEmpty() && before.startsWith(name)) { + if (auraLabel(before, name)) { return true; } } return false; } + /** + * True when {@code before} is the element label followed only by its amount. + * A short id such as {@code fire} must not match {@code fire resistance 5}. + */ + private static boolean auraLabel(String before, String name) { + if (before == null || name == null || name.isEmpty() || !before.startsWith(name)) { + return false; + } + String rest = before.substring(name.length()).trim(); + if (rest.isEmpty()) { + return false; + } + boolean digit = false; + for (int i = 0; i < rest.length(); i++) { + char c = rest.charAt(i); + if (Character.isDigit(c)) { + digit = true; + continue; + } + if (c == '.' && digit) { + continue; + } + return false; + } + return digit; + } + /** Same label {@link MagicText#elementName} writes, so a blank name still matches its id. */ private static String loreName(ElementDef element) { String name = plain(element.getName()).toLowerCase(Locale.ROOT);