This commit is contained in:
2026-03-11 18:17:01 +01:00
parent d0d2183c77
commit ce206409a5
43 changed files with 718 additions and 7 deletions

View File

@@ -0,0 +1,27 @@
package dev.tggamesyt.szar.client;
public class ConfigEntry {
public final String id;
public final String displayName;
public final boolean defaultValue;
public final String linkedResourcePack; // null if no resourcepack linked
private boolean value;
public ConfigEntry(String id, String displayName, boolean defaultValue, String linkedResourcePack) {
this.id = id;
this.displayName = displayName;
this.defaultValue = defaultValue;
this.linkedResourcePack = linkedResourcePack;
this.value = defaultValue;
}
// Convenience constructor — no resourcepack
public ConfigEntry(String id, String displayName, boolean defaultValue) {
this(id, displayName, defaultValue, null);
}
public boolean get() { return value; }
public void set(boolean v) { value = v; }
public boolean hasResourcePack() { return linkedResourcePack != null; }
}

View File

@@ -0,0 +1,17 @@
package dev.tggamesyt.szar.client;
import java.util.Map;
public class ConfigPreset {
public final String id;
public final String displayName;
public final Map<String, Boolean> values; // setting id → value, null means "leave as-is" (custom)
public ConfigPreset(String id, String displayName, Map<String, Boolean> values) {
this.id = id;
this.displayName = displayName;
this.values = values; // null map = Custom preset
}
public boolean isCustom() { return values == null; }
}

View File

@@ -0,0 +1,145 @@
package dev.tggamesyt.szar.client;
import dev.tggamesyt.szar.Szar;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.text.Text;
import java.util.*;
public class ConfigScreen extends Screen {
private final Screen parent;
private final List<ButtonWidget> presetButtons = new ArrayList<>();
private final List<ButtonWidget> toggleButtons = new ArrayList<>();
public ConfigScreen(Screen parent) {
super(Text.literal("Your Mod Config"));
this.parent = parent;
}
@Override
protected void init() {
int cx = width / 2;
int y = 40;
// ── Preset buttons ────────────────────────────────────────────────────
List<ConfigPreset> presets = new ArrayList<>(ModConfig.allPresets());
int btnW = Math.min(70, (width - 40) / presets.size() - 4);
int total = presets.size() * (btnW + 4);
int px = cx - total / 2;
presetButtons.clear();
for (ConfigPreset preset : presets) {
int bx = px;
ButtonWidget btn = ButtonWidget.builder(
presetButtonLabel(preset),
b -> {
ModConfig.applyPreset(preset.id);
refreshPresetButtons();
refreshToggles();
})
.dimensions(bx, y, btnW, 20)
.build();
addDrawableChild(btn);
presetButtons.add(btn);
px += btnW + 4;
}
y += 34;
// ── Toggle buttons — generated from registered settings ───────────────
toggleButtons.clear();
for (ConfigEntry entry : ModConfig.allSettings()) {
ButtonWidget btn = ButtonWidget.builder(
toggleLabel(entry),
b -> {
ModConfig.setAndMarkCustom(entry.id, !entry.get());
b.setMessage(toggleLabel(entry));
refreshPresetButtons(); // update custom highlight
})
.dimensions(cx - 100, y, 200, 20)
.build();
addDrawableChild(btn);
toggleButtons.add(btn);
y += 26;
}
refreshPresetButtons();
refreshToggles();
// ── Done ──────────────────────────────────────────────────────────────
addDrawableChild(ButtonWidget.builder(Text.literal("Done"), b -> {
ModConfig.save();
ResourcePackHelper.applyAll(client);
PacketByteBuf buf = PacketByteBufs.create();
// Write each setting as: id (string), value (boolean)
var settings = ModConfig.allSettings();
buf.writeInt(settings.size());
for (ConfigEntry entry : settings) {
buf.writeString(entry.id);
buf.writeBoolean(entry.get());
}
ClientPlayNetworking.send(Szar.CONFIG_SYNC, buf);
client.setScreen(parent);
}).dimensions(cx - 75, height - 30, 150, 20).build());
}
private void refreshPresetButtons() {
List<ConfigPreset> presets = new ArrayList<>(ModConfig.allPresets());
for (int i = 0; i < presetButtons.size(); i++) {
presetButtons.get(i).setMessage(presetButtonLabel(presets.get(i)));
}
}
private void refreshToggles() {
boolean isCustom = PRESETS.values().stream()
.filter(ConfigPreset::isCustom)
.anyMatch(p -> p.id.equals(ModConfig.getActivePresetId()));
List<ConfigEntry> entries = new ArrayList<>(ModConfig.allSettings());
for (int i = 0; i < toggleButtons.size(); i++) {
ConfigEntry entry = entries.get(i);
toggleButtons.get(i).active = isCustom;
toggleButtons.get(i).setMessage(toggleLabel(entry));
}
}
// Grab presets map for isCustom check
private static final java.util.LinkedHashMap<String, ConfigPreset> PRESETS;
static {
try {
var field = ModConfig.class.getDeclaredField("PRESETS");
field.setAccessible(true);
//noinspection unchecked
PRESETS = (java.util.LinkedHashMap<String, ConfigPreset>) field.get(null);
} catch (Exception e) { throw new RuntimeException(e); }
}
private Text presetButtonLabel(ConfigPreset preset) {
boolean active = preset.id.equals(ModConfig.getActivePresetId());
return active
? Text.literal("§e[" + preset.displayName + "]§r")
: Text.literal(preset.displayName);
}
private Text toggleLabel(ConfigEntry entry) {
return Text.literal(entry.displayName + ": " + (entry.get() ? "§aON§r" : "§cOFF§r"));
}
@Override
public void render(DrawContext ctx, int mouseX, int mouseY, float delta) {
renderBackground(ctx);
ctx.drawCenteredTextWithShadow(textRenderer, title, width / 2, 15, 0xFFFFFF);
ctx.drawTextWithShadow(textRenderer, Text.literal("§7Preset:"), width / 2 - 130, 43, 0xAAAAAA);
super.render(ctx, mouseX, mouseY, delta);
}
@Override
public void close() { client.setScreen(parent); }
}

