From b3a446843619a206fb69129ea7cb020db88b87e0 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 6 Aug 2026 13:01:59 +0100 Subject: [PATCH 01/11] fix: better player perms testing in commands --- .../dev/ftb/mods/ftbranks/FTBRanksCommands.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java b/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java index 8505193..b30f10b 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java @@ -39,11 +39,22 @@ public class FTBRanksCommands { (object) -> Component.literal("Unknown rank: " + object.toString()) ); + private static boolean isCommandSourceAllowed(CommandSourceStack source) { + // source.getServer() *can* return null: https://github.com/FTBTeam/FTB-Mods-Issues/issues/766 + //noinspection ConstantValue + if (source.getServer() == null) { + return false; + } + + // from console, or owner of SSP world (incl open to LAN), or has GM perm level or better + return source.getPlayer() == null + || source.getServer().isSingleplayerOwner(source.getPlayer().getGameProfile()) + || source.getPlayer().hasPermissions(Commands.LEVEL_GAMEMASTERS); + } + public static void register(CommandDispatcher dispatcher, CommandBuildContext commandBuildContext, Commands.CommandSelection selection) { - // source.getServer() *can* return null: https://github.com/FTBTeam/FTB-Mods-Issues/issues/766 - //noinspection ConstantValue dispatcher.register(Commands.literal("ftbranks") - .requires(source -> source.getServer() != null && source.getServer().isSingleplayer() || source.hasPermission(2)) + .requires(FTBRanksCommands::isCommandSourceAllowed) .then(Commands.literal("reload") .executes(context -> reloadRanks(context.getSource())) ) From 0e42f38ccbfa8f315567a095861d3a98985cc173 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 6 Aug 2026 13:02:37 +0100 Subject: [PATCH 02/11] chore: indentation fix oops --- .../ftb/mods/ftbranks/FTBRanksCommands.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java b/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java index b30f10b..cbfdbc4 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java @@ -39,19 +39,19 @@ public class FTBRanksCommands { (object) -> Component.literal("Unknown rank: " + object.toString()) ); - private static boolean isCommandSourceAllowed(CommandSourceStack source) { - // source.getServer() *can* return null: https://github.com/FTBTeam/FTB-Mods-Issues/issues/766 - //noinspection ConstantValue - if (source.getServer() == null) { - return false; - } - - // from console, or owner of SSP world (incl open to LAN), or has GM perm level or better - return source.getPlayer() == null - || source.getServer().isSingleplayerOwner(source.getPlayer().getGameProfile()) - || source.getPlayer().hasPermissions(Commands.LEVEL_GAMEMASTERS); + private static boolean isCommandSourceAllowed(CommandSourceStack source) { + // source.getServer() *can* return null: https://github.com/FTBTeam/FTB-Mods-Issues/issues/766 + //noinspection ConstantValue + if (source.getServer() == null) { + return false; } + // from console, or owner of SSP world (incl open to LAN), or has GM perm level or better + return source.getPlayer() == null + || source.getServer().isSingleplayerOwner(source.getPlayer().getGameProfile()) + || source.getPlayer().hasPermissions(Commands.LEVEL_GAMEMASTERS); + } + public static void register(CommandDispatcher dispatcher, CommandBuildContext commandBuildContext, Commands.CommandSelection selection) { dispatcher.register(Commands.literal("ftbranks") .requires(FTBRanksCommands::isCommandSourceAllowed) From c6144a8a17f94193ba26237bba60d60c98a1bebb Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 17 Aug 2026 11:46:57 +0100 Subject: [PATCH 03/11] fix: RankManagerImpl#getOrCreatePlayerData Marking rank data dirty instead of player data, oops --- .../main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java index 40d985f..f18ead7 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java @@ -316,7 +316,7 @@ PlayerRankData getOrCreatePlayerData(GameProfile profile) { if (data == null) { data = new PlayerRankData(this, profile.getId(), profile.getName()); playerData.put(profile.getId(), data); - markRanksDirty(); + markPlayerDataDirty(); } return data; From c4eadaf807f622a2314037ada955faadd07bf929 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 17 Aug 2026 11:50:57 +0100 Subject: [PATCH 04/11] chore: remove unused and broken player-specific permissions feature --- .../mods/ftbranks/impl/PlayerRankData.java | 26 +++++-------------- .../mods/ftbranks/impl/RankManagerImpl.java | 14 +++------- 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/impl/PlayerRankData.java b/common/src/main/java/dev/ftb/mods/ftbranks/impl/PlayerRankData.java index bc8fa18..0f84f8d 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/impl/PlayerRankData.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/impl/PlayerRankData.java @@ -15,14 +15,12 @@ public class PlayerRankData { private final UUID playerId; private final String name; private final Map added; - private final Map permissions; public PlayerRankData(RankManagerImpl manager, UUID playerId, String name) { this.manager = manager; this.playerId = playerId; this.name = name; this.added = new LinkedHashMap<>(); - this.permissions = new LinkedHashMap<>(); } public UUID getPlayerId() { @@ -63,9 +61,15 @@ public int hashCode() { return Objects.hash(playerId); } + /** + * Player-specific permission nodes have never worked correctly and will be removed. + * @param node the node + * @return always returns MISSING + */ + @Deprecated(forRemoval = true) @NotNull public PermissionValue getPermission(String node) { - return permissions.getOrDefault(node, PermissionValue.MISSING); + return PermissionValue.MISSING; } SNBTCompoundTag writeSNBT() { @@ -83,11 +87,6 @@ SNBTCompoundTag writeSNBT() { res.put("ranks", ranksTag); } - SNBTCompoundTag permTag = RankManagerImpl.writePermissions(permissions, new SNBTCompoundTag()); - if (!permTag.isEmpty()) { - res.put("permissions", permTag); - } - return res; } @@ -106,17 +105,6 @@ static PlayerRankData fromSNBT(RankManagerImpl manager, UUID playerId, SNBTCompo } } - SNBTCompoundTag permTag = tag.getCompound("permissions"); - for (String permKey : permTag.getAllKeys()) { - while (permKey.endsWith(".*")) { - permKey = permKey.substring(0, permKey.length() - 2); - manager.markPlayerDataDirty(); - } - if (!permKey.isEmpty()) { - data.permissions.put(playerId.toString(), RankManagerImpl.ofTag(permTag, permKey)); - } - } - return data; } } \ No newline at end of file diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java index f18ead7..045b0a6 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java @@ -160,10 +160,9 @@ public PermissionValue getPermissionValue(ServerPlayer player, String node) { try { List list = sortedRanks.stream().filter(rank -> rank.isActive(player)).collect(Collectors.toList()); - return getPermissionValue(getOrCreatePlayerData(player.getGameProfile()), list, node); + return getPermissionValue(list, node); } catch (Exception ex) { - FTBRanks.LOGGER.error("Error getting permission value for node " + node + "!"); - ex.printStackTrace(); + FTBRanks.LOGGER.error("Error getting permission value for node {}! {} / {}", node, ex.getClass().getName(), ex.getMessage()); } return PermissionValue.MISSING; @@ -174,16 +173,11 @@ public MinecraftServer getServer() { return server; } - private PermissionValue getPermissionValue(PlayerRankData data, List ranks, String node) { + private PermissionValue getPermissionValue(List ranks, String node) { if (node.isEmpty()) { return PermissionValue.MISSING; } - PermissionValue value = data.getPermission(node); - if (!value.isEmpty()) { - return value; - } - for (Rank rank : ranks) { PermissionValue value1 = rank.getPermission(node); if (!value1.isEmpty()) { @@ -192,7 +186,7 @@ private PermissionValue getPermissionValue(PlayerRankData data, List ranks } int i = node.lastIndexOf('.'); - return i == -1 ? PermissionValue.MISSING : getPermissionValue(data, ranks, node.substring(0, i)); + return i == -1 ? PermissionValue.MISSING : getPermissionValue(ranks, node.substring(0, i)); } public void reload() throws Exception { From 0e144375ec4869a059c8e9e3d35e3e816a4cd2d1 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 17 Aug 2026 12:00:03 +0100 Subject: [PATCH 05/11] fix: reject invalid perm node names in /ftbranks node command "name" and "power" are now rejected in addition to "condition" --- .../dev/ftb/mods/ftbranks/impl/RankImpl.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java index c4a88b3..c15cf35 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java @@ -79,8 +79,12 @@ public int getPower() { @Override public void setPermission(String node, PermissionValue value) { - if (node.equals("condition")) { - throw new IllegalArgumentException("use '/ftbranks condition' to set conditions"); + if (SPECIAL_FIELDS.contains(node)) { + String err = "'" + node + "' is a reserved field"; + if (node.equals("condition")) { + err += " (use '/ftbranks condition' to set conditions)"; + } + throw new IllegalArgumentException(err); } PermissionValue oldValue = getPermission(node); @@ -163,21 +167,23 @@ public static RankImpl readSNBT(RankManagerImpl manager, String rankId, SNBTComp } for (String key : tag.getAllKeys()) { - if (!SPECIAL_FIELDS.contains(key)) { - while (key.endsWith(".*")) { - key = key.substring(0, key.length() - 2); - manager.markRanksDirty(); - } - - if (!key.isEmpty()) { - rank.permissions.put(key, RankManagerImpl.ofTag(tag, key)); - } - } + if (!key.isEmpty() && !SPECIAL_FIELDS.contains(key)) { + String stripped = stripLegacyPermNodeSuffix(key); + rank.permissions.put(stripped, RankManagerImpl.ofTag(tag, stripped)); + } } return rank; } + private static String stripLegacyPermNodeSuffix(String key) { + // legacy ".*" suffix on command permission nodes is no longer required + while (key.endsWith(".*")) { + key = key.substring(0, key.length() - 2); + } + return key; + } + public SNBTCompoundTag writeSNBT() { SNBTCompoundTag res = new SNBTCompoundTag(); From 5680bb7bfdfca03fe0de9f7db9e0d98ae18a48bb Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 17 Aug 2026 14:01:37 +0100 Subject: [PATCH 06/11] fix: a few fixes for rank permissions/conditions - rank creation during load no longer uses setCondition() but injects the condition directly, avoiding side effects which are unwanted during load - moved readPermssions and writePermissions into RankImpl, the only class which uses them - on read, non-primitive json permission nodes are now warned about and not added to the permissions map at all - on write, unexpected permission node classes are warned about and not added to the json object at all --- .../dev/ftb/mods/ftbranks/impl/RankImpl.java | 48 +++++++++++++++++-- .../mods/ftbranks/impl/RankManagerImpl.java | 38 --------------- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java index c15cf35..6496f7d 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java @@ -2,15 +2,22 @@ import com.mojang.authlib.GameProfile; import dev.ftb.mods.ftblibrary.snbt.SNBTCompoundTag; +import dev.ftb.mods.ftbranks.FTBRanks; import dev.ftb.mods.ftbranks.PlayerNameFormatting; import dev.ftb.mods.ftbranks.api.*; import dev.ftb.mods.ftbranks.api.event.*; import dev.ftb.mods.ftbranks.impl.condition.AlwaysActiveCondition; import dev.ftb.mods.ftbranks.impl.condition.DefaultCondition; +import dev.ftb.mods.ftbranks.impl.permission.BooleanPermissionValue; +import dev.ftb.mods.ftbranks.impl.permission.NumberPermissionValue; +import dev.ftb.mods.ftbranks.impl.permission.StringPermissionValue; +import net.minecraft.nbt.*; import org.jetbrains.annotations.NotNull; import java.util.*; +import static dev.ftb.mods.ftbranks.FTBRanks.LOGGER; + public class RankImpl implements Rank, Comparable { private static final Set SPECIAL_FIELDS = Set.of("name", "power", "condition"); @@ -29,7 +36,7 @@ public static RankImpl create(RankManagerImpl manager, String id, String name, i public static RankImpl create(RankManagerImpl manager, String id, String name, int power, RankFileSource source) { RankImpl rank = new RankImpl(manager, id, name, power, AlwaysActiveCondition.INSTANCE, source); - rank.setCondition(new DefaultCondition(rank)); + rank.condition = new DefaultCondition(rank); return rank; } @@ -168,8 +175,11 @@ public static RankImpl readSNBT(RankManagerImpl manager, String rankId, SNBTComp for (String key : tag.getAllKeys()) { if (!key.isEmpty() && !SPECIAL_FIELDS.contains(key)) { - String stripped = stripLegacyPermNodeSuffix(key); - rank.permissions.put(stripped, RankManagerImpl.ofTag(tag, stripped)); + String strippedKey = stripLegacyPermNodeSuffix(key); + readPermissions(tag, key).ifPresentOrElse( + perm -> rank.permissions.put(stripLegacyPermNodeSuffix(key), perm), + () -> FTBRanks.LOGGER.warn("readPermissions: ignoring non-primitive member {} of rank {}", key, rankId) + ); } } @@ -201,7 +211,7 @@ public SNBTCompoundTag writeSNBT() { } } - RankManagerImpl.writePermissions(permissions, res); + writePermissions(permissions, res); return res; } @@ -209,4 +219,34 @@ public SNBTCompoundTag writeSNBT() { public RankFileSource getSource() { return source; } + + private static Optional readPermissions(SNBTCompoundTag tag, String key) { + Tag v = tag.get(key); + + if (v == null || v instanceof ListTag || v instanceof CompoundTag) { + return Optional.empty(); + } + + if (tag.isBoolean(key)) { + return Optional.of(BooleanPermissionValue.of(tag.getBoolean(key))); + } + + return switch (v) { + case NumericTag numericTag -> Optional.of(NumberPermissionValue.of(numericTag.getAsNumber())); + case StringTag stringTag -> Optional.of(StringPermissionValue.of(stringTag.getAsString())); + default -> Optional.empty(); + }; + } + + private static void writePermissions(Map map, SNBTCompoundTag res) { + map.forEach((key, value) -> { + switch (value) { + case BooleanPermissionValue b -> res.putBoolean(key, b.value); + case StringPermissionValue s -> res.putString(key, s.value); + case NumberPermissionValue n -> res.putNumber(key, n.value); + default -> LOGGER.warn("writePermissions: ignoring unknown perm val {} (class {})", key, value.getClass().getName()); + } + }); + } + } \ No newline at end of file diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java index 045b0a6..1185c52 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java @@ -13,11 +13,7 @@ import dev.ftb.mods.ftbranks.api.event.RanksReloadedEvent; import dev.ftb.mods.ftbranks.impl.condition.AlwaysActiveCondition; import dev.ftb.mods.ftbranks.impl.condition.OPCondition; -import dev.ftb.mods.ftbranks.impl.permission.BooleanPermissionValue; -import dev.ftb.mods.ftbranks.impl.permission.NumberPermissionValue; import dev.ftb.mods.ftbranks.impl.permission.StringPermissionValue; -import net.minecraft.nbt.EndTag; -import net.minecraft.nbt.NumericTag; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import net.minecraft.server.MinecraftServer; @@ -364,38 +360,4 @@ void savePlayersNow() { } } - static PermissionValue ofTag(SNBTCompoundTag tag, String key) { - if (tag.isBoolean(key)) { - return BooleanPermissionValue.of(tag.getBoolean(key)); - } - - Tag v = tag.get(key); - - if (v == null || v instanceof EndTag) { - return PermissionValue.MISSING; - } else if (v instanceof NumericTag) { - return NumberPermissionValue.of(((NumericTag) v).getAsNumber()); - } else if (v instanceof StringTag) { - return StringPermissionValue.of(v.getAsString()); - } - - return StringPermissionValue.of(v.toString()); - } - - static SNBTCompoundTag writePermissions(Map map, SNBTCompoundTag res) { - map.forEach((key, value) -> { - if (value.isEmpty()) { - res.putNull(key); - } else if (value instanceof BooleanPermissionValue b) { - res.putBoolean(key, b.value); - } else if (value instanceof StringPermissionValue s) { - res.putString(key, s.value); - } else if (value instanceof NumberPermissionValue n) { - res.putNumber(key, n.value); - } else { - res.putString(key, value.asString().orElse("")); - } - }); - return res; - } } \ No newline at end of file From de160485691e3bba91421e0a97b60021641c3853 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 17 Aug 2026 14:03:49 +0100 Subject: [PATCH 07/11] fix: Don't use setCondition() in RankImpl#readSNBT --- common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java index 6496f7d..cc34b76 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankImpl.java @@ -170,12 +170,11 @@ public static RankImpl readSNBT(RankManagerImpl manager, String rankId, SNBTComp RankImpl rank = create(manager, rankId, displayName, tag.getInt("power"), source); if (tag.contains("condition")) { - rank.setCondition(manager.createCondition(rank, tag.get("condition"))); + rank.condition = manager.createCondition(rank, tag.get("condition")); } for (String key : tag.getAllKeys()) { if (!key.isEmpty() && !SPECIAL_FIELDS.contains(key)) { - String strippedKey = stripLegacyPermNodeSuffix(key); readPermissions(tag, key).ifPresentOrElse( perm -> rank.permissions.put(stripLegacyPermNodeSuffix(key), perm), () -> FTBRanks.LOGGER.warn("readPermissions: ignoring non-primitive member {} of rank {}", key, rankId) From e41c3da4f0bf29c6d6bdf051a62631d0e536164b Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 17 Aug 2026 14:07:59 +0100 Subject: [PATCH 08/11] fix: RankManagerImpl#getAllRanks now returns immutable view --- .../main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java index 1185c52..245f7bd 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/impl/RankManagerImpl.java @@ -66,7 +66,7 @@ public void markPlayerDataDirty() { @Override public Collection getAllRanks() { - return sortedRanks; + return Collections.unmodifiableCollection(sortedRanks); } @Override From 3944e53a84bc3165009e9190cd7d9e51e733d648 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 18 Aug 2026 08:47:56 +0100 Subject: [PATCH 09/11] fix: FTBRanksAPIImpl#manager now private Also tightened up caller check in FTBRanksAPI#setup --- .../java/dev/ftb/mods/ftbranks/FTBRanksCommands.java | 9 +++++---- .../main/java/dev/ftb/mods/ftbranks/api/FTBRanksAPI.java | 5 +++-- common/src/main/java/dev/ftb/mods/ftbranks/api/Rank.java | 2 ++ .../main/java/dev/ftb/mods/ftbranks/api/RankManager.java | 2 ++ .../java/dev/ftb/mods/ftbranks/impl/FTBRanksAPIImpl.java | 4 ++-- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java b/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java index cbfdbc4..0ec766e 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java @@ -13,6 +13,7 @@ import dev.ftb.mods.ftblibrary.snbt.SNBT; import dev.ftb.mods.ftbranks.api.*; import dev.ftb.mods.ftbranks.impl.FTBRanksAPIImpl; +import dev.ftb.mods.ftbranks.impl.RankManagerImpl; import dev.ftb.mods.ftbranks.impl.condition.DefaultCondition; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandBuildContext; @@ -160,7 +161,7 @@ private static String normalizeRankName(String name) { private static int reloadRanks(CommandSourceStack source) { try { - FTBRanksAPIImpl.manager.reload(); + ((RankManagerImpl) FTBRanksAPIImpl.getInstance().getManager()).reload(); source.sendSuccess(() -> Component.literal("Ranks reloaded from disk!"), true); for (ServerPlayer p : source.getServer().getPlayerList().getPlayers()) { @@ -177,7 +178,7 @@ private static int reloadRanks(CommandSourceStack source) { private static int refreshReadme(CommandSourceStack source) { try { - FTBRanksAPIImpl.manager.refreshReadme(); + ((RankManagerImpl) FTBRanksAPIImpl.getInstance().getManager()).refreshReadme(); } catch (IOException ex) { ex.printStackTrace(); } @@ -202,7 +203,7 @@ private static Component makeRankNameClicky(Rank rank) { private static int listAllRanks(CommandSourceStack source) { source.sendSuccess(() -> Component.literal("Ranks:"), false); - for (Rank rank : FTBRanksAPIImpl.manager.getAllRanks()) { + for (Rank rank : FTBRanksAPIImpl.getInstance().getManager().getAllRanks()) { source.sendSuccess(() -> Component.literal("- ").append(makeRankNameClicky(rank)), false); } @@ -253,7 +254,7 @@ private static int removeRank(CommandSourceStack source, Collection private static int listRanksOf(CommandSourceStack source, ServerPlayer player) { source.sendSuccess(() -> Component.literal(String.format("Ranks added to player '%s':", player.getGameProfile().getName())), false); - for (Rank rank : FTBRanksAPIImpl.manager.getAllRanks()) { + for (Rank rank : FTBRanksAPIImpl.getInstance().getManager().getAllRanks()) { if (rank.isActive(player)) { source.sendSuccess(() -> Component.literal("- ").append(makeRankNameClicky(rank)), false); } diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/api/FTBRanksAPI.java b/common/src/main/java/dev/ftb/mods/ftbranks/api/FTBRanksAPI.java index c80802c..c0ddc05 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/api/FTBRanksAPI.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/api/FTBRanksAPI.java @@ -8,6 +8,7 @@ /** * Top-level API object */ +@ApiStatus.NonExtendable public abstract class FTBRanksAPI { private static FTBRanksAPI instance; @@ -56,7 +57,7 @@ public static PermissionValue getPermissionValue(ServerPlayer player, String nod */ @ApiStatus.Internal public static void setup(FTBRanksAPI theInstance) { - if (instance != null || !theInstance.getClass().getName().startsWith("dev.ftb.mods.ftbranks")) { + if (instance != null || !theInstance.getClass().getPackageName().equals("dev.ftb.mods.ftbranks.impl")) { throw new IllegalStateException("don't do this"); } instance = theInstance; @@ -66,5 +67,5 @@ public static void setup(FTBRanksAPI theInstance) { * Get the manager * @return the manager */ - protected abstract RankManager getManager(); + public abstract RankManager getManager(); } \ No newline at end of file diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/api/Rank.java b/common/src/main/java/dev/ftb/mods/ftbranks/api/Rank.java index 03778c8..a578aea 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/api/Rank.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/api/Rank.java @@ -2,6 +2,7 @@ import com.mojang.authlib.GameProfile; import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -12,6 +13,7 @@ * players the rank applies to. A rank also has a "power" level; the highest-powered rank will apply to a player if * more than one rank is applicable. */ +@ApiStatus.NonExtendable public interface Rank { /** * Convenience method to get the rank manager. diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/api/RankManager.java b/common/src/main/java/dev/ftb/mods/ftbranks/api/RankManager.java index b847caf..38e6db5 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/api/RankManager.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/api/RankManager.java @@ -4,6 +4,7 @@ import net.minecraft.nbt.Tag; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,6 +16,7 @@ /** * Top-level manager object. */ +@ApiStatus.NonExtendable public interface RankManager { /** * Get all the known ranks, ordered by rank power, from highest to lowest. The rank's power determines which rank diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/impl/FTBRanksAPIImpl.java b/common/src/main/java/dev/ftb/mods/ftbranks/impl/FTBRanksAPIImpl.java index 6e39e68..62e9e8f 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/impl/FTBRanksAPIImpl.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/impl/FTBRanksAPIImpl.java @@ -14,10 +14,10 @@ import org.apache.commons.lang3.math.NumberUtils; public class FTBRanksAPIImpl extends FTBRanksAPI { - public static RankManagerImpl manager; + private static RankManagerImpl manager; @Override - protected RankManager getManager() { + public RankManager getManager() { return manager; } From 48e2e6b2b869cc531b576220fd29ae8334bac932 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 14 Sep 2026 09:35:39 +0100 Subject: [PATCH 10/11] chore: require perm lvl 4 for commands that change state --- .../dev/ftb/mods/ftbranks/FTBRanksCommands.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java b/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java index 0ec766e..b9a2e6c 100644 --- a/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java +++ b/common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java @@ -53,19 +53,26 @@ private static boolean isCommandSourceAllowed(CommandSourceStack source) { || source.getPlayer().hasPermissions(Commands.LEVEL_GAMEMASTERS); } + private static boolean isServerOp(CommandSourceStack sourceStack) { + return sourceStack.hasPermission(Commands.LEVEL_OWNERS); + } + public static void register(CommandDispatcher dispatcher, CommandBuildContext commandBuildContext, Commands.CommandSelection selection) { dispatcher.register(Commands.literal("ftbranks") .requires(FTBRanksCommands::isCommandSourceAllowed) .then(Commands.literal("reload") + .requires(FTBRanksCommands::isServerOp) .executes(context -> reloadRanks(context.getSource())) ) .then(Commands.literal("refresh_readme") + .requires(FTBRanksCommands::isServerOp) .executes(context -> refreshReadme(context.getSource())) ) .then(Commands.literal("list_all_ranks") .executes(context -> listAllRanks(context.getSource())) ) .then(Commands.literal("create") + .requires(FTBRanksCommands::isServerOp) .then(Commands.argument("name", StringArgumentType.word()) .then(Commands.argument("power", IntegerArgumentType.integer(1)) .executes(context -> createRank(context.getSource(), StringArgumentType.getString(context, "name"), IntegerArgumentType.getInteger(context,"power")))) @@ -73,12 +80,14 @@ public static void register(CommandDispatcher dispatcher, Co ) ) .then(Commands.literal("delete") + .requires(FTBRanksCommands::isServerOp) .then(Commands.argument("rank", StringArgumentType.word()) .suggests((context, builder) -> suggestRanks(builder)) .executes(context -> deleteRank(context.getSource(), StringArgumentType.getString(context, "rank"))) ) ) .then(Commands.literal("add") + .requires(FTBRanksCommands::isServerOp) .then(Commands.argument("players", GameProfileArgument.gameProfile()) .then(Commands.argument("rank", StringArgumentType.word()) .suggests((context, builder) -> suggestRanks(builder)) @@ -87,6 +96,7 @@ public static void register(CommandDispatcher dispatcher, Co ) ) .then(Commands.literal("remove") + .requires(FTBRanksCommands::isServerOp) .then(Commands.argument("players", GameProfileArgument.gameProfile()) .then(Commands.argument("rank", StringArgumentType.word()) .suggests((context, builder) -> suggestRanks(builder)) @@ -107,6 +117,7 @@ public static void register(CommandDispatcher dispatcher, Co ) .then(Commands.literal("node") .then(Commands.literal("add") + .requires(FTBRanksCommands::isServerOp) .then(Commands.argument("rank", StringArgumentType.word()) .suggests((context, builder) -> suggestRanks(builder)) .then(Commands.argument("node", StringArgumentType.word()) @@ -117,6 +128,7 @@ public static void register(CommandDispatcher dispatcher, Co ) ) .then(Commands.literal("remove") + .requires(FTBRanksCommands::isServerOp) .then(Commands.argument("rank", StringArgumentType.word()) .suggests((context, builder) -> suggestRanks(builder)) .then(Commands.argument("node", StringArgumentType.word()) @@ -132,6 +144,7 @@ public static void register(CommandDispatcher dispatcher, Co ) ) .then(Commands.literal("condition") + .requires(FTBRanksCommands::isServerOp) .then(Commands.argument("rank", StringArgumentType.word()) .suggests((context, builder) -> suggestRanks(builder)) .then(Commands.argument("value", StringArgumentType.greedyString()) From c7f4dab572db6ebe77f3e90cef339222decc9577 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 14 Sep 2026 09:36:27 +0100 Subject: [PATCH 11/11] build: version -> 2101.1.5 --- CHANGELOG.md | 11 +++++++++++ gradle.properties | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b31554..9992625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2101.1.5] + +### Changed +* FTB Ranks commands which change rank state in any way now require permission level 4 (server owner) instead of 2 + * In particular this means that running ranks commands from command blocks or signs is no longer possible +* For SSP, open-to-lan behaviour is now safer: players joining a published server no longer have access to ranks commands + +### Fixed +* The `/ftbranks` node command no longer accepts `name` and `power` as node names, since they're reserved +* Fixed rank player membership data not always getting marked as save-needed on change + ## [2101.1.4] ### Fixed diff --git a/gradle.properties b/gradle.properties index 43d33fd..652d27a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ mod_id=ftbranks readable_name=FTB Ranks archives_base_name=ftb-ranks maven_group=dev.ftb.mods -mod_version=2101.1.4 +mod_version=2101.1.5 mod_author=FTB Team minecraft_version=1.21.1