From c801479dca4e35a7da9b9bf22a5500df91b908c8 Mon Sep 17 00:00:00 2001 From: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Date: Wed, 4 Nov 2026 11:08:30 +0100 Subject: [PATCH 1/2] Send up to five wardrobe skins and a staff ladder entry to the website The website now holds up to five swappable skins per character, so the rpc player-meta cap goes from 3 to 5. Default ladder: Noble 2, Gilded 3, Ascended and Legacy 5, plus rpchar.group.staff (8 characters, 5 skins) for staff and helpers on and off duty. Co-Authored-By: Claude Opus 5.5 --- .../tfmcweb/entitlements/EntitlementResolver.java | 4 +++- src/main/resources/config.yml | 14 ++++++++++---- .../tfmcweb/entitlements/EntitlementsTest.java | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/tfminecraft/tfmcweb/entitlements/EntitlementResolver.java b/src/main/java/net/tfminecraft/tfmcweb/entitlements/EntitlementResolver.java index 2babc07..ccf5312 100644 --- a/src/main/java/net/tfminecraft/tfmcweb/entitlements/EntitlementResolver.java +++ b/src/main/java/net/tfminecraft/tfmcweb/entitlements/EntitlementResolver.java @@ -21,6 +21,8 @@ public final class EntitlementResolver { public static final String KEY_WARDROBE_SLOTS = "wardrobe-skin-slots"; public static final String KEY_MAX_3D_PAIR = "max-3d-pair-bytes"; public static final String KEY_SKIN_COOLDOWN = "skin-token-cooldown-days"; + /** Most swappable wardrobe skins per character; the website and RPCharacters use the same cap. */ + public static final int MAX_WARDROBE_SKIN_SLOTS = 5; private EntitlementResolver() {} @@ -71,7 +73,7 @@ public static ResolvedMeta resolve(Player player) { int maxAlive = maxInt(player, Cache.rpcMetaGroups, Cache.rpcMetaDefaults, KEY_MAX_ALIVE); int wardrobeSlots = Math.max( 1, - Math.min(3, maxInt(player, Cache.rpcMetaGroups, Cache.rpcMetaDefaults, KEY_WARDROBE_SLOTS)) + Math.min(MAX_WARDROBE_SKIN_SLOTS, maxInt(player, Cache.rpcMetaGroups, Cache.rpcMetaDefaults, KEY_WARDROBE_SLOTS)) ); int pairBytes = maxInt(player, Cache.skinsMetaGroups, Cache.skinsMetaDefaults, KEY_MAX_3D_PAIR); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 934183e..84a4300 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -44,6 +44,7 @@ token-cooldowns: # armourshop/src/main/resources/permission-groups.yml # drinkbuilder/src/main/resources/permission-groups.yml # TFMCWeb only posts values; plugins still own in-game unlock semantics. +# wardrobe-skin-slots is 1 to 5 swappable skins per character (base + Skin 2 … Skin 5). # Optional per-realm overrides (falls back to top-level rpc/skins/drinks): # player-meta.by-realm.dev.rpc: { defaults: ..., groups: [...] } player-meta: @@ -72,22 +73,27 @@ player-meta: tier: 1 name-colour-stops: 1 max-alive-characters: 3 - wardrobe-skin-slots: 1 + wardrobe-skin-slots: 2 - permission: rpchar.group.gilded tier: 2 name-colour-stops: 2 max-alive-characters: 4 - wardrobe-skin-slots: 2 + wardrobe-skin-slots: 3 - permission: rpchar.group.ascended tier: 3 name-colour-stops: 20 max-alive-characters: 5 - wardrobe-skin-slots: 3 + wardrobe-skin-slots: 5 - permission: rpchar.group.legacy tier: 4 name-colour-stops: 20 max-alive-characters: 5 - wardrobe-skin-slots: 3 + wardrobe-skin-slots: 5 + # Staff and helpers (RPCharacters' hidden staff group), on and off duty. + - permission: rpchar.group.staff + tier: 100 + max-alive-characters: 8 + wardrobe-skin-slots: 5 skins: defaults: diff --git a/src/test/java/net/tfminecraft/tfmcweb/entitlements/EntitlementsTest.java b/src/test/java/net/tfminecraft/tfmcweb/entitlements/EntitlementsTest.java index 0c89ccf..c0d690c 100644 --- a/src/test/java/net/tfminecraft/tfmcweb/entitlements/EntitlementsTest.java +++ b/src/test/java/net/tfminecraft/tfmcweb/entitlements/EntitlementsTest.java @@ -52,7 +52,7 @@ MetaGroupDefinition group(String permission, int tier, Map perks Cache.drinksMetaGroups=Arrays.asList(null,blank,low,high,higher); Cache.playerMetaSyncPermissions=Arrays.asList(null," "," allowed ","denied"); var meta=EntitlementResolver.resolve(player); - assertEquals(4,meta.nameColourStops);assertEquals(8,meta.maxAliveCharacters);assertEquals(3,meta.wardrobeSkinSlots);assertEquals(60000,meta.max3dPairBytes); + assertEquals(4,meta.nameColourStops);assertEquals(8,meta.maxAliveCharacters);assertEquals(EntitlementResolver.MAX_WARDROBE_SKIN_SLOTS,meta.wardrobeSkinSlots);assertEquals(5,meta.wardrobeSkinSlots);assertEquals(60000,meta.max3dPairBytes); assertEquals(5,meta.skinTokenCooldownDays);assertEquals(List.of("item","book","armor"),meta.skinKinds);assertTrue(meta.allowArmor3dHelmet && meta.allowDrinkTexture && meta.allowDrinkMessage); assertEquals(Map.of("allowed",true,"denied",false),meta.permissionFlags); Cache.skinsMetaGroups=List.of(high); assertEquals(-1,EntitlementResolver.resolve(player).skinTokenCooldownDays); assertFalse(EntitlementResolver.resolve(player).allowArmor3dHelmet); From fce5a7671a68d887c518664bfe29313736f299f1 Mon Sep 17 00:00:00 2001 From: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Date: Wed, 4 Nov 2026 12:33:26 +0100 Subject: [PATCH 2/2] Test that ranks set to 4 or 5 wardrobe skins resolve as configured Co-Authored-By: Claude Opus 5.5 --- .../tfmcweb/entitlements/EntitlementsTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/net/tfminecraft/tfmcweb/entitlements/EntitlementsTest.java b/src/test/java/net/tfminecraft/tfmcweb/entitlements/EntitlementsTest.java index c0d690c..acd84f8 100644 --- a/src/test/java/net/tfminecraft/tfmcweb/entitlements/EntitlementsTest.java +++ b/src/test/java/net/tfminecraft/tfmcweb/entitlements/EntitlementsTest.java @@ -58,6 +58,16 @@ MetaGroupDefinition group(String permission, int tier, Map perks Cache.skinsMetaGroups=List.of(high); assertEquals(-1,EntitlementResolver.resolve(player).skinTokenCooldownDays); assertFalse(EntitlementResolver.resolve(player).allowArmor3dHelmet); Cache.drinksMetaGroups=List.of(low);when(player.hasPermission("rank.low")).thenReturn(true);assertFalse(EntitlementResolver.resolve(player).allowDrinkTexture);assertFalse(EntitlementResolver.resolve(player).allowDrinkMessage); } + @Test void wardrobeSlotsUpToTheCapResolveAsConfigured() { + Cache.rpcMetaDefaults=Map.of(); Cache.skinsMetaDefaults=Map.of(); Cache.drinksMetaDefaults=Map.of(); + Cache.skinsMetaGroups=List.of(); Cache.drinksMetaGroups=List.of(); Cache.playerMetaSyncPermissions=List.of(); + Cache.rpcMetaGroups=List.of(group("rank.four",2,Map.of("wardrobe-skin-slots",4),null,null,null,null), + group("rank.five",3,Map.of("wardrobe-skin-slots",5),null,null,null,null)); + Player four=mock(Player.class); when(four.hasPermission("rank.four")).thenReturn(true); + Player five=mock(Player.class); when(five.hasPermission("rank.five")).thenReturn(true); + assertEquals(4,EntitlementResolver.resolve(four).wardrobeSkinSlots); + assertEquals(5,EntitlementResolver.resolve(five).wardrobeSkinSlots); + } @Test void syncJsonCopiesValuesAndSkipsBlankKeys() { UUID id=UUID.randomUUID(); Cache.realmId="dev"; var meta=new EntitlementResolver.ResolvedMeta(3,true,false,8,2,40000,5,List.of(" BOOK "," ","item"),true,Map.of("p\"\\",true,"q",false," ",true));