View File

@@ -0,0 +1,120 @@
package dev.tggamesyt.szar.client;
import com.google.gson.*;
import net.fabricmc.loader.api.FabricLoader;
import java.io.*;
import java.nio.file.*;
import java.util.*;
public class ModConfig {
private static final Path CONFIG_PATH =
FabricLoader.getInstance().getConfigDir().resolve("yourmod.json");
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
// ── Registry ──────────────────────────────────────────────────────────────
private static final LinkedHashMap<String, ConfigEntry> SETTINGS = new LinkedHashMap<>();
private static final LinkedHashMap<String, ConfigPreset> PRESETS = new LinkedHashMap<>();
private static String activePresetId = null;
// ── Registration API ──────────────────────────────────────────────────────
/**
* Register a toggle setting.
* @param id Unique ID, used in save file and code
* @param displayName Label shown in the config screen
* @param defaultValue Default on/off state
* @param resourcePackId Resource pack to enable/disable with this setting, or null
*/
public static ConfigEntry newSetting(String id, String displayName,
boolean defaultValue, String resourcePackId) {
ConfigEntry entry = new ConfigEntry(id, displayName, defaultValue, resourcePackId);
SETTINGS.put(id, entry);
return entry;
}
/** Register a toggle setting with no linked resource pack. */
public static ConfigEntry newSetting(String id, String displayName, boolean defaultValue) {
return newSetting(id, displayName, defaultValue, null);
}
/**
* Register a preset.
* @param id Unique ID
* @param displayName Label shown on the button
* @param values Map of setting id → value. Pass null to make this the "Custom" preset.
*/
public static ConfigPreset newPreset(String id, String displayName, Map<String, Boolean> values) {
ConfigPreset preset = new ConfigPreset(id, displayName, values);
PRESETS.put(id, preset);
if (activePresetId == null) activePresetId = id; // first preset is default
return preset;
}
// ── Accessors ─────────────────────────────────────────────────────────────
public static boolean get(String settingId) {
ConfigEntry e = SETTINGS.get(settingId);
if (e == null) throw new IllegalArgumentException("Unknown setting: " + settingId);
return e.get();
}
public static Collection<ConfigEntry> allSettings() { return SETTINGS.values(); }
public static Collection<ConfigPreset> allPresets() { return PRESETS.values(); }
public static String getActivePresetId() { return activePresetId; }
public static void applyPreset(String presetId) {
ConfigPreset preset = PRESETS.get(presetId);
if (preset == null) throw new IllegalArgumentException("Unknown preset: " + presetId);
activePresetId = presetId;
if (!preset.isCustom()) {
preset.values.forEach((id, val) -> {
ConfigEntry e = SETTINGS.get(id);
if (e != null) e.set(val);
});
}
// Custom preset: leave all values as-is
}
/** Call this when the user manually changes a toggle — auto-switches to the custom preset. */
public static void setAndMarkCustom(String settingId, boolean value) {
ConfigEntry e = SETTINGS.get(settingId);
if (e == null) throw new IllegalArgumentException("Unknown setting: " + settingId);
e.set(value);
// Find custom preset and switch to it
PRESETS.values().stream()
.filter(ConfigPreset::isCustom)
.findFirst()
.ifPresent(p -> activePresetId = p.id);
}
// ── Save / Load ───────────────────────────────────────────────────────────
public static void save() {
JsonObject root = new JsonObject();
root.addProperty("preset", activePresetId);
JsonObject values = new JsonObject();
SETTINGS.forEach((id, entry) -> values.addProperty(id, entry.get()));
root.add("values", values);
try (Writer w = Files.newBufferedWriter(CONFIG_PATH)) {
GSON.toJson(root, w);
} catch (IOException e) { e.printStackTrace(); }
}
public static void load() {
if (!Files.exists(CONFIG_PATH)) { save(); return; }
try (Reader r = Files.newBufferedReader(CONFIG_PATH)) {
JsonObject root = GSON.fromJson(r, JsonObject.class);
if (root == null) return;
if (root.has("preset")) activePresetId = root.get("preset").getAsString();
if (root.has("values")) {
JsonObject values = root.getAsJsonObject("values");
SETTINGS.forEach((id, entry) -> {
if (values.has(id)) entry.set(values.get(id).getAsBoolean());
});
}
} catch (IOException e) { e.printStackTrace(); }
}
}

