270 lines
12 KiB
Java
270 lines
12 KiB
Java
package games.dmg.spigottyrant;
|
|
|
|
import java.time.Duration;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.stream.Collectors;
|
|
import org.bukkit.configuration.ConfigurationSection;
|
|
|
|
public record PluginSettings(
|
|
double tyrantRangeBlocks,
|
|
double followerRangeBlocks,
|
|
Duration optOutDuration,
|
|
Duration roleInactivity,
|
|
Duration candidateActivityWindow,
|
|
Duration pendingSelectionTimeout,
|
|
Duration selectionRetryInterval,
|
|
Duration assassinCooldown,
|
|
Duration assassinInvisibilityDuration,
|
|
Duration assassinDoubleJumpCooldown,
|
|
Duration assassinSpeedDuration,
|
|
Duration assassinWeaknessDuration,
|
|
int assassinWeaknessLevel,
|
|
Duration fixerCooldown,
|
|
Duration fixerEffectDuration,
|
|
int fixerStrengthLevel,
|
|
int fixerNormalHeartRows,
|
|
int fixerNearTyrantHeartRows,
|
|
Duration vigilanteCombatDuration,
|
|
Duration rosterIntelligenceCooldown,
|
|
int tyrantStrengthLevel,
|
|
int tyrantResistanceLevel,
|
|
int followerStrengthCap,
|
|
int followerResistanceCap,
|
|
Set<String> deniedMobTypes,
|
|
AbilityItemSettings assassinItem,
|
|
AbilityItemSettings fixerItem,
|
|
AbilityItemSettings tamerItem,
|
|
String recoveryCommand,
|
|
String inventoryFullMessage,
|
|
boolean freezeTimersWhilePaused
|
|
) {
|
|
public PluginSettings {
|
|
requirePositiveFinite(tyrantRangeBlocks, "tyrant-range-blocks");
|
|
requirePositiveFinite(followerRangeBlocks, "follower-range-blocks");
|
|
requirePositive(optOutDuration, "opt-out-seconds");
|
|
requirePositive(roleInactivity, "role-inactivity-seconds");
|
|
requirePositive(candidateActivityWindow, "candidate-activity-window-seconds");
|
|
requirePositive(pendingSelectionTimeout, "pending-selection-timeout-seconds");
|
|
requirePositive(selectionRetryInterval, "selection-retry-seconds");
|
|
requirePositive(assassinCooldown, "assassin-cooldown-seconds");
|
|
requirePositive(assassinInvisibilityDuration, "assassin-invisibility-seconds");
|
|
requirePositive(assassinDoubleJumpCooldown, "assassin-double-jump-cooldown-seconds");
|
|
requirePositive(assassinSpeedDuration, "assassin-speed-seconds");
|
|
requirePositive(assassinWeaknessDuration, "assassin-weakness-seconds");
|
|
requireEffectLevel(assassinWeaknessLevel, "assassin-weakness-level");
|
|
requirePositive(fixerCooldown, "fixer-cooldown-seconds");
|
|
requirePositive(fixerEffectDuration, "fixer-effect-seconds");
|
|
requireEffectLevel(fixerStrengthLevel, "fixer-strength-level");
|
|
requireHeartRows(fixerNormalHeartRows, "fixer-normal-heart-rows");
|
|
requireHeartRows(fixerNearTyrantHeartRows, "fixer-near-tyrant-heart-rows");
|
|
if (fixerNearTyrantHeartRows < fixerNormalHeartRows) {
|
|
throw new IllegalArgumentException(
|
|
"fixer-near-tyrant-heart-rows must be at least fixer-normal-heart-rows"
|
|
);
|
|
}
|
|
requirePositive(vigilanteCombatDuration, "vigilante-combat-seconds");
|
|
requirePositive(rosterIntelligenceCooldown, "roster-intelligence-cooldown-seconds");
|
|
requireEffectLevel(tyrantStrengthLevel, "tyrant-strength-level");
|
|
requireEffectLevel(tyrantResistanceLevel, "tyrant-resistance-level");
|
|
requireEffectLevel(followerStrengthCap, "follower-strength-cap");
|
|
requireEffectLevel(followerResistanceCap, "follower-resistance-cap");
|
|
deniedMobTypes = normalizedSet(deniedMobTypes, "denied-mob-types");
|
|
assassinItem = Objects.requireNonNull(assassinItem, "assassinItem");
|
|
fixerItem = Objects.requireNonNull(fixerItem, "fixerItem");
|
|
tamerItem = Objects.requireNonNull(tamerItem, "tamerItem");
|
|
recoveryCommand = requireText(recoveryCommand, "recovery-command");
|
|
inventoryFullMessage = requireText(inventoryFullMessage, "inventory-full-message");
|
|
}
|
|
|
|
public static PluginSettings from(Map<String, ?> values) {
|
|
Objects.requireNonNull(values, "values");
|
|
return new PluginSettings(
|
|
decimal(values, "tyrant-range-blocks", 50.0),
|
|
decimal(values, "follower-range-blocks", 50.0),
|
|
duration(values, "opt-out-seconds", Duration.ofDays(7)),
|
|
duration(values, "role-inactivity-seconds", Duration.ofHours(48)),
|
|
duration(values, "candidate-activity-window-seconds", Duration.ofHours(24)),
|
|
duration(values, "pending-selection-timeout-seconds", Duration.ofHours(24)),
|
|
duration(values, "selection-retry-seconds", Duration.ofMinutes(1)),
|
|
duration(values, "assassin-cooldown-seconds", Duration.ofHours(1)),
|
|
duration(values, "assassin-invisibility-seconds", Duration.ofMinutes(10)),
|
|
duration(values, "assassin-double-jump-cooldown-seconds", Duration.ofSeconds(60)),
|
|
duration(values, "assassin-speed-seconds", Duration.ofSeconds(15)),
|
|
duration(values, "assassin-weakness-seconds", Duration.ofSeconds(20)),
|
|
integer(values, "assassin-weakness-level", 3),
|
|
duration(values, "fixer-cooldown-seconds", Duration.ofHours(1)),
|
|
duration(values, "fixer-effect-seconds", Duration.ofMinutes(10)),
|
|
integer(values, "fixer-strength-level", 1),
|
|
integer(values, "fixer-normal-heart-rows", 2),
|
|
integer(values, "fixer-near-tyrant-heart-rows", 3),
|
|
duration(values, "vigilante-combat-seconds", Duration.ofSeconds(15)),
|
|
duration(values, "roster-intelligence-cooldown-seconds", Duration.ofHours(24)),
|
|
integer(values, "tyrant-strength-level", 1),
|
|
integer(values, "tyrant-resistance-level", 1),
|
|
integer(values, "follower-strength-cap", 5),
|
|
integer(values, "follower-resistance-cap", 4),
|
|
stringSet(values, "denied-mob-types", Set.of("ENDER_DRAGON", "WITHER")),
|
|
item(values, "assassin-item", new AbilityItemSettings("STICK", "Assassin Cloak")),
|
|
item(values, "fixer-item", new AbilityItemSettings("STICK", "Fixer's Wrench")),
|
|
item(values, "tamer-item", new AbilityItemSettings("FISHING_ROD", "Tamer's Lead")),
|
|
string(values, "recovery-command", "/tyrant item"),
|
|
string(
|
|
values,
|
|
"inventory-full-message",
|
|
"Your inventory is full. Make room and use /tyrant item."
|
|
),
|
|
bool(values, "freeze-timers-while-paused", true)
|
|
);
|
|
}
|
|
|
|
private static AbilityItemSettings item(
|
|
Map<String, ?> values,
|
|
String key,
|
|
AbilityItemSettings defaultValue
|
|
) {
|
|
Object raw = values.get(key);
|
|
if (raw == null) {
|
|
return defaultValue;
|
|
}
|
|
Map<?, ?> map;
|
|
if (raw instanceof ConfigurationSection section) {
|
|
map = section.getValues(false);
|
|
} else if (raw instanceof Map<?, ?> itemValues) {
|
|
map = itemValues;
|
|
} else {
|
|
throw new IllegalArgumentException(key + " must be a section");
|
|
}
|
|
Object material = map.get("material");
|
|
Object name = map.get("name");
|
|
return new AbilityItemSettings(
|
|
material == null ? defaultValue.material() : material.toString(),
|
|
name == null ? defaultValue.name() : name.toString()
|
|
);
|
|
}
|
|
|
|
private static double decimal(Map<String, ?> values, String key, double defaultValue) {
|
|
Object raw = values.get(key);
|
|
if (raw == null) {
|
|
return defaultValue;
|
|
}
|
|
if (!(raw instanceof Number number)) {
|
|
throw new IllegalArgumentException(key + " must be numeric");
|
|
}
|
|
return number.doubleValue();
|
|
}
|
|
|
|
private static int integer(Map<String, ?> values, String key, int defaultValue) {
|
|
Object raw = values.get(key);
|
|
if (raw == null) {
|
|
return defaultValue;
|
|
}
|
|
if (!(raw instanceof Number number)) {
|
|
throw new IllegalArgumentException(key + " must be an integer");
|
|
}
|
|
double value = number.doubleValue();
|
|
if (!Double.isFinite(value) || value != Math.rint(value)
|
|
|| value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
|
|
throw new IllegalArgumentException(key + " must be a 32-bit integer");
|
|
}
|
|
return number.intValue();
|
|
}
|
|
|
|
private static String string(Map<String, ?> values, String key, String defaultValue) {
|
|
Object raw = values.get(key);
|
|
return raw == null ? defaultValue : raw.toString();
|
|
}
|
|
|
|
private static boolean bool(Map<String, ?> values, String key, boolean defaultValue) {
|
|
Object raw = values.get(key);
|
|
if (raw == null) {
|
|
return defaultValue;
|
|
}
|
|
if (!(raw instanceof Boolean value)) {
|
|
throw new IllegalArgumentException(key + " must be true or false");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private static Set<String> stringSet(
|
|
Map<String, ?> values,
|
|
String key,
|
|
Set<String> defaultValue
|
|
) {
|
|
Object raw = values.get(key);
|
|
if (raw == null) {
|
|
return defaultValue;
|
|
}
|
|
if (!(raw instanceof List<?> list)) {
|
|
throw new IllegalArgumentException(key + " must be a list");
|
|
}
|
|
return list.stream().map(Object::toString).collect(Collectors.toUnmodifiableSet());
|
|
}
|
|
|
|
private static void requirePositiveFinite(double value, String key) {
|
|
if (!Double.isFinite(value) || value <= 0.0) {
|
|
throw new IllegalArgumentException(key + " must be positive and finite");
|
|
}
|
|
}
|
|
|
|
private static void requirePositive(Duration value, String key) {
|
|
Objects.requireNonNull(value, key);
|
|
if (value.isZero() || value.isNegative()) {
|
|
throw new IllegalArgumentException(key + " must be positive");
|
|
}
|
|
}
|
|
|
|
private static void requireEffectLevel(int value, String key) {
|
|
if (value < 1 || value > 255) {
|
|
throw new IllegalArgumentException(key + " must be between 1 and 255");
|
|
}
|
|
}
|
|
|
|
private static void requireHeartRows(int value, String key) {
|
|
if (value < 1 || value > 100) {
|
|
throw new IllegalArgumentException(key + " must be between 1 and 100");
|
|
}
|
|
}
|
|
|
|
private static Set<String> normalizedSet(Set<String> values, String key) {
|
|
if (values == null || values.isEmpty()) {
|
|
throw new IllegalArgumentException(key + " must not be empty");
|
|
}
|
|
return values.stream()
|
|
.map(value -> requireText(value, key).toUpperCase(Locale.ROOT))
|
|
.collect(Collectors.toUnmodifiableSet());
|
|
}
|
|
|
|
private static String requireText(String value, String key) {
|
|
if (value == null || value.isBlank()) {
|
|
throw new IllegalArgumentException(key + " must not be blank");
|
|
}
|
|
return value.trim();
|
|
}
|
|
|
|
private static Duration duration(Map<String, ?> values, String key, Duration defaultValue) {
|
|
Object raw = values.get(key);
|
|
if (raw == null) {
|
|
return defaultValue;
|
|
}
|
|
if (!(raw instanceof Number number)) {
|
|
throw new IllegalArgumentException(key + " must be an integer number of seconds");
|
|
}
|
|
double seconds = number.doubleValue();
|
|
if (!Double.isFinite(seconds) || seconds != Math.rint(seconds)) {
|
|
throw new IllegalArgumentException(key + " must be an integer number of seconds");
|
|
}
|
|
return Duration.ofSeconds(number.longValue());
|
|
}
|
|
|
|
public record AbilityItemSettings(String material, String name) {
|
|
public AbilityItemSettings {
|
|
material = requireText(material, "item material").toUpperCase(Locale.ROOT);
|
|
name = requireText(name, "item name");
|
|
}
|
|
}
|
|
}
|