diff --git a/src/main/java/net/tfminecraft/magic/command/MagicCommand.java b/src/main/java/net/tfminecraft/magic/command/MagicCommand.java index a1e9586..f6c418f 100644 --- a/src/main/java/net/tfminecraft/magic/command/MagicCommand.java +++ b/src/main/java/net/tfminecraft/magic/command/MagicCommand.java @@ -342,25 +342,12 @@ private static boolean applyResonance( return false; } if ("all".equalsIgnoreCase(elementArg)) { - List skipped = new ArrayList<>(); - int applied = 0; for (ElementDef element : ElementRegistry.getAll()) { double current = session.getResonance(element.getId()); double next = add ? current + amount : amount; - if (isGain(current, next) && !element.isUnlocked(target)) { - skipped.add(element.getId()); - continue; - } session.setResonance(element.getId(), next); - applied++; - } - if (!skipped.isEmpty()) { - sender.sendMessage(Messages.get( - "resonance.admin.locked_skipped", - "player", target.getName(), - "elements", String.join(", ", skipped))); } - return applied > 0; + return true; } ElementDef element = ElementRegistry.getById(elementArg.toLowerCase(Locale.ROOT)); if (element == null) { @@ -369,22 +356,10 @@ private static boolean applyResonance( } double current = session.getResonance(element.getId()); double next = add ? current + amount : amount; - if (isGain(current, next) && !element.isUnlocked(target)) { - sender.sendMessage(Messages.get( - "resonance.admin.locked", - "player", target.getName(), - "permission", element.getPermission(), - "element", element.getId())); - return false; - } session.setResonance(element.getId(), next); return true; } - private static boolean isGain(double current, double next) { - return next > current + 0.0001; - } - private static void persistResonance(Player target, ResonanceSession session) { MagicProfileService profiles = Magic.plugin.getProfileService(); if (profiles != null) { diff --git a/src/main/java/net/tfminecraft/magic/meditation/MeditationCeiling.java b/src/main/java/net/tfminecraft/magic/meditation/MeditationCeiling.java new file mode 100644 index 0000000..5de90dc --- /dev/null +++ b/src/main/java/net/tfminecraft/magic/meditation/MeditationCeiling.java @@ -0,0 +1,24 @@ +package net.tfminecraft.magic.meditation; + +/** + * Meditation cannot raise an element above the aura of that element stored in the circle. + * A character already at or above that total gains nothing; otherwise the hit is cut to the gap. + */ +public final class MeditationCeiling { + + static final double EPSILON = 0.005; + + private MeditationCeiling() {} + + public static double allowed(double current, double circlePower, double elementMax, double offered) { + if (offered <= 0.0 || circlePower <= 0.0) { + return 0.0; + } + double ceiling = Math.min(circlePower, Math.max(0.0, elementMax)); + double headroom = ceiling - current; + if (headroom <= EPSILON) { + return 0.0; + } + return Math.min(offered, headroom); + } +} diff --git a/src/main/java/net/tfminecraft/magic/meditation/MeditationCircle.java b/src/main/java/net/tfminecraft/magic/meditation/MeditationCircle.java index 62e32dc..55d4460 100644 --- a/src/main/java/net/tfminecraft/magic/meditation/MeditationCircle.java +++ b/src/main/java/net/tfminecraft/magic/meditation/MeditationCircle.java @@ -13,6 +13,7 @@ import org.bukkit.block.Block; import org.bukkit.entity.Entity; import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; @@ -24,7 +25,9 @@ import net.tfminecraft.magic.artifact.ArtifactCareStore; import net.tfminecraft.magic.artifact.ArtifactIds; import net.tfminecraft.magic.artifact.ArtifactLore; +import net.tfminecraft.magic.model.ElementDef; import net.tfminecraft.magic.registry.ElementRegistry; +import net.tfminecraft.magic.session.ResonanceSession; public final class MeditationCircle { @@ -93,6 +96,13 @@ public void stampArtifacts(String characterId, long nowMs) { } public MeditationSitYield snapshotYield(long nowMs) { + return snapshotYield(nowMs, null); + } + + /** + * @param extraCharacterId counted as a user when not already active, without writing the item + */ + public MeditationSitYield snapshotYield(long nowMs, String extraCharacterId) { Map sessionCaps = new HashMap<>(); Map users = new HashMap<>(); for (Furniture furniture : artifactPedestals) { @@ -107,6 +117,9 @@ public MeditationSitYield snapshotYield(long nowMs) { continue; } int n = Math.max(1, ArtifactCareStore.activeUserCount(item, nowMs)); + if (countsAsExtraUser(item, extraCharacterId, nowMs)) { + n++; + } Artifact aura = Artifact.fromItem(item); double fill = aura != null ? aura.getFill(def.elementId) : 0.0; double cap = aura != null ? aura.getCap(def.elementId) : 0.0; @@ -142,6 +155,66 @@ public Map getPowerByElement() { return powerByElement; } + /** Stored fill of one element across every artifact in the circle. */ + public double elementPower(String elementId) { + if (elementId == null || elementId.isBlank()) { + return 0.0; + } + Double exact = powerByElement.get(elementId); + if (exact != null) { + return exact; + } + for (Map.Entry entry : powerByElement.entrySet()) { + if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(elementId)) { + return entry.getValue() != null ? entry.getValue() : 0.0; + } + } + return 0.0; + } + + /** + * True when this sit can still raise at least one artifact's element. + * The character's resonance in that element must sit below the circle's stored total. + */ + public boolean canGainResonance(Player player, ResonanceSession session, MeditationSitYield yield) { + if (session == null || yield == null) { + return false; + } + for (Furniture furniture : artifactPedestals) { + String artifactId = artifactIdOn(furniture); + MeditationCache.ArtifactDef def = artifactByFurniture.get(furniture.getEntityId()); + if (artifactId == null || def == null || def.elementId == null) { + continue; + } + double cap = yield.sessionCap(artifactId); + if (cap <= MeditationCeiling.EPSILON) { + continue; + } + ElementDef element = ElementRegistry.getById(def.elementId); + if (element != null && !element.isUnlocked(player)) { + continue; + } + double elementMax = element != null ? element.getMaxResonance() : Double.MAX_VALUE; + double allowed = MeditationCeiling.allowed( + session.getResonance(def.elementId), + elementPower(def.elementId), + elementMax, + cap); + if (allowed > MeditationCeiling.EPSILON) { + return true; + } + } + return false; + } + + private static boolean countsAsExtraUser(ItemStack item, String characterId, long nowMs) { + if (item == null || characterId == null || characterId.isBlank()) { + return false; + } + Long until = ArtifactCareStore.readUsers(item).get(characterId.trim()); + return until == null || until <= nowMs; + } + public double getTotalPower() { return totalPower; } diff --git a/src/main/java/net/tfminecraft/magic/meditation/MeditationService.java b/src/main/java/net/tfminecraft/magic/meditation/MeditationService.java index 57896b1..5aca060 100644 --- a/src/main/java/net/tfminecraft/magic/meditation/MeditationService.java +++ b/src/main/java/net/tfminecraft/magic/meditation/MeditationService.java @@ -165,12 +165,14 @@ private void tryStart(Player player) { } long nowMs = System.currentTimeMillis(); String characterId = profileService != null ? profileService.characterId(player) : null; - MeditationSitYield yield = circle.stampAndSnapshot(characterId, nowMs); - if (!yield.hasAnyCap()) { + ResonanceSession resonance = sessionManager.getOrCreate(player); + MeditationSitYield preview = circle.snapshotYield(nowMs, characterId); + if (!preview.hasAnyCap() || !circle.canGainResonance(player, resonance, preview)) { notifySit(player, Messages.get("meditation.nothing")); return; } - sessions.put(player.getUniqueId(), new MeditationSession(player, circle, yield)); + MeditationSitYield yield = circle.stampAndSnapshot(characterId, nowMs); + sessions.put(player.getUniqueId(), new MeditationSession(player, circle, yield, resonance)); } private void notifySit(Player player, String message) { diff --git a/src/main/java/net/tfminecraft/magic/meditation/MeditationSession.java b/src/main/java/net/tfminecraft/magic/meditation/MeditationSession.java index 834c3fa..b7695d1 100644 --- a/src/main/java/net/tfminecraft/magic/meditation/MeditationSession.java +++ b/src/main/java/net/tfminecraft/magic/meditation/MeditationSession.java @@ -18,7 +18,9 @@ import net.tfminecraft.interactiblefurniture.furniture.Furniture; import net.tfminecraft.magic.GuiCache; import net.tfminecraft.magic.attunement.AttunementCaptureService; +import net.tfminecraft.magic.model.ElementDef; import net.tfminecraft.magic.profile.MagicProfileService; +import net.tfminecraft.magic.registry.ElementRegistry; import net.tfminecraft.magic.session.ResonanceSession; import net.tfminecraft.magic.util.MagicText; import net.tfminecraft.magic.util.PedestalFx; @@ -47,9 +49,14 @@ public enum Phase { private final Map attunedByArtifact = new HashMap<>(); private boolean windingDown; - public MeditationSession(Player player, MeditationCircle circle, MeditationSitYield yield) { + public MeditationSession( + Player player, + MeditationCircle circle, + MeditationSitYield yield, + ResonanceSession resonance) { this.circle = circle; this.yield = yield != null ? yield : MeditationSitYield.empty(); + closeCappedElements(player, resonance); spawnStarter(player); } @@ -463,6 +470,9 @@ private void applyRewards( ResonanceSession session, MeditationOrb orb, MagicProfileService profiles) { + if (session == null) { + return; + } ThreadLocalRandom random = ThreadLocalRandom.current(); double eqDelta; if (orb.isFlow()) { @@ -475,28 +485,103 @@ private void applyRewards( UUID sourceId = orb.getSourceId(); MeditationCache.ArtifactDef artifact = sourceId == null ? null : circle.artifactFor(sourceId); if (artifact == null) { + save(player, profiles); return; } Furniture furniture = furniture(sourceId); String artifactId = circle.artifactIdOn(furniture); if (artifactId == null) { + save(player, profiles); return; } double cap = yield.sessionCap(artifactId); double credit = attunedByArtifact.getOrDefault(artifactId, 0.0); int n = yield.users(artifactId); double remaining = Math.max(0.0, cap - credit); - double gain = Math.min(MeditationCache.resonancePerHit / n, remaining); + double offered = Math.min(MeditationCache.resonancePerHit / n, remaining); + double gain = MeditationCeiling.allowed( + session.getResonance(artifact.elementId), + circle.elementPower(artifact.elementId), + elementMax(artifact.elementId), + offered); if (gain <= 0) { + closeElement(artifact.elementId); + save(player, profiles); return; } attunedByArtifact.put(artifactId, credit + gain); AttunementCaptureService.credit(player, session, artifactId, artifact.elementId, gain); + if (MeditationCeiling.allowed( + session.getResonance(artifact.elementId), + circle.elementPower(artifact.elementId), + elementMax(artifact.elementId), + remaining) <= 0) { + closeElement(artifact.elementId); + } + save(player, profiles); + } + + private static void save(Player player, MagicProfileService profiles) { if (profiles != null) { profiles.savePlayer(player); } } + private void closeCappedElements(Player player, ResonanceSession session) { + if (session == null) { + return; + } + for (Furniture furniture : circle.getArtifactPedestals()) { + MeditationCache.ArtifactDef def = circle.artifactFor(furniture.getEntityId()); + if (def == null || def.elementId == null) { + continue; + } + String artifactId = circle.artifactIdOn(furniture); + if (artifactId == null) { + continue; + } + double cap = yield.sessionCap(artifactId); + if (cap <= MeditationCeiling.EPSILON) { + continue; + } + ElementDef element = ElementRegistry.getById(def.elementId); + if (element != null && !element.isUnlocked(player)) { + attunedByArtifact.put(artifactId, cap); + continue; + } + double allowed = MeditationCeiling.allowed( + session.getResonance(def.elementId), + circle.elementPower(def.elementId), + elementMax(def.elementId), + cap); + if (allowed <= MeditationCeiling.EPSILON) { + attunedByArtifact.put(artifactId, cap); + } + } + } + + private void closeElement(String elementId) { + if (elementId == null) { + return; + } + for (Furniture furniture : circle.getArtifactPedestals()) { + MeditationCache.ArtifactDef def = circle.artifactFor(furniture.getEntityId()); + if (def == null || def.elementId == null || !elementId.equalsIgnoreCase(def.elementId)) { + continue; + } + String artifactId = circle.artifactIdOn(furniture); + if (artifactId == null) { + continue; + } + attunedByArtifact.put(artifactId, yield.sessionCap(artifactId)); + } + } + + private static double elementMax(String elementId) { + ElementDef element = ElementRegistry.getById(elementId); + return element != null ? element.getMaxResonance() : Double.MAX_VALUE; + } + private Furniture furniture(UUID furnitureId) { if (furnitureId == null) { return null;