Skip to content
Merged
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
64 changes: 36 additions & 28 deletions docs/api/CustomBoostEffect.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,66 +10,74 @@
```java
public interface CustomBoostEffect {
/**
* Called when the boost is activated for a player.
* @param player The player receiving the boost.
* @param boost The boost definition.
* Apply the effect to the player.
* @param player the player to apply the effect to
* @param amplifier effect strength as configured in the boost
*/
void onEnable(Player player, BoostDefinition boost);
void apply(org.bukkit.entity.Player player, int amplifier);

/**
* Called when the boost is deactivated for a player.
* @param player The player losing the boost.
* @param boost The boost definition.
* Remove the effect from the player.
* @param player the player to remove the effect from
*/
void onDisable(Player player, BoostDefinition boost);
void remove(org.bukkit.entity.Player player);

/**
* @return The unique effect type key (e.g., "mycustom").
* @return unique effect name used in configuration
*/
String getType();
String getName();

/**
* @return cooldown duration in seconds (default 0 = no cooldown)
*/
default int getCooldownSeconds() {
return 0;
}
}
```

## Methods

### `void onEnable(Player player, BoostDefinition boost)`
Called when the boost is activated for a player.
- **player**: The player receiving the boost.
- **boost**: The boost definition being applied.
### `void apply(Player player, int amplifier)`
Called when the boost's custom effect should be applied to a player.
- **player**: The player receiving the effect.
- **amplifier**: The configured amplifier for this effect in the boost.

**Usage Example:**
```java
@Override
public void onEnable(Player player, BoostDefinition boost) {
public void apply(Player player, int amplifier) {
player.sendMessage("Your custom boost is now active!");
}
```

---

### `void onDisable(Player player, BoostDefinition boost)`
Called when the boost is deactivated for a player.
- **player**: The player losing the boost.
- **boost**: The boost definition being removed.
### `void remove(Player player)`
Called when the boost's custom effect should be removed from a player.
- **player**: The player losing the effect.

**Usage Example:**
```java
@Override
public void onDisable(Player player, BoostDefinition boost) {
public void remove(Player player) {
player.sendMessage("Your custom boost has ended.");
}
```

---

### `String getType()`
Returns the unique effect type key. This is used in boost configuration to identify the effect.
- **Returns**: The effect type key (e.g., "mycustom").
### `String getName()`
Returns the unique effect name used in boost configuration to identify the custom effect.
- **Returns**: The effect name (e.g., "mycustom").

### `int getCooldownSeconds()`
Returns the cooldown duration (in seconds) associated with this custom effect. When `settings.cooldown-per-effect` is enabled, this value is used to set per-effect cooldown timestamps after activation. Returning `0` means no cooldown.

**Usage Example:**
```java
@Override
public String getType() {
public String getName() {
return "mycustom";
}
```
Expand All @@ -86,27 +94,27 @@ import org.bukkit.entity.Player;

