Skip to content

Commit 6b01d70

Browse files
authored
Merge pull request #282 from BentoBoxWorld/feat/241-member-limit-perms
Add opt-in support for team members' limit permissions
2 parents b0d1191 + 7f6b3b5 commit 6b01d70

4 files changed

Lines changed: 171 additions & 9 deletions

File tree

src/main/java/world/bentobox/limits/Settings.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ enum GeneralGroup {
5151
private final boolean asyncGolums;
5252
private final boolean showLimitMessages;
5353
private final boolean stackedPlantsCountAsOne;
54+
private final boolean applyMemberLimitPerms;
5455
private static final List<EntityType> DISALLOWED = Arrays.asList(
5556
EntityType.TNT,
5657
EntityType.EVOKER_FANGS,
@@ -99,6 +100,8 @@ public Settings(Limits addon) {
99100
showLimitMessages = addon.getConfig().getBoolean("show-limit-messages", true);
100101
// Count a stackable plant column (sugar cane, bamboo) as a single plant
101102
stackedPlantsCountAsOne = addon.getConfig().getBoolean("stacked-plants-count-as-one", false);
103+
// Apply team members' limit permissions, not just the owner's
104+
applyMemberLimitPerms = addon.getConfig().getBoolean("apply-member-limit-perms", false);
102105

103106
addon.log("Entity limits:");
104107
envLimits.forEach((env, m) -> m.entrySet().stream()
@@ -277,6 +280,13 @@ public boolean isStackedPlantsCountAsOne() {
277280
return stackedPlantsCountAsOne;
278281
}
279282

283+
/**
284+
* @return true if team members' limit permissions are applied to the island, not just the owner's
285+
*/
286+
public boolean isApplyMemberLimitPerms() {
287+
return applyMemberLimitPerms;
288+
}
289+
280290
public Map<GeneralGroup, Integer> getGeneral() {
281291
return general;
282292
}

src/main/java/world/bentobox/limits/listeners/JoinListener.java

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.bukkit.permissions.PermissionAttachmentInfo;
2121
import org.eclipse.jdt.annotation.NonNull;
2222

23+
import world.bentobox.bentobox.api.addons.GameModeAddon;
2324
import world.bentobox.bentobox.api.events.island.IslandEvent;
2425
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
2526
import world.bentobox.bentobox.api.events.team.TeamSetownerEvent;
@@ -34,6 +35,12 @@
3435
/**
3536
* Applies permission-based block, entity, and entity-group limits to islands.
3637
*
38+
* <p>By default only the island owner's permissions count. When
39+
* {@code apply-member-limit-perms} is enabled in the config, team members' permissions
40+
* are merged in as well: a member's login merges their limits on top (highest value
41+
* wins), and the owner's login recalculates from scratch and then merges the
42+
* permissions of all online members.
43+
*
3744
* <p>Permission format:
3845
* <ul>
3946
* <li>5-segment: {@code <gm>.island.limit.<KEY>.<N>} — same limit applied independently
@@ -64,6 +71,16 @@ public void checkPerms(Player player, String permissionPrefix, String islandId,
6471
islandBlockCount.clearAllEntityGroupLimits();
6572
islandBlockCount.clearAllBlockLimits();
6673
}
74+
mergePerms(player, permissionPrefix, islandId, gameMode);
75+
}
76+
77+
/**
78+
* Reads every limit-shaped permission the player has and merges it into the island's
79+
* existing permission-based limits — the highest value wins. Unlike
80+
* {@link #checkPerms}, nothing is cleared first.
81+
*/
82+
public void mergePerms(Player player, String permissionPrefix, String islandId, String gameMode) {
83+
IslandBlockCount islandBlockCount = addon.getBlockLimitListener().getIsland(islandId);
6784
for (PermissionAttachmentInfo permissionInfo : player.getEffectivePermissions()) {
6885
if (!permissionInfo.getValue() || !permissionInfo.getPermission().startsWith(permissionPrefix)) {
6986
continue;
@@ -251,16 +268,47 @@ public void onOwnerChange(TeamSetownerEvent event) {
251268

252269
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
253270
public void onPlayerJoin(PlayerJoinEvent event) {
271+
UUID playerUUID = event.getPlayer().getUniqueId();
254272
addon.getGameModes().forEach(gameMode -> addon.getIslands()
255-
.getIslands(gameMode.getOverWorld(), event.getPlayer().getUniqueId()).stream()
256-
.filter(island -> event.getPlayer().getUniqueId().equals(island.getOwner()))
257-
.map(Island::getUniqueId).forEach(islandId -> {
258-
IslandBlockCount islandBlockCount = addon.getBlockLimitListener().getIsland(islandId);
259-
if (!joinEventCheck(event.getPlayer(), islandId, islandBlockCount)) {
260-
checkPerms(event.getPlayer(), gameMode.getPermissionPrefix() + "island.limit.", islandId,
261-
gameMode.getDescription().getName());
262-
}
263-
}));
273+
.getIslands(gameMode.getOverWorld(), playerUUID).stream()
274+
.filter(island -> playerUUID.equals(island.getOwner())
275+
|| (addon.getSettings().isApplyMemberLimitPerms()
276+
&& island.getMemberSet().contains(playerUUID)))
277+
.forEach(island -> processJoin(event.getPlayer(), island, gameMode)));
278+
}
279+
280+
private void processJoin(Player player, Island island, GameModeAddon gameMode) {
281+
String islandId = island.getUniqueId();
282+
IslandBlockCount islandBlockCount = addon.getBlockLimitListener().getIsland(islandId);
283+
if (joinEventCheck(player, islandId, islandBlockCount)) {
284+
return;
285+
}
286+
String permissionPrefix = gameMode.getPermissionPrefix() + "island.limit.";
287+
String gameModeName = gameMode.getDescription().getName();
288+
if (player.getUniqueId().equals(island.getOwner())) {
289+
// Owner login recalculates the limits from scratch...
290+
checkPerms(player, permissionPrefix, islandId, gameModeName);
291+
// ...then merges in the perms of any online team members
292+
mergeOnlineMemberPerms(island, permissionPrefix, gameModeName);
293+
} else {
294+
// Member login merges their perms on top of whatever is set — highest wins
295+
mergePerms(player, permissionPrefix, islandId, gameModeName);
296+
}
297+
}
298+
299+
/**
300+
* Merges the limit permissions of every online team member (excluding the owner)
301+
* into the island's limits. No-op unless {@code apply-member-limit-perms} is enabled.
302+
*/
303+
private void mergeOnlineMemberPerms(Island island, String permissionPrefix, String gameModeName) {
304+
if (!addon.getSettings().isApplyMemberLimitPerms()) {
305+
return;
306+
}
307+
island.getMemberSet().stream()
308+
.filter(memberUUID -> !memberUUID.equals(island.getOwner()))
309+
.map(Bukkit::getPlayer)
310+
.filter(Objects::nonNull)
311+
.forEach(member -> mergePerms(member, permissionPrefix, island.getUniqueId(), gameModeName));
264312
}
265313

266314
private boolean joinEventCheck(Player player, String islandId, IslandBlockCount islandBlockCount) {
@@ -303,6 +351,7 @@ private void setOwnerPerms(Island island, UUID ownerUUID) {
303351
if (!permissionPrefix.isEmpty() && !gameModeName.isEmpty() && owner.getPlayer() != null) {
304352
checkPerms(Objects.requireNonNull(owner.getPlayer()), permissionPrefix + "island.limit.",
305353
island.getUniqueId(), gameModeName);
354+
mergeOnlineMemberPerms(island, permissionPrefix + "island.limit.", gameModeName);
306355
}
307356
}
308357
}

src/main/resources/config.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ async-golums: true
3434
# silently - placements and spawns are still blocked, but no message is sent.
3535
show-limit-messages: true
3636

37+
# Apply limit permissions from team members as well as the island owner.
38+
# When enabled, a team member's <gamemode>.island.limit.* permissions are merged into
39+
# the island's limits when the member logs in (the highest value wins). The owner's
40+
# login still recalculates the limits from scratch and then merges in the permissions
41+
# of any online team members.
42+
# Limits only recalculate on login, so removing a member (or revoking their permission)
43+
# takes effect the next time the owner logs in.
44+
# Coop and trusted players are not team members and their permissions never apply.
45+
apply-member-limit-perms: false
46+
3747
# General block limiting
3848
# Use this section to limit how many blocks can be added to an island.
3949
# 0 means the item will be blocked from placement completely.

src/test/java/world/bentobox/limits/JoinListenerTest.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
import org.mockito.junit.jupiter.MockitoSettings;
4747
import org.mockito.quality.Strictness;
4848

49+
import com.google.common.collect.ImmutableSet;
50+
4951
import world.bentobox.bentobox.api.addons.AddonDescription;
5052
import world.bentobox.bentobox.api.addons.GameModeAddon;
5153
import world.bentobox.bentobox.api.events.island.IslandEvent;
@@ -398,6 +400,97 @@ void testOnPlayerJoinWithPermLimitsMultiPerms() {
398400
verify(ibc).setEntityLimit(Environment.NORMAL, EntityType.CAVE_SPIDER, 4);
399401
}
400402

403+
// --- Team member limit perms (#241) ---
404+
405+
private PermissionAttachmentInfo mockPerm(String permission) {
406+
PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class);
407+
when(permAtt.getPermission()).thenReturn(permission);
408+
when(permAtt.getValue()).thenReturn(true);
409+
return permAtt;
410+
}
411+
412+
@Test
413+
void testOnPlayerJoinMemberIgnoredWhenDisabled() {
414+
// Player is a team member, not the owner; feature off (default)
415+
when(island.getOwner()).thenReturn(UUID.randomUUID());
416+
when(settings.isApplyMemberLimitPerms()).thenReturn(false);
417+
Set<PermissionAttachmentInfo> perms = Set.of(mockPerm("bskyblock.island.limit.STONE.24"));
418+
when(player.getEffectivePermissions()).thenReturn(perms);
419+
420+
jl.onPlayerJoin(new PlayerJoinEvent(player, Component.text("welcome")));
421+
422+
verify(ibc, never()).setBlockLimit(any(), any(), anyInt());
423+
verify(bll, never()).setIsland(anyString(), any());
424+
}
425+
426+
@Test
427+
void testOnPlayerJoinMemberPermsMergedWhenEnabled() {
428+
// Player is a team member, not the owner; feature on
429+
when(island.getOwner()).thenReturn(UUID.randomUUID());
430+
when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid));
431+
when(settings.isApplyMemberLimitPerms()).thenReturn(true);
432+
Set<PermissionAttachmentInfo> perms = Set.of(mockPerm("bskyblock.island.limit.STONE.24"));
433+
when(player.getEffectivePermissions()).thenReturn(perms);
434+
435+
jl.onPlayerJoin(new PlayerJoinEvent(player, Component.text("welcome")));
436+
437+
verify(ibc).setBlockLimit(Environment.NORMAL, Material.STONE.getKey(), 24);
438+
// A member join merges — it must never clear the island's existing perm limits
439+
verify(ibc, never()).clearAllBlockLimits();
440+
verify(ibc, never()).clearAllEntityLimits();
441+
verify(ibc, never()).clearAllEntityGroupLimits();
442+
}
443+
444+
@Test
445+
void testOnPlayerJoinMemberMergeKeepsHigherExistingLimit() {
446+
// Island already has STONE at 30 (e.g. from the owner); member perm of 24 must not lower it
447+
when(island.getOwner()).thenReturn(UUID.randomUUID());
448+
when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid));
449+
when(settings.isApplyMemberLimitPerms()).thenReturn(true);
450+
when(ibc.getBlockLimit(any(Environment.class), any(NamespacedKey.class))).thenReturn(30);
451+
Set<PermissionAttachmentInfo> perms = Set.of(mockPerm("bskyblock.island.limit.STONE.24"));
452+
when(player.getEffectivePermissions()).thenReturn(perms);
453+
454+
jl.onPlayerJoin(new PlayerJoinEvent(player, Component.text("welcome")));
455+
456+
verify(ibc).setBlockLimit(Environment.NORMAL, Material.STONE.getKey(), 30);
457+
verify(ibc, never()).setBlockLimit(Environment.NORMAL, Material.STONE.getKey(), 24);
458+
}
459+
460+
@Test
461+
void testOnPlayerJoinOwnerMergesOnlineMemberPerms() {
462+
// Owner joins with no perms of their own; an online member has STONE.44
463+
UUID memberUUID = UUID.randomUUID();
464+
when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid, memberUUID));
465+
when(settings.isApplyMemberLimitPerms()).thenReturn(true);
466+
Player member = mock(Player.class);
467+
when(member.getUniqueId()).thenReturn(memberUUID);
468+
when(member.getName()).thenReturn("member");
469+
Set<PermissionAttachmentInfo> memberPerms = Set.of(mockPerm("bskyblock.island.limit.STONE.44"));
470+
when(member.getEffectivePermissions()).thenReturn(memberPerms);
471+
mockedBukkit.when(() -> Bukkit.getPlayer(memberUUID)).thenReturn(member);
472+
473+
jl.onPlayerJoin(new PlayerJoinEvent(player, Component.text("welcome")));
474+
475+
// Owner join recalculates from scratch, then the member's perm is merged in
476+
verify(ibc).clearAllBlockLimits();
477+
verify(ibc).setBlockLimit(Environment.NORMAL, Material.STONE.getKey(), 44);
478+
}
479+
480+
@Test
481+
void testOnPlayerJoinOwnerIgnoresOfflineMemberPerms() {
482+
// Owner joins; the only member is offline, so nothing extra is merged
483+
UUID memberUUID = UUID.randomUUID();
484+
when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid, memberUUID));
485+
when(settings.isApplyMemberLimitPerms()).thenReturn(true);
486+
mockedBukkit.when(() -> Bukkit.getPlayer(memberUUID)).thenReturn(null);
487+
488+
jl.onPlayerJoin(new PlayerJoinEvent(player, Component.text("welcome")));
489+
490+
verify(ibc).clearAllBlockLimits();
491+
verify(ibc, never()).setBlockLimit(any(), any(), anyInt());
492+
}
493+
401494
/**
402495
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
403496
*/

0 commit comments

Comments
 (0)