View File

@@ -0,0 +1,11 @@
package dev.tggamesyt.szar.client;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
public class ModMenuIntegration implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return ConfigScreen::new;
}
}

View File

@@ -0,0 +1,40 @@
package dev.tggamesyt.szar.client;
import java.util.Map;
public class ModSettings {
// ── Declare setting references for easy typed access ──────────────────────
public static ConfigEntry RACIST;
public static ConfigEntry GAMBLING;
public static ConfigEntry NSFW;
// To add a new setting: just add a line here and in init() below. That's it.
public static void init() {
// newSetting(id, displayName, defaultValue)
// newSetting(id, displayName, defaultValue, "resourcepack/id")
RACIST = ModConfig.newSetting("racist", "Block Racist content", true, "szar:racist");
GAMBLING = ModConfig.newSetting("gambling", "Block Gambling", true);
NSFW = ModConfig.newSetting("nsfw", "Block NSFW content",true, "szar:nsfw");
// ── Presets ───────────────────────────────────────────────────────────
// newPreset(id, displayName, Map<settingId, value>)
// Pass null map for the "custom" preset (user-editable, no fixed values)
ModConfig.newPreset("none", "18+", Map.of(
"racist", false,
"gambling", false,
"nsfw", false
));
ModConfig.newPreset("some", "17+", Map.of(
"racist", false,
"gambling", false,
"nsfw", true
));
ModConfig.newPreset("all", "Minor", Map.of(
"racist", true,
"gambling", true,
"nsfw", true
));
ModConfig.newPreset("custom", "Custom", null); // null = custom, toggles stay editable
}
}

View File

@@ -0,0 +1,66 @@
package dev.tggamesyt.szar.client;
import net.minecraft.client.MinecraftClient;
import net.minecraft.resource.ResourcePackManager;
import net.minecraft.resource.ResourcePackProfile;
import java.util.*;
public class ResourcePackHelper {
public static void applyAll(MinecraftClient client) {
ResourcePackManager manager = client.getResourcePackManager();
manager.scanPacks();
Set<String> original = new HashSet<>(
manager.getEnabledProfiles().stream()
.map(p -> p.getName())
.toList()
);
Set<String> enabledNames = new HashSet<>(
manager.getEnabledProfiles().stream()
.map(p -> p.getName())
.toList()
);
for (ConfigEntry entry : ModConfig.allSettings()) {
if (!entry.hasResourcePack()) continue;
if (entry.get()) {
enabledNames.add(entry.linkedResourcePack);
} else {
enabledNames.remove(entry.linkedResourcePack);
}
}
if (enabledNames.equals(original)) {
return;
}
// Use the manager to set enabled packs properly — this is the key fix
manager.setEnabledProfiles(enabledNames);
// Sync back to options and save
client.options.resourcePacks.clear();
client.options.resourcePacks.addAll(
manager.getEnabledProfiles().stream()
.map(p -> p.getName())
.toList()
);
client.options.write();
client.reloadResources();
}
public static Set<String> getManagedPacks() {
Set<String> managed = new HashSet<>();
for (ConfigEntry entry : ModConfig.allSettings()) {
if (entry.hasResourcePack()) managed.add(entry.linkedResourcePack);
}
return managed;
}
public static boolean isManaged(String packName) {
return getManagedPacks().contains(packName);
}
}