public class MyCustomEffect implements CustomBoostEffect {
@Override
public void onEnable(Player player, BoostDefinition boost) {
public void apply(Player player, int amplifier) {
player.sendMessage("You received a special custom effect!");
// Add your custom logic here
}

@Override
public void onDisable(Player player, BoostDefinition boost) {
public void remove(Player player) {
player.sendMessage("Your special custom effect has ended.");
// Cleanup logic here
}

@Override
public String getType() {
public String getName() {
return "mycustom";
}
}

// Register the effect in your plugin's onEnable method:
@Override
public void onEnable() {
EzBoostAPI.registerCustomEffect(new MyCustomEffect());
EzBoostAPI.registerCustomBoostEffect(new MyCustomEffect());
}
```

Expand Down
51 changes: 14 additions & 37 deletions docs/api/EzBoostAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,68 +15,45 @@ public final class EzBoostAPI {

## Methods

### `static void registerCustomEffect(CustomBoostEffect effect)`
Registers a custom boost effect implementation. This allows other plugins to add new effect types that will be executed when a boost is activated or deactivated.
- **effect**: The custom effect implementation to register. Must implement `CustomBoostEffect`.
### `static boolean registerCustomBoostEffect(CustomBoostEffect effect)`
Register a custom boost effect implementation so other plugins can provide new effect types.
- **effect**: The `CustomBoostEffect` implementation to register.
- **Returns**: `true` if registration succeeded; `false` if the API isn't initialized or the effect name is already registered.

**Usage Example:**
```java
EzBoostAPI.registerCustomEffect(new MyCustomEffect());
boolean ok = EzBoostAPI.registerCustomBoostEffect(new MyCustomEffect());
```

---

### `static boolean isBoostActive(Player player)`
Checks if a player currently has an active boost.
- **player**: The player to check.
- **Returns**: `true` if the player has an active boost, `false` otherwise.
### `static Map<String, CustomBoostEffect> getCustomBoostEffects()`
Returns an unmodifiable map of all registered custom boost effects keyed by their normalized names.

**Usage Example:**
```java
if (EzBoostAPI.isBoostActive(player)) {
// Player has a boost
}
```

---

### `static BoostDefinition getActiveBoost(Player player)`
Gets the active `BoostDefinition` for a player, or `null` if none.
- **player**: The player to query.
- **Returns**: The active `BoostDefinition`, or `null` if no boost is active.

**Usage Example:**
```java
BoostDefinition boost = EzBoostAPI.getActiveBoost(player);
if (boost != null) {
// Do something with the boost
}
Map<String, CustomBoostEffect> effects = EzBoostAPI.getCustomBoostEffects();
```

---

### `static void clearBoost(Player player)`
Removes any active boost from a player.
- **player**: The player whose boost should be cleared.
### `static BoostManager getBoostManager()`
Returns the internal `BoostManager` instance. This exposes advanced integration points but should be used carefully.

**Usage Example:**
```java
EzBoostAPI.clearBoost(player);
BoostManager manager = EzBoostAPI.getBoostManager();
```

---

### `static void setBoost(Player player, BoostDefinition definition, int durationSeconds)`
Forces a boost to start for a player with a specific definition and duration.
- **player**: The player to receive the boost.
- **definition**: The `BoostDefinition` to apply.
- **durationSeconds**: The duration of the boost in seconds.
### `static long getCooldownRemainingForEffect(Player player, com.skyblockexp.ezboost.boost.BoostEffect effect)`
Convenience helper that returns remaining cooldown (in seconds) for a specific `BoostEffect` on a player. Returns `0` if no cooldown is present or the API is not initialized.

**Usage Example:**
```java
EzBoostAPI.setBoost(player, myBoostDefinition, 120);
long remaining = EzBoostAPI.getCooldownRemainingForEffect(player, myBoostEffect);
```

## Notes
- All methods are static for ease of use.
- Designed for open-source extensibility and professional integrations.
Expand Down
6 changes: 6 additions & 0 deletions docs/boosts.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ speed:
- **Permission**: Controls who can use each boost.
- **Enabled**: Set to `false` to hide a boost from the GUI.

### Per-effect cooldowns and custom effects

- EzBoost supports optional per-effect cooldown tracking. When `settings.cooldown-per-effect` is enabled (see `settings.yml`), each effect in a boost may have its own cooldown tracked independently. This is useful when a boost contains multiple effects and you want separate cooldowns for each.
- For built-in potion effects the boost uses the boost-level `cooldown` unless a custom effect provides its own cooldown value.
- For custom effects, implement `CustomBoostEffect.getCooldownSeconds()` to return the desired cooldown (default 0 = no cooldown). See the API docs for details.

For more details, see the [EzBoost documentation](https://github.com/ez-plugins/ezboost).
11 changes: 11 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ settings:
cooldown-per-boost-type: true # Separate cooldowns for each boost type
```

The plugin now supports optional per-effect cooldowns. When enabled, each effect (including custom effects) can define its own cooldown duration. To enable per-effect cooldown tracking, set:

```yaml
settings:
cooldown-per-effect: false # When true, cooldowns are tracked per-effect instead of per-boost
```

Behavior:
- `cooldown-per-boost-type: true` (legacy) keeps the existing per-boost-type cooldown behavior.
- `cooldown-per-effect: true` enables per-effect cooldown checks. Lookups will fall back to the boost-level/global cooldown if no effect-specific cooldown is present.

---

## limits.yml
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/skyblockexp/ezboost/api/EzBoostAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,13 @@ public static Map<String, CustomBoostEffect> getCustomBoostEffects() {
public static BoostManager getBoostManager() {
return boostManager;
}

/**
* Convenience API: get cooldown remaining (seconds) for a specific effect on a player.
* Returns 0 if API not initialized or inputs invalid.
*/
public static long getCooldownRemainingForEffect(org.bukkit.entity.Player player, com.skyblockexp.ezboost.boost.BoostEffect effect) {
if (boostManager == null || player == null || effect == null) return 0L;
return boostManager.getCooldownRemainingForEffect(player, effect);
}
}
110 changes: 102 additions & 8 deletions src/main/java/com/skyblockexp/ezboost/boost/BoostManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,40 @@ public long getCooldownRemaining(Player player, String boostKey) {
if (state == null) {
return 0L;
}
long remaining = state.cooldownEnd(cooldownKey(boostKey)) - System.currentTimeMillis();
long now = System.currentTimeMillis();
// If per-effect cooldowns are enabled, compute the maximum remaining across effects for the effective boost
if (config.settings().cooldownPerEffect()) {
Optional<BoostDefinition> def = getBoost(boostKey, player);
if (def.isPresent()) {
long maxRem = 0L;
for (BoostEffect effect : def.get().effects()) {
long end = state.cooldownEnd(effectCooldownKey(effect));
long rem = Math.max(0L, (end - now) / 1000L);
if (rem > maxRem) maxRem = rem;
}
// fallback to boost-level key as well
long boostRem = Math.max(0L, (state.cooldownEnd(cooldownKey(boostKey)) - now) / 1000L);
return Math.max(maxRem, boostRem);
}
}
long remaining = state.cooldownEnd(cooldownKey(boostKey)) - now;
return Math.max(0L, remaining / 1000L);
}

/**
* Returns remaining cooldown in seconds for a specific effect for the given player.
* Returns 0 if no cooldown is present.
*/
public long getCooldownRemainingForEffect(Player player, BoostEffect effect) {
if (player == null || effect == null) return 0L;
BoostState state = states.get(player.getUniqueId());
if (state == null) return 0L;
long now = System.currentTimeMillis();
long end = state.cooldownEnd(effectCooldownKey(effect));
long rem = Math.max(0L, (end - now) / 1000L);
return rem;
}


public boolean activate(Player player, String boostKey, ActivationSource source) {
Optional<BoostDefinition> definition = getBoost(boostKey, player);
Expand Down Expand Up @@ -210,11 +240,45 @@ public boolean activate(Player player, BoostDefinition boost, ActivationSource s
player.sendMessage(messages.message("boost-replaced"));
}
if (!player.hasPermission("ezboost.cooldown.bypass")) {
long cooldownEnd = state.cooldownEnd(cooldownKey(effective.key()));
if (cooldownEnd > now) {
long remaining = Math.max(0L, (cooldownEnd - now) / 1000L);
player.sendMessage(messages.message("boost-cooldown", Placeholder.parsed("time", String.valueOf(remaining))));
return false;
if (config.settings().cooldownPerEffect()) {
for (BoostEffect effect : effective.effects()) {
String key = effectCooldownKey(effect);
long end = state.cooldownEnd(key);
if (end > now) {
long remaining = Math.max(0L, (end - now) / 1000L);
// Determine a human-friendly effect name
String effectName;
if (effect.type() != null) {
effectName = effect.type().getName();
} else {
CustomBoostEffect custom = customEffects.get(effect.customName().toLowerCase(Locale.ROOT));
effectName = custom != null ? custom.getName() : effect.customName();
}
try {
player.sendMessage(messages.message("boost-effect-cooldown",
Placeholder.parsed("time", String.valueOf(remaining)),
Placeholder.parsed("effect", effectName)));
} catch (Exception ex) {
// Fallback to generic message if key missing
player.sendMessage(messages.message("boost-cooldown", Placeholder.parsed("time", String.valueOf(remaining))));
}
return false;
}
}
// also check boost-level fallback
long cooldownEnd = state.cooldownEnd(cooldownKey(effective.key()));
if (cooldownEnd > now) {
long remaining = Math.max(0L, (cooldownEnd - now) / 1000L);
player.sendMessage(messages.message("boost-cooldown", Placeholder.parsed("time", String.valueOf(remaining))));
return false;
}
} else {
long cooldownEnd = state.cooldownEnd(cooldownKey(effective.key()));
if (cooldownEnd > now) {
long remaining = Math.max(0L, (cooldownEnd - now) / 1000L);
player.sendMessage(messages.message("boost-cooldown", Placeholder.parsed("time", String.valueOf(remaining))));
return false;
}
}
}
double cost = effective.cost();
Expand Down Expand Up @@ -250,8 +314,27 @@ public boolean activate(Player player, BoostDefinition boost, ActivationSource s
}
long endTimestamp = now + (effective.durationSeconds() * 1000L);
state.setActiveBoost(effective.key(), endTimestamp);
if (effective.cooldownSeconds() > 0) {
state.setCooldownEnd(cooldownKey(effective.key()), now + (effective.cooldownSeconds() * 1000L));
if (config.settings().cooldownPerEffect()) {
for (BoostEffect effect : effective.effects()) {
int seconds = 0;
if (effect.type() == null) {
CustomBoostEffect custom = customEffects.get(effect.customName().toLowerCase(Locale.ROOT));
if (custom != null) {
seconds = custom.getCooldownSeconds();
} else {
seconds = effective.cooldownSeconds();
}
} else {
seconds = effective.cooldownSeconds();
}
if (seconds > 0) {
state.setCooldownEnd(effectCooldownKey(effect), now + (seconds * 1000L));
}
}
} else {
if (effective.cooldownSeconds() > 0) {
state.setCooldownEnd(cooldownKey(effective.key()), now + (effective.cooldownSeconds() * 1000L));
}
}
scheduleExpiry(player, effective, endTimestamp);
scheduleActionbar(player, effective);
Expand Down Expand Up @@ -472,6 +555,17 @@ private String cooldownKey(String boostKey) {
return GLOBAL_COOLDOWN_KEY;
}

private String effectCooldownKey(BoostEffect effect) {
if (effect == null) return GLOBAL_COOLDOWN_KEY;
if (effect.type() != null) {
String name = effect.type().getName();
return ("effect:potion:" + name).toLowerCase(Locale.ROOT);
} else {
String cname = effect.customName() != null ? effect.customName() : "";
return ("effect:custom:" + cname).toLowerCase(Locale.ROOT);
}
}

private void sendActionBar(Player player, String message) {
if (message == null || message.isBlank()) {
return;
Expand Down
Loading