feat(tyrant): add permanent powers and intelligence
Release / release (push) Successful in 2m15s
CI / build (push) Successful in 1m3s

This commit is contained in:
dmg
2026-08-14 23:14:26 -04:00
parent a82d20c7e6
commit cd38e87487
10 changed files with 260 additions and 16 deletions
@@ -2,6 +2,9 @@ package games.dmg.spigottyrant;
import java.time.Clock;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Server;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
@@ -15,6 +18,7 @@ public final class FixerEffectController implements Runnable {
private final Server server;
private final PluginSettings settings;
private final Clock clock;
private final Set<UUID> managedPlayers = new HashSet<>();
public FixerEffectController(
TyrantStateManager stateManager,
@@ -39,9 +43,12 @@ public final class FixerEffectController implements Runnable {
Ability.FIXER_BOOST_ACTIVE, Instant.MIN
).isAfter(now);
if (!active) {
clear(player);
if (managedPlayers.remove(player.getUniqueId())) {
clear(player);
}
continue;
}
managedPlayers.add(player.getUniqueId());
boolean nearTier = state.cooldownEnds().getOrDefault(
Ability.FIXER_NEAR_TYRANT_ACTIVE, Instant.MIN
).isAfter(now);
@@ -60,7 +67,13 @@ public final class FixerEffectController implements Runnable {
}
public void clearAll() {
server.getOnlinePlayers().forEach(FixerEffectController::clear);
for (UUID playerId : Set.copyOf(managedPlayers)) {
Player player = server.getPlayer(playerId);
if (player != null) {
clear(player);
}
}
managedPlayers.clear();
}
private static void clear(Player player) {
@@ -0,0 +1,8 @@
package games.dmg.spigottyrant;
public record IntelligenceResult(
PlayerState tyrant,
AbilityUseStatus status,
int memberCount
) {
}
@@ -14,6 +14,7 @@ public final class SpigotTyrantPlugin extends JavaPlugin {
private VigilanteEffectController vigilanteEffects;
private AssassinEffectController assassinEffects;
private FixerEffectController fixerEffects;
private TyrantEffectController tyrantEffects;
@Override
public void onEnable() {
@@ -60,6 +61,7 @@ public final class SpigotTyrantPlugin extends JavaPlugin {
);
FixerAbilityService fixerAbilities = new FixerAbilityService(readiness, settings);
fixerEffects = new FixerEffectController(stateManager, getServer(), settings, clock);
tyrantEffects = new TyrantEffectController(stateManager, getServer(), settings);
VigilanteCombatTracker combatTracker = new VigilanteCombatTracker();
vigilanteEffects = new VigilanteEffectController(
stateManager, getServer(), combatTracker, settings, clock
@@ -79,7 +81,8 @@ public final class SpigotTyrantPlugin extends JavaPlugin {
new ParticipationService(settings.optOutDuration()),
new RoleRelinquishmentService(succession, maintenance),
tyrantPresentation,
abilityItems
abilityItems,
new TyrantAbilityService(settings.rosterIntelligenceCooldown())
));
Objects.requireNonNull(getCommand("vigilante"), "Missing vigilante metadata")
.setExecutor(new VigilanteCommand(stateManager, followers, onlinePlayers));
@@ -152,6 +155,7 @@ public final class SpigotTyrantPlugin extends JavaPlugin {
getServer().getScheduler().runTaskTimer(this, vigilanteEffects, 10L, 10L);
getServer().getScheduler().runTaskTimer(this, assassinEffects, 10L, 10L);
getServer().getScheduler().runTaskTimer(this, fixerEffects, 10L, 10L);
getServer().getScheduler().runTaskTimer(this, tyrantEffects, 10L, 10L);
getServer().getScheduler().runTaskTimer(
this,
new AbilityItemRefreshTask(
@@ -166,6 +170,9 @@ public final class SpigotTyrantPlugin extends JavaPlugin {
@Override
public void onDisable() {
if (tyrantEffects != null) {
tyrantEffects.clearAll();
}
if (fixerEffects != null) {
fixerEffects.clearAll();
}
@@ -0,0 +1,53 @@
package games.dmg.spigottyrant;
import java.time.Duration;
import java.time.Instant;
import java.util.EnumMap;
import java.util.Map;
public final class TyrantAbilityService {
private final Duration intelligenceCooldown;
public TyrantAbilityService(Duration intelligenceCooldown) {
this.intelligenceCooldown = intelligenceCooldown;
}
public IntelligenceResult useIntelligence(
GameState game,
PlayerState tyrant,
Map<java.util.UUID, PlayerState> players,
Instant now
) {
if (game.lifecycle() != GameLifecycle.RUNNING) {
return new IntelligenceResult(tyrant, AbilityUseStatus.GAME_NOT_RUNNING, 0);
}
if (game.tyrantId().filter(tyrant.playerId()::equals).isEmpty()) {
return new IntelligenceResult(tyrant, AbilityUseStatus.WRONG_OWNER, 0);
}
if (!game.purchases().contains(TyrantUnlock.ROSTER_INTELLIGENCE)) {
return new IntelligenceResult(tyrant, AbilityUseStatus.NOT_READY, 0);
}
if (tyrant.cooldownEnds().getOrDefault(
Ability.ROSTER_INTELLIGENCE, Instant.MIN
).isAfter(now)) {
return new IntelligenceResult(tyrant, AbilityUseStatus.COOLDOWN, 0);
}
int count = game.vigilanteId().isPresent() ? 1 : 0;
if (game.vigilanteId().isPresent()) {
java.util.UUID vigilante = game.vigilanteId().orElseThrow();
count += (int) players.values().stream()
.filter(player -> player.followerOf().filter(vigilante::equals).isPresent())
.filter(player -> player.optedOutUntil().isEmpty())
.count();
}
Map<Ability, Instant> cooldowns = new EnumMap<>(Ability.class);
cooldowns.putAll(tyrant.cooldownEnds());
cooldowns.put(Ability.ROSTER_INTELLIGENCE, now.plus(intelligenceCooldown));
PlayerState updated = new PlayerState(
tyrant.playerId(), tyrant.latestName(), tyrant.lastLogin(),
tyrant.optedOutUntil(), tyrant.tyrantClass(), tyrant.followerOf(), cooldowns,
tyrant.readyAbilityItems(), tyrant.capturedMobs()
);
return new IntelligenceResult(updated, AbilityUseStatus.ACTIVATED, count);
}
}
@@ -19,6 +19,7 @@ public final class TyrantCommand implements CommandExecutor {
private final RoleRelinquishmentService relinquishment;
private final TyrantPresentation presentation;
private final AbilityItemService abilityItems;
private final TyrantAbilityService tyrantAbilities;
public TyrantCommand(
TyrantStateManager stateManager,
@@ -42,7 +43,8 @@ public final class TyrantCommand implements CommandExecutor {
) {
this(
stateManager, progression, assignments, onlinePlayers, clock,
new ParticipationService(Duration.ofDays(7)), null, null, null
new ParticipationService(Duration.ofDays(7)), null, null, null,
new TyrantAbilityService(Duration.ofHours(24))
);
}
@@ -55,7 +57,8 @@ public final class TyrantCommand implements CommandExecutor {
ParticipationService participation,
RoleRelinquishmentService relinquishment,
TyrantPresentation presentation,
AbilityItemService abilityItems
AbilityItemService abilityItems,
TyrantAbilityService tyrantAbilities
) {
this.stateManager = stateManager;
this.progression = progression;
@@ -66,6 +69,7 @@ public final class TyrantCommand implements CommandExecutor {
this.relinquishment = relinquishment;
this.presentation = presentation;
this.abilityItems = abilityItems;
this.tyrantAbilities = tyrantAbilities;
}
@Override
@@ -111,9 +115,13 @@ public final class TyrantCommand implements CommandExecutor {
recoverItems(player);
return true;
}
if (arguments.length == 1 && arguments[0].equalsIgnoreCase("intelligence")) {
useIntelligence(player);
return true;
}
player.sendMessage(
ChatColor.YELLOW
+ "Usage: /tyrant <status|choices|buy|assign|item|optout|optin|"
+ "Usage: /tyrant <status|choices|buy|assign|item|intelligence|optout|optin|"
+ "relinquish confirm>"
);
return true;
@@ -241,6 +249,24 @@ public final class TyrantCommand implements CommandExecutor {
+ readable(result[0].status()) + ".");
}
private void useIntelligence(Player player) {
PlayerState state = stateManager.player(player.getUniqueId(), player.getName());
IntelligenceResult result = tyrantAbilities.useIntelligence(
stateManager.game(), state, stateManager.players(), clock.instant()
);
if (result.status() == AbilityUseStatus.ACTIVATED) {
stateManager.updatePlayer(
player.getUniqueId(), player.getName(), current -> result.tyrant()
);
stateManager.saveIfDirty();
player.sendMessage(ChatColor.YELLOW + "Vigilante-side members: "
+ result.memberCount() + ".");
} else {
player.sendMessage(ChatColor.RED + "Intelligence unavailable: "
+ readable(result.status()) + ".");
}
}
private void recoverItems(Player player) {
if (abilityItems == null) {
player.sendMessage(ChatColor.RED + "Ability item recovery is unavailable.");
@@ -0,0 +1,77 @@
package games.dmg.spigottyrant;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
public final class TyrantEffectController implements Runnable {
private static final int EFFECT_TICKS = 30;
private final TyrantStateManager stateManager;
private final Server server;
private final PluginSettings settings;
private Optional<UUID> previousTyrant = Optional.empty();
public TyrantEffectController(
TyrantStateManager stateManager,
Server server,
PluginSettings settings
) {
this.stateManager = stateManager;
this.server = server;
this.settings = settings;
}
@Override
public void run() {
GameState game = stateManager.game();
if (!previousTyrant.equals(game.tyrantId())) {
previousTyrant.map(server::getPlayer).ifPresent(TyrantEffectController::clear);
previousTyrant = game.tyrantId();
}
Player tyrant = game.tyrantId().map(server::getPlayer).orElse(null);
if (tyrant == null || game.lifecycle() != GameLifecycle.RUNNING) {
if (tyrant != null) {
clear(tyrant);
}
return;
}
apply(
tyrant, PotionEffectType.STRENGTH,
game.purchases().contains(TyrantUnlock.STRENGTH),
settings.tyrantStrengthLevel()
);
apply(
tyrant, PotionEffectType.RESISTANCE,
game.purchases().contains(TyrantUnlock.RESISTANCE),
settings.tyrantResistanceLevel()
);
}
public void clearAll() {
previousTyrant.map(server::getPlayer).ifPresent(TyrantEffectController::clear);
previousTyrant = Optional.empty();
}
private static void apply(
Player player,
PotionEffectType type,
boolean purchased,
int level
) {
if (purchased) {
player.addPotionEffect(new PotionEffect(
type, EFFECT_TICKS, level - 1, false, true, true
));
} else {
player.removePotionEffect(type);
}
}
private static void clear(Player player) {
player.removePotionEffect(PotionEffectType.STRENGTH);
player.removePotionEffect(PotionEffectType.RESISTANCE);
}
}