View File

@@ -8,6 +8,7 @@ import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.client.rendering.v1.BlockEntityRendererRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry;
@@ -16,6 +17,9 @@ import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.object.builder.v1.client.model.FabricModelPredicateProviderRegistry;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ingame.HandledScreens;
import net.minecraft.client.network.AbstractClientPlayerEntity;
@@ -43,6 +47,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
import net.minecraft.util.math.Box;
@@ -91,6 +96,34 @@ public class SzarClient implements ClientModInitializer {
int loopStart = startOffset + startLength;
@Override
public void onInitializeClient() {
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
PacketByteBuf buf = PacketByteBufs.create();
// Write each setting as: id (string), value (boolean)
var settings = ModConfig.allSettings();
buf.writeInt(settings.size());
for (ConfigEntry entry : settings) {
buf.writeString(entry.id);
buf.writeBoolean(entry.get());
}
ClientPlayNetworking.send(Szar.CONFIG_SYNC, buf);
});
ModSettings.init(); // register all settings & presets FIRST
ModConfig.load(); // then load saved values
ResourceManagerHelper.registerBuiltinResourcePack(
new Identifier(MOD_ID, "nsfw"),
FabricLoader.getInstance().getModContainer(MOD_ID).get(),
Text.literal("NSFW Censorship"),
ResourcePackActivationType.NORMAL
);
ResourceManagerHelper.registerBuiltinResourcePack(
new Identifier(MOD_ID, "racist"),
FabricLoader.getInstance().getModContainer(MOD_ID).get(),
Text.literal("Racism Censorship"),
ResourcePackActivationType.NORMAL
);
EntityRendererRegistry.register(Szar.RADIATION_AREA, EmptyEntityRenderer::new);
ClientPlayNetworking.registerGlobalReceiver(Szar.PLAY_VIDEO,
(client, handler, buf, responseSender) -> {

View File

@@ -0,0 +1,30 @@
package dev.tggamesyt.szar.client.mixin;
import dev.tggamesyt.szar.client.ResourcePackHelper;
import net.minecraft.client.gui.screen.pack.ResourcePackOrganizer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ResourcePackOrganizer.Pack.class)
public interface PackMixin {
@Shadow
String getName();
@Inject(method = "canBeEnabled()Z", at = @At("RETURN"), cancellable = true)
private void onCanBeEnabled(CallbackInfoReturnable<Boolean> cir) {
if (ResourcePackHelper.isManaged(this.getName())) {
cir.setReturnValue(false);
}
}
@Inject(method = "canBeDisabled()Z", at = @At("RETURN"), cancellable = true)
private void onCanBeDisabled(CallbackInfoReturnable<Boolean> cir) {
if (ResourcePackHelper.isManaged(this.getName())) {
cir.setReturnValue(false);
}
}
}

View File

@@ -0,0 +1,17 @@
package dev.tggamesyt.szar.client.mixin;
import dev.tggamesyt.szar.client.ResourcePackHelper;
import net.minecraft.client.gui.screen.pack.PackScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PackScreen.class)
public class PackScreenCloseMixin {
@Inject(method = "close", at = @At("HEAD"))
private void onClose(CallbackInfo ci) {
ResourcePackHelper.applyAll(net.minecraft.client.MinecraftClient.getInstance());
}
}

View File

@@ -9,6 +9,8 @@
"HeldItemRendererMixin",
"ItemRendererMixin",
"MouseMixin",
"PackMixin",
"PackScreenCloseMixin",
"PlayerEntityRendererMixin",
"PlayerHeldItemFeatureRendererMixin",
"PlayerModelMixin",