feat(protection): add configurable Strength effect
This commit is contained in:
@@ -13,8 +13,10 @@ import org.bukkit.entity.Player;
|
||||
/** Implements the permission-aware /leaf command tree. */
|
||||
public final class LeafCommand implements TabExecutor {
|
||||
private static final List<String> PLAYER_COMMANDS = List.of("on", "off", "status");
|
||||
private static final List<String> ADMIN_COMMANDS = List.of("enabled", "strength", "player");
|
||||
private static final List<String> ADMIN_COMMANDS =
|
||||
List.of("enabled", "effect", "strength", "player");
|
||||
private static final List<String> ON_OFF = List.of("on", "off");
|
||||
private static final List<String> EFFECTS = List.of("resistance", "strength");
|
||||
private static final List<String> LEVELS = List.of("1", "2", "3", "4", "5");
|
||||
private static final List<String> PLAYER_PROPERTIES = List.of("status", "enabled", "locked");
|
||||
private final LeafRuntime runtime;
|
||||
@@ -73,11 +75,15 @@ public final class LeafCommand implements TabExecutor {
|
||||
if (arguments.length == 2) {
|
||||
return switch (root) {
|
||||
case "enabled" -> matching(ON_OFF, arguments[1]);
|
||||
case "effect" -> matching(EFFECTS, arguments[1]);
|
||||
case "strength" -> matching(LEVELS, arguments[1]);
|
||||
case "player" -> matching(runtime.knownTargets(), arguments[1]);
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
if (arguments.length == 3 && root.equals("effect")) {
|
||||
return matching(LEVELS, arguments[2]);
|
||||
}
|
||||
if (arguments.length == 3 && root.equals("player")) {
|
||||
return matching(PLAYER_PROPERTIES, arguments[2]);
|
||||
}
|
||||
@@ -115,7 +121,8 @@ public final class LeafCommand implements TabExecutor {
|
||||
}
|
||||
switch (action) {
|
||||
case "enabled" -> executeGlobalEnabled(sender, arguments);
|
||||
case "strength" -> executeStrength(sender, arguments);
|
||||
case "effect" -> executeEffect(sender, arguments);
|
||||
case "strength" -> executeLegacyResistanceStrength(sender, arguments);
|
||||
case "player" -> executeTargeted(sender, arguments);
|
||||
default -> throw new IllegalStateException("validated action was not handled");
|
||||
}
|
||||
@@ -130,24 +137,56 @@ public final class LeafCommand implements TabExecutor {
|
||||
sender.sendMessage(changeMessage("Global Leaf", enabled, change));
|
||||
}
|
||||
|
||||
private void executeStrength(CommandSender sender, String[] arguments) throws IOException {
|
||||
private void executeEffect(CommandSender sender, String[] arguments) throws IOException {
|
||||
if (arguments.length != 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"Usage: /leaf effect <resistance|strength> <1-5>"
|
||||
);
|
||||
}
|
||||
String effect = arguments[1].toLowerCase(Locale.ROOT);
|
||||
int level = parseLevel(arguments[2]);
|
||||
LeafRuntime.Change change = switch (effect) {
|
||||
case "resistance" -> runtime.setResistanceLevel(level);
|
||||
case "strength" -> runtime.setStrengthLevel(level);
|
||||
default -> throw new IllegalArgumentException(
|
||||
"effect must be resistance or strength"
|
||||
);
|
||||
};
|
||||
String display = effect.equals("resistance") ? "Resistance" : "Strength";
|
||||
sender.sendMessage(
|
||||
change == LeafRuntime.Change.CHANGED
|
||||
? "Leaf " + display + " level changed to " + level + "."
|
||||
: "Leaf " + display + " level was already " + level + "."
|
||||
);
|
||||
}
|
||||
|
||||
private void executeLegacyResistanceStrength(CommandSender sender, String[] arguments)
|
||||
throws IOException {
|
||||
if (arguments.length != 2) {
|
||||
throw new IllegalArgumentException("Usage: /leaf strength <1-5>");
|
||||
}
|
||||
int level;
|
||||
try {
|
||||
level = Integer.parseInt(arguments[1]);
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new IllegalArgumentException("strength must be an integer from 1 through 5", exception);
|
||||
}
|
||||
int level = parseLevel(arguments[1]);
|
||||
LeafRuntime.Change change = runtime.setResistanceLevel(level);
|
||||
sender.sendMessage(
|
||||
change == LeafRuntime.Change.CHANGED
|
||||
? "Leaf Resistance strength changed to " + level + "."
|
||||
: "Leaf Resistance strength was already " + level + "."
|
||||
? "Leaf Resistance level changed to " + level
|
||||
+ ". Use /leaf effect resistance in future."
|
||||
: "Leaf Resistance level was already " + level
|
||||
+ ". Use /leaf effect resistance in future."
|
||||
);
|
||||
}
|
||||
|
||||
private static int parseLevel(String value) {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new IllegalArgumentException(
|
||||
"effect level must be an integer from 1 through 5",
|
||||
exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeTargeted(CommandSender sender, String[] arguments) throws IOException {
|
||||
if (arguments.length < 3 || arguments.length > 4) {
|
||||
throw new IllegalArgumentException(
|
||||
@@ -220,7 +259,7 @@ public final class LeafCommand implements TabExecutor {
|
||||
}
|
||||
|
||||
private static void sendUsage(CommandSender sender) {
|
||||
sender.sendMessage("Usage: /leaf <on|off|status|enabled|strength|player>");
|
||||
sender.sendMessage("Usage: /leaf <on|off|status|enabled|effect|strength|player>");
|
||||
}
|
||||
|
||||
static List<String> matching(List<String> candidates, String partial) {
|
||||
|
||||
@@ -79,6 +79,7 @@ public final class LeafPlugin extends JavaPlugin {
|
||||
void persistRuntimeSettings(LeafSettings settings) {
|
||||
getConfig().set("enabled", settings.enabled());
|
||||
getConfig().set("resistance-level", settings.resistanceLevel());
|
||||
getConfig().set("strength-level", settings.strengthLevel());
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,54 +7,72 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
/** Applies and conservatively removes the Resistance effect owned by Leaf. */
|
||||
/** Applies and conservatively removes the Resistance and Strength effects owned by Leaf. */
|
||||
public final class LeafProtection {
|
||||
private final Map<UUID, PotionEffect> appliedEffects = new HashMap<>();
|
||||
private final Map<UUID, Map<PotionEffectType, PotionEffect>> appliedEffects = new HashMap<>();
|
||||
private final PotionEffectType resistanceType;
|
||||
private final PotionEffectType strengthType;
|
||||
|
||||
public synchronized boolean apply(Player player, int level) {
|
||||
PotionEffect desired = effectForLevel(level);
|
||||
PotionEffect previous = appliedEffects.get(player.getUniqueId());
|
||||
if (previous != null && !previous.equals(desired)) {
|
||||
removeMatching(player, previous);
|
||||
appliedEffects.remove(player.getUniqueId());
|
||||
}
|
||||
public LeafProtection() {
|
||||
this(PotionEffectType.RESISTANCE, PotionEffectType.STRENGTH);
|
||||
}
|
||||
|
||||
PotionEffect active = player.getPotionEffect(PotionEffectType.RESISTANCE);
|
||||
if (desired.equals(active) && desired.equals(appliedEffects.get(player.getUniqueId()))) {
|
||||
return true;
|
||||
LeafProtection(PotionEffectType resistanceType, PotionEffectType strengthType) {
|
||||
this.resistanceType = resistanceType;
|
||||
this.strengthType = strengthType;
|
||||
}
|
||||
|
||||
public synchronized boolean apply(Player player, int resistanceLevel, int strengthLevel) {
|
||||
boolean resistance = applyEffect(
|
||||
player,
|
||||
effectFor(resistanceType, resistanceLevel)
|
||||
);
|
||||
boolean strength = applyEffect(
|
||||
player,
|
||||
effectFor(strengthType, strengthLevel)
|
||||
);
|
||||
if (!resistance || !strength) {
|
||||
remove(player);
|
||||
return false;
|
||||
}
|
||||
boolean applied = player.addPotionEffect(desired);
|
||||
if (applied) {
|
||||
appliedEffects.put(player.getUniqueId(), desired);
|
||||
}
|
||||
return applied || desired.equals(active);
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized void remove(Player player) {
|
||||
PotionEffect expected = appliedEffects.remove(player.getUniqueId());
|
||||
if (expected != null) {
|
||||
removeMatching(player, expected);
|
||||
Map<PotionEffectType, PotionEffect> expected = appliedEffects.remove(player.getUniqueId());
|
||||
if (expected == null) {
|
||||
return;
|
||||
}
|
||||
for (PotionEffect effect : expected.values()) {
|
||||
removeMatching(player, effect);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isEffective(Player player, int level) {
|
||||
return effectForLevel(level).equals(
|
||||
player.getPotionEffect(PotionEffectType.RESISTANCE)
|
||||
public synchronized boolean isEffective(
|
||||
Player player,
|
||||
int resistanceLevel,
|
||||
int strengthLevel
|
||||
) {
|
||||
return effectFor(resistanceType, resistanceLevel).equals(
|
||||
player.getPotionEffect(resistanceType)
|
||||
) && effectFor(strengthType, strengthLevel).equals(
|
||||
player.getPotionEffect(strengthType)
|
||||
);
|
||||
}
|
||||
|
||||
public synchronized boolean owns(Player player) {
|
||||
PotionEffect expected = appliedEffects.get(player.getUniqueId());
|
||||
return expected != null
|
||||
&& expected.equals(player.getPotionEffect(PotionEffectType.RESISTANCE));
|
||||
Map<PotionEffectType, PotionEffect> expected = appliedEffects.get(player.getUniqueId());
|
||||
return expected != null && !expected.isEmpty() && expected.values().stream().allMatch(
|
||||
effect -> effect.equals(player.getPotionEffect(effect.getType()))
|
||||
);
|
||||
}
|
||||
|
||||
static PotionEffect effectForLevel(int level) {
|
||||
static PotionEffect effectFor(PotionEffectType type, int level) {
|
||||
if (level < 1 || level > 5) {
|
||||
throw new IllegalArgumentException("Resistance level must be between 1 and 5");
|
||||
throw new IllegalArgumentException("effect level must be between 1 and 5");
|
||||
}
|
||||
return new PotionEffect(
|
||||
PotionEffectType.RESISTANCE,
|
||||
type,
|
||||
PotionEffect.INFINITE_DURATION,
|
||||
level - 1,
|
||||
true,
|
||||
@@ -63,10 +81,36 @@ public final class LeafProtection {
|
||||
);
|
||||
}
|
||||
|
||||
private boolean applyEffect(Player player, PotionEffect desired) {
|
||||
UUID playerId = player.getUniqueId();
|
||||
Map<PotionEffectType, PotionEffect> owned = appliedEffects.computeIfAbsent(
|
||||
playerId,
|
||||
ignored -> new HashMap<>()
|
||||
);
|
||||
PotionEffect previous = owned.get(desired.getType());
|
||||
if (previous != null && !previous.equals(desired)) {
|
||||
removeMatching(player, previous);
|
||||
owned.remove(desired.getType());
|
||||
}
|
||||
|
||||
PotionEffect active = player.getPotionEffect(desired.getType());
|
||||
if (desired.equals(active) && desired.equals(owned.get(desired.getType()))) {
|
||||
return true;
|
||||
}
|
||||
boolean applied = player.addPotionEffect(desired);
|
||||
if (applied) {
|
||||
owned.put(desired.getType(), desired);
|
||||
}
|
||||
if (owned.isEmpty()) {
|
||||
appliedEffects.remove(playerId);
|
||||
}
|
||||
return applied || desired.equals(active);
|
||||
}
|
||||
|
||||
private static void removeMatching(Player player, PotionEffect expected) {
|
||||
PotionEffect active = player.getPotionEffect(PotionEffectType.RESISTANCE);
|
||||
PotionEffect active = player.getPotionEffect(expected.getType());
|
||||
if (expected.equals(active)) {
|
||||
player.removePotionEffect(PotionEffectType.RESISTANCE);
|
||||
player.removePotionEffect(expected.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,18 @@ public final class LeafRuntime {
|
||||
return Change.CHANGED;
|
||||
}
|
||||
|
||||
public Change setStrengthLevel(int level) throws IOException {
|
||||
LeafSettings current = settingsProvider.current();
|
||||
LeafSettings replacement = current.withStrengthLevel(level);
|
||||
if (current.strengthLevel() == level) {
|
||||
return Change.UNCHANGED;
|
||||
}
|
||||
settingsPersistence.save(replacement);
|
||||
settingsProvider.replace(replacement);
|
||||
reconcileAllOnline();
|
||||
return Change.CHANGED;
|
||||
}
|
||||
|
||||
public Change setLocked(UUID playerId, boolean locked) throws IOException {
|
||||
PlayerLeafState state = requiredState(playerId);
|
||||
if (state.locked() == locked) {
|
||||
@@ -160,7 +172,11 @@ public final class LeafRuntime {
|
||||
boolean active = online != null
|
||||
&& globallyEnabled
|
||||
&& state.optedIn()
|
||||
&& protection.isEffective(online, settingsProvider.current().resistanceLevel());
|
||||
&& protection.isEffective(
|
||||
online,
|
||||
settingsProvider.current().resistanceLevel(),
|
||||
settingsProvider.current().strengthLevel()
|
||||
);
|
||||
return new Status(
|
||||
state.optedIn(),
|
||||
active,
|
||||
@@ -219,7 +235,11 @@ public final class LeafRuntime {
|
||||
PlayerLeafState state = stateManager.find(player.getUniqueId()).orElse(null);
|
||||
LeafSettings settings = settingsProvider.current();
|
||||
if (state != null && state.optedIn() && settings.enabled()) {
|
||||
if (protection.apply(player, settings.resistanceLevel())) {
|
||||
if (protection.apply(
|
||||
player,
|
||||
settings.resistanceLevel(),
|
||||
settings.strengthLevel()
|
||||
)) {
|
||||
identity.apply(player, settings.prefix());
|
||||
} else {
|
||||
identity.remove(player, settings.prefix());
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.Objects;
|
||||
public record LeafSettings(
|
||||
boolean enabled,
|
||||
int resistanceLevel,
|
||||
int strengthLevel,
|
||||
String prefix,
|
||||
int onboardingDays,
|
||||
String welcomeMessage,
|
||||
@@ -16,6 +17,9 @@ public record LeafSettings(
|
||||
if (resistanceLevel < 1 || resistanceLevel > 5) {
|
||||
throw new IllegalArgumentException("resistance-level must be between 1 and 5");
|
||||
}
|
||||
if (strengthLevel < 1 || strengthLevel > 5) {
|
||||
throw new IllegalArgumentException("strength-level must be between 1 and 5");
|
||||
}
|
||||
if (onboardingDays <= 0) {
|
||||
throw new IllegalArgumentException("onboarding-days must be positive");
|
||||
}
|
||||
@@ -30,12 +34,13 @@ public record LeafSettings(
|
||||
return new LeafSettings(
|
||||
bool(values, "enabled", true),
|
||||
integer(values, "resistance-level", 1),
|
||||
integer(values, "strength-level", 1),
|
||||
string(values, "prefix", "&a🍃 "),
|
||||
integer(values, "onboarding-days", 7),
|
||||
string(
|
||||
values,
|
||||
"welcome-message",
|
||||
"&aLeaf protection is available: /leaf on, /leaf off, or /leaf status. "
|
||||
"&aLeaf Resistance and Strength are available: /leaf on, /leaf off, or /leaf status. "
|
||||
+ "Attacking another player opts you out."
|
||||
),
|
||||
string(
|
||||
@@ -52,6 +57,7 @@ public record LeafSettings(
|
||||
return new LeafSettings(
|
||||
newEnabled,
|
||||
resistanceLevel,
|
||||
strengthLevel,
|
||||
prefix,
|
||||
onboardingDays,
|
||||
welcomeMessage,
|
||||
@@ -64,6 +70,20 @@ public record LeafSettings(
|
||||
return new LeafSettings(
|
||||
enabled,
|
||||
level,
|
||||
strengthLevel,
|
||||
prefix,
|
||||
onboardingDays,
|
||||
welcomeMessage,
|
||||
combatDisabledMessage,
|
||||
lockedMessage
|
||||
);
|
||||
}
|
||||
|
||||
public LeafSettings withStrengthLevel(int level) {
|
||||
return new LeafSettings(
|
||||
enabled,
|
||||
resistanceLevel,
|
||||
level,
|
||||
prefix,
|
||||
onboardingDays,
|
||||
welcomeMessage,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Whether Leaf protection is available server-wide.
|
||||
enabled: true
|
||||
|
||||
# Visible Minecraft Resistance level (I-V).
|
||||
# Visible Minecraft Resistance and Strength levels (I-V).
|
||||
resistance-level: 1
|
||||
strength-level: 1
|
||||
|
||||
# Legacy color codes are supported.
|
||||
prefix: "&a🍃 "
|
||||
@@ -10,6 +11,6 @@ prefix: "&a🍃 "
|
||||
# Calendar-day onboarding window measured from first join.
|
||||
onboarding-days: 7
|
||||
|
||||
welcome-message: "&aLeaf protection is available: /leaf on, /leaf off, or /leaf status. Attacking another player opts you out."
|
||||
welcome-message: "&aLeaf Resistance and Strength are available: /leaf on, /leaf off, or /leaf status. Attacking another player opts you out."
|
||||
combat-disabled-message: "&cLeaf protection was disabled because you attacked another player. You may use /leaf on again when permitted."
|
||||
locked-message: "&cAn administrator locked your Leaf setting."
|
||||
|
||||
@@ -2,12 +2,12 @@ name: Leaf
|
||||
version: ${version}
|
||||
main: games.dmg.leaf.LeafPlugin
|
||||
api-version: "1.20"
|
||||
description: Voluntary visible Resistance protection for non-aggressive players.
|
||||
description: Voluntary visible Resistance and Strength protection for non-aggressive players.
|
||||
author: dmg.games
|
||||
commands:
|
||||
leaf:
|
||||
description: Control or administer Leaf protection.
|
||||
usage: /leaf <on|off|status|enabled|strength|player>
|
||||
usage: /leaf <on|off|status|enabled|effect|strength|player>
|
||||
permissions:
|
||||
leaf.use:
|
||||
description: Allows a player to control their own Leaf protection.
|
||||
|
||||
@@ -48,7 +48,7 @@ final class LeafCommandTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void administratorCanMutateGlobalStrengthAndTargetedSettings() throws Exception {
|
||||
void administratorCanMutateGlobalEffectsAndTargetedSettings() throws Exception {
|
||||
LeafRuntime runtime = mock(LeafRuntime.class);
|
||||
UUID targetId = UUID.randomUUID();
|
||||
PlayerLeafState state = new PlayerLeafState(
|
||||
@@ -62,6 +62,8 @@ final class LeafCommandTest {
|
||||
when(runtime.playerState(targetId)).thenReturn(state);
|
||||
when(runtime.setGlobalEnabled(false)).thenReturn(LeafRuntime.Change.CHANGED);
|
||||
when(runtime.setResistanceLevel(5)).thenReturn(LeafRuntime.Change.CHANGED);
|
||||
when(runtime.setResistanceLevel(4)).thenReturn(LeafRuntime.Change.CHANGED);
|
||||
when(runtime.setStrengthLevel(2)).thenReturn(LeafRuntime.Change.CHANGED);
|
||||
when(runtime.setChoice(targetId, true)).thenReturn(LeafRuntime.Change.CHANGED);
|
||||
CommandSender admin = mock(CommandSender.class);
|
||||
when(admin.hasPermission("leaf.admin")).thenReturn(true);
|
||||
@@ -69,7 +71,19 @@ final class LeafCommandTest {
|
||||
Command command = mock(Command.class);
|
||||
|
||||
leaf.onCommand(admin, command, "leaf", new String[] {"enabled", "off"});
|
||||
leaf.onCommand(admin, command, "leaf", new String[] {"strength", "5"});
|
||||
leaf.onCommand(
|
||||
admin,
|
||||
command,
|
||||
"leaf",
|
||||
new String[] {"effect", "resistance", "5"}
|
||||
);
|
||||
leaf.onCommand(
|
||||
admin,
|
||||
command,
|
||||
"leaf",
|
||||
new String[] {"effect", "strength", "2"}
|
||||
);
|
||||
leaf.onCommand(admin, command, "leaf", new String[] {"strength", "4"});
|
||||
leaf.onCommand(
|
||||
admin,
|
||||
command,
|
||||
@@ -79,6 +93,8 @@ final class LeafCommandTest {
|
||||
|
||||
verify(runtime).setGlobalEnabled(false);
|
||||
verify(runtime).setResistanceLevel(5);
|
||||
verify(runtime).setResistanceLevel(4);
|
||||
verify(runtime).setStrengthLevel(2);
|
||||
verify(runtime).setChoice(targetId, true);
|
||||
verify(admin, atLeastOnce()).sendMessage(contains("changed"));
|
||||
}
|
||||
@@ -93,9 +109,22 @@ final class LeafCommandTest {
|
||||
Command command = mock(Command.class);
|
||||
|
||||
assertEquals(
|
||||
List.of("enabled", "strength", "player"),
|
||||
List.of("enabled", "effect", "strength", "player"),
|
||||
leaf.onTabComplete(admin, command, "leaf", new String[] {""})
|
||||
);
|
||||
assertEquals(
|
||||
List.of("resistance", "strength"),
|
||||
leaf.onTabComplete(admin, command, "leaf", new String[] {"effect", ""})
|
||||
);
|
||||
assertEquals(
|
||||
List.of("1", "2", "3", "4", "5"),
|
||||
leaf.onTabComplete(
|
||||
admin,
|
||||
command,
|
||||
"leaf",
|
||||
new String[] {"effect", "strength", ""}
|
||||
)
|
||||
);
|
||||
assertEquals(
|
||||
List.of("Alex"),
|
||||
leaf.onTabComplete(admin, command, "leaf", new String[] {"player", "A"})
|
||||
|
||||
@@ -33,18 +33,39 @@ final class LeafRuntimeTest {
|
||||
Server server = mock(Server.class);
|
||||
when(server.getPlayer(playerId)).thenReturn(player);
|
||||
LeafProtection protection = mock(LeafProtection.class);
|
||||
when(protection.apply(player, 1)).thenReturn(true);
|
||||
when(protection.apply(player, 1, 1)).thenReturn(true);
|
||||
Path stateFile = temporaryDirectory.resolve("state.yml");
|
||||
LeafRuntime runtime = runtime(server, protection, stateFile);
|
||||
runtime.observe(player, Instant.parse("2026-08-10T00:00:00Z"));
|
||||
|
||||
assertEquals(LeafRuntime.Change.CHANGED, runtime.setOwnChoice(player, true));
|
||||
verify(protection).apply(player, 1);
|
||||
verify(protection).apply(player, 1, 1);
|
||||
|
||||
LeafRuntime restarted = runtime(server, protection, stateFile);
|
||||
restarted.reconcile(player);
|
||||
assertTrue(restarted.status(playerId).savedChoice());
|
||||
verify(protection, org.mockito.Mockito.times(2)).apply(player, 1);
|
||||
verify(protection, org.mockito.Mockito.times(2)).apply(player, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void statusIsActiveOnlyWhenBothConfiguredEffectsAreEffective() throws Exception {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Player player = player(playerId, "Alex");
|
||||
Server server = mock(Server.class);
|
||||
when(server.getPlayer(playerId)).thenReturn(player);
|
||||
LeafProtection protection = mock(LeafProtection.class);
|
||||
when(protection.apply(player, 1, 1)).thenReturn(true);
|
||||
when(protection.isEffective(player, 1, 1)).thenReturn(true);
|
||||
LeafRuntime runtime = runtime(
|
||||
server,
|
||||
protection,
|
||||
temporaryDirectory.resolve("both-effects.yml")
|
||||
);
|
||||
runtime.observe(player, Instant.parse("2026-08-10T00:00:00Z"));
|
||||
runtime.setOwnChoice(player, true);
|
||||
|
||||
assertTrue(runtime.status(playerId).activeProtection());
|
||||
verify(protection).isEffective(player, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,7 +108,7 @@ final class LeafRuntimeTest {
|
||||
|
||||
runtime.observe(player, Instant.parse("2026-08-01T00:00:00Z"));
|
||||
|
||||
verify(player, never()).sendMessage(contains("Leaf protection is available"));
|
||||
verify(player, never()).sendMessage(contains("Leaf Resistance and Strength are available"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,8 +119,9 @@ final class LeafRuntimeTest {
|
||||
when(server.getPlayer(playerId)).thenReturn(player);
|
||||
org.mockito.Mockito.doReturn(List.of(player)).when(server).getOnlinePlayers();
|
||||
LeafProtection protection = mock(LeafProtection.class);
|
||||
when(protection.apply(player, 1)).thenReturn(true);
|
||||
when(protection.apply(player, 4)).thenReturn(true);
|
||||
when(protection.apply(player, 1, 1)).thenReturn(true);
|
||||
when(protection.apply(player, 4, 1)).thenReturn(true);
|
||||
when(protection.apply(player, 4, 3)).thenReturn(true);
|
||||
LeafIdentity identity = mock(LeafIdentity.class);
|
||||
LeafSettingsProvider settings = new LeafSettingsProvider(LeafSettings.from(Map.of()));
|
||||
ArrayList<LeafSettings> persisted = new ArrayList<>();
|
||||
@@ -119,12 +141,16 @@ final class LeafRuntimeTest {
|
||||
assertEquals(LeafRuntime.Change.CHANGED, runtime.setGlobalEnabled(false));
|
||||
assertEquals(LeafRuntime.Change.CHANGED, runtime.setGlobalEnabled(true));
|
||||
assertEquals(LeafRuntime.Change.CHANGED, runtime.setResistanceLevel(4));
|
||||
assertEquals(LeafRuntime.Change.CHANGED, runtime.setStrengthLevel(3));
|
||||
|
||||
assertEquals(3, persisted.size());
|
||||
assertEquals(4, persisted.size());
|
||||
assertFalse(persisted.get(0).enabled());
|
||||
assertEquals(4, settings.current().resistanceLevel());
|
||||
assertEquals(3, settings.current().strengthLevel());
|
||||
assertEquals(3, persisted.get(3).strengthLevel());
|
||||
verify(protection, org.mockito.Mockito.atLeastOnce()).remove(player);
|
||||
verify(protection).apply(player, 4);
|
||||
verify(protection).apply(player, 4, 1);
|
||||
verify(protection).apply(player, 4, 3);
|
||||
verify(identity, org.mockito.Mockito.atLeastOnce()).remove(player, "&a🍃 ");
|
||||
}
|
||||
|
||||
@@ -195,7 +221,7 @@ final class LeafRuntimeTest {
|
||||
|
||||
assertEquals(LeafRuntime.Change.LOCKED, runtime.setOwnChoice(player, true));
|
||||
assertFalse(runtime.status(playerId).savedChoice());
|
||||
verify(protection, never()).apply(player, 1);
|
||||
verify(protection, never()).apply(player, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -204,9 +230,10 @@ final class LeafRuntimeTest {
|
||||
Player player = player(playerId, "Alex");
|
||||
Server server = mock(Server.class);
|
||||
when(server.getPlayer(playerId)).thenReturn(player);
|
||||
LeafProtection protection = mock(LeafProtection.class);
|
||||
LeafRuntime runtime = runtime(
|
||||
server,
|
||||
mock(LeafProtection.class),
|
||||
protection,
|
||||
temporaryDirectory.resolve("combat.yml")
|
||||
);
|
||||
runtime.observe(player, Instant.parse("2026-08-10T00:00:00Z"));
|
||||
@@ -217,6 +244,7 @@ final class LeafRuntimeTest {
|
||||
assertEquals(LeafRuntime.Change.UNCHANGED, runtime.combatOptOut(playerId));
|
||||
|
||||
assertFalse(runtime.status(playerId).savedChoice());
|
||||
verify(protection, org.mockito.Mockito.atLeastOnce()).remove(player);
|
||||
verify(player).sendMessage(contains("attacked another player"));
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ final class LeafSettingsTest {
|
||||
|
||||
assertEquals(true, settings.enabled());
|
||||
assertEquals(1, settings.resistanceLevel());
|
||||
assertEquals(1, settings.strengthLevel());
|
||||
assertEquals("&a🍃 ", settings.prefix());
|
||||
assertEquals(7, settings.onboardingDays());
|
||||
}
|
||||
@@ -24,4 +25,12 @@ final class LeafSettingsTest {
|
||||
() -> LeafSettings.from(Map.of("resistance-level", 6))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsStrengthOutsideConfiguredLevels() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> LeafSettings.from(Map.of("strength-level", 0))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user