Skip to content

Commit cb91594

Browse files
committed
Improve config and caching
1 parent 2bfb9d9 commit cb91594

File tree

10 files changed

+148
-88
lines changed

10 files changed

+148
-88
lines changed

changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 1.1.0
4+
5+
- Added Menace (TikTok) cape
6+
- Added Home (Twitch) cape
7+
- Improved config to allow per-cape control of elytra rendering - existing config will be lost
8+
39
## 1.0.3
410

511
- Added Minecraft Experience cape

common/src/main/java/dev/terminalmc/nocapes/NoCapes.java

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
import java.util.*;
2929

30+
import static dev.terminalmc.nocapes.config.Config.options;
31+
3032
public class NoCapes {
3133
public static final String MOD_ID = "nocapes";
3234
public static final String MOD_NAME = "NoCapes";
@@ -45,7 +47,6 @@ with open(<texture_file_path>, 'rb') as file:
4547
print(f"https://textures.minecraft.net/texture/{hashlib.sha256(file.read()).hexdigest()}")
4648
*/
4749
public static final String[] CAPES = {
48-
"all",
4950
"2340c0e03dd24a11b15a8b33c2a7e9e32abb2051b2481d0ba7defd635ca7a933",
5051
"cd9d82ab17fd92022dbd4a86cde4c382a7540e117fae7b9a2853658505a80625",
5152
"f9a76537647989f9a0b6d001e320dac591c359e9e61a31f4ce11c88f207f0ad4",
@@ -79,46 +80,51 @@ with open(<texture_file_path>, 'rb') as file:
7980
"2e002d5e1758e79ba51d08d92a0f3a95119f2f435ae7704916507b6c565a7da8",
8081
"ca29f5dd9e94fb1748203b92e36b66fda80750c87ebc18d6eafdb0e28cc1d05f",
8182
};
82-
private static final Map<UUID, String> CAPE_CACHE = new HashMap<>();
83-
private static boolean blockAll = false;
83+
public static final Map<UUID, String> CAPE_CACHE = new HashMap<>();
8484

8585
public static void init() {
8686
Config config = Config.getAndSave();
8787
for (String id : CAPES) {
8888
if (!config.options.capes.containsKey(id)) {
89-
config.options.capes.put(id, true);
89+
config.options.capes.put(id, Config.ShowMode.BOTH);
9090
}
9191
}
9292
}
9393

9494
public static void onConfigSaved(Config config) {
95-
blockAll = !config.options.capes.get("all");
95+
// Cache update method
9696
}
9797

98-
private static @Nullable String getCapeId(GameProfile profile) {
98+
private static @Nullable String getPlayerCapeId(GameProfile profile) {
9999
UUID uuid = profile.getId();
100-
@Nullable String capeId = CAPE_CACHE.get(uuid);
101-
102-
if (capeId == null) {
103-
MinecraftProfileTexture capeTexture = Minecraft.getInstance()
100+
if (CAPE_CACHE.containsKey(uuid)) {
101+
return CAPE_CACHE.get(uuid);
102+
} else {
103+
MinecraftProfileTexture texture = Minecraft.getInstance()
104104
.getMinecraftSessionService().getTextures(profile).cape();
105-
if (capeTexture != null) capeId = capeTexture.getUrl();
106-
if (capeId == null || !capeId.contains("textures.minecraft.net/texture/")) return null;
107-
capeId = capeId.split("/texture/")[1];
108-
CAPE_CACHE.put(uuid, capeId);
105+
// Texture is null is when checking the local player's GameProfile
106+
// on Hypixel, for some reason. Not sure about other cases.
107+
if (texture != null) {
108+
String url = texture.getUrl();
109+
if (url != null && url.contains("textures.minecraft.net/texture/")) {
110+
String capeId = url.split("/texture/")[1];
111+
CAPE_CACHE.put(uuid, capeId);
112+
return capeId;
113+
}
114+
}
109115
}
110-
111-
return capeId;
116+
return null;
112117
}
113118

114119
public static boolean blockCape(GameProfile profile) {
115-
if (blockAll) return true;
120+
if (options().hideEverything) return true;
121+
@Nullable Config.ShowMode mode = Config.get().options.capes.get(getPlayerCapeId(profile));
122+
return mode != null && !mode.showCape();
123+
}
116124

117-
String capeId = getCapeId(profile);
118-
if (capeId != null) {
119-
@Nullable Boolean render = Config.get().options.capes.get(capeId);
120-
return render != null && !render;
121-
}
122-
return false;
125+
public static boolean blockElytra(GameProfile profile) {
126+
if (options().hideEverything) return true;
127+
@Nullable Config.ShowMode mode = Config.get().options.capes.get(getPlayerCapeId(profile));
128+
return mode != null && !mode.showElytra();
123129
}
124130
}

common/src/main/java/dev/terminalmc/nocapes/config/Config.java

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import com.google.gson.Gson;
2020
import com.google.gson.GsonBuilder;
2121
import dev.terminalmc.nocapes.NoCapes;
22+
import dev.terminalmc.nocapes.platform.Services;
23+
import net.minecraft.ChatFormatting;
2224
import org.jetbrains.annotations.NotNull;
2325
import org.jetbrains.annotations.Nullable;
2426

@@ -31,7 +33,7 @@
3133
import java.util.Map;
3234

3335
public class Config {
34-
private static final Path DIR_PATH = Path.of("config");
36+
private static final Path CONFIG_DIR = Services.PLATFORM.getConfigDir();
3537
private static final String FILE_NAME = NoCapes.MOD_ID + ".json";
3638
private static final String BACKUP_FILE_NAME = NoCapes.MOD_ID + ".unreadable.json";
3739
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
@@ -45,18 +47,38 @@ public static Options options() {
4547
}
4648

4749
public static class Options {
48-
public static final boolean hideCapeDefault = true;
49-
public boolean hideCape = hideCapeDefault;
50+
public static final boolean hideEverythingDefault = true;
51+
public boolean hideEverything = hideEverythingDefault;
5052

51-
public static final boolean hideElytraDefault = true;
52-
public boolean hideElytra = hideElytraDefault;
53-
54-
public Map<String, Boolean> capes = defaultCapes();
53+
public Map<String, ShowMode> capes = defaultCapes();
54+
}
55+
56+
public enum ShowMode {
57+
BOTH(0, ChatFormatting.GREEN),
58+
CAPE(1, ChatFormatting.YELLOW),
59+
ELYTRA(2, ChatFormatting.GOLD),
60+
NEITHER(3, ChatFormatting.RED);
61+
62+
public final int index;
63+
public final ChatFormatting format;
64+
65+
ShowMode(int index, ChatFormatting format) {
66+
this.index = index;
67+
this.format = format;
68+
}
69+
70+
public boolean showCape() {
71+
return index == 0 || index == 1;
72+
}
73+
74+
public boolean showElytra() {
75+
return index == 0 || index == 2;
76+
}
5577
}
5678

57-
public static Map<String, Boolean> defaultCapes() {
58-
Map<String, Boolean> capes = new LinkedHashMap<>();
59-
for (String id : NoCapes.CAPES) capes.put(id, !id.equals("all"));
79+
public static Map<String, ShowMode> defaultCapes() {
80+
Map<String, ShowMode> capes = new LinkedHashMap<>();
81+
for (String id : NoCapes.CAPES) capes.put(id, ShowMode.BOTH);
6082
return capes;
6183
}
6284

@@ -83,16 +105,16 @@ public static Config resetAndSave() {
83105
return instance;
84106
}
85107

86-
// Cleanup
108+
// Validation
87109

88-
private void cleanup() {
110+
private void validate() {
89111
// Called before config is saved
90112
}
91113

92114
// Load and save
93115

94116
public static @NotNull Config load() {
95-
Path file = DIR_PATH.resolve(FILE_NAME);
117+
Path file = CONFIG_DIR.resolve(FILE_NAME);
96118
Config config = null;
97119
if (Files.exists(file)) {
98120
config = load(file, GSON);
@@ -119,8 +141,8 @@ private void cleanup() {
119141
private static void backup() {
120142
try {
121143
NoCapes.LOG.warn("Copying {} to {}", FILE_NAME, BACKUP_FILE_NAME);
122-
if (!Files.isDirectory(DIR_PATH)) Files.createDirectories(DIR_PATH);
123-
Path file = DIR_PATH.resolve(FILE_NAME);
144+
if (!Files.isDirectory(CONFIG_DIR)) Files.createDirectories(CONFIG_DIR);
145+
Path file = CONFIG_DIR.resolve(FILE_NAME);
124146
Path backupFile = file.resolveSibling(BACKUP_FILE_NAME);
125147
Files.move(file, backupFile, StandardCopyOption.ATOMIC_MOVE,
126148
StandardCopyOption.REPLACE_EXISTING);
@@ -131,10 +153,10 @@ private static void backup() {
131153

132154
public static void save() {
133155
if (instance == null) return;
134-
instance.cleanup();
156+
instance.validate();
135157
try {
136-
if (!Files.isDirectory(DIR_PATH)) Files.createDirectories(DIR_PATH);
137-
Path file = DIR_PATH.resolve(FILE_NAME);
158+
if (!Files.isDirectory(CONFIG_DIR)) Files.createDirectories(CONFIG_DIR);
159+
Path file = CONFIG_DIR.resolve(FILE_NAME);
138160
Path tempFile = file.resolveSibling(file.getFileName() + ".tmp");
139161
try (OutputStreamWriter writer = new OutputStreamWriter(
140162
new FileOutputStream(tempFile.toFile()), StandardCharsets.UTF_8)) {

common/src/main/java/dev/terminalmc/nocapes/gui/screen/ClothScreenProvider.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import dev.terminalmc.nocapes.config.Config;
2020
import me.shedaniel.clothconfig2.api.*;
21-
import net.minecraft.ChatFormatting;
21+
import me.shedaniel.clothconfig2.impl.builders.SubCategoryBuilder;
2222
import net.minecraft.client.gui.screens.Screen;
2323

2424
import static dev.terminalmc.nocapes.util.Localization.localized;
@@ -42,28 +42,31 @@ static Screen getConfigScreen(Screen parent) {
4242

4343
ConfigCategory modSettings = builder.getOrCreateCategory(localized("option", "category.cape_render"));
4444

45-
modSettings.addEntry(eb.startBooleanToggle(
46-
localized("option", "hideCape"), options.hideCape)
47-
.setDefaultValue(Config.Options.hideCapeDefault)
48-
.setSaveConsumer(val -> options.hideCape = val)
45+
modSettings.addEntry(eb.startBooleanToggle(localized("option", "hideEverything"),
46+
options.hideEverything)
47+
.setTooltip(localized("option", "hideEverything.tooltip"))
48+
.setDefaultValue(Config.Options.hideEverythingDefault)
49+
.setSaveConsumer(val -> options.hideEverything = val)
4950
.build());
5051

51-
modSettings.addEntry(eb.startBooleanToggle(
52-
localized("option", "hideElytra"), options.hideElytra)
53-
.setDefaultValue(Config.Options.hideElytraDefault)
54-
.setSaveConsumer(val -> options.hideElytra = val)
55-
.build());
52+
SubCategoryBuilder capeGroup = eb.startSubCategory(localized("option", "individualCapes"))
53+
.setExpanded(!options.hideEverything);
5654

5755
for (String url : options.capes.keySet()) {
58-
modSettings.addEntry(eb.startBooleanToggle(
59-
localized("cape", url), options.capes.get(url))
60-
.setYesNoTextSupplier((val) -> localized("option", val ? "show" : "hide")
61-
.withStyle(val ? ChatFormatting.GREEN : ChatFormatting.RED))
62-
.setDefaultValue(false)
63-
.setSaveConsumer(val -> options.capes.put(url, val))
56+
Config.ShowMode mode = options.capes.get(url);
57+
capeGroup.add(eb.startIntSlider(localized("cape", url),
58+
mode.index, 0, Config.ShowMode.values().length - 1)
59+
.setTextGetter((val) -> {
60+
Config.ShowMode m = Config.ShowMode.values()[val];
61+
return localized("option", "showMode." + m).withStyle(m.format);
62+
})
63+
.setDefaultValue(0)
64+
.setSaveConsumer(val -> options.capes.put(url, Config.ShowMode.values()[val]))
6465
.build());
6566
}
6667

68+
modSettings.addEntry(capeGroup.build());
69+
6770
return builder.build();
6871
}
6972
}

common/src/main/java/dev/terminalmc/nocapes/mixin/MixinCapeLayer.java

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,18 @@
2626
import net.minecraft.world.entity.player.PlayerModelPart;
2727
import org.spongepowered.asm.mixin.Mixin;
2828

29-
import static dev.terminalmc.nocapes.config.Config.options;
30-
3129
@Mixin(CapeLayer.class)
3230
public class MixinCapeLayer {
3331
@WrapMethod(
3432
method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/player/AbstractClientPlayer;FFFFFF)V"
3533
)
36-
private void wrapRender(PoseStack poseStack, MultiBufferSource buffer, int packedLight,
37-
AbstractClientPlayer player, float limbSwing, float limbSwingAmount,
38-
float partialTicks, float ageInTicks, float netHeadYaw, float headPitch,
34+
private void wrapRender(PoseStack poseStack, MultiBufferSource buffer, int packedLight,
35+
AbstractClientPlayer player, float limbSwing, float limbSwingAmount,
36+
float partialTicks, float ageInTicks, float netHeadYaw, float headPitch,
3937
Operation<Void> original) {
40-
if (
41-
!player.isInvisible()
42-
&& player.isModelPartShown(PlayerModelPart.CAPE)
43-
&& (
44-
!options().hideCape
45-
|| !NoCapes.blockCape(player.getGameProfile())
46-
)
47-
) {
48-
original.call(poseStack, buffer, packedLight, player, limbSwing, limbSwingAmount,
38+
if (!player.isInvisible() && player.isModelPartShown(PlayerModelPart.CAPE)
39+
&& !NoCapes.blockCape(player.getGameProfile())) {
40+
original.call(poseStack, buffer, packedLight, player, limbSwing, limbSwingAmount,
4941
partialTicks, ageInTicks, netHeadYaw, headPitch);
5042
}
5143
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2025 TerminalMC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dev.terminalmc.nocapes.mixin;
18+
19+
import dev.terminalmc.nocapes.NoCapes;
20+
import net.minecraft.client.multiplayer.ClientPacketListener;
21+
import net.minecraft.network.protocol.game.ClientboundLoginPacket;
22+
import org.spongepowered.asm.mixin.Mixin;
23+
import org.spongepowered.asm.mixin.injection.At;
24+
import org.spongepowered.asm.mixin.injection.Inject;
25+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
26+
27+
@Mixin(ClientPacketListener.class)
28+
public class MixinClientPacketListener {
29+
@Inject(
30+
method = "handleLogin",
31+
at = @At("HEAD")
32+
)
33+
private void onHandleLogin(ClientboundLoginPacket packet, CallbackInfo ci) {
34+
NoCapes.CAPE_CACHE.clear();
35+
}
36+
}

common/src/main/java/dev/terminalmc/nocapes/mixin/MixinElytraLayer.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828
import org.spongepowered.asm.mixin.Mixin;
2929
import org.spongepowered.asm.mixin.injection.At;
3030

31-
import static dev.terminalmc.nocapes.config.Config.options;
32-
3331
@Mixin(ElytraLayer.class)
3432
public class MixinElytraLayer {
3533
@WrapOperation(
@@ -39,16 +37,10 @@ public class MixinElytraLayer {
3937
target = "Lnet/minecraft/client/resources/PlayerSkin;capeTexture()Lnet/minecraft/resources/ResourceLocation;"
4038
)
4139
)
42-
private @Nullable ResourceLocation nullIfBlocked(PlayerSkin instance,
43-
Operation<ResourceLocation> original,
40+
private @Nullable ResourceLocation nullIfBlocked(PlayerSkin instance,
41+
Operation<ResourceLocation> original,
4442
@Local AbstractClientPlayer player) {
45-
if (
46-
instance.capeTexture() != null
47-
&& (
48-
!options().hideElytra
49-
|| !NoCapes.blockCape(player.getGameProfile())
50-
)
51-
) {
43+
if (instance.capeTexture() != null && !NoCapes.blockElytra(player.getGameProfile())) {
5244
return original.call(instance);
5345
}
5446
return null;

common/src/main/resources/assets/nocapes/lang/en_us.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
"message.nocapes.viewModrinth": "View on Modrinth",
55
"message.nocapes.installCloth": "Install Cloth Config API to access mod options",
66

7-
"option.nocapes.show": "Show",
8-
"option.nocapes.hide": "Hide",
7+
"option.nocapes.hideEverything": "Hide Everything",
8+
"option.nocapes.hideEverything.tooltip": "Overrides the individual controls below",
9+
"option.nocapes.individualCapes": "Individual Cape Controls",
910

10-
"option.nocapes.hideCape": "Hide Cape",
11-
"option.nocapes.hideElytra": "Hide Elytra",
11+
"option.nocapes.showMode.BOTH": "Show",
12+
"option.nocapes.showMode.CAPE": "Show Cape Only",
13+
"option.nocapes.showMode.ELYTRA": "Show Elytra Only",
14+
"option.nocapes.showMode.NEITHER": "Hide",
1215

13-
"cape.nocapes.all": "All Capes",
1416
"cape.nocapes.2340c0e03dd24a11b15a8b33c2a7e9e32abb2051b2481d0ba7defd635ca7a933": "Migrator",
1517
"cape.nocapes.cd9d82ab17fd92022dbd4a86cde4c382a7540e117fae7b9a2853658505a80625": "15th Anniversary",
1618
"cape.nocapes.f9a76537647989f9a0b6d001e320dac591c359e9e61a31f4ce11c88f207f0ad4": "Vanilla",

common/src/main/resources/nocapes.mixins.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
],
99
"client": [
1010
"MixinCapeLayer",
11+
"MixinClientPacketListener",
1112
"MixinElytraLayer"
1213
],
1314
"server": [

0 commit comments

Comments
 (0)