From f90e5d0e5530cc17bd9fbbf22a7a81986fcb96dd Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 6 Oct 2026 10:27:17 +0000 Subject: [PATCH 1/2] fix: stop empty artifacts from muffling in storage Chests were raising muffle on artifacts with no stored aura, so a later fill arrived already muffled. Co-authored-by: Cursor --- .../magic/artifact/ArtifactCareStore.java | 8 ++++- src/main/resources/config.yml | 2 +- .../magic/artifact/ArtifactCareTest.java | 29 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java b/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java index d8fe2c1..c5c607e 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java +++ b/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java @@ -93,6 +93,7 @@ public static boolean apply(ItemStack stack, boolean housed, long nowMs, Persist if (stack == null || stack.getType().isAir() || ChargeIds.isCharge(stack)) { return false; } + boolean storesAura = storesAura(stack); ItemMeta meta = metaOf(stack); PersistentDataContainer root = meta.getPersistentDataContainer(); long last = readLong(root, ArtifactKeys.careLastTick()); @@ -107,7 +108,7 @@ public static boolean apply(ItemStack stack, boolean housed, long nowMs, Persist if (ArtifactCareCache.muffledEnabled && last > 0L) { if (housed) { muffle = clamp01(muffle - ArtifactCareCache.muffledRecoverPerHour * dtHours); - } else { + } else if (storesAura) { muffle = clamp01(muffle + ArtifactCareCache.muffledOffPerHour * dtHours); } } @@ -145,6 +146,11 @@ public static void stampUser(ItemStack stack, String characterId, long nowMs) { stack.setItemMeta(meta); } + private static boolean storesAura(ItemStack stack) { + Artifact artifact = Artifact.fromItem(stack); + return artifact != null && artifact.hasStoredAura(); + } + private static void decayFill(ItemStack stack, double dtHours) { if (dtHours <= 0.0) { return; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 073ed4a..15f3b1b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -98,7 +98,7 @@ attunement: # Muffle 0-1. Scales with tick.seconds_per_hour (3600 = real time). # Off: 1/24 per hour (full muffle in 24h). Recover: 1/168 per hour (clear in 7d). # Accepted: player inv, pedestal, artifact_display, item frames. - # Chests and other containers raise muffle. No chat. + # Chests and other containers raise muffle only while aura is stored. No chat. muffled: recover_per_hour: 0.00595238095 off_per_hour: 0.04166666667 diff --git a/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java b/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java index 0dd46fc..9d35b5b 100644 --- a/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java +++ b/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java @@ -115,6 +115,35 @@ void housedArtifactsRecoverAndUnhousedArtifactsMuffleOnlyAfterFirstTick() { assertEquals(0, ArtifactCareStore.usableMax(10, 2)); } + @Test + void unhousedArtifactsMuffleOnlyWhileAuraIsStored() { + var empty = artifact(); + var emptied = Artifact.fromItem(empty); + emptied.setFill("fire", 0); + emptied.persistPdc(empty); + try (var lore = mockStatic(ArtifactLore.class)) { + assertTrue(ArtifactCareStore.tick(empty, false, 1000)); + assertTrue(ArtifactCareStore.tick(empty, false, 11000)); + assertEquals(0, ArtifactCareStore.readMuffle(empty)); + assertEquals(11000, ArtifactCareStore.readLastTickMs(empty)); + + emptied.setFill("fire", 5); + emptied.persistPdc(empty); + assertTrue(ArtifactCareStore.tick(empty, false, 12000)); + assertEquals(.1, ArtifactCareStore.readMuffle(empty), 1e-10); + + var drained = artifact(); + assertTrue(ArtifactCareStore.setMuffle(drained, .4, 1000)); + var drainedAura = Artifact.fromItem(drained); + drainedAura.setFill("fire", 0); + drainedAura.persistPdc(drained); + assertTrue(ArtifactCareStore.tick(drained, false, 5000)); + assertEquals(.4, ArtifactCareStore.readMuffle(drained), 1e-10); + assertTrue(ArtifactCareStore.tick(drained, true, 7000)); + assertEquals(0, ArtifactCareStore.readMuffle(drained), 1e-10); + } + } + @Test void setMuffleClampsTheValueAndResetsTheCareClock() { var item = artifact(); From 5efa31d540a3e9a583682e8cd989479b3a40f6bd Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 6 Oct 2026 10:41:36 +0000 Subject: [PATCH 2/2] fix: keep empty artifact care clocks current in chests Chest scans skip items whose lore does not change, so an empty artifact kept its old clock and muffled for that empty time once it was filled. Co-authored-by: Cursor --- .../magic/artifact/ArtifactCareStore.java | 22 ++++++++++++++- .../magic/artifact/ArtifactCareTest.java | 27 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java b/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java index c5c607e..1a5ab32 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java +++ b/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java @@ -14,6 +14,7 @@ import net.tfminecraft.magic.attunement.ArtifactCareCache; import net.tfminecraft.magic.model.ElementDef; import net.tfminecraft.magic.registry.ElementRegistry; +import net.tfminecraft.magic.util.MagicNumbers; public final class ArtifactCareStore { @@ -114,7 +115,12 @@ public static boolean apply(ItemStack stack, boolean housed, long nowMs, Persist } boolean force = persist == Persist.ALWAYS; boolean visible = ArtifactLore.careVisibleWouldChange(stack, muffle, dtHours); - if (!force && !visible) { + boolean anchorEmptyClock = ArtifactCareCache.muffledEnabled + && !housed + && !storesAura + && last > 0L + && emptyStorageClockIsStale(dtHours); + if (!force && !visible && !anchorEmptyClock) { return false; } if (ArtifactCareCache.muffledEnabled) { @@ -151,6 +157,20 @@ private static boolean storesAura(ItemStack stack) { return artifact != null && artifact.hasStoredAura(); } + /** + * Chest scans persist only when lore would change. An empty artifact has no lore + * change, so its clock would stay put and a later fill would muffle for the empty time. + * Anchor once the gap would have shown on the muffle percent, and leave shorter gaps + * alone so an open chest is not rewritten on every scan. + */ + private static boolean emptyStorageClockIsStale(double dtHours) { + double gained = ArtifactCareCache.muffledOffPerHour * dtHours; + if (gained <= 0.0) { + return false; + } + return !MagicNumbers.format(gained * 100.0).equals(MagicNumbers.format(0)); + } + private static void decayFill(ItemStack stack, double dtHours) { if (dtHours <= 0.0) { return; diff --git a/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java b/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java index 9d35b5b..d57a255 100644 --- a/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java +++ b/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java @@ -144,6 +144,33 @@ void unhousedArtifactsMuffleOnlyWhileAuraIsStored() { } } + @Test + void emptyUnhousedArtifactsAnchorTheClockWhenAChestScanWouldSkipThem() { + var item = artifact(); + var aura = Artifact.fromItem(item); + aura.setFill("fire", 0); + aura.persistPdc(item); + try (var lore = mockStatic(ArtifactLore.class)) { + assertTrue(ArtifactCareStore.tick(item, false, 1000)); + assertTrue(ArtifactCareStore.apply(item, false, 11000, ArtifactCareStore.Persist.IF_VISIBLE)); + assertEquals(0, ArtifactCareStore.readMuffle(item)); + assertEquals(11000, ArtifactCareStore.readLastTickMs(item)); + lore.verify(() -> ArtifactLore.apply(any()), never()); + + assertFalse(ArtifactCareStore.apply(item, false, 11000, ArtifactCareStore.Persist.IF_VISIBLE)); + assertEquals(11000, ArtifactCareStore.readLastTickMs(item)); + ArtifactCareCache.muffledOffPerHour = .00001; + assertFalse(ArtifactCareStore.apply(item, false, 12000, ArtifactCareStore.Persist.IF_VISIBLE)); + assertEquals(11000, ArtifactCareStore.readLastTickMs(item)); + ArtifactCareCache.muffledOffPerHour = .1; + + aura.setFill("fire", 5); + aura.persistPdc(item); + assertTrue(ArtifactCareStore.tick(item, false, 12000)); + assertEquals(.1, ArtifactCareStore.readMuffle(item), 1e-10); + } + } + @Test void setMuffleClampsTheValueAndResetsTheCareClock() { var item = artifact();