Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ public interface ExilePearlApi extends Plugin, PearlAccess, PearlLogger, PlayerP
*/
PearlConfig getPearlConfig();

/**
* Gets the effective per-tick decay amount for a pearl, accounting for external modifiers
* (e.g. vote-streak multipliers from other plugins) applied via {@link com.devotedmc.ExilePearl.event.PearlDecayEvent}.
* <p>
* This fires a preview decay event and reads back the resulting amount, so lore and the real
* decay loop agree on how fast a pearl actually decays.
*
* @param pearl The pearl to evaluate
* @return The decay amount that would be applied per decay tick, or 0 if decay is cancelled
*/
int getEffectivePearlDecayAmount(ExilePearl pearl);

/**
* Gets the pearl lore provider
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,11 @@ private List<String> generateLoreInternal(ExilePearl pearl, int health, boolean

lore.add(parse("<a>Health: <n>%s/%s", health, config.getPearlHealthMaxValue()));
String unit = config.getPearlHealthDecayHumanInterval();
int effectiveDecayAmount = ExilePearlPlugin.getApi().getEffectivePearlDecayAmount(pearl);
int decayPerHumanInterval = PearlDecayMath.decayPerHumanInterval(
config.getPearlHealthDecayHumanIntervalMin(),
config.getPearlHealthDecayIntervalMin(),
config.getPearlHealthDecayAmount());
effectiveDecayAmount);
int intervalsRemaining = PearlDecayMath.intervalsRemaining(health, decayPerHumanInterval);
if (intervalsRemaining > 0 && pearl.isActive()) {
lore.add(parse("<a>Time remaining: <n>%d %s", intervalsRemaining, unit));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.devotedmc.ExilePearl.*;
import com.devotedmc.ExilePearl.command.*;
import com.devotedmc.ExilePearl.config.PearlConfig;
import com.devotedmc.ExilePearl.event.PearlDecayEvent;
import com.devotedmc.ExilePearl.holder.PearlHolder;
import com.devotedmc.ExilePearl.listener.BanStickListener;
import com.devotedmc.ExilePearl.listener.BastionListener;
Expand Down Expand Up @@ -259,6 +260,13 @@ public PearlConfig getPearlConfig() {
return pearlConfig;
}

@Override
public int getEffectivePearlDecayAmount(ExilePearl pearl) {
PearlDecayEvent preview = new PearlDecayEvent(pearl, pearlConfig.getPearlHealthDecayAmount(), true);
getServer().getPluginManager().callEvent(preview);
return preview.isCancelled() ? 0 : preview.getDamageAmount();
}

/**
* Gets the plugin storage provider
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class PearlDecayEvent extends Event implements Cancellable {
private boolean cancelled;
private ExilePearl pearl;
private int amount;
private final boolean preview;

/**
* Creates a new PearlDecayEvent instance.
Expand All @@ -30,9 +31,30 @@ public class PearlDecayEvent extends Event implements Cancellable {
* @param amount Health amount the pearl health is reduced by
*/
public PearlDecayEvent(ExilePearl pearl, int amount) {
this(pearl, amount, false);
}

/**
* Creates a new PearlDecayEvent instance.
*
* @param pearl Pearl to decay
* @param amount Health amount the pearl health is reduced by
* @param preview true when fired only to compute the effective decay amount (e.g. for lore),
* without any health actually being removed. Listeners that apply side effects
* (logging, metrics) should skip preview events; modifiers may still adjust the amount.
*/
public PearlDecayEvent(ExilePearl pearl, int amount, boolean preview) {
Preconditions.checkNotNull(pearl, "pearl");
this.pearl = pearl;
this.amount = amount;
this.preview = preview;
}

/**
* @return true when this event is a dry-run to compute the effective decay amount, not a real decay
*/
public boolean isPreview() {
return preview;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class CoreLoreGeneratorTest {
private PearlConfig config;
private ExilePearl pearl;
private CoreLoreGenerator generator;
private ExilePearlApi api;
private MockedStatic<ExilePearlPlugin> pluginStatic;

@BeforeEach
Expand All @@ -47,8 +48,9 @@ void setUp() {
Mockito.when(pearl.isActive()).thenReturn(true);
Mockito.when(pearl.getLongTimeMultiplier()).thenReturn(1.0);

ExilePearlApi api = Mockito.mock(ExilePearlApi.class);
api = Mockito.mock(ExilePearlApi.class);
Mockito.when(api.isBanStickEnabled()).thenReturn(false);
Mockito.when(api.getEffectivePearlDecayAmount(Mockito.any())).thenReturn(1); // base, no streak multiplier
pluginStatic = Mockito.mockStatic(ExilePearlPlugin.class);
pluginStatic.when(ExilePearlPlugin::getApi).thenReturn(api);

Expand Down Expand Up @@ -86,11 +88,22 @@ void generateLore_omitsTimeRemainingWhenHealthIsZero() {

@Test
void generateLore_omitsTimeRemainingWhenDecayDisabled() {
Mockito.when(config.getPearlHealthDecayAmount()).thenReturn(0);
Mockito.when(api.getEffectivePearlDecayAmount(Mockito.any())).thenReturn(0);
List<String> lore = generator.generateLore(pearl);
Assertions.assertNull(findLine(lore, "Time remaining:"), "Decay disabled should hide time remaining; got: " + lore);
}

@Test
void generateLore_timeRemainingReflectsStreakMultiplier() {
// EssenceGlue doubles decay for a streaked player: 24/day -> 48/day, so 240 health = 5 days, not 10
Mockito.when(api.getEffectivePearlDecayAmount(Mockito.any())).thenReturn(2);
List<String> lore = generator.generateLore(pearl);
String timeRemaining = findLine(lore, "Time remaining:");
Assertions.assertNotNull(timeRemaining);
Assertions.assertTrue(timeRemaining.contains("5"),
"Doubled decay should halve time remaining (240/48 = 5 days), got: " + timeRemaining);
}

@Test
void generateLore_timeRemainingRoundsUpForPartialInterval() {
Mockito.when(pearl.getHealth()).thenReturn(241); // 24*10 + 1
Expand Down