diff --git a/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java b/src/main/java/net/tfminecraft/magic/artifact/ArtifactCareStore.java index d8fe2c1..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 { @@ -93,6 +94,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,13 +109,18 @@ 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); } } 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) { @@ -145,6 +152,25 @@ 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(); + } + + /** + * 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/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..d57a255 100644 --- a/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java +++ b/src/test/java/net/tfminecraft/magic/artifact/ArtifactCareTest.java @@ -115,6 +115,62 @@ 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 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();